Merge "otafault: Fix setting of have_eio_error." into nyc-dev
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 9f5e2f2..9d8a217 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -56,8 +56,6 @@
 //
 // Return 0 on success.
 int LoadFileContents(const char* filename, FileContents* file) {
-    file->data = NULL;
-
     // A special 'filename' beginning with "MTD:" or "EMMC:" means to
     // load the contents of a partition.
     if (strncmp(filename, "MTD:", 4) == 0 ||
@@ -70,31 +68,22 @@
         return -1;
     }
 
-    file->size = file->st.st_size;
-    file->data = nullptr;
-
-    std::unique_ptr<unsigned char, decltype(&free)> data(
-            static_cast<unsigned char*>(malloc(file->size)), free);
-    if (data == nullptr) {
-        printf("failed to allocate memory: %s\n", strerror(errno));
-        return -1;
-    }
-
+    std::vector<unsigned char> data(file->st.st_size);
     FILE* f = ota_fopen(filename, "rb");
     if (f == NULL) {
         printf("failed to open \"%s\": %s\n", filename, strerror(errno));
         return -1;
     }
 
-    size_t bytes_read = ota_fread(data.get(), 1, file->size, f);
-    if (bytes_read != static_cast<size_t>(file->size)) {
-        printf("short read of \"%s\" (%zu bytes of %zd)\n", filename, bytes_read, file->size);
-        fclose(f);
+    size_t bytes_read = ota_fread(data.data(), 1, data.size(), f);
+    if (bytes_read != data.size()) {
+        printf("short read of \"%s\" (%zu bytes of %zd)\n", filename, bytes_read, data.size());
+        ota_fclose(f);
         return -1;
     }
     ota_fclose(f);
-    file->data = data.release();
-    SHA1(file->data, file->size, file->sha1);
+    file->data = std::move(data);
+    SHA1(file->data.data(), file->data.size(), file->sha1);
     return 0;
 }
 
@@ -193,17 +182,17 @@
     uint8_t parsed_sha[SHA_DIGEST_LENGTH];
 
     // Allocate enough memory to hold the largest size.
-    file->data = static_cast<unsigned char*>(malloc(size[index[pairs-1]]));
-    char* p = (char*)file->data;
-    file->size = 0;                // # bytes read so far
+    std::vector<unsigned char> data(size[index[pairs-1]]);
+    char* p = reinterpret_cast<char*>(data.data());
+    size_t data_size = 0;                // # bytes read so far
     bool found = false;
 
     for (size_t i = 0; i < pairs; ++i) {
         // Read enough additional bytes to get us up to the next size. (Again,
         // we're trying the possibilities in order of increasing size).
-        size_t next = size[index[i]] - file->size;
-        size_t read = 0;
+        size_t next = size[index[i]] - data_size;
         if (next > 0) {
+            size_t read = 0;
             switch (type) {
                 case MTD:
                     read = mtd_read_data(ctx, p, next);
@@ -216,12 +205,11 @@
             if (next != read) {
                 printf("short read (%zu bytes of %zu) for partition \"%s\"\n",
                        read, next, partition);
-                free(file->data);
-                file->data = NULL;
                 return -1;
             }
             SHA1_Update(&sha_ctx, p, read);
-            file->size += read;
+            data_size += read;
+            p += read;
         }
 
         // Duplicate the SHA context and finalize the duplicate so we can
@@ -233,8 +221,6 @@
 
         if (ParseSha1(sha1sum[index[i]].c_str(), parsed_sha) != 0) {
             printf("failed to parse sha1 %s in %s\n", sha1sum[index[i]].c_str(), filename);
-            free(file->data);
-            file->data = NULL;
             return -1;
         }
 
@@ -246,8 +232,6 @@
             found = true;
             break;
         }
-
-        p += read;
     }
 
     switch (type) {
@@ -264,13 +248,13 @@
     if (!found) {
         // Ran off the end of the list of (size,sha1) pairs without finding a match.
         printf("contents of partition \"%s\" didn't match %s\n", partition, filename);
-        free(file->data);
-        file->data = NULL;
         return -1;
     }
 
     SHA1_Final(file->sha1, &sha_ctx);
 
+    data.resize(data_size);
+    file->data = std::move(data);
     // Fake some stat() info.
     file->st.st_mode = 0644;
     file->st.st_uid = 0;
@@ -289,10 +273,10 @@
         return -1;
     }
 
-    ssize_t bytes_written = FileSink(file->data, file->size, &fd);
-    if (bytes_written != file->size) {
-        printf("short write of \"%s\" (%zd bytes of %zd) (%s)\n",
-               filename, bytes_written, file->size, strerror(errno));
+    ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd);
+    if (bytes_written != static_cast<ssize_t>(file->data.size())) {
+        printf("short write of \"%s\" (%zd bytes of %zu) (%s)\n",
+               filename, bytes_written, file->data.size(), strerror(errno));
         ota_close(fd);
         return -1;
     }
@@ -543,7 +527,6 @@
 int applypatch_check(const char* filename, int num_patches,
                      char** const patch_sha1_str) {
     FileContents file;
-    file.data = NULL;
 
     // It's okay to specify no sha1s; the check will pass if the
     // LoadFileContents is successful.  (Useful for reading
@@ -555,9 +538,6 @@
         printf("file \"%s\" doesn't have any of expected "
                "sha1 sums; checking cache\n", filename);
 
-        free(file.data);
-        file.data = NULL;
-
         // If the source file is missing or corrupted, it might be because
         // we were killed in the middle of patching it.  A copy of it
         // should have been made in CACHE_TEMP_SOURCE.  If that file
@@ -571,12 +551,9 @@
 
         if (FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0) {
             printf("cache bits don't match any sha1 for \"%s\"\n", filename);
-            free(file.data);
             return 1;
         }
     }
-
-    free(file.data);
     return 0;
 }
 
@@ -674,8 +651,6 @@
 
     FileContents copy_file;
     FileContents source_file;
-    copy_file.data = NULL;
-    source_file.data = NULL;
     const Value* source_patch_value = NULL;
     const Value* copy_patch_value = NULL;
 
@@ -685,22 +660,20 @@
             // The early-exit case:  the patch was already applied, this file
             // has the desired hash, nothing for us to do.
             printf("already %s\n", short_sha1(target_sha1).c_str());
-            free(source_file.data);
             return 0;
         }
     }
 
-    if (source_file.data == NULL ||
+    if (source_file.data.empty() ||
         (target_filename != source_filename &&
          strcmp(target_filename, source_filename) != 0)) {
         // Need to load the source file:  either we failed to load the
         // target file, or we did but it's different from the source file.
-        free(source_file.data);
-        source_file.data = NULL;
+        source_file.data.clear();
         LoadFileContents(source_filename, &source_file);
     }
 
-    if (source_file.data != NULL) {
+    if (!source_file.data.empty()) {
         int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str, num_patches);
         if (to_use >= 0) {
             source_patch_value = patch_data[to_use];
@@ -708,8 +681,7 @@
     }
 
     if (source_patch_value == NULL) {
-        free(source_file.data);
-        source_file.data = NULL;
+        source_file.data.clear();
         printf("source file is bad; trying copy\n");
 
         if (LoadFileContents(CACHE_TEMP_SOURCE, &copy_file) < 0) {
@@ -726,19 +698,14 @@
         if (copy_patch_value == NULL) {
             // fail.
             printf("copy file doesn't match source SHA-1s either\n");
-            free(copy_file.data);
             return 1;
         }
     }
 
-    int result = GenerateTarget(&source_file, source_patch_value,
-                                &copy_file, copy_patch_value,
-                                source_filename, target_filename,
-                                target_sha1, target_size, bonus_data);
-    free(source_file.data);
-    free(copy_file.data);
-
-    return result;
+    return GenerateTarget(&source_file, source_patch_value,
+                          &copy_file, copy_patch_value,
+                          source_filename, target_filename,
+                          target_sha1, target_size, bonus_data);
 }
 
 /*
@@ -759,7 +726,6 @@
     }
 
     FileContents source_file;
-    source_file.data = NULL;
     std::string target_str(target_filename);
 
     std::vector<std::string> pieces = android::base::Split(target_str, ":");
@@ -777,7 +743,6 @@
         // The early-exit case: the image was already applied, this partition
         // has the desired hash, nothing for us to do.
         printf("already %s\n", short_sha1(target_sha1).c_str());
-        free(source_file.data);
         return 0;
     }
 
@@ -787,18 +752,14 @@
             printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename);
             printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(),
                     short_sha1(source_file.sha1).c_str());
-            free(source_file.data);
             return 1;
         }
     }
 
-    if (WriteToPartition(source_file.data, target_size, target_filename) != 0) {
+    if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
         printf("write of copied data to %s failed\n", target_filename);
-        free(source_file.data);
         return 1;
     }
-
-    free(source_file.data);
     return 0;
 }
 
@@ -867,7 +828,7 @@
 
             // We still write the original source to cache, in case
             // the partition write is interrupted.
-            if (MakeFreeSpaceOnCache(source_file->size) < 0) {
+            if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
                 printf("not enough free space on /cache\n");
                 return 1;
             }
@@ -908,7 +869,7 @@
                     return 1;
                 }
 
-                if (MakeFreeSpaceOnCache(source_file->size) < 0) {
+                if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
                     printf("not enough free space on /cache\n");
                     return 1;
                 }
@@ -951,10 +912,10 @@
 
         int result;
         if (use_bsdiff) {
-            result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size,
+            result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(),
                                       patch, 0, sink, token, &ctx);
         } else {
-            result = ApplyImagePatch(source_to_use->data, source_to_use->size,
+            result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(),
                                      patch, sink, token, &ctx, bonus_data);
         }
 
diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h
index 14fb490..f392c55 100644
--- a/applypatch/applypatch.h
+++ b/applypatch/applypatch.h
@@ -24,17 +24,11 @@
 #include "openssl/sha.h"
 #include "edify/expr.h"
 
-typedef struct _Patch {
+struct FileContents {
   uint8_t sha1[SHA_DIGEST_LENGTH];
-  const char* patch_filename;
-} Patch;
-
-typedef struct _FileContents {
-  uint8_t sha1[SHA_DIGEST_LENGTH];
-  unsigned char* data;
-  ssize_t size;
+  std::vector<unsigned char> data;
   struct stat st;
-} FileContents;
+};
 
 // When there isn't enough room on the target filesystem to hold the
 // patched version of the file, we copy the original here and delete
diff --git a/applypatch/main.cpp b/applypatch/main.cpp
index 7606d5d..9013760 100644
--- a/applypatch/main.cpp
+++ b/applypatch/main.cpp
@@ -46,40 +46,32 @@
     return CacheSizeCheck(bytes);
 }
 
-// Parse arguments (which should be of the form "<sha1>" or
-// "<sha1>:<filename>" into the new parallel arrays *sha1s and
-// *patches (loading file contents into the patches).  Returns true on
+// Parse arguments (which should be of the form "<sha1>:<filename>"
+// into the new parallel arrays *sha1s and *files.Returns true on
 // success.
 static bool ParsePatchArgs(int argc, char** argv, std::vector<char*>* sha1s,
-                           std::vector<std::unique_ptr<Value, decltype(&FreeValue)>>* patches) {
+                           std::vector<FileContents>* files) {
     uint8_t digest[SHA_DIGEST_LENGTH];
 
     for (int i = 0; i < argc; ++i) {
         char* colon = strchr(argv[i], ':');
-        if (colon != NULL) {
-            *colon = '\0';
-            ++colon;
+        if (colon == nullptr) {
+            printf("no ':' in patch argument \"%s\"\n", argv[i]);
+            return false;
         }
-
+        *colon = '\0';
+        ++colon;
         if (ParseSha1(argv[i], digest) != 0) {
             printf("failed to parse sha1 \"%s\"\n", argv[i]);
             return false;
         }
 
         sha1s->push_back(argv[i]);
-        if (colon == NULL) {
-            patches->emplace_back(nullptr, FreeValue);
-        } else {
-            FileContents fc;
-            if (LoadFileContents(colon, &fc) != 0) {
-                return false;
-            }
-            std::unique_ptr<Value, decltype(&FreeValue)> value(new Value, FreeValue);
-            value->type = VAL_BLOB;
-            value->size = fc.size;
-            value->data = reinterpret_cast<char*>(fc.data);
-            patches->push_back(std::move(value));
+        FileContents fc;
+        if (LoadFileContents(colon, &fc) != 0) {
+            return false;
         }
+        files->push_back(std::move(fc));
     }
     return true;
 }
@@ -90,17 +82,19 @@
 }
 
 static int PatchMode(int argc, char** argv) {
-    std::unique_ptr<Value, decltype(&FreeValue)> bonus(nullptr, FreeValue);
+    FileContents bonusFc;
+    Value bonusValue;
+    Value* bonus = nullptr;
+
     if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
-        FileContents fc;
-        if (LoadFileContents(argv[2], &fc) != 0) {
+        if (LoadFileContents(argv[2], &bonusFc) != 0) {
             printf("failed to load bonus file %s\n", argv[2]);
             return 1;
         }
-        bonus.reset(new Value);
+        bonus = &bonusValue;
         bonus->type = VAL_BLOB;
-        bonus->size = fc.size;
-        bonus->data = reinterpret_cast<char*>(fc.data);
+        bonus->size = bonusFc.data.size();
+        bonus->data = reinterpret_cast<char*>(bonusFc.data.data());
         argc -= 2;
         argv += 2;
     }
@@ -118,28 +112,29 @@
 
     // If no <src-sha1>:<patch> is provided, it is in flash mode.
     if (argc == 5) {
-        if (bonus != NULL) {
+        if (bonus != nullptr) {
             printf("bonus file not supported in flash mode\n");
             return 1;
         }
         return FlashMode(argv[1], argv[2], argv[3], target_size);
     }
-
-
     std::vector<char*> sha1s;
-    std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> patches;
-    if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &patches)) {
+    std::vector<FileContents> files;
+    if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) {
         printf("failed to parse patch args\n");
         return 1;
     }
-
-    std::vector<Value*> patch_ptrs;
-    for (const auto& p : patches) {
-        patch_ptrs.push_back(p.get());
+    std::vector<Value> patches(files.size());
+    std::vector<Value*> patch_ptrs(files.size());
+    for (size_t i = 0; i < files.size(); ++i) {
+        patches[i].type = VAL_BLOB;
+        patches[i].size = files[i].data.size();
+        patches[i].data = reinterpret_cast<char*>(files[i].data.data());
+        patch_ptrs[i] = &patches[i];
     }
     return applypatch(argv[1], argv[2], argv[3], target_size,
                       patch_ptrs.size(), sha1s.data(),
-                      patch_ptrs.data(), bonus.get());
+                      patch_ptrs.data(), bonus);
 }
 
 // This program applies binary patches to files in a way that is safe
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 9f72de4..3614e7a 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -40,8 +40,7 @@
 #include "screen_ui.h"
 #include "ui.h"
 
-static int char_width;
-static int char_height;
+#define TEXT_INDENT     4
 
 // Return the current time as a double (including fractions of a second).
 static double now() {
@@ -213,14 +212,14 @@
     *y += 4;
 }
 
-void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) {
-    gr_text(4, *y, line, bold);
-    *y += char_height + 4;
+void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) {
+    gr_text(x, *y, line, bold);
+    *y += char_height_ + 4;
 }
 
-void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) {
+void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) {
     for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
-        DrawTextLine(y, lines[i], false);
+        DrawTextLine(x, y, lines[i], false);
     }
 }
 
@@ -251,14 +250,15 @@
             property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, "");
 
             SetColor(INFO);
-            DrawTextLine(&y, "Android Recovery", true);
+            DrawTextLine(TEXT_INDENT, &y, "Android Recovery", true);
             for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) {
-                DrawTextLine(&y, chunk.c_str(), false);
+                DrawTextLine(TEXT_INDENT, &y, chunk.c_str(), false);
             }
-            DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
+            DrawTextLines(TEXT_INDENT, &y,
+                    HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
 
             SetColor(HEADER);
-            DrawTextLines(&y, menu_headers_);
+            DrawTextLines(TEXT_INDENT, &y, menu_headers_);
 
             SetColor(MENU);
             DrawHorizontalRule(&y);
@@ -267,7 +267,7 @@
                 if (i == menu_sel) {
                     // Draw the highlight bar.
                     SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG);
-                    gr_fill(0, y - 2, gr_fb_width(), y + char_height + 2);
+                    gr_fill(0, y - 2, gr_fb_width(), y + char_height_ + 2);
                     // Bold white text for the selected item.
                     SetColor(MENU_SEL_FG);
                     gr_text(4, y, menu_[i], true);
@@ -275,7 +275,7 @@
                 } else {
                     gr_text(4, y, menu_[i], false);
                 }
-                y += char_height + 4;
+                y += char_height_ + 4;
             }
             DrawHorizontalRule(&y);
         }
@@ -286,9 +286,9 @@
         SetColor(LOG);
         int row = (text_top_ + text_rows_ - 1) % text_rows_;
         size_t count = 0;
-        for (int ty = gr_fb_height() - char_height;
+        for (int ty = gr_fb_height() - char_height_;
              ty >= y && count < text_rows_;
-             ty -= char_height, ++count) {
+             ty -= char_height_, ++count) {
             gr_text(0, ty, text_[row], false);
             --row;
             if (row < 0) row = text_rows_ - 1;
@@ -394,9 +394,9 @@
 void ScreenRecoveryUI::Init() {
     gr_init();
 
-    gr_font_size(&char_width, &char_height);
-    text_rows_ = gr_fb_height() / char_height;
-    text_cols_ = gr_fb_width() / char_width;
+    gr_font_size(&char_width_, &char_height_);
+    text_rows_ = gr_fb_height() / char_height_;
+    text_cols_ = gr_fb_width() / char_width_;
 
     text_ = Alloc2d(text_rows_, text_cols_ + 1);
     file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
diff --git a/screen_ui.h b/screen_ui.h
index 6d11910..9e1b2df 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -131,15 +131,17 @@
     void ClearText();
 
     void DrawHorizontalRule(int* y);
-    void DrawTextLine(int* y, const char* line, bool bold);
-    void DrawTextLines(int* y, const char* const* lines);
 
     void LoadBitmapArray(const char* filename, int* frames, int* fps, GRSurface*** surface);
     void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
   protected:
+    int char_width_;
+    int char_height_;
     pthread_mutex_t updateMutex;
     bool rtl_locale;
     void LoadBitmap(const char* filename, GRSurface** surface);
+    void DrawTextLine(int x, int* y, const char* line, bool bold);
+    void DrawTextLines(int x, int* y, const char* const* lines);
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/tests/Android.mk b/tests/Android.mk
index 3f3c433..9d83c9a 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -29,21 +29,30 @@
 # Component tests
 include $(CLEAR_VARS)
 LOCAL_CLANG := true
+LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 LOCAL_MODULE := recovery_component_test
 LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_SRC_FILES := component/verifier_test.cpp
+LOCAL_SRC_FILES := \
+    component/verifier_test.cpp \
+    component/applypatch_test.cpp
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_STATIC_LIBRARIES := \
+    libapplypatch \
+    libotafault \
+    libmtdutils \
     libbase \
     libverifier \
     libmincrypt \
+    libcrypto_static \
     libminui \
     libminzip \
     libcutils \
+    libbz \
+    libz \
     libc
 
-testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE)
+testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/recovery
 testdata_files := $(call find-subdir-files, testdata/*)
 
 GEN := $(addprefix $(testdata_out_path)/, $(testdata_files))
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
new file mode 100644
index 0000000..3490f68
--- /dev/null
+++ b/tests/common/test_constants.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agree to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _OTA_TEST_CONSTANTS_H
+#define _OTA_TEST_CONSTANTS_H
+
+#if defined(__LP64__)
+#define NATIVE_TEST_PATH "/nativetest64"
+#else
+#define NATIVE_TEST_PATH "/nativetest"
+#endif
+
+#endif
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
new file mode 100644
index 0000000..b44ddd1
--- /dev/null
+++ b/tests/component/applypatch_test.cpp
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agree to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
+
+#include "applypatch/applypatch.h"
+#include "common/test_constants.h"
+#include "openssl/sha.h"
+#include "print_sha1.h"
+
+static const std::string DATA_PATH = getenv("ANDROID_DATA");
+static const std::string TESTDATA_PATH = "/recovery/testdata";
+static const std::string WORK_FS = "/data";
+
+static std::string sha1sum(const std::string& fname) {
+    uint8_t digest[SHA_DIGEST_LENGTH];
+    std::string data;
+    android::base::ReadFileToString(fname, &data);
+
+    SHA1((const uint8_t*)data.c_str(), data.size(), digest);
+    return print_sha1(digest);
+}
+
+static void mangle_file(const std::string& fname) {
+    FILE* fh = fopen(&fname[0], "w");
+    int r;
+    for (int i=0; i < 1024; i++) {
+        r = rand();
+        fwrite(&r, sizeof(short), 1, fh);
+    }
+    fclose(fh);
+}
+
+static bool file_cmp(std::string& f1, std::string& f2) {
+    std::string c1;
+    std::string c2;
+    android::base::ReadFileToString(f1, &c1);
+    android::base::ReadFileToString(f2, &c2);
+    return c1 == c2;
+}
+
+static std::string from_testdata_base(const std::string fname) {
+    return android::base::StringPrintf("%s%s%s/%s",
+            &DATA_PATH[0],
+            &NATIVE_TEST_PATH[0],
+            &TESTDATA_PATH[0],
+            &fname[0]);
+}
+
+class ApplyPatchTest : public ::testing::Test {
+    public:
+        static void SetUpTestCase() {
+            // set up files
+            old_file = from_testdata_base("old.file");
+            new_file = from_testdata_base("new.file");
+            patch_file = from_testdata_base("patch.bsdiff");
+            rand_file = "/cache/applypatch_test_rand.file";
+            cache_file = "/cache/saved.file";
+
+            // write stuff to rand_file
+            android::base::WriteStringToFile("hello", rand_file);
+
+            // set up SHA constants
+            old_sha1 = sha1sum(old_file);
+            new_sha1 = sha1sum(new_file);
+            srand(time(NULL));
+            bad_sha1_a = android::base::StringPrintf("%040x", rand());
+            bad_sha1_b = android::base::StringPrintf("%040x", rand());
+
+            struct stat st;
+            stat(&new_file[0], &st);
+            new_size = st.st_size;
+        }
+
+        static std::string old_file;
+        static std::string new_file;
+        static std::string rand_file;
+        static std::string cache_file;
+        static std::string patch_file;
+
+        static std::string old_sha1;
+        static std::string new_sha1;
+        static std::string bad_sha1_a;
+        static std::string bad_sha1_b;
+
+        static size_t new_size;
+};
+
+std::string ApplyPatchTest::old_file;
+std::string ApplyPatchTest::new_file;
+
+static void cp(std::string src, std::string tgt) {
+    std::string cmd = android::base::StringPrintf("cp %s %s",
+            &src[0],
+            &tgt[0]);
+    system(&cmd[0]);
+}
+
+static void backup_old() {
+    cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
+}
+
+static void restore_old() {
+    cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
+}
+
+class ApplyPatchCacheTest : public ApplyPatchTest {
+    public:
+        virtual void SetUp() {
+            backup_old();
+        }
+
+        virtual void TearDown() {
+            restore_old();
+        }
+};
+
+class ApplyPatchFullTest : public ApplyPatchCacheTest {
+    public:
+        static void SetUpTestCase() {
+            ApplyPatchTest::SetUpTestCase();
+            unsigned long free_kb = FreeSpaceForFile(&WORK_FS[0]);
+            ASSERT_GE(free_kb * 1024, new_size * 3 / 2);
+            output_f = new TemporaryFile();
+            output_loc = std::string(output_f->path);
+
+            struct FileContents fc;
+
+            ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc));
+            Value* patch1 = new Value();
+            patch1->type = VAL_BLOB;
+            patch1->size = fc.data.size();
+            patch1->data = static_cast<char*>(malloc(fc.data.size()));
+            memcpy(patch1->data, fc.data.data(), fc.data.size());
+            patches.push_back(patch1);
+
+            ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc));
+            Value* patch2 = new Value();
+            patch2->type = VAL_BLOB;
+            patch2->size = fc.st.st_size;
+            patch2->data = static_cast<char*>(malloc(fc.data.size()));
+            memcpy(patch2->data, fc.data.data(), fc.data.size());
+            patches.push_back(patch2);
+        }
+        static void TearDownTestCase() {
+            delete output_f;
+            for (auto it = patches.begin(); it != patches.end(); ++it) {
+                free((*it)->data);
+                delete *it;
+            }
+            patches.clear();
+        }
+
+        static std::vector<Value*> patches;
+        static TemporaryFile* output_f;
+        static std::string output_loc;
+};
+
+class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest {
+    public:
+        virtual void SetUp() {
+            ApplyPatchCacheTest::SetUp();
+            cp(cache_file, "/cache/reallysaved.file");
+        }
+
+        virtual void TearDown() {
+            cp("/cache/reallysaved.file", cache_file);
+            ApplyPatchCacheTest::TearDown();
+        }
+};
+
+std::string ApplyPatchTest::rand_file;
+std::string ApplyPatchTest::patch_file;
+std::string ApplyPatchTest::cache_file;
+std::string ApplyPatchTest::old_sha1;
+std::string ApplyPatchTest::new_sha1;
+std::string ApplyPatchTest::bad_sha1_a;
+std::string ApplyPatchTest::bad_sha1_b;
+
+size_t ApplyPatchTest::new_size;
+
+std::vector<Value*> ApplyPatchFullTest::patches;
+TemporaryFile* ApplyPatchFullTest::output_f;
+std::string ApplyPatchFullTest::output_loc;
+
+TEST_F(ApplyPatchTest, CheckModeSingle) {
+    char* s = &old_sha1[0];
+    ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+}
+
+TEST_F(ApplyPatchTest, CheckModeMultiple) {
+    char* argv[3] = {
+        &bad_sha1_a[0],
+        &old_sha1[0],
+        &bad_sha1_b[0]
+    };
+    ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+}
+
+TEST_F(ApplyPatchTest, CheckModeFailure) {
+    char* argv[2] = {
+        &bad_sha1_a[0],
+        &bad_sha1_b[0]
+    };
+    ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+}
+
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) {
+    mangle_file(old_file);
+    char* s = &old_sha1[0];
+    ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+}
+
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) {
+    mangle_file(old_file);
+    char* argv[3] = {
+        &bad_sha1_a[0],
+        &old_sha1[0],
+        &bad_sha1_b[0]
+    };
+    ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+}
+
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) {
+    mangle_file(old_file);
+    char* argv[2] = {
+        &bad_sha1_a[0],
+        &bad_sha1_b[0]
+    };
+    ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+}
+
+TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) {
+    unlink(&old_file[0]);
+    char* s = &old_sha1[0];
+    ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+}
+
+TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) {
+    unlink(&old_file[0]);
+    char* argv[3] = {
+        &bad_sha1_a[0],
+        &old_sha1[0],
+        &bad_sha1_b[0]
+    };
+    ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+}
+
+TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) {
+    unlink(&old_file[0]);
+    char* argv[2] = {
+        &bad_sha1_a[0],
+        &bad_sha1_b[0]
+    };
+    ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+}
+
+TEST_F(ApplyPatchFullTest, ApplyInPlace) {
+    std::vector<char*> sha1s;
+    sha1s.push_back(&bad_sha1_a[0]);
+    sha1s.push_back(&old_sha1[0]);
+
+    int ap_result = applypatch(&old_file[0],
+            "-",
+            &new_sha1[0],
+            new_size,
+            2,
+            sha1s.data(),
+            patches.data(),
+            nullptr);
+    ASSERT_EQ(0, ap_result);
+    ASSERT_TRUE(file_cmp(old_file, new_file));
+    // reapply, applypatch is idempotent so it should succeed
+    ap_result = applypatch(&old_file[0],
+            "-",
+            &new_sha1[0],
+            new_size,
+            2,
+            sha1s.data(),
+            patches.data(),
+            nullptr);
+    ASSERT_EQ(0, ap_result);
+    ASSERT_TRUE(file_cmp(old_file, new_file));
+}
+
+TEST_F(ApplyPatchFullTest, ApplyInNewLocation) {
+    std::vector<char*> sha1s;
+    sha1s.push_back(&bad_sha1_a[0]);
+    sha1s.push_back(&old_sha1[0]);
+    int ap_result = applypatch(&old_file[0],
+            &output_loc[0],
+            &new_sha1[0],
+            new_size,
+            2,
+            sha1s.data(),
+            patches.data(),
+            nullptr);
+    ASSERT_EQ(0, ap_result);
+    ASSERT_TRUE(file_cmp(output_loc, new_file));
+    ap_result = applypatch(&old_file[0],
+            &output_loc[0],
+            &new_sha1[0],
+            new_size,
+            2,
+            sha1s.data(),
+            patches.data(),
+            nullptr);
+    ASSERT_EQ(0, ap_result);
+    ASSERT_TRUE(file_cmp(output_loc, new_file));
+}
+
+TEST_F(ApplyPatchFullTest, ApplyCorruptedInNewLocation) {
+    mangle_file(old_file);
+    std::vector<char*> sha1s;
+    sha1s.push_back(&bad_sha1_a[0]);
+    sha1s.push_back(&old_sha1[0]);
+    int ap_result = applypatch(&old_file[0],
+            &output_loc[0],
+            &new_sha1[0],
+            new_size,
+            2,
+            sha1s.data(),
+            patches.data(),
+            nullptr);
+    ASSERT_EQ(0, ap_result);
+    ASSERT_TRUE(file_cmp(output_loc, new_file));
+    ap_result = applypatch(&old_file[0],
+            &output_loc[0],
+            &new_sha1[0],
+            new_size,
+            2,
+            sha1s.data(),
+            patches.data(),
+            nullptr);
+    ASSERT_EQ(0, ap_result);
+    ASSERT_TRUE(file_cmp(output_loc, new_file));
+}
+
+TEST_F(ApplyPatchDoubleCacheTest, ApplyDoubleCorruptedInNewLocation) {
+    mangle_file(old_file);
+    mangle_file(cache_file);
+
+    std::vector<char*> sha1s;
+    sha1s.push_back(&bad_sha1_a[0]);
+    sha1s.push_back(&old_sha1[0]);
+    int ap_result = applypatch(&old_file[0],
+            &output_loc[0],
+            &new_sha1[0],
+            new_size,
+            2,
+            sha1s.data(),
+            patches.data(),
+            nullptr);
+    ASSERT_NE(0, ap_result);
+    ASSERT_FALSE(file_cmp(output_loc, new_file));
+    ap_result = applypatch(&old_file[0],
+            &output_loc[0],
+            &new_sha1[0],
+            new_size,
+            2,
+            sha1s.data(),
+            patches.data(),
+            nullptr);
+    ASSERT_NE(0, ap_result);
+    ASSERT_FALSE(file_cmp(output_loc, new_file));
+}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index c54aa11..73f6ac9 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -29,18 +29,13 @@
 #include <android-base/stringprintf.h>
 
 #include "common.h"
+#include "common/test_constants.h"
 #include "mincrypt/sha.h"
 #include "mincrypt/sha256.h"
 #include "minzip/SysUtil.h"
 #include "ui.h"
 #include "verifier.h"
 
-#if defined(__LP64__)
-#define NATIVE_TEST_PATH "/nativetest64"
-#else
-#define NATIVE_TEST_PATH "/nativetest"
-#endif
-
 static const char* DATA_PATH = getenv("ANDROID_DATA");
 static const char* TESTDATA_PATH = "/recovery/testdata/";
 
diff --git a/tests/testdata/new.file b/tests/testdata/new.file
new file mode 100644
index 0000000..cdeb8fd
--- /dev/null
+++ b/tests/testdata/new.file
Binary files differ
diff --git a/tests/testdata/old.file b/tests/testdata/old.file
new file mode 100644
index 0000000..166c873
--- /dev/null
+++ b/tests/testdata/old.file
Binary files differ
diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff
new file mode 100644
index 0000000..b78d385
--- /dev/null
+++ b/tests/testdata/patch.bsdiff
Binary files differ
diff --git a/updater/install.cpp b/updater/install.cpp
index a2efc0b..b7d9e85 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -1398,21 +1398,22 @@
     char* filename;
     if (ReadArgs(state, argv, 1, &filename) < 0) return NULL;
 
-    Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value)));
+    Value* v = static_cast<Value*>(malloc(sizeof(Value)));
+    if (v == nullptr) {
+        return nullptr;
+    }
     v->type = VAL_BLOB;
+    v->size = -1;
+    v->data = nullptr;
 
     FileContents fc;
     if (LoadFileContents(filename, &fc) != 0) {
-        free(filename);
-        v->size = -1;
-        v->data = NULL;
-        free(fc.data);
-        return v;
+        v->data = static_cast<char*>(malloc(fc.data.size()));
+        if (v->data != nullptr) {
+            memcpy(v->data, fc.data.data(), fc.data.size());
+            v->size = fc.data.size();
+        }
     }
-
-    v->size = fc.size;
-    v->data = (char*)fc.data;
-
     free(filename);
     return v;
 }
diff --git a/wear_ui.cpp b/wear_ui.cpp
index e76af8d..2502313 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -34,9 +34,6 @@
 #include "android-base/strings.h"
 #include "android-base/stringprintf.h"
 
-static int char_width;
-static int char_height;
-
 // There's only (at most) one of these objects, and global callbacks
 // (for pthread_create, and the input event system) need to find it,
 // so use a global variable.
@@ -143,41 +140,6 @@
     }
 }
 
-void WearRecoveryUI::SetColor(UIElement e) {
-    switch (e) {
-        case HEADER:
-            gr_color(247, 0, 6, 255);
-            break;
-        case MENU:
-        case MENU_SEL_BG:
-            gr_color(0, 106, 157, 255);
-            break;
-        case MENU_SEL_FG:
-            gr_color(255, 255, 255, 255);
-            break;
-        case LOG:
-            gr_color(249, 194, 0, 255);
-            break;
-        case TEXT_FILL:
-            gr_color(0, 0, 0, 160);
-            break;
-        default:
-            gr_color(255, 255, 255, 255);
-            break;
-    }
-}
-
-void WearRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) {
-    gr_text(x, *y, line, bold);
-    *y += char_height + 4;
-}
-
-void WearRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) {
-    for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
-        DrawTextLine(x, y, lines[i], false);
-    }
-}
-
 static const char* HEADERS[] = {
     "Swipe up/down to move.",
     "Swipe left/right to select.",
@@ -216,7 +178,7 @@
             if (menu_items > menu_end - menu_start) {
                 sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items);
                 gr_text(x+4, y, cur_selection_str, 1);
-                y += char_height+4;
+                y += char_height_+4;
             }
 
             // Menu begins here
@@ -227,7 +189,7 @@
                 if (i == menu_sel) {
                     // draw the highlight bar
                     SetColor(MENU_SEL_BG);
-                    gr_fill(x, y-2, gr_fb_width()-x, y+char_height+2);
+                    gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2);
                     // white text of selected item
                     SetColor(MENU_SEL_FG);
                     if (menu[i][0]) gr_text(x+4, y, menu[i], 1);
@@ -235,7 +197,7 @@
                 } else {
                     if (menu[i][0]) gr_text(x+4, y, menu[i], 0);
                 }
-                y += char_height+4;
+                y += char_height_+4;
             }
             SetColor(MENU);
             y += 4;
@@ -251,9 +213,9 @@
         int ty;
         int row = (text_top+text_rows-1) % text_rows;
         size_t count = 0;
-        for (int ty = gr_fb_height() - char_height - outer_height;
+        for (int ty = gr_fb_height() - char_height_ - outer_height;
              ty > y+2 && count < text_rows;
-             ty -= char_height, ++count) {
+             ty -= char_height_, ++count) {
             gr_text(x+4, ty, text[row], 0);
             --row;
             if (row < 0) row = text_rows-1;
@@ -323,15 +285,15 @@
 {
     gr_init();
 
-    gr_font_size(&char_width, &char_height);
+    gr_font_size(&char_width_, &char_height_);
 
     text_col = text_row = 0;
-    text_rows = (gr_fb_height()) / char_height;
-    visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height;
+    text_rows = (gr_fb_height()) / char_height_;
+    visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_;
     if (text_rows > kMaxRows) text_rows = kMaxRows;
     text_top = 1;
 
-    text_cols = (gr_fb_width() - (outer_width * 2)) / char_width;
+    text_cols = (gr_fb_width() - (outer_width * 2)) / char_width_;
     if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
 
     LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]);
diff --git a/wear_ui.h b/wear_ui.h
index 92cd320..e2d6fe0 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -53,9 +53,6 @@
 
     void Redraw();
 
-    enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL };
-    virtual void SetColor(UIElement e);
-
   protected:
     int progress_bar_height, progress_bar_width;
 
@@ -122,8 +119,6 @@
     void progress_loop();
     void PutChar(char);
     void ClearText();
-    void DrawTextLine(int x, int* y, const char* line, bool bold);
-    void DrawTextLines(int x, int* y, const char* const* lines);
     void PrintV(const char*, bool, va_list);
 };