[automerger skipped] DO NOT MERGE: Initialize the ZipArchive to zero before parsing skipped: f39f139103 skipped: cce521e439 skipped: 4a53559e98

Change-Id: I4299e6a86c1006f2545f925da4552be7fda75f32
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..5322788
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,15 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: true
+
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 2
+PointerAlignment: Left
+TabWidth: 2
+UseTab: Never
+PenaltyExcessCharacter: 32
+
+Cpp11BracedListStyle: false
diff --git a/Android.mk b/Android.mk
index 589bff4..408b146 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,18 +14,54 @@
 
 LOCAL_PATH := $(call my-dir)
 
-include $(CLEAR_VARS)
+# Needed by build/make/core/Makefile.
+RECOVERY_API_VERSION := 3
+RECOVERY_FSTAB_VERSION := 2
 
+# libfusesideload (static library)
+# ===============================
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := fuse_sideload.cpp
 LOCAL_CLANG := true
-LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter
+LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter -Werror
 LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
-
 LOCAL_MODULE := libfusesideload
-
-LOCAL_STATIC_LIBRARIES := libcutils libc libcrypto_static
+LOCAL_STATIC_LIBRARIES := libcutils libc libcrypto
 include $(BUILD_STATIC_LIBRARY)
 
+# libmounts (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := mounts.cpp
+LOCAL_CLANG := true
+LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror
+LOCAL_MODULE := libmounts
+include $(BUILD_STATIC_LIBRARY)
+
+# librecovery (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    install.cpp
+LOCAL_CFLAGS := -Wno-unused-parameter -Werror
+LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
+
+ifeq ($(AB_OTA_UPDATER),true)
+    LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+endif
+
+LOCAL_MODULE := librecovery
+LOCAL_STATIC_LIBRARIES := \
+    libminui \
+    libvintf_recovery \
+    libcrypto_utils \
+    libcrypto \
+    libbase
+
+include $(BUILD_STATIC_LIBRARY)
+
+# recovery (static executable)
+# ===============================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
@@ -33,9 +69,9 @@
     asn1_decoder.cpp \
     device.cpp \
     fuse_sdcard_provider.cpp \
-    install.cpp \
     recovery.cpp \
     roots.cpp \
+    rotate_logs.cpp \
     screen_ui.cpp \
     ui.cpp \
     verifier.cpp \
@@ -52,31 +88,34 @@
 endif
 endif
 
-RECOVERY_API_VERSION := 3
-RECOVERY_FSTAB_VERSION := 2
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
-LOCAL_CFLAGS += -Wno-unused-parameter
+LOCAL_CFLAGS += -Wno-unused-parameter -Werror
 LOCAL_CLANG := true
 
 LOCAL_C_INCLUDES += \
     system/vold \
-    system/extras/ext4_utils \
     system/core/adb \
 
 LOCAL_STATIC_LIBRARIES := \
+    librecovery \
     libbatterymonitor \
     libbootloader_message \
-    libext4_utils_static \
-    libsparse_static \
-    libminzip \
+    libext4_utils \
+    libsparse \
+    libziparchive \
+    libotautil \
+    libmounts \
     libz \
-    libmtdutils \
     libminadbd \
     libfusesideload \
     libminui \
     libpng \
     libfs_mgr \
-    libcrypto_static \
+    libcrypto_utils \
+    libcrypto \
+    libvintf_recovery \
+    libvintf \
+    libtinyxml2 \
     libbase \
     libcutils \
     libutils \
@@ -87,12 +126,6 @@
 
 LOCAL_HAL_STATIC_LIBRARIES := libhealthd
 
-ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
-    LOCAL_CFLAGS += -DUSE_EXT4
-    LOCAL_C_INCLUDES += system/extras/ext4_utils
-    LOCAL_STATIC_LIBRARIES += libext4_utils_static libz
-endif
-
 ifeq ($(AB_OTA_UPDATER),true)
     LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
 endif
@@ -114,7 +147,9 @@
 # recovery-persist (system partition dynamic executable run after /data mounts)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := recovery-persist.cpp
+LOCAL_SRC_FILES := \
+    recovery-persist.cpp \
+    rotate_logs.cpp
 LOCAL_MODULE := recovery-persist
 LOCAL_SHARED_LIBRARIES := liblog libbase
 LOCAL_CFLAGS := -Werror
@@ -124,34 +159,38 @@
 # recovery-refresh (system partition dynamic executable run at init)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := recovery-refresh.cpp
+LOCAL_SRC_FILES := \
+    recovery-refresh.cpp \
+    rotate_logs.cpp
 LOCAL_MODULE := recovery-refresh
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_SHARED_LIBRARIES := liblog libbase
 LOCAL_CFLAGS := -Werror
 LOCAL_INIT_RC := recovery-refresh.rc
 include $(BUILD_EXECUTABLE)
 
-# All the APIs for testing
+# libverifier (static library)
+# ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_MODULE := libverifier
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := \
     asn1_decoder.cpp \
-    verifier.cpp \
-    ui.cpp
-LOCAL_STATIC_LIBRARIES := libcrypto_static
+    verifier.cpp
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto_utils \
+    libcrypto \
+    libbase
+LOCAL_CFLAGS := -Werror
 include $(BUILD_STATIC_LIBRARY)
 
 include \
     $(LOCAL_PATH)/applypatch/Android.mk \
     $(LOCAL_PATH)/bootloader_message/Android.mk \
     $(LOCAL_PATH)/edify/Android.mk \
-    $(LOCAL_PATH)/minui/Android.mk \
-    $(LOCAL_PATH)/minzip/Android.mk \
     $(LOCAL_PATH)/minadbd/Android.mk \
-    $(LOCAL_PATH)/mtdutils/Android.mk \
+    $(LOCAL_PATH)/minui/Android.mk \
     $(LOCAL_PATH)/otafault/Android.mk \
+    $(LOCAL_PATH)/otautil/Android.mk \
     $(LOCAL_PATH)/tests/Android.mk \
     $(LOCAL_PATH)/tools/Android.mk \
     $(LOCAL_PATH)/uncrypt/Android.mk \
diff --git a/README.md b/README.md
index 01fab94..8e20b5a 100644
--- a/README.md
+++ b/README.md
@@ -27,3 +27,23 @@
     # Or 64-bit device
     adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test
     adb shell /data/nativetest64/recovery_component_test/recovery_component_test
+
+Running the manual tests
+------------------------
+
+`recovery-refresh` and `recovery-persist` executables exist only on systems without
+/cache partition. And we need to follow special steps to run tests for them.
+
+- Execute the test on an A/B device first. The test should fail but it will log
+  some contents to pmsg.
+
+- Reboot the device immediately and run the test again. The test should save the
+  contents of pmsg buffer into /data/misc/recovery/inject.txt. Test will pass if
+  this file has expected contents.
+
+`ResourceTest` validates whether the png files are qualified as background text
+image under recovery.
+
+    1. `adb sync data` to make sure the test-dir has the images to test.
+    2. The test will automatically pickup and verify all `_text.png` files in
+       the test dir.
diff --git a/adb_install.cpp b/adb_install.cpp
index 4aed9d4..79b8df9 100644
--- a/adb_install.cpp
+++ b/adb_install.cpp
@@ -26,17 +26,15 @@
 #include <fcntl.h>
 
 #include "ui.h"
-#include "cutils/properties.h"
 #include "install.h"
 #include "common.h"
 #include "adb_install.h"
 #include "minadbd/fuse_adb_provider.h"
 #include "fuse_sideload.h"
 
-static RecoveryUI* ui = NULL;
+#include <android-base/properties.h>
 
-static void
-set_usb_driver(bool enabled) {
+static void set_usb_driver(RecoveryUI* ui, bool enabled) {
     int fd = open("/sys/class/android_usb/android0/enable", O_WRONLY);
     if (fd < 0) {
         ui->Print("failed to open driver control: %s\n", strerror(errno));
@@ -50,19 +48,17 @@
     }
 }
 
-static void
-stop_adbd() {
-    property_set("ctl.stop", "adbd");
-    set_usb_driver(false);
+static void stop_adbd(RecoveryUI* ui) {
+    ui->Print("Stopping adbd...\n");
+    android::base::SetProperty("ctl.stop", "adbd");
+    set_usb_driver(ui, false);
 }
 
-
-static void
-maybe_restart_adbd() {
+static void maybe_restart_adbd(RecoveryUI* ui) {
     if (is_ro_debuggable()) {
         ui->Print("Restarting adbd...\n");
-        set_usb_driver(true);
-        property_set("ctl.start", "adbd");
+        set_usb_driver(ui, true);
+        android::base::SetProperty("ctl.start", "adbd");
     }
 }
 
@@ -70,14 +66,11 @@
 // package, before timing out.
 #define ADB_INSTALL_TIMEOUT 300
 
-int
-apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) {
+int apply_from_adb(RecoveryUI* ui, bool* wipe_cache, const char* install_file) {
     modified_flash = true;
 
-    ui = ui_;
-
-    stop_adbd();
-    set_usb_driver(true);
+    stop_adbd(ui);
+    set_usb_driver(ui, true);
 
     ui->Print("\n\nNow send the package you want to apply\n"
               "to the device with \"adb sideload <filename>\"...\n");
@@ -85,7 +78,7 @@
     pid_t child;
     if ((child = fork()) == 0) {
         execl("/sbin/recovery", "recovery", "--adbd", NULL);
-        _exit(-1);
+        _exit(EXIT_FAILURE);
     }
 
     // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host
@@ -137,8 +130,8 @@
         }
     }
 
-    set_usb_driver(false);
-    maybe_restart_adbd();
+    set_usb_driver(ui, false);
+    maybe_restart_adbd(ui);
 
     return result;
 }
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index 887a570..a7412d2 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -14,61 +14,169 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# libapplypatch (static library)
+# ===============================
 include $(CLEAR_VARS)
-
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := applypatch.cpp bspatch.cpp freecache.cpp imgpatch.cpp utils.cpp
+LOCAL_SRC_FILES := \
+    applypatch.cpp \
+    bspatch.cpp \
+    freecache.cpp \
+    imgpatch.cpp
 LOCAL_MODULE := libapplypatch
 LOCAL_MODULE_TAGS := eng
-LOCAL_C_INCLUDES += bootable/recovery
-LOCAL_STATIC_LIBRARIES += libbase libotafault libmtdutils libcrypto_static libbz libz
-
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include \
+    bootable/recovery
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_STATIC_LIBRARIES := \
+    libotafault \
+    libbase \
+    libcrypto \
+    libbspatch \
+    libbz \
+    libz
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
 include $(BUILD_STATIC_LIBRARY)
 
+# libimgpatch (static library)
+# ===============================
 include $(CLEAR_VARS)
-
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp
+LOCAL_SRC_FILES := \
+    bspatch.cpp \
+    imgpatch.cpp
 LOCAL_MODULE := libimgpatch
-LOCAL_C_INCLUDES += bootable/recovery
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include \
+    bootable/recovery
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES += libcrypto_static libbz libz
-
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbspatch \
+    libbase \
+    libbz \
+    libz
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
 include $(BUILD_STATIC_LIBRARY)
 
-ifeq ($(HOST_OS),linux)
+# libimgpatch (host static library)
+# ===============================
 include $(CLEAR_VARS)
-
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp
+LOCAL_SRC_FILES := \
+    bspatch.cpp \
+    imgpatch.cpp
 LOCAL_MODULE := libimgpatch
-LOCAL_C_INCLUDES += bootable/recovery
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include \
+    bootable/recovery
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES += libcrypto_static libbz libz
-
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbspatch \
+    libbase \
+    libbz \
+    libz
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
 include $(BUILD_HOST_STATIC_LIBRARY)
-endif  # HOST_OS == linux
 
+# libapplypatch_modes (static library)
+# ===============================
 include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    applypatch_modes.cpp
+LOCAL_MODULE := libapplypatch_modes
+LOCAL_C_INCLUDES := bootable/recovery
+LOCAL_STATIC_LIBRARIES := \
+    libapplypatch \
+    libbase \
+    libedify \
+    libcrypto
+LOCAL_CFLAGS := -Werror
+include $(BUILD_STATIC_LIBRARY)
 
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := main.cpp
+# applypatch (target executable)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := applypatch_main.cpp
 LOCAL_MODULE := applypatch
-LOCAL_C_INCLUDES += bootable/recovery
-LOCAL_STATIC_LIBRARIES += libapplypatch libbase libotafault libmtdutils libcrypto_static libbz \
-                          libedify \
-
-LOCAL_SHARED_LIBRARIES += libz libcutils libc
-
+LOCAL_C_INCLUDES := bootable/recovery
+LOCAL_STATIC_LIBRARIES := \
+    libapplypatch_modes \
+    libapplypatch \
+    libbase \
+    libedify \
+    libotafault \
+    libcrypto \
+    libbspatch \
+    libbz
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libz \
+    libcutils
+LOCAL_CFLAGS := -Werror
 include $(BUILD_EXECUTABLE)
 
+libimgdiff_src_files := imgdiff.cpp
+
+# libbsdiff is compiled with -D_FILE_OFFSET_BITS=64.
+libimgdiff_cflags := \
+    -Werror \
+    -D_FILE_OFFSET_BITS=64
+
+libimgdiff_static_libraries := \
+    libbsdiff \
+    libdivsufsort \
+    libdivsufsort64 \
+    libziparchive \
+    libutils \
+    liblog \
+    libbase \
+    libz
+
+# libimgdiff (static library)
+# ===============================
 include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(libimgdiff_src_files)
+LOCAL_MODULE := libimgdiff
+LOCAL_CFLAGS := \
+    $(libimgdiff_cflags)
+LOCAL_STATIC_LIBRARIES := \
+    $(libimgdiff_static_libraries)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+include $(BUILD_STATIC_LIBRARY)
 
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := imgdiff.cpp utils.cpp bsdiff.cpp
+# libimgdiff (host static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(libimgdiff_src_files)
+LOCAL_MODULE := libimgdiff
+LOCAL_CFLAGS := \
+    $(libimgdiff_cflags)
+LOCAL_STATIC_LIBRARIES := \
+    $(libimgdiff_static_libraries)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+# imgdiff (host static executable)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := imgdiff_main.cpp
 LOCAL_MODULE := imgdiff
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_C_INCLUDES += external/zlib external/bzip2
-LOCAL_STATIC_LIBRARIES += libz libbz
-
+LOCAL_CFLAGS := -Werror
+LOCAL_STATIC_LIBRARIES := \
+    libimgdiff \
+    $(libimgdiff_static_libraries) \
+    libbz
 include $(BUILD_HOST_EXECUTABLE)
diff --git a/applypatch/Makefile b/applypatch/Makefile
new file mode 100644
index 0000000..fb49843
--- /dev/null
+++ b/applypatch/Makefile
@@ -0,0 +1,33 @@
+# 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 agreed 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.
+
+# This file is for building imgdiff in Chrome OS.
+
+CPPFLAGS += -iquote.. -Iinclude
+CXXFLAGS += -std=c++11 -O3 -Wall -Werror
+LDLIBS += -lbz2 -lz
+
+.PHONY: all clean
+
+all: imgdiff libimgpatch.a
+
+clean:
+	rm -f *.o imgdiff libimgpatch.a
+
+imgdiff: imgdiff.o bsdiff.o utils.o
+	$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDLIBS) -o $@ $^
+
+libimgpatch.a utils.o: CXXFLAGS += -fPIC
+libimgpatch.a: imgpatch.o bspatch.o utils.o
+	${AR} rcs $@ $^
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 7985fc0..7be3fdb 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "applypatch/applypatch.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
@@ -27,70 +29,57 @@
 
 #include <memory>
 #include <string>
+#include <utility>
+#include <vector>
 
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/strings.h>
+#include <openssl/sha.h>
 
-#include "openssl/sha.h"
-#include "applypatch.h"
-#include "mtdutils/mtdutils.h"
 #include "edify/expr.h"
 #include "ota_io.h"
 #include "print_sha1.h"
 
-static int LoadPartitionContents(const char* filename, FileContents* file);
+static int LoadPartitionContents(const std::string& filename, FileContents* file);
 static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token);
-static int GenerateTarget(FileContents* source_file,
-                          const Value* source_patch_value,
-                          FileContents* copy_file,
-                          const Value* copy_patch_value,
-                          const char* source_filename,
-                          const char* target_filename,
-                          const uint8_t target_sha1[SHA_DIGEST_LENGTH],
-                          size_t target_size,
-                          const Value* bonus_data);
+static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
+                          const std::string& target_filename,
+                          const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data);
 
-static bool mtd_partitions_scanned = false;
-
-// Read a file into memory; store the file contents and associated
-// metadata in *file.
-//
+// Read a file into memory; store the file contents and associated metadata in *file.
 // Return 0 on success.
 int LoadFileContents(const char* filename, FileContents* file) {
-    // A special 'filename' beginning with "MTD:" or "EMMC:" means to
-    // load the contents of a partition.
-    if (strncmp(filename, "MTD:", 4) == 0 ||
-        strncmp(filename, "EMMC:", 5) == 0) {
-        return LoadPartitionContents(filename, file);
-    }
+  // A special 'filename' beginning with "EMMC:" means to load the contents of a partition.
+  if (strncmp(filename, "EMMC:", 5) == 0) {
+    return LoadPartitionContents(filename, file);
+  }
 
-    if (stat(filename, &file->st) != 0) {
-        printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
-        return -1;
-    }
+  if (stat(filename, &file->st) == -1) {
+    printf("failed to stat \"%s\": %s\n", filename, 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;
-    }
+  std::vector<unsigned char> data(file->st.st_size);
+  unique_file f(ota_fopen(filename, "rb"));
+  if (!f) {
+    printf("failed to open \"%s\": %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    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 = std::move(data);
-    SHA1(file->data.data(), file->data.size(), file->sha1);
-    return 0;
+  size_t bytes_read = ota_fread(data.data(), 1, data.size(), f.get());
+  if (bytes_read != data.size()) {
+    printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size());
+    return -1;
+  }
+  file->data = std::move(data);
+  SHA1(file->data.data(), file->data.size(), file->sha1);
+  return 0;
 }
 
-// Load the contents of an MTD or EMMC partition into the provided
+// Load the contents of an EMMC partition into the provided
 // FileContents.  filename should be a string of the form
-// "MTD:<partition_name>:<size_1>:<sha1_1>:<size_2>:<sha1_2>:..."  (or
-// "EMMC:<partition_device>:...").  The smallest size_n bytes for
+// "EMMC:<partition_device>:...".  The smallest size_n bytes for
 // which that prefix of the partition contents has the corresponding
 // sha1 hash will be loaded.  It is acceptable for a size value to be
 // repeated with different sha1s.  Will return 0 on success.
@@ -102,381 +91,268 @@
 // "end-of-file" marker), so the caller must specify the possible
 // lengths and the hash of the data, and we'll do the load expecting
 // to find one of those hashes.
-enum PartitionType { MTD, EMMC };
+static int LoadPartitionContents(const std::string& filename, FileContents* file) {
+  std::vector<std::string> pieces = android::base::Split(filename, ":");
+  if (pieces.size() < 4 || pieces.size() % 2 != 0 || pieces[0] != "EMMC") {
+    printf("LoadPartitionContents called with bad filename \"%s\"\n", filename.c_str());
+    return -1;
+  }
 
-static int LoadPartitionContents(const char* filename, FileContents* file) {
-    std::string copy(filename);
-    std::vector<std::string> pieces = android::base::Split(copy, ":");
-    if (pieces.size() < 4 || pieces.size() % 2 != 0) {
-        printf("LoadPartitionContents called with bad filename (%s)\n", filename);
+  size_t pair_count = (pieces.size() - 2) / 2;  // # of (size, sha1) pairs in filename
+  std::vector<std::pair<size_t, std::string>> pairs;
+  for (size_t i = 0; i < pair_count; ++i) {
+    size_t size;
+    if (!android::base::ParseUint(pieces[i * 2 + 2], &size) || size == 0) {
+      printf("LoadPartitionContents called with bad size \"%s\"\n", pieces[i * 2 + 2].c_str());
+      return -1;
+    }
+    pairs.push_back({ size, pieces[i * 2 + 3] });
+  }
+
+  // Sort the pairs array so that they are in order of increasing size.
+  std::sort(pairs.begin(), pairs.end());
+
+  const char* partition = pieces[1].c_str();
+  unique_file dev(ota_fopen(partition, "rb"));
+  if (!dev) {
+    printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno));
+    return -1;
+  }
+
+  SHA_CTX sha_ctx;
+  SHA1_Init(&sha_ctx);
+
+  // Allocate enough memory to hold the largest size.
+  std::vector<unsigned char> buffer(pairs[pair_count - 1].first);
+  unsigned char* buffer_ptr = buffer.data();
+  size_t buffer_size = 0;  // # bytes read so far
+  bool found = false;
+
+  for (const auto& pair : pairs) {
+    size_t current_size = pair.first;
+    const std::string& current_sha1 = pair.second;
+
+    // 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 = current_size - buffer_size;
+    if (next > 0) {
+      size_t read = ota_fread(buffer_ptr, 1, next, dev.get());
+      if (next != read) {
+        printf("short read (%zu bytes of %zu) for partition \"%s\"\n", read, next, partition);
         return -1;
+      }
+      SHA1_Update(&sha_ctx, buffer_ptr, read);
+      buffer_size += read;
+      buffer_ptr += read;
     }
 
-    enum PartitionType type;
-    if (pieces[0] == "MTD") {
-        type = MTD;
-    } else if (pieces[0] == "EMMC") {
-        type = EMMC;
-    } else {
-        printf("LoadPartitionContents called with bad filename (%s)\n", filename);
-        return -1;
-    }
-    const char* partition = pieces[1].c_str();
+    // Duplicate the SHA context and finalize the duplicate so we can
+    // check it against this pair's expected hash.
+    SHA_CTX temp_ctx;
+    memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX));
+    uint8_t sha_so_far[SHA_DIGEST_LENGTH];
+    SHA1_Final(sha_so_far, &temp_ctx);
 
-    size_t pairs = (pieces.size() - 2) / 2;    // # of (size, sha1) pairs in filename
-    std::vector<size_t> index(pairs);
-    std::vector<size_t> size(pairs);
-    std::vector<std::string> sha1sum(pairs);
-
-    for (size_t i = 0; i < pairs; ++i) {
-        size[i] = strtol(pieces[i*2+2].c_str(), NULL, 10);
-        if (size[i] == 0) {
-            printf("LoadPartitionContents called with bad size (%s)\n", filename);
-            return -1;
-        }
-        sha1sum[i] = pieces[i*2+3].c_str();
-        index[i] = i;
-    }
-
-    // Sort the index[] array so it indexes the pairs in order of increasing size.
-    sort(index.begin(), index.end(),
-        [&](const size_t& i, const size_t& j) {
-            return (size[i] < size[j]);
-        }
-    );
-
-    MtdReadContext* ctx = NULL;
-    FILE* dev = NULL;
-
-    switch (type) {
-        case MTD: {
-            if (!mtd_partitions_scanned) {
-                mtd_scan_partitions();
-                mtd_partitions_scanned = true;
-            }
-
-            const MtdPartition* mtd = mtd_find_partition_by_name(partition);
-            if (mtd == NULL) {
-                printf("mtd partition \"%s\" not found (loading %s)\n", partition, filename);
-                return -1;
-            }
-
-            ctx = mtd_read_partition(mtd);
-            if (ctx == NULL) {
-                printf("failed to initialize read of mtd partition \"%s\"\n", partition);
-                return -1;
-            }
-            break;
-        }
-
-        case EMMC:
-            dev = ota_fopen(partition, "rb");
-            if (dev == NULL) {
-                printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno));
-                return -1;
-            }
-    }
-
-    SHA_CTX sha_ctx;
-    SHA1_Init(&sha_ctx);
     uint8_t parsed_sha[SHA_DIGEST_LENGTH];
-
-    // Allocate enough memory to hold the largest size.
-    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]] - data_size;
-        if (next > 0) {
-            size_t read = 0;
-            switch (type) {
-                case MTD:
-                    read = mtd_read_data(ctx, p, next);
-                    break;
-
-                case EMMC:
-                    read = ota_fread(p, 1, next, dev);
-                    break;
-            }
-            if (next != read) {
-                printf("short read (%zu bytes of %zu) for partition \"%s\"\n",
-                       read, next, partition);
-                return -1;
-            }
-            SHA1_Update(&sha_ctx, p, read);
-            data_size += read;
-            p += read;
-        }
-
-        // Duplicate the SHA context and finalize the duplicate so we can
-        // check it against this pair's expected hash.
-        SHA_CTX temp_ctx;
-        memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX));
-        uint8_t sha_so_far[SHA_DIGEST_LENGTH];
-        SHA1_Final(sha_so_far, &temp_ctx);
-
-        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);
-            return -1;
-        }
-
-        if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) {
-            // we have a match.  stop reading the partition; we'll return
-            // the data we've read so far.
-            printf("partition read matched size %zu sha %s\n",
-                   size[index[i]], sha1sum[index[i]].c_str());
-            found = true;
-            break;
-        }
+    if (ParseSha1(current_sha1.c_str(), parsed_sha) != 0) {
+      printf("failed to parse SHA-1 %s in %s\n", current_sha1.c_str(), filename.c_str());
+      return -1;
     }
 
-    switch (type) {
-        case MTD:
-            mtd_read_close(ctx);
-            break;
-
-        case EMMC:
-            ota_fclose(dev);
-            break;
+    if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) {
+      // We have a match. Stop reading the partition; we'll return the data we've read so far.
+      printf("partition read matched size %zu SHA-1 %s\n", current_size, current_sha1.c_str());
+      found = true;
+      break;
     }
+  }
 
+  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.c_str());
+    return -1;
+  }
 
-    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);
-        return -1;
-    }
+  SHA1_Final(file->sha1, &sha_ctx);
 
-    SHA1_Final(file->sha1, &sha_ctx);
+  buffer.resize(buffer_size);
+  file->data = std::move(buffer);
+  // Fake some stat() info.
+  file->st.st_mode = 0644;
+  file->st.st_uid = 0;
+  file->st.st_gid = 0;
 
-    data.resize(data_size);
-    file->data = std::move(data);
-    // Fake some stat() info.
-    file->st.st_mode = 0644;
-    file->st.st_uid = 0;
-    file->st.st_gid = 0;
-
-    return 0;
+  return 0;
 }
 
-
 // Save the contents of the given FileContents object under the given
 // filename.  Return 0 on success.
 int SaveFileContents(const char* filename, const FileContents* file) {
-    int fd = ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR);
-    if (fd < 0) {
-        printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno));
-        return -1;
-    }
+  unique_fd fd(ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR));
+  if (fd == -1) {
+    printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    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;
-    }
-    if (ota_fsync(fd) != 0) {
-        printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno));
-        return -1;
-    }
-    if (ota_close(fd) != 0) {
-        printf("close of \"%s\" failed: %s\n", filename, strerror(errno));
-        return -1;
-    }
+  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));
+    return -1;
+  }
+  if (ota_fsync(fd) != 0) {
+    printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno));
+    return -1;
+  }
+  if (ota_close(fd) != 0) {
+    printf("close of \"%s\" failed: %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    if (chmod(filename, file->st.st_mode) != 0) {
-        printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
-        return -1;
-    }
-    if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) {
-        printf("chown of \"%s\" failed: %s\n", filename, strerror(errno));
-        return -1;
-    }
+  if (chmod(filename, file->st.st_mode) != 0) {
+    printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
+    return -1;
+  }
+  if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) {
+    printf("chown of \"%s\" failed: %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    return 0;
+  return 0;
 }
 
 // Write a memory buffer to 'target' partition, a string of the form
-// "MTD:<partition>[:...]" or "EMMC:<partition_device>[:...]". The target name
+// "EMMC:<partition_device>[:...]". The target name
 // might contain multiple colons, but WriteToPartition() only uses the first
 // two and ignores the rest. Return 0 on success.
-int WriteToPartition(const unsigned char* data, size_t len, const char* target) {
-    std::string copy(target);
-    std::vector<std::string> pieces = android::base::Split(copy, ":");
+int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) {
+  std::vector<std::string> pieces = android::base::Split(target, ":");
+  if (pieces.size() < 2 || pieces[0] != "EMMC") {
+    printf("WriteToPartition called with bad target (%s)\n", target.c_str());
+    return -1;
+  }
 
-    if (pieces.size() < 2) {
-        printf("WriteToPartition called with bad target (%s)\n", target);
+  const char* partition = pieces[1].c_str();
+  unique_fd fd(ota_open(partition, O_RDWR));
+  if (fd == -1) {
+    printf("failed to open %s: %s\n", partition, strerror(errno));
+    return -1;
+  }
+
+  size_t start = 0;
+  bool success = false;
+  for (size_t attempt = 0; attempt < 2; ++attempt) {
+    if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
+      printf("failed seek on %s: %s\n", partition, strerror(errno));
+      return -1;
+    }
+    while (start < len) {
+      size_t to_write = len - start;
+      if (to_write > 1 << 20) to_write = 1 << 20;
+
+      ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data + start, to_write));
+      if (written == -1) {
+        printf("failed write writing to %s: %s\n", partition, strerror(errno));
         return -1;
+      }
+      start += written;
     }
 
-    enum PartitionType type;
-    if (pieces[0] == "MTD") {
-        type = MTD;
-    } else if (pieces[0] == "EMMC") {
-        type = EMMC;
+    if (ota_fsync(fd) != 0) {
+      printf("failed to sync to %s: %s\n", partition, strerror(errno));
+      return -1;
+    }
+    if (ota_close(fd) != 0) {
+      printf("failed to close %s: %s\n", partition, strerror(errno));
+      return -1;
+    }
+
+    fd.reset(ota_open(partition, O_RDONLY));
+    if (fd == -1) {
+      printf("failed to reopen %s for verify: %s\n", partition, strerror(errno));
+      return -1;
+    }
+
+    // Drop caches so our subsequent verification read won't just be reading the cache.
+    sync();
+    unique_fd dc(ota_open("/proc/sys/vm/drop_caches", O_WRONLY));
+    if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) {
+      printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno));
     } else {
-        printf("WriteToPartition called with bad target (%s)\n", target);
-        return -1;
+      printf("  caches dropped\n");
     }
-    const char* partition = pieces[1].c_str();
+    ota_close(dc);
+    sleep(1);
 
-    switch (type) {
-        case MTD: {
-            if (!mtd_partitions_scanned) {
-                mtd_scan_partitions();
-                mtd_partitions_scanned = true;
-            }
-
-            const MtdPartition* mtd = mtd_find_partition_by_name(partition);
-            if (mtd == NULL) {
-                printf("mtd partition \"%s\" not found for writing\n", partition);
-                return -1;
-            }
-
-            MtdWriteContext* ctx = mtd_write_partition(mtd);
-            if (ctx == NULL) {
-                printf("failed to init mtd partition \"%s\" for writing\n", partition);
-                return -1;
-            }
-
-            size_t written = mtd_write_data(ctx, reinterpret_cast<const char*>(data), len);
-            if (written != len) {
-                printf("only wrote %zu of %zu bytes to MTD %s\n", written, len, partition);
-                mtd_write_close(ctx);
-                return -1;
-            }
-
-            if (mtd_erase_blocks(ctx, -1) < 0) {
-                printf("error finishing mtd write of %s\n", partition);
-                mtd_write_close(ctx);
-                return -1;
-            }
-
-            if (mtd_write_close(ctx)) {
-                printf("error closing mtd write of %s\n", partition);
-                return -1;
-            }
-            break;
-        }
-
-        case EMMC: {
-            size_t start = 0;
-            bool success = false;
-            int fd = ota_open(partition, O_RDWR | O_SYNC);
-            if (fd < 0) {
-                printf("failed to open %s: %s\n", partition, strerror(errno));
-                return -1;
-            }
-
-            for (size_t attempt = 0; attempt < 2; ++attempt) {
-                if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
-                    printf("failed seek on %s: %s\n", partition, strerror(errno));
-                    return -1;
-                }
-                while (start < len) {
-                    size_t to_write = len - start;
-                    if (to_write > 1<<20) to_write = 1<<20;
-
-                    ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data+start, to_write));
-                    if (written == -1) {
-                        printf("failed write writing to %s: %s\n", partition, strerror(errno));
-                        return -1;
-                    }
-                    start += written;
-                }
-                if (ota_fsync(fd) != 0) {
-                   printf("failed to sync to %s (%s)\n", partition, strerror(errno));
-                   return -1;
-                }
-                if (ota_close(fd) != 0) {
-                   printf("failed to close %s (%s)\n", partition, strerror(errno));
-                   return -1;
-                }
-                fd = ota_open(partition, O_RDONLY);
-                if (fd < 0) {
-                   printf("failed to reopen %s for verify (%s)\n", partition, strerror(errno));
-                   return -1;
-                }
-
-                // Drop caches so our subsequent verification read
-                // won't just be reading the cache.
-                sync();
-                int dc = ota_open("/proc/sys/vm/drop_caches", O_WRONLY);
-                if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) {
-                    printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno));
-                } else {
-                    printf("  caches dropped\n");
-                }
-                ota_close(dc);
-                sleep(1);
-
-                // verify
-                if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
-                    printf("failed to seek back to beginning of %s: %s\n",
-                           partition, strerror(errno));
-                    return -1;
-                }
-                unsigned char buffer[4096];
-                start = len;
-                for (size_t p = 0; p < len; p += sizeof(buffer)) {
-                    size_t to_read = len - p;
-                    if (to_read > sizeof(buffer)) {
-                        to_read = sizeof(buffer);
-                    }
-
-                    size_t so_far = 0;
-                    while (so_far < to_read) {
-                        ssize_t read_count =
-                                TEMP_FAILURE_RETRY(ota_read(fd, buffer+so_far, to_read-so_far));
-                        if (read_count == -1) {
-                            printf("verify read error %s at %zu: %s\n",
-                                   partition, p, strerror(errno));
-                            return -1;
-                        }
-                        if (static_cast<size_t>(read_count) < to_read) {
-                            printf("short verify read %s at %zu: %zd %zu %s\n",
-                                   partition, p, read_count, to_read, strerror(errno));
-                        }
-                        so_far += read_count;
-                    }
-
-                    if (memcmp(buffer, data+p, to_read) != 0) {
-                        printf("verification failed starting at %zu\n", p);
-                        start = p;
-                        break;
-                    }
-                }
-
-                if (start == len) {
-                    printf("verification read succeeded (attempt %zu)\n", attempt+1);
-                    success = true;
-                    break;
-                }
-            }
-
-            if (!success) {
-                printf("failed to verify after all attempts\n");
-                return -1;
-            }
-
-            if (ota_close(fd) != 0) {
-                printf("error closing %s (%s)\n", partition, strerror(errno));
-                return -1;
-            }
-            sync();
-            break;
-        }
+    // Verify.
+    if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
+      printf("failed to seek back to beginning of %s: %s\n", partition, strerror(errno));
+      return -1;
     }
 
-    return 0;
+    unsigned char buffer[4096];
+    start = len;
+    for (size_t p = 0; p < len; p += sizeof(buffer)) {
+      size_t to_read = len - p;
+      if (to_read > sizeof(buffer)) {
+        to_read = sizeof(buffer);
+      }
+
+      size_t so_far = 0;
+      while (so_far < to_read) {
+        ssize_t read_count = TEMP_FAILURE_RETRY(ota_read(fd, buffer + so_far, to_read - so_far));
+        if (read_count == -1) {
+          printf("verify read error %s at %zu: %s\n", partition, p, strerror(errno));
+          return -1;
+        } else if (read_count == 0) {
+          printf("verify read reached unexpected EOF, %s at %zu\n", partition, p);
+          return -1;
+        }
+        if (static_cast<size_t>(read_count) < to_read) {
+          printf("short verify read %s at %zu: %zd %zu\n", partition, p, read_count, to_read);
+        }
+        so_far += read_count;
+      }
+
+      if (memcmp(buffer, data + p, to_read) != 0) {
+        printf("verification failed starting at %zu\n", p);
+        start = p;
+        break;
+      }
+    }
+
+    if (start == len) {
+      printf("verification read succeeded (attempt %zu)\n", attempt + 1);
+      success = true;
+      break;
+    }
+
+    if (ota_close(fd) != 0) {
+      printf("failed to close %s: %s\n", partition, strerror(errno));
+      return -1;
+    }
+
+    fd.reset(ota_open(partition, O_RDWR));
+    if (fd == -1) {
+      printf("failed to reopen %s for retry write && verify: %s\n", partition, strerror(errno));
+      return -1;
+    }
+  }
+
+  if (!success) {
+    printf("failed to verify after all attempts\n");
+    return -1;
+  }
+
+  if (ota_close(fd) == -1) {
+    printf("error closing %s: %s\n", partition, strerror(errno));
+    return -1;
+  }
+  sync();
+
+  return 0;
 }
 
-
 // Take a string 'str' of 40 hex digits and parse it into the 20
 // byte array 'digest'.  'str' may contain only the digest or be of
 // the form "<digest>:<anything>".  Return 0 on success, -1 on any
@@ -509,52 +385,47 @@
 // Search an array of sha1 strings for one matching the given sha1.
 // Return the index of the match on success, or -1 if no match is
 // found.
-int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str,
-                      int num_patches) {
+static int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str) {
+  for (size_t i = 0; i < patch_sha1_str.size(); ++i) {
     uint8_t patch_sha1[SHA_DIGEST_LENGTH];
-    for (int i = 0; i < num_patches; ++i) {
-        if (ParseSha1(patch_sha1_str[i], patch_sha1) == 0 &&
-            memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) {
-            return i;
-        }
+    if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 &&
+        memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) {
+      return i;
     }
-    return -1;
+  }
+  return -1;
 }
 
 // Returns 0 if the contents of the file (argv[2]) or the cached file
 // match any of the sha1's on the command line (argv[3:]).  Returns
 // nonzero otherwise.
-int applypatch_check(const char* filename, int num_patches,
-                     char** const patch_sha1_str) {
-    FileContents file;
+int applypatch_check(const char* filename, const std::vector<std::string>& patch_sha1_str) {
+  FileContents file;
 
-    // It's okay to specify no sha1s; the check will pass if the
-    // LoadFileContents is successful.  (Useful for reading
-    // partitions, where the filename encodes the sha1s; no need to
-    // check them twice.)
-    if (LoadFileContents(filename, &file) != 0 ||
-        (num_patches > 0 &&
-         FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0)) {
-        printf("file \"%s\" doesn't have any of expected "
-               "sha1 sums; checking cache\n", filename);
+  // It's okay to specify no sha1s; the check will pass if the
+  // LoadFileContents is successful.  (Useful for reading
+  // partitions, where the filename encodes the sha1s; no need to
+  // check them twice.)
+  if (LoadFileContents(filename, &file) != 0 ||
+      (!patch_sha1_str.empty() && FindMatchingPatch(file.sha1, patch_sha1_str) < 0)) {
+    printf("file \"%s\" doesn't have any of expected sha1 sums; checking cache\n", filename);
 
-        // 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
-        // exists and matches the sha1 we're looking for, the check still
-        // passes.
-
-        if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) {
-            printf("failed to load cache file\n");
-            return 1;
-        }
-
-        if (FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0) {
-            printf("cache bits don't match any sha1 for \"%s\"\n", filename);
-            return 1;
-        }
+    // 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
+    // exists and matches the sha1 we're looking for, the check still
+    // passes.
+    if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) {
+      printf("failed to load cache file\n");
+      return 1;
     }
-    return 0;
+
+    if (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) {
+      printf("cache bits don't match any sha1 for \"%s\"\n", filename);
+      return 1;
+    }
+  }
+  return 0;
 }
 
 int ShowLicenses() {
@@ -596,116 +467,97 @@
 
 int CacheSizeCheck(size_t bytes) {
     if (MakeFreeSpaceOnCache(bytes) < 0) {
-        printf("unable to make %ld bytes available on /cache\n", (long)bytes);
+        printf("unable to make %zu bytes available on /cache\n", bytes);
         return 1;
     } else {
         return 0;
     }
 }
 
-// This function applies binary patches to files in a way that is safe
-// (the original file is not touched until we have the desired
-// replacement for it) and idempotent (it's okay to run this program
-// multiple times).
+// This function applies binary patches to EMMC target files in a way that is safe (the original
+// file is not touched until we have the desired replacement for it) and idempotent (it's okay to
+// run this program multiple times).
 //
-// - if the sha1 hash of <target_filename> is <target_sha1_string>,
-//   does nothing and exits successfully.
+// - If the SHA-1 hash of <target_filename> is <target_sha1_string>, does nothing and exits
+//   successfully.
 //
-// - otherwise, if the sha1 hash of <source_filename> is one of the
-//   entries in <patch_sha1_str>, the corresponding patch from
-//   <patch_data> (which must be a VAL_BLOB) is applied to produce a
-//   new file (the type of patch is automatically detected from the
-//   blob data).  If that new file has sha1 hash <target_sha1_str>,
-//   moves it to replace <target_filename>, and exits successfully.
-//   Note that if <source_filename> and <target_filename> are not the
-//   same, <source_filename> is NOT deleted on success.
-//   <target_filename> may be the string "-" to mean "the same as
-//   source_filename".
+// - Otherwise, if the SHA-1 hash of <source_filename> is one of the entries in <patch_sha1_str>,
+//   the corresponding patch from <patch_data> (which must be a VAL_BLOB) is applied to produce a
+//   new file (the type of patch is automatically detected from the blob data). If that new file
+//   has SHA-1 hash <target_sha1_str>, moves it to replace <target_filename>, and exits
+//   successfully. Note that if <source_filename> and <target_filename> are not the same,
+//   <source_filename> is NOT deleted on success. <target_filename> may be the string "-" to mean
+//   "the same as <source_filename>".
 //
-// - otherwise, or if any error is encountered, exits with non-zero
-//   status.
+// - Otherwise, or if any error is encountered, exits with non-zero status.
 //
-// <source_filename> may refer to a partition to read the source data.
-// See the comments for the LoadPartitionContents() function above
-// for the format of such a filename.
+// <source_filename> must refer to an EMMC partition to read the source data. See the comments for
+// the LoadPartitionContents() function above for the format of such a filename. <target_size> has
+// become obsolete since we have dropped the support for patching non-EMMC targets (EMMC targets
+// have the size embedded in the filename).
+int applypatch(const char* source_filename, const char* target_filename,
+               const char* target_sha1_str, size_t target_size __unused,
+               const std::vector<std::string>& patch_sha1_str,
+               const std::vector<std::unique_ptr<Value>>& patch_data, const Value* bonus_data) {
+  printf("patch %s: ", source_filename);
 
-int applypatch(const char* source_filename,
-               const char* target_filename,
-               const char* target_sha1_str,
-               size_t target_size,
-               int num_patches,
-               char** const patch_sha1_str,
-               Value** patch_data,
-               Value* bonus_data) {
-    printf("patch %s: ", source_filename);
+  if (target_filename[0] == '-' && target_filename[1] == '\0') {
+    target_filename = source_filename;
+  }
 
-    if (target_filename[0] == '-' && target_filename[1] == '\0') {
-        target_filename = source_filename;
+  if (strncmp(target_filename, "EMMC:", 5) != 0) {
+    printf("Supporting patching EMMC targets only.\n");
+    return 1;
+  }
+
+  uint8_t target_sha1[SHA_DIGEST_LENGTH];
+  if (ParseSha1(target_sha1_str, target_sha1) != 0) {
+    printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
+    return 1;
+  }
+
+  // We try to load the target file into the source_file object.
+  FileContents source_file;
+  if (LoadFileContents(target_filename, &source_file) == 0) {
+    if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
+      // 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());
+      return 0;
     }
+  }
 
-    uint8_t target_sha1[SHA_DIGEST_LENGTH];
-    if (ParseSha1(target_sha1_str, target_sha1) != 0) {
-        printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
-        return 1;
+  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 expected.
+    source_file.data.clear();
+    LoadFileContents(source_filename, &source_file);
+  }
+
+  if (!source_file.data.empty()) {
+    int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str);
+    if (to_use != -1) {
+      return GenerateTarget(source_file, patch_data[to_use], target_filename, target_sha1,
+                            bonus_data);
     }
+  }
 
-    FileContents copy_file;
-    FileContents source_file;
-    const Value* source_patch_value = NULL;
-    const Value* copy_patch_value = NULL;
+  printf("source file is bad; trying copy\n");
 
-    // We try to load the target file into the source_file object.
-    if (LoadFileContents(target_filename, &source_file) == 0) {
-        if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
-            // 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());
-            return 0;
-        }
-    }
+  FileContents copy_file;
+  if (LoadFileContents(CACHE_TEMP_SOURCE, &copy_file) < 0) {
+    printf("failed to read copy file\n");
+    return 1;
+  }
 
-    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.
-        source_file.data.clear();
-        LoadFileContents(source_filename, &source_file);
-    }
+  int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1_str);
+  if (to_use == -1) {
+    printf("copy file doesn't match source SHA-1s either\n");
+    return 1;
+  }
 
-    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];
-        }
-    }
-
-    if (source_patch_value == NULL) {
-        source_file.data.clear();
-        printf("source file is bad; trying copy\n");
-
-        if (LoadFileContents(CACHE_TEMP_SOURCE, &copy_file) < 0) {
-            // fail.
-            printf("failed to read copy file\n");
-            return 1;
-        }
-
-        int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1_str, num_patches);
-        if (to_use >= 0) {
-            copy_patch_value = patch_data[to_use];
-        }
-
-        if (copy_patch_value == NULL) {
-            // fail.
-            printf("copy file doesn't match source SHA-1s either\n");
-            return 1;
-        }
-    }
-
-    return GenerateTarget(&source_file, source_patch_value,
-                          &copy_file, copy_patch_value,
-                          source_filename, target_filename,
-                          target_sha1, target_size, bonus_data);
+  return GenerateTarget(copy_file, patch_data[to_use], target_filename, target_sha1, bonus_data);
 }
 
 /*
@@ -717,278 +569,124 @@
  */
 int applypatch_flash(const char* source_filename, const char* target_filename,
                      const char* target_sha1_str, size_t target_size) {
-    printf("flash %s: ", target_filename);
+  printf("flash %s: ", target_filename);
 
-    uint8_t target_sha1[SHA_DIGEST_LENGTH];
-    if (ParseSha1(target_sha1_str, target_sha1) != 0) {
-        printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
-        return 1;
-    }
+  uint8_t target_sha1[SHA_DIGEST_LENGTH];
+  if (ParseSha1(target_sha1_str, target_sha1) != 0) {
+    printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
+    return 1;
+  }
 
-    FileContents source_file;
-    std::string target_str(target_filename);
+  std::string target_str(target_filename);
+  std::vector<std::string> pieces = android::base::Split(target_str, ":");
+  if (pieces.size() != 2 || pieces[0] != "EMMC") {
+    printf("invalid target name \"%s\"", target_filename);
+    return 1;
+  }
 
-    std::vector<std::string> pieces = android::base::Split(target_str, ":");
-    if (pieces.size() != 2 || (pieces[0] != "MTD" && pieces[0] != "EMMC")) {
-        printf("invalid target name \"%s\"", target_filename);
-        return 1;
-    }
-
-    // Load the target into the source_file object to see if already applied.
-    pieces.push_back(std::to_string(target_size));
-    pieces.push_back(target_sha1_str);
-    std::string fullname = android::base::Join(pieces, ':');
-    if (LoadPartitionContents(fullname.c_str(), &source_file) == 0 &&
-        memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
-        // 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());
-        return 0;
-    }
-
-    if (LoadFileContents(source_filename, &source_file) == 0) {
-        if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
-            // The source doesn't have desired checksum.
-            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());
-            return 1;
-        }
-    }
-
-    if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
-        printf("write of copied data to %s failed\n", target_filename);
-        return 1;
-    }
+  // Load the target into the source_file object to see if already applied.
+  pieces.push_back(std::to_string(target_size));
+  pieces.push_back(target_sha1_str);
+  std::string fullname = android::base::Join(pieces, ':');
+  FileContents source_file;
+  if (LoadPartitionContents(fullname, &source_file) == 0 &&
+      memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
+    // 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());
     return 0;
+  }
+
+  if (LoadFileContents(source_filename, &source_file) == 0) {
+    if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
+      // The source doesn't have desired checksum.
+      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());
+      return 1;
+    }
+  }
+
+  if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
+    printf("write of copied data to %s failed\n", target_filename);
+    return 1;
+  }
+  return 0;
 }
 
-static int GenerateTarget(FileContents* source_file,
-                          const Value* source_patch_value,
-                          FileContents* copy_file,
-                          const Value* copy_patch_value,
-                          const char* source_filename,
-                          const char* target_filename,
-                          const uint8_t target_sha1[SHA_DIGEST_LENGTH],
-                          size_t target_size,
-                          const Value* bonus_data) {
-    int retry = 1;
-    SHA_CTX ctx;
-    std::string memory_sink_str;
-    FileContents* source_to_use;
-    int made_copy = 0;
+static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
+                          const std::string& target_filename,
+                          const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data) {
+  if (patch->type != VAL_BLOB) {
+    printf("patch is not a blob\n");
+    return 1;
+  }
 
-    bool target_is_partition = (strncmp(target_filename, "MTD:", 4) == 0 ||
-                                strncmp(target_filename, "EMMC:", 5) == 0);
-    const std::string tmp_target_filename = std::string(target_filename) + ".patch";
+  const char* header = &patch->data[0];
+  size_t header_bytes_read = patch->data.size();
+  bool use_bsdiff = false;
+  if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
+    use_bsdiff = true;
+  } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) {
+    use_bsdiff = false;
+  } else {
+    printf("Unknown patch file format\n");
+    return 1;
+  }
 
-    // assume that target_filename (eg "/system/app/Foo.apk") is located
-    // on the same filesystem as its top-level directory ("/system").
-    // We need something that exists for calling statfs().
-    std::string target_fs = target_filename;
-    auto slash_pos = target_fs.find('/', 1);
-    if (slash_pos != std::string::npos) {
-        target_fs.resize(slash_pos);
-    }
+  CHECK(android::base::StartsWith(target_filename, "EMMC:"));
 
-    const Value* patch;
-    if (source_patch_value != NULL) {
-        source_to_use = source_file;
-        patch = source_patch_value;
-    } else {
-        source_to_use = copy_file;
-        patch = copy_patch_value;
-    }
-    if (patch->type != VAL_BLOB) {
-        printf("patch is not a blob\n");
-        return 1;
-    }
-    char* header = patch->data;
-    ssize_t header_bytes_read = patch->size;
-    bool use_bsdiff = false;
-    if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
-        use_bsdiff = true;
-    } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) {
-        use_bsdiff = false;
-    } else {
-        printf("Unknown patch file format\n");
-        return 1;
-    }
+  // We still write the original source to cache, in case the partition write is interrupted.
+  if (MakeFreeSpaceOnCache(source_file.data.size()) < 0) {
+    printf("not enough free space on /cache\n");
+    return 1;
+  }
+  if (SaveFileContents(CACHE_TEMP_SOURCE, &source_file) < 0) {
+    printf("failed to back up source file\n");
+    return 1;
+  }
 
-    do {
-        // Is there enough room in the target filesystem to hold the patched
-        // file?
+  // We store the decoded output in memory.
+  SinkFn sink = MemorySink;
+  std::string memory_sink_str;  // Don't need to reserve space.
+  void* token = &memory_sink_str;
 
-        if (target_is_partition) {
-            // If the target is a partition, we're actually going to
-            // write the output to /tmp and then copy it to the
-            // partition.  statfs() always returns 0 blocks free for
-            // /tmp, so instead we'll just assume that /tmp has enough
-            // space to hold the file.
+  SHA_CTX ctx;
+  SHA1_Init(&ctx);
 
-            // We still write the original source to cache, in case
-            // the partition write is interrupted.
-            if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
-                printf("not enough free space on /cache\n");
-                return 1;
-            }
-            if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
-                printf("failed to back up source file\n");
-                return 1;
-            }
-            made_copy = 1;
-            retry = 0;
-        } else {
-            int enough_space = 0;
-            if (retry > 0) {
-                size_t free_space = FreeSpaceForFile(target_fs.c_str());
-                enough_space =
-                    (free_space > (256 << 10)) &&          // 256k (two-block) minimum
-                    (free_space > (target_size * 3 / 2));  // 50% margin of error
-                if (!enough_space) {
-                    printf("target %zu bytes; free space %zu bytes; retry %d; enough %d\n",
-                           target_size, free_space, retry, enough_space);
-                }
-            }
+  int result;
+  if (use_bsdiff) {
+    result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch.get(), 0,
+                              sink, token, &ctx);
+  } else {
+    result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink,
+                             token, &ctx, bonus_data);
+  }
 
-            if (!enough_space) {
-                retry = 0;
-            }
+  if (result != 0) {
+    printf("applying patch failed\n");
+    return 1;
+  }
 
-            if (!enough_space && source_patch_value != NULL) {
-                // Using the original source, but not enough free space.  First
-                // copy the source file to cache, then delete it from the original
-                // location.
+  uint8_t current_target_sha1[SHA_DIGEST_LENGTH];
+  SHA1_Final(current_target_sha1, &ctx);
+  if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
+    printf("patch did not produce expected sha1\n");
+    return 1;
+  } else {
+    printf("now %s\n", short_sha1(target_sha1).c_str());
+  }
 
-                if (strncmp(source_filename, "MTD:", 4) == 0 ||
-                    strncmp(source_filename, "EMMC:", 5) == 0) {
-                    // It's impossible to free space on the target filesystem by
-                    // deleting the source if the source is a partition.  If
-                    // we're ever in a state where we need to do this, fail.
-                    printf("not enough free space for target but source is partition\n");
-                    return 1;
-                }
+  // Write back the temp file to the partition.
+  if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()),
+                       memory_sink_str.size(), target_filename) != 0) {
+    printf("write of patched data to %s failed\n", target_filename.c_str());
+    return 1;
+  }
 
-                if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
-                    printf("not enough free space on /cache\n");
-                    return 1;
-                }
+  // Delete the backup copy of the source.
+  unlink(CACHE_TEMP_SOURCE);
 
-                if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
-                    printf("failed to back up source file\n");
-                    return 1;
-                }
-                made_copy = 1;
-                unlink(source_filename);
-
-                size_t free_space = FreeSpaceForFile(target_fs.c_str());
-                printf("(now %zu bytes free for target) ", free_space);
-            }
-        }
-
-
-        SinkFn sink = NULL;
-        void* token = NULL;
-        int output_fd = -1;
-        if (target_is_partition) {
-            // We store the decoded output in memory.
-            sink = MemorySink;
-            token = &memory_sink_str;
-        } else {
-            // We write the decoded output to "<tgt-file>.patch".
-            output_fd = ota_open(tmp_target_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC,
-                          S_IRUSR | S_IWUSR);
-            if (output_fd < 0) {
-                printf("failed to open output file %s: %s\n", tmp_target_filename.c_str(),
-                       strerror(errno));
-                return 1;
-            }
-            sink = FileSink;
-            token = &output_fd;
-        }
-
-
-        SHA1_Init(&ctx);
-
-        int result;
-        if (use_bsdiff) {
-            result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(),
-                                      patch, 0, sink, token, &ctx);
-        } else {
-            result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(),
-                                     patch, sink, token, &ctx, bonus_data);
-        }
-
-        if (!target_is_partition) {
-            if (ota_fsync(output_fd) != 0) {
-                printf("failed to fsync file \"%s\" (%s)\n", tmp_target_filename.c_str(),
-                       strerror(errno));
-                result = 1;
-            }
-            if (ota_close(output_fd) != 0) {
-                printf("failed to close file \"%s\" (%s)\n", tmp_target_filename.c_str(),
-                       strerror(errno));
-                result = 1;
-            }
-        }
-
-        if (result != 0) {
-            if (retry == 0) {
-                printf("applying patch failed\n");
-                return result != 0;
-            } else {
-                printf("applying patch failed; retrying\n");
-            }
-            if (!target_is_partition) {
-                unlink(tmp_target_filename.c_str());
-            }
-        } else {
-            // succeeded; no need to retry
-            break;
-        }
-    } while (retry-- > 0);
-
-    uint8_t current_target_sha1[SHA_DIGEST_LENGTH];
-    SHA1_Final(current_target_sha1, &ctx);
-    if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
-        printf("patch did not produce expected sha1\n");
-        return 1;
-    } else {
-        printf("now %s\n", short_sha1(target_sha1).c_str());
-    }
-
-    if (target_is_partition) {
-        // Copy the temp file to the partition.
-        if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()),
-                             memory_sink_str.size(), target_filename) != 0) {
-            printf("write of patched data to %s failed\n", target_filename);
-            return 1;
-        }
-    } else {
-        // Give the .patch file the same owner, group, and mode of the
-        // original source file.
-        if (chmod(tmp_target_filename.c_str(), source_to_use->st.st_mode) != 0) {
-            printf("chmod of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno));
-            return 1;
-        }
-        if (chown(tmp_target_filename.c_str(), source_to_use->st.st_uid, source_to_use->st.st_gid) != 0) {
-            printf("chown of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno));
-            return 1;
-        }
-
-        // Finally, rename the .patch file to replace the target file.
-        if (rename(tmp_target_filename.c_str(), target_filename) != 0) {
-            printf("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno));
-            return 1;
-        }
-    }
-
-    // If this run of applypatch created the copy, and we're here, we
-    // can delete it.
-    if (made_copy) {
-        unlink(CACHE_TEMP_SOURCE);
-    }
-
-    // Success!
-    return 0;
+  // Success!
+  return 0;
 }
diff --git a/applypatch/applypatch_main.cpp b/applypatch/applypatch_main.cpp
new file mode 100644
index 0000000..197077c
--- /dev/null
+++ b/applypatch/applypatch_main.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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 agreed 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 "applypatch_modes.h"
+
+// This program (applypatch) applies binary patches to files in a way that
+// is safe (the original file is not touched until we have the desired
+// replacement for it) and idempotent (it's okay to run this program
+// multiple times).
+//
+// See the comments to applypatch_modes() function.
+
+int main(int argc, char** argv) {
+    return applypatch_modes(argc, const_cast<const char**>(argv));
+}
diff --git a/applypatch/main.cpp b/applypatch/applypatch_modes.cpp
similarity index 65%
rename from applypatch/main.cpp
rename to applypatch/applypatch_modes.cpp
index 9013760..7b191a8 100644
--- a/applypatch/main.cpp
+++ b/applypatch/applypatch_modes.cpp
@@ -14,61 +14,72 @@
  * limitations under the License.
  */
 
+#include "applypatch_modes.h"
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
 #include <memory>
+#include <string>
 #include <vector>
 
-#include "applypatch.h"
-#include "edify/expr.h"
-#include "openssl/sha.h"
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <openssl/sha.h>
 
-static int CheckMode(int argc, char** argv) {
+#include "applypatch/applypatch.h"
+#include "edify/expr.h"
+
+static int CheckMode(int argc, const char** argv) {
     if (argc < 3) {
         return 2;
     }
-    return applypatch_check(argv[2], argc-3, argv+3);
+    std::vector<std::string> sha1;
+    for (int i = 3; i < argc; i++) {
+        sha1.push_back(argv[i]);
+    }
+
+    return applypatch_check(argv[2], sha1);
 }
 
-static int SpaceMode(int argc, char** argv) {
+static int SpaceMode(int argc, const char** argv) {
     if (argc != 3) {
         return 2;
     }
-    char* endptr;
-    size_t bytes = strtol(argv[2], &endptr, 10);
-    if (bytes == 0 && endptr == argv[2]) {
+
+    size_t bytes;
+    if (!android::base::ParseUint(argv[2], &bytes) || bytes == 0) {
         printf("can't parse \"%s\" as byte count\n\n", argv[2]);
         return 1;
     }
     return CacheSizeCheck(bytes);
 }
 
-// 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,
+// 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, const char** argv, std::vector<std::string>* sha1s,
                            std::vector<FileContents>* files) {
-    uint8_t digest[SHA_DIGEST_LENGTH];
-
+    if (sha1s == nullptr) {
+        return false;
+    }
     for (int i = 0; i < argc; ++i) {
-        char* colon = strchr(argv[i], ':');
-        if (colon == nullptr) {
-            printf("no ':' in patch argument \"%s\"\n", argv[i]);
+        std::vector<std::string> pieces = android::base::Split(argv[i], ":");
+        if (pieces.size() != 2) {
+            printf("failed to parse patch argument \"%s\"\n", argv[i]);
             return false;
         }
-        *colon = '\0';
-        ++colon;
-        if (ParseSha1(argv[i], digest) != 0) {
+
+        uint8_t digest[SHA_DIGEST_LENGTH];
+        if (ParseSha1(pieces[0].c_str(), digest) != 0) {
             printf("failed to parse sha1 \"%s\"\n", argv[i]);
             return false;
         }
 
-        sha1s->push_back(argv[i]);
+        sha1s->push_back(pieces[0]);
         FileContents fc;
-        if (LoadFileContents(colon, &fc) != 0) {
+        if (LoadFileContents(pieces[1].c_str(), &fc) != 0) {
             return false;
         }
         files->push_back(std::move(fc));
@@ -81,20 +92,17 @@
     return applypatch_flash(src_filename, tgt_filename, tgt_sha1, tgt_size);
 }
 
-static int PatchMode(int argc, char** argv) {
+static int PatchMode(int argc, const char** argv) {
     FileContents bonusFc;
-    Value bonusValue;
-    Value* bonus = nullptr;
+    Value bonus(VAL_INVALID, "");
 
     if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
         if (LoadFileContents(argv[2], &bonusFc) != 0) {
             printf("failed to load bonus file %s\n", argv[2]);
             return 1;
         }
-        bonus = &bonusValue;
-        bonus->type = VAL_BLOB;
-        bonus->size = bonusFc.data.size();
-        bonus->data = reinterpret_cast<char*>(bonusFc.data.data());
+        bonus.type = VAL_BLOB;
+        bonus.data = std::string(bonusFc.data.cbegin(), bonusFc.data.cend());
         argc -= 2;
         argv += 2;
     }
@@ -103,42 +111,38 @@
         return 2;
     }
 
-    char* endptr;
-    size_t target_size = strtol(argv[4], &endptr, 10);
-    if (target_size == 0 && endptr == argv[4]) {
+    size_t target_size;
+    if (!android::base::ParseUint(argv[4], &target_size) || target_size == 0) {
         printf("can't parse \"%s\" as byte count\n\n", argv[4]);
         return 1;
     }
 
     // If no <src-sha1>:<patch> is provided, it is in flash mode.
     if (argc == 5) {
-        if (bonus != nullptr) {
+        if (bonus.type != VAL_INVALID) {
             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::string> sha1s;
     std::vector<FileContents> files;
     if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) {
         printf("failed to parse patch args\n");
         return 1;
     }
-    std::vector<Value> patches(files.size());
-    std::vector<Value*> patch_ptrs(files.size());
+
+    std::vector<std::unique_ptr<Value>> patches;
     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];
+        patches.push_back(std::make_unique<Value>(
+                VAL_BLOB, std::string(files[i].data.cbegin(), files[i].data.cend())));
     }
-    return applypatch(argv[1], argv[2], argv[3], target_size,
-                      patch_ptrs.size(), sha1s.data(),
-                      patch_ptrs.data(), bonus);
+    return applypatch(argv[1], argv[2], argv[3], target_size, sha1s, patches, &bonus);
 }
 
-// This program applies binary patches to files in a way that is safe
-// (the original file is not touched until we have the desired
+// This program (applypatch) applies binary patches to files in a way that
+// is safe (the original file is not touched until we have the desired
 // replacement for it) and idempotent (it's okay to run this program
 // multiple times).
 //
@@ -160,11 +164,11 @@
 // - otherwise, or if any error is encountered, exits with non-zero
 //   status.
 //
-// <src-file> (or <file> in check mode) may refer to an MTD partition
+// <src-file> (or <file> in check mode) may refer to an EMMC partition
 // to read the source data.  See the comments for the
-// LoadMTDContents() function above for the format of such a filename.
+// LoadPartitionContents() function for the format of such a filename.
 
-int main(int argc, char** argv) {
+int applypatch_modes(int argc, const char** argv) {
     if (argc < 2) {
       usage:
         printf(
@@ -175,8 +179,8 @@
             "   or  %s -l\n"
             "\n"
             "Filenames may be of the form\n"
-            "  MTD:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n"
-            "to specify reading from or writing to an MTD partition.\n\n",
+            "  EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n"
+            "to specify reading from or writing to an EMMC partition.\n\n",
             argv[0], argv[0], argv[0], argv[0]);
         return 2;
     }
diff --git a/minzip/inline_magic.h b/applypatch/applypatch_modes.h
similarity index 67%
copy from minzip/inline_magic.h
copy to applypatch/applypatch_modes.h
index 59c659f..3d9d08d 100644
--- a/minzip/inline_magic.h
+++ b/applypatch/applypatch_modes.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -14,13 +14,9 @@
  * limitations under the License.
  */
 
-#ifndef MINZIP_INLINE_MAGIC_H_
-#define MINZIP_INLINE_MAGIC_H_
+#ifndef _APPLYPATCH_MODES_H
+#define _APPLYPATCH_MODES_H
 
-#ifndef MINZIP_GENERATE_INLINES
-#define INLINE extern inline __attribute((__gnu_inline__))
-#else
-#define INLINE
-#endif
+int applypatch_modes(int argc, const char** argv);
 
-#endif  // MINZIP_INLINE_MAGIC_H_
+#endif // _APPLYPATCH_MODES_H
diff --git a/applypatch/bsdiff.cpp b/applypatch/bsdiff.cpp
deleted file mode 100644
index 55dbe5c..0000000
--- a/applypatch/bsdiff.cpp
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2009 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 agreed 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.
- */
-
-/*
- * Most of this code comes from bsdiff.c from the bsdiff-4.3
- * distribution, which is:
- */
-
-/*-
- * Copyright 2003-2005 Colin Percival
- * All rights reserved
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted providing that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <sys/types.h>
-
-#include <bzlib.h>
-#include <err.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#define MIN(x,y) (((x)<(y)) ? (x) : (y))
-
-static void split(off_t *I,off_t *V,off_t start,off_t len,off_t h)
-{
-	off_t i,j,k,x,tmp,jj,kk;
-
-	if(len<16) {
-		for(k=start;k<start+len;k+=j) {
-			j=1;x=V[I[k]+h];
-			for(i=1;k+i<start+len;i++) {
-				if(V[I[k+i]+h]<x) {
-					x=V[I[k+i]+h];
-					j=0;
-				};
-				if(V[I[k+i]+h]==x) {
-					tmp=I[k+j];I[k+j]=I[k+i];I[k+i]=tmp;
-					j++;
-				};
-			};
-			for(i=0;i<j;i++) V[I[k+i]]=k+j-1;
-			if(j==1) I[k]=-1;
-		};
-		return;
-	};
-
-	x=V[I[start+len/2]+h];
-	jj=0;kk=0;
-	for(i=start;i<start+len;i++) {
-		if(V[I[i]+h]<x) jj++;
-		if(V[I[i]+h]==x) kk++;
-	};
-	jj+=start;kk+=jj;
-
-	i=start;j=0;k=0;
-	while(i<jj) {
-		if(V[I[i]+h]<x) {
-			i++;
-		} else if(V[I[i]+h]==x) {
-			tmp=I[i];I[i]=I[jj+j];I[jj+j]=tmp;
-			j++;
-		} else {
-			tmp=I[i];I[i]=I[kk+k];I[kk+k]=tmp;
-			k++;
-		};
-	};
-
-	while(jj+j<kk) {
-		if(V[I[jj+j]+h]==x) {
-			j++;
-		} else {
-			tmp=I[jj+j];I[jj+j]=I[kk+k];I[kk+k]=tmp;
-			k++;
-		};
-	};
-
-	if(jj>start) split(I,V,start,jj-start,h);
-
-	for(i=0;i<kk-jj;i++) V[I[jj+i]]=kk-1;
-	if(jj==kk-1) I[jj]=-1;
-
-	if(start+len>kk) split(I,V,kk,start+len-kk,h);
-}
-
-static void qsufsort(off_t *I,off_t *V,u_char *old,off_t oldsize)
-{
-	off_t buckets[256];
-	off_t i,h,len;
-
-	for(i=0;i<256;i++) buckets[i]=0;
-	for(i=0;i<oldsize;i++) buckets[old[i]]++;
-	for(i=1;i<256;i++) buckets[i]+=buckets[i-1];
-	for(i=255;i>0;i--) buckets[i]=buckets[i-1];
-	buckets[0]=0;
-
-	for(i=0;i<oldsize;i++) I[++buckets[old[i]]]=i;
-	I[0]=oldsize;
-	for(i=0;i<oldsize;i++) V[i]=buckets[old[i]];
-	V[oldsize]=0;
-	for(i=1;i<256;i++) if(buckets[i]==buckets[i-1]+1) I[buckets[i]]=-1;
-	I[0]=-1;
-
-	for(h=1;I[0]!=-(oldsize+1);h+=h) {
-		len=0;
-		for(i=0;i<oldsize+1;) {
-			if(I[i]<0) {
-				len-=I[i];
-				i-=I[i];
-			} else {
-				if(len) I[i-len]=-len;
-				len=V[I[i]]+1-i;
-				split(I,V,i,len,h);
-				i+=len;
-				len=0;
-			};
-		};
-		if(len) I[i-len]=-len;
-	};
-
-	for(i=0;i<oldsize+1;i++) I[V[i]]=i;
-}
-
-static off_t matchlen(u_char *olddata,off_t oldsize,u_char *newdata,off_t newsize)
-{
-	off_t i;
-
-	for(i=0;(i<oldsize)&&(i<newsize);i++)
-		if(olddata[i]!=newdata[i]) break;
-
-	return i;
-}
-
-static off_t search(off_t *I,u_char *old,off_t oldsize,
-		u_char *newdata,off_t newsize,off_t st,off_t en,off_t *pos)
-{
-	off_t x,y;
-
-	if(en-st<2) {
-		x=matchlen(old+I[st],oldsize-I[st],newdata,newsize);
-		y=matchlen(old+I[en],oldsize-I[en],newdata,newsize);
-
-		if(x>y) {
-			*pos=I[st];
-			return x;
-		} else {
-			*pos=I[en];
-			return y;
-		}
-	};
-
-	x=st+(en-st)/2;
-	if(memcmp(old+I[x],newdata,MIN(oldsize-I[x],newsize))<0) {
-		return search(I,old,oldsize,newdata,newsize,x,en,pos);
-	} else {
-		return search(I,old,oldsize,newdata,newsize,st,x,pos);
-	};
-}
-
-static void offtout(off_t x,u_char *buf)
-{
-	off_t y;
-
-	if(x<0) y=-x; else y=x;
-
-		buf[0]=y%256;y-=buf[0];
-	y=y/256;buf[1]=y%256;y-=buf[1];
-	y=y/256;buf[2]=y%256;y-=buf[2];
-	y=y/256;buf[3]=y%256;y-=buf[3];
-	y=y/256;buf[4]=y%256;y-=buf[4];
-	y=y/256;buf[5]=y%256;y-=buf[5];
-	y=y/256;buf[6]=y%256;y-=buf[6];
-	y=y/256;buf[7]=y%256;
-
-	if(x<0) buf[7]|=0x80;
-}
-
-// This is main() from bsdiff.c, with the following changes:
-//
-//    - old, oldsize, newdata, newsize are arguments; we don't load this
-//      data from files.  old and newdata are owned by the caller; we
-//      don't free them at the end.
-//
-//    - the "I" block of memory is owned by the caller, who passes a
-//      pointer to *I, which can be NULL.  This way if we call
-//      bsdiff() multiple times with the same 'old' data, we only do
-//      the qsufsort() step the first time.
-//
-int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* newdata, off_t newsize,
-           const char* patch_filename)
-{
-	int fd;
-	off_t *I;
-	off_t scan,pos,len;
-	off_t lastscan,lastpos,lastoffset;
-	off_t oldscore,scsc;
-	off_t s,Sf,lenf,Sb,lenb;
-	off_t overlap,Ss,lens;
-	off_t i;
-	off_t dblen,eblen;
-	u_char *db,*eb;
-	u_char buf[8];
-	u_char header[32];
-	FILE * pf;
-	BZFILE * pfbz2;
-	int bz2err;
-
-        if (*IP == NULL) {
-            off_t* V;
-            *IP = reinterpret_cast<off_t*>(malloc((oldsize+1) * sizeof(off_t)));
-            V = reinterpret_cast<off_t*>(malloc((oldsize+1) * sizeof(off_t)));
-            qsufsort(*IP, V, old, oldsize);
-            free(V);
-        }
-        I = *IP;
-
-	if(((db=reinterpret_cast<u_char*>(malloc(newsize+1)))==NULL) ||
-		((eb=reinterpret_cast<u_char*>(malloc(newsize+1)))==NULL)) err(1,NULL);
-	dblen=0;
-	eblen=0;
-
-	/* Create the patch file */
-	if ((pf = fopen(patch_filename, "w")) == NULL)
-              err(1, "%s", patch_filename);
-
-	/* Header is
-		0	8	 "BSDIFF40"
-		8	8	length of bzip2ed ctrl block
-		16	8	length of bzip2ed diff block
-		24	8	length of new file */
-	/* File is
-		0	32	Header
-		32	??	Bzip2ed ctrl block
-		??	??	Bzip2ed diff block
-		??	??	Bzip2ed extra block */
-	memcpy(header,"BSDIFF40",8);
-	offtout(0, header + 8);
-	offtout(0, header + 16);
-	offtout(newsize, header + 24);
-	if (fwrite(header, 32, 1, pf) != 1)
-		err(1, "fwrite(%s)", patch_filename);
-
-	/* Compute the differences, writing ctrl as we go */
-	if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
-		errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
-	scan=0;len=0;
-	lastscan=0;lastpos=0;lastoffset=0;
-	while(scan<newsize) {
-		oldscore=0;
-
-		for(scsc=scan+=len;scan<newsize;scan++) {
-			len=search(I,old,oldsize,newdata+scan,newsize-scan,
-					0,oldsize,&pos);
-
-			for(;scsc<scan+len;scsc++)
-			if((scsc+lastoffset<oldsize) &&
-				(old[scsc+lastoffset] == newdata[scsc]))
-				oldscore++;
-
-			if(((len==oldscore) && (len!=0)) ||
-				(len>oldscore+8)) break;
-
-			if((scan+lastoffset<oldsize) &&
-				(old[scan+lastoffset] == newdata[scan]))
-				oldscore--;
-		};
-
-		if((len!=oldscore) || (scan==newsize)) {
-			s=0;Sf=0;lenf=0;
-			for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
-				if(old[lastpos+i]==newdata[lastscan+i]) s++;
-				i++;
-				if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
-			};
-
-			lenb=0;
-			if(scan<newsize) {
-				s=0;Sb=0;
-				for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
-					if(old[pos-i]==newdata[scan-i]) s++;
-					if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
-				};
-			};
-
-			if(lastscan+lenf>scan-lenb) {
-				overlap=(lastscan+lenf)-(scan-lenb);
-				s=0;Ss=0;lens=0;
-				for(i=0;i<overlap;i++) {
-					if(newdata[lastscan+lenf-overlap+i]==
-					   old[lastpos+lenf-overlap+i]) s++;
-					if(newdata[scan-lenb+i]==
-					   old[pos-lenb+i]) s--;
-					if(s>Ss) { Ss=s; lens=i+1; };
-				};
-
-				lenf+=lens-overlap;
-				lenb-=lens;
-			};
-
-			for(i=0;i<lenf;i++)
-				db[dblen+i]=newdata[lastscan+i]-old[lastpos+i];
-			for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
-				eb[eblen+i]=newdata[lastscan+lenf+i];
-
-			dblen+=lenf;
-			eblen+=(scan-lenb)-(lastscan+lenf);
-
-			offtout(lenf,buf);
-			BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
-			if (bz2err != BZ_OK)
-				errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
-
-			offtout((scan-lenb)-(lastscan+lenf),buf);
-			BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
-			if (bz2err != BZ_OK)
-				errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
-
-			offtout((pos-lenb)-(lastpos+lenf),buf);
-			BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
-			if (bz2err != BZ_OK)
-				errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
-
-			lastscan=scan-lenb;
-			lastpos=pos-lenb;
-			lastoffset=pos-scan;
-		};
-	};
-	BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
-	if (bz2err != BZ_OK)
-		errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
-
-	/* Compute size of compressed ctrl data */
-	if ((len = ftello(pf)) == -1)
-		err(1, "ftello");
-	offtout(len-32, header + 8);
-
-	/* Write compressed diff data */
-	if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
-		errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
-	BZ2_bzWrite(&bz2err, pfbz2, db, dblen);
-	if (bz2err != BZ_OK)
-		errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
-	BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
-	if (bz2err != BZ_OK)
-		errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
-
-	/* Compute size of compressed diff data */
-	if ((newsize = ftello(pf)) == -1)
-		err(1, "ftello");
-	offtout(newsize - len, header + 16);
-
-	/* Write compressed extra data */
-	if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
-		errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
-	BZ2_bzWrite(&bz2err, pfbz2, eb, eblen);
-	if (bz2err != BZ_OK)
-		errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
-	BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
-	if (bz2err != BZ_OK)
-		errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
-
-	/* Seek to the beginning, write the header, and close the file */
-	if (fseeko(pf, 0, SEEK_SET))
-		err(1, "fseeko");
-	if (fwrite(header, 32, 1, pf) != 1)
-		err(1, "fwrite(%s)", patch_filename);
-	if (fclose(pf))
-		err(1, "fclose");
-
-	/* Free the memory we used */
-	free(db);
-	free(eb);
-
-	return 0;
-}
diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp
index ebb55f1..9920c2b 100644
--- a/applypatch/bspatch.cpp
+++ b/applypatch/bspatch.cpp
@@ -21,16 +21,12 @@
 // notice.
 
 #include <stdio.h>
-#include <sys/stat.h>
 #include <sys/types.h>
-#include <errno.h>
-#include <unistd.h>
-#include <string.h>
 
-#include <bzlib.h>
+#include <bspatch.h>
 
+#include "applypatch/applypatch.h"
 #include "openssl/sha.h"
-#include "applypatch.h"
 
 void ShowBSDiffLicense() {
     puts("The bsdiff library used herein is:\n"
@@ -64,184 +60,25 @@
         );
 }
 
-static off_t offtin(u_char *buf)
-{
-    off_t y;
-
-    y=buf[7]&0x7F;
-    y=y*256;y+=buf[6];
-    y=y*256;y+=buf[5];
-    y=y*256;y+=buf[4];
-    y=y*256;y+=buf[3];
-    y=y*256;y+=buf[2];
-    y=y*256;y+=buf[1];
-    y=y*256;y+=buf[0];
-
-    if(buf[7]&0x80) y=-y;
-
-    return y;
+int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, const Value* patch,
+                     ssize_t patch_offset, SinkFn sink, void* token, SHA_CTX* ctx) {
+  auto sha_sink = [&](const uint8_t* data, size_t len) {
+    len = sink(data, len, token);
+    if (ctx) SHA1_Update(ctx, data, len);
+    return len;
+  };
+  return bsdiff::bspatch(old_data, old_size,
+                         reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]),
+                         patch->data.size(), sha_sink);
 }
 
-int FillBuffer(unsigned char* buffer, int size, bz_stream* stream) {
-    stream->next_out = (char*)buffer;
-    stream->avail_out = size;
-    while (stream->avail_out > 0) {
-        int bzerr = BZ2_bzDecompress(stream);
-        if (bzerr != BZ_OK && bzerr != BZ_STREAM_END) {
-            printf("bz error %d decompressing\n", bzerr);
-            return -1;
-        }
-        if (stream->avail_out > 0) {
-            printf("need %d more bytes\n", stream->avail_out);
-        }
-    }
-    return 0;
-}
-
-int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size,
-                     const Value* patch, ssize_t patch_offset,
-                     SinkFn sink, void* token, SHA_CTX* ctx) {
-
-    std::vector<unsigned char> new_data;
-    if (ApplyBSDiffPatchMem(old_data, old_size, patch, patch_offset, &new_data) != 0) {
-        return -1;
-    }
-
-    if (sink(new_data.data(), new_data.size(), token) < static_cast<ssize_t>(new_data.size())) {
-        printf("short write of output: %d (%s)\n", errno, strerror(errno));
-        return 1;
-    }
-    if (ctx) SHA1_Update(ctx, new_data.data(), new_data.size());
-    return 0;
-}
-
-int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size,
-                        const Value* patch, ssize_t patch_offset,
-                        std::vector<unsigned char>* new_data) {
-    // Patch data format:
-    //   0       8       "BSDIFF40"
-    //   8       8       X
-    //   16      8       Y
-    //   24      8       sizeof(newfile)
-    //   32      X       bzip2(control block)
-    //   32+X    Y       bzip2(diff block)
-    //   32+X+Y  ???     bzip2(extra block)
-    // with control block a set of triples (x,y,z) meaning "add x bytes
-    // from oldfile to x bytes from the diff block; copy y bytes from the
-    // extra block; seek forwards in oldfile by z bytes".
-
-    unsigned char* header = (unsigned char*) patch->data + patch_offset;
-    if (memcmp(header, "BSDIFF40", 8) != 0) {
-        printf("corrupt bsdiff patch file header (magic number)\n");
-        return 1;
-    }
-
-    ssize_t ctrl_len, data_len, new_size;
-    ctrl_len = offtin(header+8);
-    data_len = offtin(header+16);
-    new_size = offtin(header+24);
-
-    if (ctrl_len < 0 || data_len < 0 || new_size < 0) {
-        printf("corrupt patch file header (data lengths)\n");
-        return 1;
-    }
-
-    int bzerr;
-
-    bz_stream cstream;
-    cstream.next_in = patch->data + patch_offset + 32;
-    cstream.avail_in = ctrl_len;
-    cstream.bzalloc = NULL;
-    cstream.bzfree = NULL;
-    cstream.opaque = NULL;
-    if ((bzerr = BZ2_bzDecompressInit(&cstream, 0, 0)) != BZ_OK) {
-        printf("failed to bzinit control stream (%d)\n", bzerr);
-    }
-
-    bz_stream dstream;
-    dstream.next_in = patch->data + patch_offset + 32 + ctrl_len;
-    dstream.avail_in = data_len;
-    dstream.bzalloc = NULL;
-    dstream.bzfree = NULL;
-    dstream.opaque = NULL;
-    if ((bzerr = BZ2_bzDecompressInit(&dstream, 0, 0)) != BZ_OK) {
-        printf("failed to bzinit diff stream (%d)\n", bzerr);
-    }
-
-    bz_stream estream;
-    estream.next_in = patch->data + patch_offset + 32 + ctrl_len + data_len;
-    estream.avail_in = patch->size - (patch_offset + 32 + ctrl_len + data_len);
-    estream.bzalloc = NULL;
-    estream.bzfree = NULL;
-    estream.opaque = NULL;
-    if ((bzerr = BZ2_bzDecompressInit(&estream, 0, 0)) != BZ_OK) {
-        printf("failed to bzinit extra stream (%d)\n", bzerr);
-    }
-
-    new_data->resize(new_size);
-
-    off_t oldpos = 0, newpos = 0;
-    off_t ctrl[3];
-    off_t len_read;
-    int i;
-    unsigned char buf[24];
-    while (newpos < new_size) {
-        // Read control data
-        if (FillBuffer(buf, 24, &cstream) != 0) {
-            printf("error while reading control stream\n");
-            return 1;
-        }
-        ctrl[0] = offtin(buf);
-        ctrl[1] = offtin(buf+8);
-        ctrl[2] = offtin(buf+16);
-
-        if (ctrl[0] < 0 || ctrl[1] < 0) {
-            printf("corrupt patch (negative byte counts)\n");
-            return 1;
-        }
-
-        // Sanity check
-        if (newpos + ctrl[0] > new_size) {
-            printf("corrupt patch (new file overrun)\n");
-            return 1;
-        }
-
-        // Read diff string
-        if (FillBuffer(new_data->data() + newpos, ctrl[0], &dstream) != 0) {
-            printf("error while reading diff stream\n");
-            return 1;
-        }
-
-        // Add old data to diff string
-        for (i = 0; i < ctrl[0]; ++i) {
-            if ((oldpos+i >= 0) && (oldpos+i < old_size)) {
-                (*new_data)[newpos+i] += old_data[oldpos+i];
-            }
-        }
-
-        // Adjust pointers
-        newpos += ctrl[0];
-        oldpos += ctrl[0];
-
-        // Sanity check
-        if (newpos + ctrl[1] > new_size) {
-            printf("corrupt patch (new file overrun)\n");
-            return 1;
-        }
-
-        // Read extra string
-        if (FillBuffer(new_data->data() + newpos, ctrl[1], &estream) != 0) {
-            printf("error while reading extra stream\n");
-            return 1;
-        }
-
-        // Adjust pointers
-        newpos += ctrl[1];
-        oldpos += ctrl[2];
-    }
-
-    BZ2_bzDecompressEnd(&cstream);
-    BZ2_bzDecompressEnd(&dstream);
-    BZ2_bzDecompressEnd(&estream);
-    return 0;
+int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, const Value* patch,
+                        ssize_t patch_offset, std::vector<unsigned char>* new_data) {
+  auto vector_sink = [new_data](const uint8_t* data, size_t len) {
+    new_data->insert(new_data->end(), data, data + len);
+    return len;
+  };
+  return bsdiff::bspatch(old_data, old_size,
+                         reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]),
+                         patch->data.size(), vector_sink);
 }
diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp
index c84f427..331cae2 100644
--- a/applypatch/freecache.cpp
+++ b/applypatch/freecache.cpp
@@ -32,7 +32,7 @@
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 
-#include "applypatch.h"
+#include "applypatch/applypatch.h"
 
 static int EliminateOpenFiles(std::set<std::string>* files) {
   std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir);
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index f22502e..41d73ab 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -121,618 +121,687 @@
  * information that is stored on the system partition.
  */
 
+#include "applypatch/imgdiff.h"
+
 #include <errno.h>
-#include <inttypes.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
-#include <unistd.h>
 #include <sys/types.h>
+#include <unistd.h>
 
-#include "zlib.h"
-#include "imgdiff.h"
-#include "utils.h"
+#include <algorithm>
+#include <string>
+#include <vector>
 
-typedef struct {
-  int type;             // CHUNK_NORMAL, CHUNK_DEFLATE
-  size_t start;         // offset of chunk in original image file
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/memory.h>
+#include <android-base/unique_fd.h>
+#include <ziparchive/zip_archive.h>
 
-  size_t len;
-  unsigned char* data;  // data to be patched (uncompressed, for deflate chunks)
+#include <bsdiff.h>
+#include <zlib.h>
 
-  size_t source_start;
-  size_t source_len;
+using android::base::get_unaligned;
 
-  off_t* I;             // used by bsdiff
+static constexpr auto BUFFER_SIZE = 0x8000;
+
+// If we use this function to write the offset and length (type size_t), their values should not
+// exceed 2^63; because the signed bit will be casted away.
+static inline bool Write8(int fd, int64_t value) {
+  return android::base::WriteFully(fd, &value, sizeof(int64_t));
+}
+
+// Similarly, the value should not exceed 2^31 if we are casting from size_t (e.g. target chunk
+// size).
+static inline bool Write4(int fd, int32_t value) {
+  return android::base::WriteFully(fd, &value, sizeof(int32_t));
+}
+
+class ImageChunk {
+ public:
+  static constexpr auto WINDOWBITS = -15;  // 32kb window; negative to indicate a raw stream.
+  static constexpr auto MEMLEVEL = 8;      // the default value.
+  static constexpr auto METHOD = Z_DEFLATED;
+  static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
+
+  ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len)
+      : type_(type),
+        start_(start),
+        input_file_ptr_(file_content),
+        raw_data_len_(raw_data_len),
+        compress_level_(6),
+        source_start_(0),
+        source_len_(0),
+        source_uncompressed_len_(0) {
+    CHECK(file_content != nullptr) << "input file container can't be nullptr";
+  }
+
+  int GetType() const {
+    return type_;
+  }
+  size_t GetRawDataLength() const {
+    return raw_data_len_;
+  }
+  const std::string& GetEntryName() const {
+    return entry_name_;
+  }
+
+  // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
+  // the raw data.
+  const uint8_t * DataForPatch() const;
+  size_t DataLengthForPatch() const;
+
+  void Dump() const {
+    printf("type %d start %zu len %zu\n", type_, start_, DataLengthForPatch());
+  }
+
+  void SetSourceInfo(const ImageChunk& other);
+  void SetEntryName(std::string entryname);
+  void SetUncompressedData(std::vector<uint8_t> data);
+  bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+
+  bool operator==(const ImageChunk& other) const;
+  bool operator!=(const ImageChunk& other) const {
+    return !(*this == other);
+  }
+
+  size_t GetHeaderSize(size_t patch_size) const;
+  // Return the offset of the next patch into the patch data.
+  size_t WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset);
+
+  /*
+   * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob
+   * of uninterpreted data).  The resulting patch will likely be about
+   * as big as the target file, but it lets us handle the case of images
+   * where some gzip chunks are reconstructible but others aren't (by
+   * treating the ones that aren't as normal chunks).
+   */
+  void ChangeDeflateChunkToNormal();
+  bool ChangeChunkToRaw(size_t patch_size);
+
+  /*
+   * Verify that we can reproduce exactly the same compressed data that
+   * we started with.  Sets the level, method, windowBits, memLevel, and
+   * strategy fields in the chunk to the encoding parameters needed to
+   * produce the right output.
+   */
+  bool ReconstructDeflateChunk();
+  bool IsAdjacentNormal(const ImageChunk& other) const;
+  void MergeAdjacentNormal(const ImageChunk& other);
+
+ private:
+  int type_;                                    // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
+  size_t start_;                                // offset of chunk in the original input file
+  const std::vector<uint8_t>* input_file_ptr_;  // ptr to the full content of original input file
+  size_t raw_data_len_;
 
   // --- for CHUNK_DEFLATE chunks only: ---
-
-  // original (compressed) deflate data
-  size_t deflate_len;
-  unsigned char* deflate_data;
-
-  char* filename;       // used for zip entries
+  std::vector<uint8_t> uncompressed_data_;
+  std::string entry_name_;  // used for zip entries
 
   // deflate encoder parameters
-  int level, method, windowBits, memLevel, strategy;
+  int compress_level_;
 
-  size_t source_uncompressed_len;
-} ImageChunk;
+  size_t source_start_;
+  size_t source_len_;
+  size_t source_uncompressed_len_;
 
-typedef struct {
-  int data_offset;
-  int deflate_len;
-  int uncomp_len;
-  char* filename;
-} ZipFileEntry;
+  const uint8_t* GetRawData() const;
+  bool TryReconstruction(int level);
+};
 
-static int fileentry_compare(const void* a, const void* b) {
-  int ao = ((ZipFileEntry*)a)->data_offset;
-  int bo = ((ZipFileEntry*)b)->data_offset;
-  if (ao < bo) {
-    return -1;
-  } else if (ao > bo) {
-    return 1;
-  } else {
-    return 0;
+const uint8_t* ImageChunk::GetRawData() const {
+  CHECK_LE(start_ + raw_data_len_, input_file_ptr_->size());
+  return input_file_ptr_->data() + start_;
+}
+
+const uint8_t * ImageChunk::DataForPatch() const {
+  if (type_ == CHUNK_DEFLATE) {
+    return uncompressed_data_.data();
+  }
+  return GetRawData();
+}
+
+size_t ImageChunk::DataLengthForPatch() const {
+  if (type_ == CHUNK_DEFLATE) {
+    return uncompressed_data_.size();
+  }
+  return raw_data_len_;
+}
+
+bool ImageChunk::operator==(const ImageChunk& other) const {
+  if (type_ != other.type_) {
+    return false;
+  }
+  return (raw_data_len_ == other.raw_data_len_ &&
+          memcmp(GetRawData(), other.GetRawData(), raw_data_len_) == 0);
+}
+
+void ImageChunk::SetSourceInfo(const ImageChunk& src) {
+  source_start_ = src.start_;
+  if (type_ == CHUNK_NORMAL) {
+    source_len_ = src.raw_data_len_;
+  } else if (type_ == CHUNK_DEFLATE) {
+    source_len_ = src.raw_data_len_;
+    source_uncompressed_len_ = src.uncompressed_data_.size();
   }
 }
 
-// from bsdiff.c
-int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* newdata, off_t newsize,
-           const char* patch_filename);
+void ImageChunk::SetEntryName(std::string entryname) {
+  entry_name_ = std::move(entryname);
+}
 
-unsigned char* ReadZip(const char* filename,
-                       int* num_chunks, ImageChunk** chunks,
-                       int include_pseudo_chunk) {
-  struct stat st;
-  if (stat(filename, &st) != 0) {
-    printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
-    return NULL;
+void ImageChunk::SetUncompressedData(std::vector<uint8_t> data) {
+  uncompressed_data_ = std::move(data);
+}
+
+bool ImageChunk::SetBonusData(const std::vector<uint8_t>& bonus_data) {
+  if (type_ != CHUNK_DEFLATE) {
+    return false;
   }
+  uncompressed_data_.insert(uncompressed_data_.end(), bonus_data.begin(), bonus_data.end());
+  return true;
+}
 
-  size_t sz = static_cast<size_t>(st.st_size);
-  unsigned char* img = reinterpret_cast<unsigned char*>(malloc(sz));
-  FILE* f = fopen(filename, "rb");
-  if (fread(img, 1, sz, f) != sz) {
-    printf("failed to read \"%s\" %s\n", filename, strerror(errno));
-    fclose(f);
-    return NULL;
+// Convert CHUNK_NORMAL & CHUNK_DEFLATE to CHUNK_RAW if the target size is
+// smaller. Also take the header size into account during size comparison.
+bool ImageChunk::ChangeChunkToRaw(size_t patch_size) {
+  if (type_ == CHUNK_RAW) {
+    return true;
+  } else if (type_ == CHUNK_NORMAL && (raw_data_len_ <= 160 || raw_data_len_ < patch_size)) {
+    type_ = CHUNK_RAW;
+    return true;
   }
-  fclose(f);
+  return false;
+}
 
-  // look for the end-of-central-directory record.
+void ImageChunk::ChangeDeflateChunkToNormal() {
+  if (type_ != CHUNK_DEFLATE) return;
+  type_ = CHUNK_NORMAL;
+  entry_name_.clear();
+  uncompressed_data_.clear();
+}
 
-  int i;
-  for (i = st.st_size-20; i >= 0 && i > st.st_size - 65600; --i) {
-    if (img[i] == 0x50 && img[i+1] == 0x4b &&
-        img[i+2] == 0x05 && img[i+3] == 0x06) {
-      break;
-    }
+// Header size:
+// header_type    4 bytes
+// CHUNK_NORMAL   8*3 = 24 bytes
+// CHUNK_DEFLATE  8*5 + 4*5 = 60 bytes
+// CHUNK_RAW      4 bytes + patch_size
+size_t ImageChunk::GetHeaderSize(size_t patch_size) const {
+  switch (type_) {
+    case CHUNK_NORMAL:
+      return 4 + 8 * 3;
+    case CHUNK_DEFLATE:
+      return 4 + 8 * 5 + 4 * 5;
+    case CHUNK_RAW:
+      return 4 + 4 + patch_size;
+    default:
+      CHECK(false) << "unexpected chunk type: " << type_;  // Should not reach here.
+      return 0;
   }
-  // double-check: this archive consists of a single "disk"
-  if (!(img[i+4] == 0 && img[i+5] == 0 && img[i+6] == 0 && img[i+7] == 0)) {
-    printf("can't process multi-disk archive\n");
-    return NULL;
-  }
+}
 
-  int cdcount = Read2(img+i+8);
-  int cdoffset = Read4(img+i+16);
-
-  ZipFileEntry* temp_entries = reinterpret_cast<ZipFileEntry*>(malloc(
-      cdcount * sizeof(ZipFileEntry)));
-  int entrycount = 0;
-
-  unsigned char* cd = img+cdoffset;
-  for (i = 0; i < cdcount; ++i) {
-    if (!(cd[0] == 0x50 && cd[1] == 0x4b && cd[2] == 0x01 && cd[3] == 0x02)) {
-      printf("bad central directory entry %d\n", i);
-      return NULL;
-    }
-
-    int clen = Read4(cd+20);   // compressed len
-    int ulen = Read4(cd+24);   // uncompressed len
-    int nlen = Read2(cd+28);   // filename len
-    int xlen = Read2(cd+30);   // extra field len
-    int mlen = Read2(cd+32);   // file comment len
-    int hoffset = Read4(cd+42);   // local header offset
-
-    char* filename = reinterpret_cast<char*>(malloc(nlen+1));
-    memcpy(filename, cd+46, nlen);
-    filename[nlen] = '\0';
-
-    int method = Read2(cd+10);
-
-    cd += 46 + nlen + xlen + mlen;
-
-    if (method != 8) {  // 8 == deflate
-      free(filename);
-      continue;
-    }
-
-    unsigned char* lh = img + hoffset;
-
-    if (!(lh[0] == 0x50 && lh[1] == 0x4b && lh[2] == 0x03 && lh[3] == 0x04)) {
-      printf("bad local file header entry %d\n", i);
-      return NULL;
-    }
-
-    if (Read2(lh+26) != nlen || memcmp(lh+30, filename, nlen) != 0) {
-      printf("central dir filename doesn't match local header\n");
-      return NULL;
-    }
-
-    xlen = Read2(lh+28);   // extra field len; might be different from CD entry?
-
-    temp_entries[entrycount].data_offset = hoffset+30+nlen+xlen;
-    temp_entries[entrycount].deflate_len = clen;
-    temp_entries[entrycount].uncomp_len = ulen;
-    temp_entries[entrycount].filename = filename;
-    ++entrycount;
-  }
-
-  qsort(temp_entries, entrycount, sizeof(ZipFileEntry), fileentry_compare);
-
-#if 0
-  printf("found %d deflated entries\n", entrycount);
-  for (i = 0; i < entrycount; ++i) {
-    printf("off %10d  len %10d unlen %10d   %p %s\n",
-           temp_entries[i].data_offset,
-           temp_entries[i].deflate_len,
-           temp_entries[i].uncomp_len,
-           temp_entries[i].filename,
-           temp_entries[i].filename);
-  }
-#endif
-
-  *num_chunks = 0;
-  *chunks = reinterpret_cast<ImageChunk*>(malloc((entrycount*2+2) * sizeof(ImageChunk)));
-  ImageChunk* curr = *chunks;
-
-  if (include_pseudo_chunk) {
-    curr->type = CHUNK_NORMAL;
-    curr->start = 0;
-    curr->len = st.st_size;
-    curr->data = img;
-    curr->filename = NULL;
-    curr->I = NULL;
-    ++curr;
-    ++*num_chunks;
-  }
-
-  int pos = 0;
-  int nextentry = 0;
-
-  while (pos < st.st_size) {
-    if (nextentry < entrycount && pos == temp_entries[nextentry].data_offset) {
-      curr->type = CHUNK_DEFLATE;
-      curr->start = pos;
-      curr->deflate_len = temp_entries[nextentry].deflate_len;
-      curr->deflate_data = img + pos;
-      curr->filename = temp_entries[nextentry].filename;
-      curr->I = NULL;
-
-      curr->len = temp_entries[nextentry].uncomp_len;
-      curr->data = reinterpret_cast<unsigned char*>(malloc(curr->len));
-
-      z_stream strm;
-      strm.zalloc = Z_NULL;
-      strm.zfree = Z_NULL;
-      strm.opaque = Z_NULL;
-      strm.avail_in = curr->deflate_len;
-      strm.next_in = curr->deflate_data;
-
-      // -15 means we are decoding a 'raw' deflate stream; zlib will
-      // not expect zlib headers.
-      int ret = inflateInit2(&strm, -15);
-
-      strm.avail_out = curr->len;
-      strm.next_out = curr->data;
-      ret = inflate(&strm, Z_NO_FLUSH);
-      if (ret != Z_STREAM_END) {
-        printf("failed to inflate \"%s\"; %d\n", curr->filename, ret);
-        return NULL;
+size_t ImageChunk::WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) {
+  Write4(fd, type_);
+  switch (type_) {
+    case CHUNK_NORMAL:
+      printf("normal   (%10zu, %10zu)  %10zu\n", start_, raw_data_len_, patch.size());
+      Write8(fd, static_cast<int64_t>(source_start_));
+      Write8(fd, static_cast<int64_t>(source_len_));
+      Write8(fd, static_cast<int64_t>(offset));
+      return offset + patch.size();
+    case CHUNK_DEFLATE:
+      printf("deflate  (%10zu, %10zu)  %10zu  %s\n", start_, raw_data_len_, patch.size(),
+             entry_name_.c_str());
+      Write8(fd, static_cast<int64_t>(source_start_));
+      Write8(fd, static_cast<int64_t>(source_len_));
+      Write8(fd, static_cast<int64_t>(offset));
+      Write8(fd, static_cast<int64_t>(source_uncompressed_len_));
+      Write8(fd, static_cast<int64_t>(uncompressed_data_.size()));
+      Write4(fd, compress_level_);
+      Write4(fd, METHOD);
+      Write4(fd, WINDOWBITS);
+      Write4(fd, MEMLEVEL);
+      Write4(fd, STRATEGY);
+      return offset + patch.size();
+    case CHUNK_RAW:
+      printf("raw      (%10zu, %10zu)\n", start_, raw_data_len_);
+      Write4(fd, static_cast<int32_t>(patch.size()));
+      if (!android::base::WriteFully(fd, patch.data(), patch.size())) {
+        CHECK(false) << "failed to write " << patch.size() <<" bytes patch";
       }
+      return offset;
+    default:
+      CHECK(false) << "unexpected chunk type: " << type_;
+      return offset;
+  }
+}
 
-      inflateEnd(&strm);
+bool ImageChunk::IsAdjacentNormal(const ImageChunk& other) const {
+  if (type_ != CHUNK_NORMAL || other.type_ != CHUNK_NORMAL) {
+    return false;
+  }
+  return (other.start_ == start_ + raw_data_len_);
+}
 
-      pos += curr->deflate_len;
-      ++nextentry;
-      ++*num_chunks;
-      ++curr;
-      continue;
-    }
+void ImageChunk::MergeAdjacentNormal(const ImageChunk& other) {
+  CHECK(IsAdjacentNormal(other));
+  raw_data_len_ = raw_data_len_ + other.raw_data_len_;
+}
 
-    // use a normal chunk to take all the data up to the start of the
-    // next deflate section.
-
-    curr->type = CHUNK_NORMAL;
-    curr->start = pos;
-    if (nextentry < entrycount) {
-      curr->len = temp_entries[nextentry].data_offset - pos;
-    } else {
-      curr->len = st.st_size - pos;
-    }
-    curr->data = img + pos;
-    curr->filename = NULL;
-    curr->I = NULL;
-    pos += curr->len;
-
-    ++*num_chunks;
-    ++curr;
+bool ImageChunk::ReconstructDeflateChunk() {
+  if (type_ != CHUNK_DEFLATE) {
+    printf("attempt to reconstruct non-deflate chunk\n");
+    return false;
   }
 
-  free(temp_entries);
-  return img;
+  // We only check two combinations of encoder parameters:  level 6
+  // (the default) and level 9 (the maximum).
+  for (int level = 6; level <= 9; level += 3) {
+    if (TryReconstruction(level)) {
+      compress_level_ = level;
+      return true;
+    }
+  }
+
+  return false;
 }
 
 /*
- * Read the given file and break it up into chunks, putting the number
- * of chunks and their info in *num_chunks and **chunks,
- * respectively.  Returns a malloc'd block of memory containing the
- * contents of the file; various pointers in the output chunk array
- * will point into this block of memory.  The caller should free the
- * return value when done with all the chunks.  Returns NULL on
- * failure.
+ * Takes the uncompressed data stored in the chunk, compresses it
+ * using the zlib parameters stored in the chunk, and checks that it
+ * matches exactly the compressed data we started with (also stored in
+ * the chunk).
  */
-unsigned char* ReadImage(const char* filename,
-                         int* num_chunks, ImageChunk** chunks) {
+bool ImageChunk::TryReconstruction(int level) {
+  z_stream strm;
+  strm.zalloc = Z_NULL;
+  strm.zfree = Z_NULL;
+  strm.opaque = Z_NULL;
+  strm.avail_in = uncompressed_data_.size();
+  strm.next_in = uncompressed_data_.data();
+  int ret = deflateInit2(&strm, level, METHOD, WINDOWBITS, MEMLEVEL, STRATEGY);
+  if (ret < 0) {
+    printf("failed to initialize deflate: %d\n", ret);
+    return false;
+  }
+
+  std::vector<uint8_t> buffer(BUFFER_SIZE);
+  size_t offset = 0;
+  do {
+    strm.avail_out = buffer.size();
+    strm.next_out = buffer.data();
+    ret = deflate(&strm, Z_FINISH);
+    if (ret < 0) {
+      printf("failed to deflate: %d\n", ret);
+      return false;
+    }
+
+    size_t compressed_size = buffer.size() - strm.avail_out;
+    if (memcmp(buffer.data(), input_file_ptr_->data() + start_ + offset, compressed_size) != 0) {
+      // mismatch; data isn't the same.
+      deflateEnd(&strm);
+      return false;
+    }
+    offset += compressed_size;
+  } while (ret != Z_STREAM_END);
+  deflateEnd(&strm);
+
+  if (offset != raw_data_len_) {
+    // mismatch; ran out of data before we should have.
+    return false;
+  }
+  return true;
+}
+
+// EOCD record
+// offset 0: signature 0x06054b50, 4 bytes
+// offset 4: number of this disk, 2 bytes
+// ...
+// offset 20: comment length, 2 bytes
+// offset 22: comment, n bytes
+static bool GetZipFileSize(const std::vector<uint8_t>& zip_file, size_t* input_file_size) {
+  if (zip_file.size() < 22) {
+    printf("file is too small to be a zip file\n");
+    return false;
+  }
+
+  // Look for End of central directory record of the zip file, and calculate the actual
+  // zip_file size.
+  for (int i = zip_file.size() - 22; i >= 0; i--) {
+    if (zip_file[i] == 0x50) {
+      if (get_unaligned<uint32_t>(&zip_file[i]) == 0x06054b50) {
+        // double-check: this archive consists of a single "disk".
+        CHECK_EQ(get_unaligned<uint16_t>(&zip_file[i + 4]), 0);
+
+        uint16_t comment_length = get_unaligned<uint16_t>(&zip_file[i + 20]);
+        size_t file_size = i + 22 + comment_length;
+        CHECK_LE(file_size, zip_file.size());
+        *input_file_size = file_size;
+        return true;
+      }
+    }
+  }
+
+  // EOCD not found, this file is likely not a valid zip file.
+  return false;
+}
+
+static bool ReadZip(const char* filename, std::vector<ImageChunk>* chunks,
+                    std::vector<uint8_t>* zip_file, bool include_pseudo_chunk) {
+  CHECK(chunks != nullptr && zip_file != nullptr);
+
+  android::base::unique_fd fd(open(filename, O_RDONLY));
+  if (fd == -1) {
+    printf("failed to open \"%s\" %s\n", filename, strerror(errno));
+    return false;
+  }
   struct stat st;
-  if (stat(filename, &st) != 0) {
+  if (fstat(fd, &st) != 0) {
     printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
-    return NULL;
+    return false;
   }
 
   size_t sz = static_cast<size_t>(st.st_size);
-  unsigned char* img = reinterpret_cast<unsigned char*>(malloc(sz + 4));
-  FILE* f = fopen(filename, "rb");
-  if (fread(img, 1, sz, f) != sz) {
+  zip_file->resize(sz);
+  if (!android::base::ReadFully(fd, zip_file->data(), sz)) {
     printf("failed to read \"%s\" %s\n", filename, strerror(errno));
-    fclose(f);
-    return NULL;
+    return false;
   }
-  fclose(f);
+  fd.reset();
 
-  // append 4 zero bytes to the data so we can always search for the
-  // four-byte string 1f8b0800 starting at any point in the actual
-  // file data, without special-casing the end of the data.
-  memset(img+sz, 0, 4);
+  // Trim the trailing zeros before we pass the file to ziparchive handler.
+  size_t zipfile_size;
+  if (!GetZipFileSize(*zip_file, &zipfile_size)) {
+    printf("failed to parse the actual size of %s\n", filename);
+    return false;
+  }
+  ZipArchiveHandle handle;
+  int err = OpenArchiveFromMemory(zip_file->data(), zipfile_size, filename, &handle);
+  if (err != 0) {
+    printf("failed to open zip file %s: %s\n", filename, ErrorCodeString(err));
+    CloseArchive(handle);
+    return false;
+  }
+
+  // Create a list of deflated zip entries, sorted by offset.
+  std::vector<std::pair<std::string, ZipEntry>> temp_entries;
+  void* cookie;
+  int ret = StartIteration(handle, &cookie, nullptr, nullptr);
+  if (ret != 0) {
+    printf("failed to iterate over entries in %s: %s\n", filename, ErrorCodeString(ret));
+    CloseArchive(handle);
+    return false;
+  }
+
+  ZipString name;
+  ZipEntry entry;
+  while ((ret = Next(cookie, &entry, &name)) == 0) {
+    if (entry.method == kCompressDeflated) {
+      std::string entryname(name.name, name.name + name.name_length);
+      temp_entries.push_back(std::make_pair(entryname, entry));
+    }
+  }
+
+  if (ret != -1) {
+    printf("Error while iterating over zip entries: %s\n", ErrorCodeString(ret));
+    CloseArchive(handle);
+    return false;
+  }
+  std::sort(temp_entries.begin(), temp_entries.end(),
+            [](auto& entry1, auto& entry2) {
+              return entry1.second.offset < entry2.second.offset;
+            });
+
+  EndIteration(cookie);
+
+  if (include_pseudo_chunk) {
+    chunks->emplace_back(CHUNK_NORMAL, 0, zip_file, zip_file->size());
+  }
+
+  size_t pos = 0;
+  size_t nextentry = 0;
+  while (pos < zip_file->size()) {
+    if (nextentry < temp_entries.size() &&
+        static_cast<off64_t>(pos) == temp_entries[nextentry].second.offset) {
+      // compose the next deflate chunk.
+      std::string entryname = temp_entries[nextentry].first;
+      size_t uncompressed_len = temp_entries[nextentry].second.uncompressed_length;
+      std::vector<uint8_t> uncompressed_data(uncompressed_len);
+      if ((ret = ExtractToMemory(handle, &temp_entries[nextentry].second, uncompressed_data.data(),
+                                 uncompressed_len)) != 0) {
+        printf("failed to extract %s with size %zu: %s\n", entryname.c_str(), uncompressed_len,
+               ErrorCodeString(ret));
+        CloseArchive(handle);
+        return false;
+      }
+
+      size_t compressed_len = temp_entries[nextentry].second.compressed_length;
+      ImageChunk curr(CHUNK_DEFLATE, pos, zip_file, compressed_len);
+      curr.SetEntryName(std::move(entryname));
+      curr.SetUncompressedData(std::move(uncompressed_data));
+      chunks->push_back(curr);
+
+      pos += compressed_len;
+      ++nextentry;
+      continue;
+    }
+
+    // Use a normal chunk to take all the data up to the start of the next deflate section.
+    size_t raw_data_len;
+    if (nextentry < temp_entries.size()) {
+      raw_data_len = temp_entries[nextentry].second.offset - pos;
+    } else {
+      raw_data_len = zip_file->size() - pos;
+    }
+    chunks->emplace_back(CHUNK_NORMAL, pos, zip_file, raw_data_len);
+
+    pos += raw_data_len;
+  }
+
+  CloseArchive(handle);
+  return true;
+}
+
+// Read the given file and break it up into chunks, and putting the data in to a vector.
+static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
+                      std::vector<uint8_t>* img) {
+  CHECK(chunks != nullptr && img != nullptr);
+
+  android::base::unique_fd fd(open(filename, O_RDONLY));
+  if (fd == -1) {
+    printf("failed to open \"%s\" %s\n", filename, strerror(errno));
+    return false;
+  }
+  struct stat st;
+  if (fstat(fd, &st) != 0) {
+    printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+    return false;
+  }
+
+  size_t sz = static_cast<size_t>(st.st_size);
+  img->resize(sz);
+  if (!android::base::ReadFully(fd, img->data(), sz)) {
+    printf("failed to read \"%s\" %s\n", filename, strerror(errno));
+    return false;
+  }
 
   size_t pos = 0;
 
-  *num_chunks = 0;
-  *chunks = NULL;
-
   while (pos < sz) {
-    unsigned char* p = img+pos;
-
-    bool processed_deflate = false;
-    if (sz - pos >= 4 &&
-        p[0] == 0x1f && p[1] == 0x8b &&
-        p[2] == 0x08 &&    // deflate compression
-        p[3] == 0x00) {    // no header flags
+    // 0x00 no header flags, 0x08 deflate compression, 0x1f8b gzip magic number
+    if (sz - pos >= 4 && get_unaligned<uint32_t>(img->data() + pos) == 0x00088b1f) {
       // 'pos' is the offset of the start of a gzip chunk.
       size_t chunk_offset = pos;
 
-      *num_chunks += 3;
-      *chunks = reinterpret_cast<ImageChunk*>(realloc(*chunks,
-          *num_chunks * sizeof(ImageChunk)));
-      ImageChunk* curr = *chunks + (*num_chunks-3);
+      // The remaining data is too small to be a gzip chunk; treat them as a normal chunk.
+      if (sz - pos < GZIP_HEADER_LEN + GZIP_FOOTER_LEN) {
+        chunks->emplace_back(CHUNK_NORMAL, pos, img, sz - pos);
+        break;
+      }
 
-      // create a normal chunk for the header.
-      curr->start = pos;
-      curr->type = CHUNK_NORMAL;
-      curr->len = GZIP_HEADER_LEN;
-      curr->data = p;
-      curr->I = NULL;
+      // We need three chunks for the deflated image in total, one normal chunk for the header,
+      // one deflated chunk for the body, and another normal chunk for the footer.
+      chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_HEADER_LEN);
+      pos += GZIP_HEADER_LEN;
 
-      pos += curr->len;
-      p += curr->len;
-      ++curr;
-
-      curr->type = CHUNK_DEFLATE;
-      curr->filename = NULL;
-      curr->I = NULL;
-
-      // We must decompress this chunk in order to discover where it
-      // ends, and so we can put the uncompressed data and its length
-      // into curr->data and curr->len.
-
-      size_t allocated = 32768;
-      curr->len = 0;
-      curr->data = reinterpret_cast<unsigned char*>(malloc(allocated));
-      curr->start = pos;
-      curr->deflate_data = p;
+      // We must decompress this chunk in order to discover where it ends, and so we can update
+      // the uncompressed_data of the image body and its length.
 
       z_stream strm;
       strm.zalloc = Z_NULL;
       strm.zfree = Z_NULL;
       strm.opaque = Z_NULL;
       strm.avail_in = sz - pos;
-      strm.next_in = p;
+      strm.next_in = img->data() + pos;
 
       // -15 means we are decoding a 'raw' deflate stream; zlib will
       // not expect zlib headers.
       int ret = inflateInit2(&strm, -15);
+      if (ret < 0) {
+        printf("failed to initialize inflate: %d\n", ret);
+        return false;
+      }
 
+      size_t allocated = BUFFER_SIZE;
+      std::vector<uint8_t> uncompressed_data(allocated);
+      size_t uncompressed_len = 0, raw_data_len = 0;
       do {
-        strm.avail_out = allocated - curr->len;
-        strm.next_out = curr->data + curr->len;
+        strm.avail_out = allocated - uncompressed_len;
+        strm.next_out = uncompressed_data.data() + uncompressed_len;
         ret = inflate(&strm, Z_NO_FLUSH);
         if (ret < 0) {
-          if (!processed_deflate) {
-            // This is the first chunk, assume that it's just a spurious
-            // gzip header instead of a real one.
-            break;
-          }
-          printf("Error: inflate failed [%s] at file offset [%zu]\n"
-                 "imgdiff only supports gzip kernel compression,"
-                 " did you try CONFIG_KERNEL_LZO?\n",
+          printf("Warning: inflate failed [%s] at offset [%zu], treating as a normal chunk\n",
                  strm.msg, chunk_offset);
-          free(img);
-          return NULL;
+          break;
         }
-        curr->len = allocated - strm.avail_out;
+        uncompressed_len = allocated - strm.avail_out;
         if (strm.avail_out == 0) {
           allocated *= 2;
-          curr->data = reinterpret_cast<unsigned char*>(realloc(curr->data, allocated));
+          uncompressed_data.resize(allocated);
         }
-        processed_deflate = true;
       } while (ret != Z_STREAM_END);
 
-      curr->deflate_len = sz - strm.avail_in - pos;
+      raw_data_len = sz - strm.avail_in - pos;
       inflateEnd(&strm);
-      pos += curr->deflate_len;
-      p += curr->deflate_len;
-      ++curr;
+
+      if (ret < 0) {
+        continue;
+      }
+
+      ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len);
+      uncompressed_data.resize(uncompressed_len);
+      body.SetUncompressedData(std::move(uncompressed_data));
+      chunks->push_back(body);
+
+      pos += raw_data_len;
 
       // create a normal chunk for the footer
+      chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN);
 
-      curr->type = CHUNK_NORMAL;
-      curr->start = pos;
-      curr->len = GZIP_FOOTER_LEN;
-      curr->data = img+pos;
-      curr->I = NULL;
-
-      pos += curr->len;
-      p += curr->len;
-      ++curr;
+      pos += GZIP_FOOTER_LEN;
 
       // The footer (that we just skipped over) contains the size of
       // the uncompressed data.  Double-check to make sure that it
       // matches the size of the data we got when we actually did
       // the decompression.
-      size_t footer_size = Read4(p-4);
-      if (footer_size != curr[-2].len) {
-        printf("Error: footer size %zu != decompressed size %zu\n",
-            footer_size, curr[-2].len);
-        free(img);
-        return NULL;
+      size_t footer_size = get_unaligned<uint32_t>(img->data() + pos - 4);
+      if (footer_size != body.DataLengthForPatch()) {
+        printf("Error: footer size %zu != decompressed size %zu\n", footer_size,
+               body.GetRawDataLength());
+        return false;
       }
     } else {
-      // Reallocate the list for every chunk; we expect the number of
-      // chunks to be small (5 for typical boot and recovery images).
-      ++*num_chunks;
-      *chunks = reinterpret_cast<ImageChunk*>(realloc(*chunks, *num_chunks * sizeof(ImageChunk)));
-      ImageChunk* curr = *chunks + (*num_chunks-1);
-      curr->start = pos;
-      curr->I = NULL;
+      // Use a normal chunk to take all the contents until the next gzip chunk (or EOF); we expect
+      // the number of chunks to be small (5 for typical boot and recovery images).
 
-      // 'pos' is not the offset of the start of a gzip chunk, so scan
-      // forward until we find a gzip header.
-      curr->type = CHUNK_NORMAL;
-      curr->data = p;
-
-      for (curr->len = 0; curr->len < (sz - pos); ++curr->len) {
-        if (p[curr->len] == 0x1f &&
-            p[curr->len+1] == 0x8b &&
-            p[curr->len+2] == 0x08 &&
-            p[curr->len+3] == 0x00) {
+      // Scan forward until we find a gzip header.
+      size_t data_len = 0;
+      while (data_len + pos < sz) {
+        if (data_len + pos + 4 <= sz &&
+            get_unaligned<uint32_t>(img->data() + pos + data_len) == 0x00088b1f) {
           break;
         }
+        data_len++;
       }
-      pos += curr->len;
+      chunks->emplace_back(CHUNK_NORMAL, pos, img, data_len);
+
+      pos += data_len;
     }
   }
 
-  return img;
+  return true;
 }
 
-#define BUFFER_SIZE 32768
-
 /*
- * Takes the uncompressed data stored in the chunk, compresses it
- * using the zlib parameters stored in the chunk, and checks that it
- * matches exactly the compressed data we started with (also stored in
- * the chunk).  Return 0 on success.
+ * Given source and target chunks, compute a bsdiff patch between them.
+ * Store the result in the patch_data.
+ * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk
+ * is used repeatedly, pass nullptr if not needed.
  */
-int TryReconstruction(ImageChunk* chunk, unsigned char* out) {
-  size_t p = 0;
+static bool MakePatch(const ImageChunk* src, ImageChunk* tgt, std::vector<uint8_t>* patch_data,
+                      saidx_t** bsdiff_cache) {
+  if (tgt->ChangeChunkToRaw(0)) {
+    size_t patch_size = tgt->DataLengthForPatch();
+    patch_data->resize(patch_size);
+    std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
+    return true;
+  }
 
-#if 0
-  printf("trying %d %d %d %d %d\n",
-          chunk->level, chunk->method, chunk->windowBits,
-          chunk->memLevel, chunk->strategy);
+#if defined(__ANDROID__)
+  char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
+#else
+  char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
 #endif
 
-  z_stream strm;
-  strm.zalloc = Z_NULL;
-  strm.zfree = Z_NULL;
-  strm.opaque = Z_NULL;
-  strm.avail_in = chunk->len;
-  strm.next_in = chunk->data;
-  int ret;
-  ret = deflateInit2(&strm, chunk->level, chunk->method, chunk->windowBits,
-                     chunk->memLevel, chunk->strategy);
-  do {
-    strm.avail_out = BUFFER_SIZE;
-    strm.next_out = out;
-    ret = deflate(&strm, Z_FINISH);
-    size_t have = BUFFER_SIZE - strm.avail_out;
-
-    if (memcmp(out, chunk->deflate_data+p, have) != 0) {
-      // mismatch; data isn't the same.
-      deflateEnd(&strm);
-      return -1;
-    }
-    p += have;
-  } while (ret != Z_STREAM_END);
-  deflateEnd(&strm);
-  if (p != chunk->deflate_len) {
-    // mismatch; ran out of data before we should have.
-    return -1;
-  }
-  return 0;
-}
-
-/*
- * Verify that we can reproduce exactly the same compressed data that
- * we started with.  Sets the level, method, windowBits, memLevel, and
- * strategy fields in the chunk to the encoding parameters needed to
- * produce the right output.  Returns 0 on success.
- */
-int ReconstructDeflateChunk(ImageChunk* chunk) {
-  if (chunk->type != CHUNK_DEFLATE) {
-    printf("attempt to reconstruct non-deflate chunk\n");
-    return -1;
-  }
-
-  size_t p = 0;
-  unsigned char* out = reinterpret_cast<unsigned char*>(malloc(BUFFER_SIZE));
-
-  // We only check two combinations of encoder parameters:  level 6
-  // (the default) and level 9 (the maximum).
-  for (chunk->level = 6; chunk->level <= 9; chunk->level += 3) {
-    chunk->windowBits = -15;  // 32kb window; negative to indicate a raw stream.
-    chunk->memLevel = 8;      // the default value.
-    chunk->method = Z_DEFLATED;
-    chunk->strategy = Z_DEFAULT_STRATEGY;
-
-    if (TryReconstruction(chunk, out) == 0) {
-      free(out);
-      return 0;
-    }
-  }
-
-  free(out);
-  return -1;
-}
-
-/*
- * Given source and target chunks, compute a bsdiff patch between them
- * by running bsdiff in a subprocess.  Return the patch data, placing
- * its length in *size.  Return NULL on failure.  We expect the bsdiff
- * program to be in the path.
- */
-unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) {
-  if (tgt->type == CHUNK_NORMAL) {
-    if (tgt->len <= 160) {
-      tgt->type = CHUNK_RAW;
-      *size = tgt->len;
-      return tgt->data;
-    }
-  }
-
-  char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
   int fd = mkstemp(ptemp);
-
   if (fd == -1) {
-    printf("MakePatch failed to create a temporary file: %s\n",
-           strerror(errno));
-    return NULL;
+    printf("MakePatch failed to create a temporary file: %s\n", strerror(errno));
+    return false;
   }
-  close(fd); // temporary file is created and we don't need its file
-             // descriptor
+  close(fd);
 
-  int r = bsdiff(src->data, src->len, &(src->I), tgt->data, tgt->len, ptemp);
+  int r = bsdiff::bsdiff(src->DataForPatch(), src->DataLengthForPatch(), tgt->DataForPatch(),
+                         tgt->DataLengthForPatch(), ptemp, bsdiff_cache);
   if (r != 0) {
     printf("bsdiff() failed: %d\n", r);
-    return NULL;
+    return false;
   }
 
+  android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
+  if (patch_fd == -1) {
+    printf("failed to open %s: %s\n", ptemp, strerror(errno));
+    return false;
+  }
   struct stat st;
-  if (stat(ptemp, &st) != 0) {
-    printf("failed to stat patch file %s: %s\n",
-            ptemp, strerror(errno));
-    return NULL;
+  if (fstat(patch_fd, &st) != 0) {
+    printf("failed to stat patch file %s: %s\n", ptemp, strerror(errno));
+    return false;
   }
 
   size_t sz = static_cast<size_t>(st.st_size);
-  // TODO: Memory leak on error return.
-  unsigned char* data = reinterpret_cast<unsigned char*>(malloc(sz));
-
-  if (tgt->type == CHUNK_NORMAL && tgt->len <= sz) {
+  // Change the chunk type to raw if the patch takes less space that way.
+  if (tgt->ChangeChunkToRaw(sz)) {
     unlink(ptemp);
-
-    tgt->type = CHUNK_RAW;
-    *size = tgt->len;
-    return tgt->data;
+    size_t patch_size = tgt->DataLengthForPatch();
+    patch_data->resize(patch_size);
+    std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
+    return true;
   }
-
-  *size = sz;
-
-  FILE* f = fopen(ptemp, "rb");
-  if (f == NULL) {
-    printf("failed to open patch %s: %s\n", ptemp, strerror(errno));
-    return NULL;
+  patch_data->resize(sz);
+  if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
+    printf("failed to read \"%s\" %s\n", ptemp, strerror(errno));
+    return false;
   }
-  if (fread(data, 1, sz, f) != sz) {
-    printf("failed to read patch %s: %s\n", ptemp, strerror(errno));
-    return NULL;
-  }
-  fclose(f);
 
   unlink(ptemp);
+  tgt->SetSourceInfo(*src);
 
-  tgt->source_start = src->start;
-  switch (tgt->type) {
-    case CHUNK_NORMAL:
-      tgt->source_len = src->len;
-      break;
-    case CHUNK_DEFLATE:
-      tgt->source_len = src->deflate_len;
-      tgt->source_uncompressed_len = src->len;
-      break;
-  }
-
-  return data;
-}
-
-/*
- * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob
- * of uninterpreted data).  The resulting patch will likely be about
- * as big as the target file, but it lets us handle the case of images
- * where some gzip chunks are reconstructible but others aren't (by
- * treating the ones that aren't as normal chunks).
- */
-void ChangeDeflateChunkToNormal(ImageChunk* ch) {
-  if (ch->type != CHUNK_DEFLATE) return;
-  ch->type = CHUNK_NORMAL;
-  free(ch->data);
-  ch->data = ch->deflate_data;
-  ch->len = ch->deflate_len;
-}
-
-/*
- * Return true if the data in the chunk is identical (including the
- * compressed representation, for gzip chunks).
- */
-int AreChunksEqual(ImageChunk* a, ImageChunk* b) {
-    if (a->type != b->type) return 0;
-
-    switch (a->type) {
-        case CHUNK_NORMAL:
-            return a->len == b->len && memcmp(a->data, b->data, a->len) == 0;
-
-        case CHUNK_DEFLATE:
-            return a->deflate_len == b->deflate_len &&
-                memcmp(a->deflate_data, b->deflate_data, a->deflate_len) == 0;
-
-        default:
-            printf("unknown chunk type %d\n", a->type);
-            return 0;
-    }
+  return true;
 }
 
 /*
@@ -740,137 +809,103 @@
  * a single chunk.  (Such runs can be produced when deflate chunks are
  * changed to normal chunks.)
  */
-void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) {
-  int out = 0;
-  int in_start = 0, in_end;
-  while (in_start < *num_chunks) {
-    if (chunks[in_start].type != CHUNK_NORMAL) {
-      in_end = in_start+1;
-    } else {
-      // in_start is a normal chunk.  Look for a run of normal chunks
-      // that constitute a solid block of data (ie, each chunk begins
-      // where the previous one ended).
-      for (in_end = in_start+1;
-           in_end < *num_chunks && chunks[in_end].type == CHUNK_NORMAL &&
-             (chunks[in_end].start ==
-              chunks[in_end-1].start + chunks[in_end-1].len &&
-              chunks[in_end].data ==
-              chunks[in_end-1].data + chunks[in_end-1].len);
-           ++in_end);
+static void MergeAdjacentNormalChunks(std::vector<ImageChunk>* chunks) {
+  size_t merged_last = 0, cur = 0;
+  while (cur < chunks->size()) {
+    // Look for normal chunks adjacent to the current one. If such chunk exists, extend the
+    // length of the current normal chunk.
+    size_t to_check = cur + 1;
+    while (to_check < chunks->size() && chunks->at(cur).IsAdjacentNormal(chunks->at(to_check))) {
+      chunks->at(cur).MergeAdjacentNormal(chunks->at(to_check));
+      to_check++;
     }
 
-    if (in_end == in_start+1) {
-#if 0
-      printf("chunk %d is now %d\n", in_start, out);
-#endif
-      if (out != in_start) {
-        memcpy(chunks+out, chunks+in_start, sizeof(ImageChunk));
-      }
-    } else {
-#if 0
-      printf("collapse normal chunks %d-%d into %d\n", in_start, in_end-1, out);
-#endif
-
-      // Merge chunks [in_start, in_end-1] into one chunk.  Since the
-      // data member of each chunk is just a pointer into an in-memory
-      // copy of the file, this can be done without recopying (the
-      // output chunk has the first chunk's start location and data
-      // pointer, and length equal to the sum of the input chunk
-      // lengths).
-      chunks[out].type = CHUNK_NORMAL;
-      chunks[out].start = chunks[in_start].start;
-      chunks[out].data = chunks[in_start].data;
-      chunks[out].len = chunks[in_end-1].len +
-        (chunks[in_end-1].start - chunks[in_start].start);
+    if (merged_last != cur) {
+      chunks->at(merged_last) = std::move(chunks->at(cur));
     }
-
-    ++out;
-    in_start = in_end;
+    merged_last++;
+    cur = to_check;
   }
-  *num_chunks = out;
+  if (merged_last < chunks->size()) {
+    chunks->erase(chunks->begin() + merged_last, chunks->end());
+  }
 }
 
-ImageChunk* FindChunkByName(const char* name,
-                            ImageChunk* chunks, int num_chunks) {
-  int i;
-  for (i = 0; i < num_chunks; ++i) {
-    if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename &&
-        strcmp(name, chunks[i].filename) == 0) {
-      return chunks+i;
+static ImageChunk* FindChunkByName(const std::string& name, std::vector<ImageChunk>& chunks) {
+  for (size_t i = 0; i < chunks.size(); ++i) {
+    if (chunks[i].GetType() == CHUNK_DEFLATE && chunks[i].GetEntryName() == name) {
+      return &chunks[i];
     }
   }
-  return NULL;
+  return nullptr;
 }
 
-void DumpChunks(ImageChunk* chunks, int num_chunks) {
-    for (int i = 0; i < num_chunks; ++i) {
-        printf("chunk %d: type %d start %zu len %zu\n",
-               i, chunks[i].type, chunks[i].start, chunks[i].len);
-    }
+static void DumpChunks(const std::vector<ImageChunk>& chunks) {
+  for (size_t i = 0; i < chunks.size(); ++i) {
+    printf("chunk %zu: ", i);
+    chunks[i].Dump();
+  }
 }
 
-int main(int argc, char** argv) {
-  int zip_mode = 0;
+int imgdiff(int argc, const char** argv) {
+  bool zip_mode = false;
 
   if (argc >= 2 && strcmp(argv[1], "-z") == 0) {
-    zip_mode = 1;
+    zip_mode = true;
     --argc;
     ++argv;
   }
 
-  size_t bonus_size = 0;
-  unsigned char* bonus_data = NULL;
+  std::vector<uint8_t> bonus_data;
   if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
-    struct stat st;
-    if (stat(argv[2], &st) != 0) {
-      printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno));
-      return 1;
-    }
-    bonus_size = st.st_size;
-    bonus_data = reinterpret_cast<unsigned char*>(malloc(bonus_size));
-    FILE* f = fopen(argv[2], "rb");
-    if (f == NULL) {
+    android::base::unique_fd fd(open(argv[2], O_RDONLY));
+    if (fd == -1) {
       printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno));
       return 1;
     }
-    if (fread(bonus_data, 1, bonus_size, f) != bonus_size) {
+    struct stat st;
+    if (fstat(fd, &st) != 0) {
+      printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno));
+      return 1;
+    }
+
+    size_t bonus_size = st.st_size;
+    bonus_data.resize(bonus_size);
+    if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) {
       printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno));
       return 1;
     }
-    fclose(f);
 
     argc -= 2;
     argv += 2;
   }
 
   if (argc != 4) {
-    usage:
     printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n",
             argv[0]);
     return 2;
   }
 
-  int num_src_chunks;
-  ImageChunk* src_chunks;
-  int num_tgt_chunks;
-  ImageChunk* tgt_chunks;
-  int i;
+  std::vector<ImageChunk> src_chunks;
+  std::vector<ImageChunk> tgt_chunks;
+  std::vector<uint8_t> src_file;
+  std::vector<uint8_t> tgt_file;
 
   if (zip_mode) {
-    if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) {
+    if (!ReadZip(argv[1], &src_chunks, &src_file, true)) {
       printf("failed to break apart source zip file\n");
       return 1;
     }
-    if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) {
+    if (!ReadZip(argv[2], &tgt_chunks, &tgt_file, false)) {
       printf("failed to break apart target zip file\n");
       return 1;
     }
   } else {
-    if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) {
+    if (!ReadImage(argv[1], &src_chunks, &src_file)) {
       printf("failed to break apart source image\n");
       return 1;
     }
-    if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) {
+    if (!ReadImage(argv[2], &tgt_chunks, &tgt_file)) {
       printf("failed to break apart target image\n");
       return 1;
     }
@@ -878,51 +913,47 @@
     // Verify that the source and target images have the same chunk
     // structure (ie, the same sequence of deflate and normal chunks).
 
-    if (!zip_mode) {
-        // Merge the gzip header and footer in with any adjacent
-        // normal chunks.
-        MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
-        MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
-    }
+    // Merge the gzip header and footer in with any adjacent normal chunks.
+    MergeAdjacentNormalChunks(&tgt_chunks);
+    MergeAdjacentNormalChunks(&src_chunks);
 
-    if (num_src_chunks != num_tgt_chunks) {
+    if (src_chunks.size() != tgt_chunks.size()) {
       printf("source and target don't have same number of chunks!\n");
       printf("source chunks:\n");
-      DumpChunks(src_chunks, num_src_chunks);
+      DumpChunks(src_chunks);
       printf("target chunks:\n");
-      DumpChunks(tgt_chunks, num_tgt_chunks);
+      DumpChunks(tgt_chunks);
       return 1;
     }
-    for (i = 0; i < num_src_chunks; ++i) {
-      if (src_chunks[i].type != tgt_chunks[i].type) {
-        printf("source and target don't have same chunk "
-                "structure! (chunk %d)\n", i);
+    for (size_t i = 0; i < src_chunks.size(); ++i) {
+      if (src_chunks[i].GetType() != tgt_chunks[i].GetType()) {
+        printf("source and target don't have same chunk structure! (chunk %zu)\n", i);
         printf("source chunks:\n");
-        DumpChunks(src_chunks, num_src_chunks);
+        DumpChunks(src_chunks);
         printf("target chunks:\n");
-        DumpChunks(tgt_chunks, num_tgt_chunks);
+        DumpChunks(tgt_chunks);
         return 1;
       }
     }
   }
 
-  for (i = 0; i < num_tgt_chunks; ++i) {
-    if (tgt_chunks[i].type == CHUNK_DEFLATE) {
+  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
+    if (tgt_chunks[i].GetType() == CHUNK_DEFLATE) {
       // Confirm that given the uncompressed chunk data in the target, we
       // can recompress it and get exactly the same bits as are in the
       // input target image.  If this fails, treat the chunk as a normal
       // non-deflated chunk.
-      if (ReconstructDeflateChunk(tgt_chunks+i) < 0) {
-        printf("failed to reconstruct target deflate chunk %d [%s]; "
-               "treating as normal\n", i, tgt_chunks[i].filename);
-        ChangeDeflateChunkToNormal(tgt_chunks+i);
+      if (!tgt_chunks[i].ReconstructDeflateChunk()) {
+        printf("failed to reconstruct target deflate chunk %zu [%s]; treating as normal\n", i,
+               tgt_chunks[i].GetEntryName().c_str());
+        tgt_chunks[i].ChangeDeflateChunkToNormal();
         if (zip_mode) {
-          ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks);
-          if (src) {
-            ChangeDeflateChunkToNormal(src);
+          ImageChunk* src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks);
+          if (src != nullptr) {
+            src->ChangeDeflateChunkToNormal();
           }
         } else {
-          ChangeDeflateChunkToNormal(src_chunks+i);
+          src_chunks[i].ChangeDeflateChunkToNormal();
         }
         continue;
       }
@@ -935,16 +966,16 @@
       // data.
       ImageChunk* src;
       if (zip_mode) {
-        src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks);
+        src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks);
       } else {
-        src = src_chunks+i;
+        src = &src_chunks[i];
       }
 
-      if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) {
-        ChangeDeflateChunkToNormal(tgt_chunks+i);
-        if (src) {
-          ChangeDeflateChunkToNormal(src);
-        }
+      if (src == nullptr) {
+        tgt_chunks[i].ChangeDeflateChunkToNormal();
+      } else if (tgt_chunks[i] == *src) {
+        tgt_chunks[i].ChangeDeflateChunkToNormal();
+        src->ChangeDeflateChunkToNormal();
       }
     }
   }
@@ -954,14 +985,15 @@
     // For zips, we only need to do this to the target:  deflated
     // chunks are matched via filename, and normal chunks are patched
     // using the entire source file as the source.
-    MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
+    MergeAdjacentNormalChunks(&tgt_chunks);
+
   } else {
     // For images, we need to maintain the parallel structure of the
     // chunk lists, so do the merging in both the source and target
     // lists.
-    MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
-    MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
-    if (num_src_chunks != num_tgt_chunks) {
+    MergeAdjacentNormalChunks(&tgt_chunks);
+    MergeAdjacentNormalChunks(&src_chunks);
+    if (src_chunks.size() != tgt_chunks.size()) {
       // This shouldn't happen.
       printf("merging normal chunks went awry\n");
       return 1;
@@ -971,35 +1003,43 @@
   // Compute bsdiff patches for each chunk's data (the uncompressed
   // data, in the case of deflate chunks).
 
-  DumpChunks(src_chunks, num_src_chunks);
+  DumpChunks(src_chunks);
 
-  printf("Construct patches for %d chunks...\n", num_tgt_chunks);
-  unsigned char** patch_data = reinterpret_cast<unsigned char**>(malloc(
-      num_tgt_chunks * sizeof(unsigned char*)));
-  size_t* patch_size = reinterpret_cast<size_t*>(malloc(num_tgt_chunks * sizeof(size_t)));
-  for (i = 0; i < num_tgt_chunks; ++i) {
+  printf("Construct patches for %zu chunks...\n", tgt_chunks.size());
+  std::vector<std::vector<uint8_t>> patch_data(tgt_chunks.size());
+  saidx_t* bsdiff_cache = nullptr;
+  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
     if (zip_mode) {
       ImageChunk* src;
-      if (tgt_chunks[i].type == CHUNK_DEFLATE &&
-          (src = FindChunkByName(tgt_chunks[i].filename, src_chunks,
-                                 num_src_chunks))) {
-        patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i);
+      if (tgt_chunks[i].GetType() == CHUNK_DEFLATE &&
+          (src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks))) {
+        if (!MakePatch(src, &tgt_chunks[i], &patch_data[i], nullptr)) {
+          printf("Failed to generate patch for target chunk %zu: ", i);
+          return 1;
+        }
       } else {
-        patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i);
+        if (!MakePatch(&src_chunks[0], &tgt_chunks[i], &patch_data[i], &bsdiff_cache)) {
+          printf("Failed to generate patch for target chunk %zu: ", i);
+          return 1;
+        }
       }
     } else {
-      if (i == 1 && bonus_data) {
-        printf("  using %zu bytes of bonus data for chunk %d\n", bonus_size, i);
-        src_chunks[i].data = reinterpret_cast<unsigned char*>(realloc(src_chunks[i].data,
-            src_chunks[i].len + bonus_size));
-        memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size);
-        src_chunks[i].len += bonus_size;
-     }
+      if (i == 1 && !bonus_data.empty()) {
+        printf("  using %zu bytes of bonus data for chunk %zu\n", bonus_data.size(), i);
+        src_chunks[i].SetBonusData(bonus_data);
+      }
 
-      patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i);
+      if (!MakePatch(&src_chunks[i], &tgt_chunks[i], &patch_data[i], nullptr)) {
+        printf("Failed to generate patch for target chunk %zu: ", i);
+        return 1;
+      }
     }
-    printf("patch %3d is %zu bytes (of %zu)\n",
-           i, patch_size[i], tgt_chunks[i].source_len);
+    printf("patch %3zu is %zu bytes (of %zu)\n", i, patch_data[i].size(),
+           src_chunks[i].GetRawDataLength());
+  }
+
+  if (bsdiff_cache != nullptr) {
+    free(bsdiff_cache);
   }
 
   // Figure out how big the imgdiff file header is going to be, so
@@ -1007,77 +1047,38 @@
   // within the file.
 
   size_t total_header_size = 12;
-  for (i = 0; i < num_tgt_chunks; ++i) {
-    total_header_size += 4;
-    switch (tgt_chunks[i].type) {
-      case CHUNK_NORMAL:
-        total_header_size += 8*3;
-        break;
-      case CHUNK_DEFLATE:
-        total_header_size += 8*5 + 4*5;
-        break;
-      case CHUNK_RAW:
-        total_header_size += 4 + patch_size[i];
-        break;
-    }
+  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
+    total_header_size += tgt_chunks[i].GetHeaderSize(patch_data[i].size());
   }
 
   size_t offset = total_header_size;
 
-  FILE* f = fopen(argv[3], "wb");
+  android::base::unique_fd patch_fd(open(argv[3], O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+  if (patch_fd == -1) {
+    printf("failed to open \"%s\": %s\n", argv[3], strerror(errno));
+    return 1;
+  }
 
   // Write out the headers.
-
-  fwrite("IMGDIFF2", 1, 8, f);
-  Write4(num_tgt_chunks, f);
-  for (i = 0; i < num_tgt_chunks; ++i) {
-    Write4(tgt_chunks[i].type, f);
-
-    switch (tgt_chunks[i].type) {
-      case CHUNK_NORMAL:
-        printf("chunk %3d: normal   (%10zu, %10zu)  %10zu\n", i,
-               tgt_chunks[i].start, tgt_chunks[i].len, patch_size[i]);
-        Write8(tgt_chunks[i].source_start, f);
-        Write8(tgt_chunks[i].source_len, f);
-        Write8(offset, f);
-        offset += patch_size[i];
-        break;
-
-      case CHUNK_DEFLATE:
-        printf("chunk %3d: deflate  (%10zu, %10zu)  %10zu  %s\n", i,
-               tgt_chunks[i].start, tgt_chunks[i].deflate_len, patch_size[i],
-               tgt_chunks[i].filename);
-        Write8(tgt_chunks[i].source_start, f);
-        Write8(tgt_chunks[i].source_len, f);
-        Write8(offset, f);
-        Write8(tgt_chunks[i].source_uncompressed_len, f);
-        Write8(tgt_chunks[i].len, f);
-        Write4(tgt_chunks[i].level, f);
-        Write4(tgt_chunks[i].method, f);
-        Write4(tgt_chunks[i].windowBits, f);
-        Write4(tgt_chunks[i].memLevel, f);
-        Write4(tgt_chunks[i].strategy, f);
-        offset += patch_size[i];
-        break;
-
-      case CHUNK_RAW:
-        printf("chunk %3d: raw      (%10zu, %10zu)\n", i,
-               tgt_chunks[i].start, tgt_chunks[i].len);
-        Write4(patch_size[i], f);
-        fwrite(patch_data[i], 1, patch_size[i], f);
-        break;
-    }
+  if (!android::base::WriteStringToFd("IMGDIFF2", patch_fd)) {
+    printf("failed to write \"IMGDIFF2\" to \"%s\": %s\n", argv[3], strerror(errno));
+    return 1;
+  }
+  Write4(patch_fd, static_cast<int32_t>(tgt_chunks.size()));
+  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
+    printf("chunk %zu: ", i);
+    offset = tgt_chunks[i].WriteHeaderToFd(patch_fd, patch_data[i], offset);
   }
 
   // Append each chunk's bsdiff patch, in order.
-
-  for (i = 0; i < num_tgt_chunks; ++i) {
-    if (tgt_chunks[i].type != CHUNK_RAW) {
-      fwrite(patch_data[i], 1, patch_size[i], f);
+  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
+    if (tgt_chunks[i].GetType() != CHUNK_RAW) {
+      if (!android::base::WriteFully(patch_fd, patch_data[i].data(), patch_data[i].size())) {
+        CHECK(false) << "failed to write " << patch_data[i].size() << " bytes patch for chunk "
+                     << i;
+      }
     }
   }
 
-  fclose(f);
-
   return 0;
 }
diff --git a/minzip/inline_magic.h b/applypatch/imgdiff_main.cpp
similarity index 67%
copy from minzip/inline_magic.h
copy to applypatch/imgdiff_main.cpp
index 59c659f..7d5bdf9 100644
--- a/minzip/inline_magic.h
+++ b/applypatch/imgdiff_main.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -14,13 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef MINZIP_INLINE_MAGIC_H_
-#define MINZIP_INLINE_MAGIC_H_
+#include "applypatch/imgdiff.h"
 
-#ifndef MINZIP_GENERATE_INLINES
-#define INLINE extern inline __attribute((__gnu_inline__))
-#else
-#define INLINE
-#endif
-
-#endif  // MINZIP_INLINE_MAGIC_H_
+int main(int argc, char** argv) {
+  return imgdiff(argc, const_cast<const char**>(argv));
+}
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index d175d63..adcc61f 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -14,31 +14,41 @@
  * limitations under the License.
  */
 
-// See imgdiff.c in this directory for a description of the patch file
+// See imgdiff.cpp in this directory for a description of the patch file
 // format.
 
+#include <applypatch/imgpatch.h>
+
+#include <errno.h>
 #include <stdio.h>
+#include <string.h>
 #include <sys/cdefs.h>
 #include <sys/stat.h>
-#include <errno.h>
 #include <unistd.h>
-#include <string.h>
 
+#include <string>
 #include <vector>
 
-#include "zlib.h"
-#include "openssl/sha.h"
-#include "applypatch.h"
-#include "imgdiff.h"
-#include "utils.h"
+#include <applypatch/applypatch.h>
+#include <applypatch/imgdiff.h>
+#include <android-base/memory.h>
+#include <openssl/sha.h>
+#include <zlib.h>
+
+static inline int64_t Read8(const void *address) {
+  return android::base::get_unaligned<int64_t>(address);
+}
+
+static inline int32_t Read4(const void *address) {
+  return android::base::get_unaligned<int32_t>(address);
+}
 
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const unsigned char* patch_data, ssize_t patch_size,
                     SinkFn sink, void* token) {
-  Value patch = {VAL_BLOB, patch_size,
-      reinterpret_cast<char*>(const_cast<unsigned char*>(patch_data))};
-  return ApplyImagePatch(
-      old_data, old_size, &patch, sink, token, nullptr, nullptr);
+  Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size));
+
+  return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr);
 }
 
 /*
@@ -47,203 +57,204 @@
  * file, and update the SHA context with the output data as well.
  * Return 0 on success.
  */
-int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
-                    const Value* patch,
-                    SinkFn sink, void* token, SHA_CTX* ctx,
-                    const Value* bonus_data) {
-    ssize_t pos = 12;
-    char* header = patch->data;
-    if (patch->size < 12) {
-        printf("patch too short to contain header\n");
+int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch,
+                    SinkFn sink, void* token, SHA_CTX* ctx, const Value* bonus_data) {
+  if (patch->data.size() < 12) {
+    printf("patch too short to contain header\n");
+    return -1;
+  }
+
+  // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW.
+  // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and
+  // CHUNK_GZIP.)
+  size_t pos = 12;
+  const char* header = &patch->data[0];
+  if (memcmp(header, "IMGDIFF2", 8) != 0) {
+    printf("corrupt patch file header (magic number)\n");
+    return -1;
+  }
+
+  int num_chunks = Read4(header + 8);
+
+  for (int i = 0; i < num_chunks; ++i) {
+    // each chunk's header record starts with 4 bytes.
+    if (pos + 4 > patch->data.size()) {
+      printf("failed to read chunk %d record\n", i);
+      return -1;
+    }
+    int type = Read4(&patch->data[pos]);
+    pos += 4;
+
+    if (type == CHUNK_NORMAL) {
+      const char* normal_header = &patch->data[pos];
+      pos += 24;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d normal header data\n", i);
         return -1;
-    }
+      }
 
-    // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW.
-    // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and
-    // CHUNK_GZIP.)
-    if (memcmp(header, "IMGDIFF2", 8) != 0) {
-        printf("corrupt patch file header (magic number)\n");
+      size_t src_start = static_cast<size_t>(Read8(normal_header));
+      size_t src_len = static_cast<size_t>(Read8(normal_header + 8));
+      size_t patch_offset = static_cast<size_t>(Read8(normal_header + 16));
+
+      if (src_start + src_len > static_cast<size_t>(old_size)) {
+        printf("source data too short\n");
         return -1;
-    }
+      }
+      ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, token, ctx);
+    } else if (type == CHUNK_RAW) {
+      const char* raw_header = &patch->data[pos];
+      pos += 4;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d raw header data\n", i);
+        return -1;
+      }
 
-    int num_chunks = Read4(header+8);
+      ssize_t data_len = Read4(raw_header);
 
-    int i;
-    for (i = 0; i < num_chunks; ++i) {
-        // each chunk's header record starts with 4 bytes.
-        if (pos + 4 > patch->size) {
-            printf("failed to read chunk %d record\n", i);
-            return -1;
+      if (pos + data_len > patch->data.size()) {
+        printf("failed to read chunk %d raw data\n", i);
+        return -1;
+      }
+      if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len);
+      if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len, token) !=
+          data_len) {
+        printf("failed to write chunk %d raw data\n", i);
+        return -1;
+      }
+      pos += data_len;
+    } else if (type == CHUNK_DEFLATE) {
+      // deflate chunks have an additional 60 bytes in their chunk header.
+      const char* deflate_header = &patch->data[pos];
+      pos += 60;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d deflate header data\n", i);
+        return -1;
+      }
+
+      size_t src_start = static_cast<size_t>(Read8(deflate_header));
+      size_t src_len = static_cast<size_t>(Read8(deflate_header + 8));
+      size_t patch_offset = static_cast<size_t>(Read8(deflate_header + 16));
+      size_t expanded_len = static_cast<size_t>(Read8(deflate_header + 24));
+      size_t target_len = static_cast<size_t>(Read8(deflate_header + 32));
+      int level = Read4(deflate_header + 40);
+      int method = Read4(deflate_header + 44);
+      int windowBits = Read4(deflate_header + 48);
+      int memLevel = Read4(deflate_header + 52);
+      int strategy = Read4(deflate_header + 56);
+
+      if (src_start + src_len > static_cast<size_t>(old_size)) {
+        printf("source data too short\n");
+        return -1;
+      }
+
+      // Decompress the source data; the chunk header tells us exactly
+      // how big we expect it to be when decompressed.
+
+      // Note: expanded_len will include the bonus data size if
+      // the patch was constructed with bonus data.  The
+      // deflation will come up 'bonus_size' bytes short; these
+      // must be appended from the bonus_data value.
+      size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->data.size() : 0;
+
+      std::vector<unsigned char> expanded_source(expanded_len);
+
+      // inflate() doesn't like strm.next_out being a nullptr even with
+      // avail_out being zero (Z_STREAM_ERROR).
+      if (expanded_len != 0) {
+        z_stream strm;
+        strm.zalloc = Z_NULL;
+        strm.zfree = Z_NULL;
+        strm.opaque = Z_NULL;
+        strm.avail_in = src_len;
+        strm.next_in = old_data + src_start;
+        strm.avail_out = expanded_len;
+        strm.next_out = expanded_source.data();
+
+        int ret = inflateInit2(&strm, -15);
+        if (ret != Z_OK) {
+          printf("failed to init source inflation: %d\n", ret);
+          return -1;
         }
-        int type = Read4(patch->data + pos);
-        pos += 4;
 
-        if (type == CHUNK_NORMAL) {
-            char* normal_header = patch->data + pos;
-            pos += 24;
-            if (pos > patch->size) {
-                printf("failed to read chunk %d normal header data\n", i);
-                return -1;
-            }
-
-            size_t src_start = Read8(normal_header);
-            size_t src_len = Read8(normal_header+8);
-            size_t patch_offset = Read8(normal_header+16);
-
-            if (src_start + src_len > static_cast<size_t>(old_size)) {
-                printf("source data too short\n");
-                return -1;
-            }
-            ApplyBSDiffPatch(old_data + src_start, src_len,
-                             patch, patch_offset, sink, token, ctx);
-        } else if (type == CHUNK_RAW) {
-            char* raw_header = patch->data + pos;
-            pos += 4;
-            if (pos > patch->size) {
-                printf("failed to read chunk %d raw header data\n", i);
-                return -1;
-            }
-
-            ssize_t data_len = Read4(raw_header);
-
-            if (pos + data_len > patch->size) {
-                printf("failed to read chunk %d raw data\n", i);
-                return -1;
-            }
-            if (ctx) SHA1_Update(ctx, patch->data + pos, data_len);
-            if (sink((unsigned char*)patch->data + pos,
-                     data_len, token) != data_len) {
-                printf("failed to write chunk %d raw data\n", i);
-                return -1;
-            }
-            pos += data_len;
-        } else if (type == CHUNK_DEFLATE) {
-            // deflate chunks have an additional 60 bytes in their chunk header.
-            char* deflate_header = patch->data + pos;
-            pos += 60;
-            if (pos > patch->size) {
-                printf("failed to read chunk %d deflate header data\n", i);
-                return -1;
-            }
-
-            size_t src_start = Read8(deflate_header);
-            size_t src_len = Read8(deflate_header+8);
-            size_t patch_offset = Read8(deflate_header+16);
-            size_t expanded_len = Read8(deflate_header+24);
-            int level = Read4(deflate_header+40);
-            int method = Read4(deflate_header+44);
-            int windowBits = Read4(deflate_header+48);
-            int memLevel = Read4(deflate_header+52);
-            int strategy = Read4(deflate_header+56);
-
-            if (src_start + src_len > static_cast<size_t>(old_size)) {
-                printf("source data too short\n");
-                return -1;
-            }
-
-            // Decompress the source data; the chunk header tells us exactly
-            // how big we expect it to be when decompressed.
-
-            // Note: expanded_len will include the bonus data size if
-            // the patch was constructed with bonus data.  The
-            // deflation will come up 'bonus_size' bytes short; these
-            // must be appended from the bonus_data value.
-            size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->size : 0;
-
-            std::vector<unsigned char> expanded_source(expanded_len);
-
-            // inflate() doesn't like strm.next_out being a nullptr even with
-            // avail_out being zero (Z_STREAM_ERROR).
-            if (expanded_len != 0) {
-                z_stream strm;
-                strm.zalloc = Z_NULL;
-                strm.zfree = Z_NULL;
-                strm.opaque = Z_NULL;
-                strm.avail_in = src_len;
-                strm.next_in = (unsigned char*)(old_data + src_start);
-                strm.avail_out = expanded_len;
-                strm.next_out = expanded_source.data();
-
-                int ret;
-                ret = inflateInit2(&strm, -15);
-                if (ret != Z_OK) {
-                    printf("failed to init source inflation: %d\n", ret);
-                    return -1;
-                }
-
-                // Because we've provided enough room to accommodate the output
-                // data, we expect one call to inflate() to suffice.
-                ret = inflate(&strm, Z_SYNC_FLUSH);
-                if (ret != Z_STREAM_END) {
-                    printf("source inflation returned %d\n", ret);
-                    return -1;
-                }
-                // We should have filled the output buffer exactly, except
-                // for the bonus_size.
-                if (strm.avail_out != bonus_size) {
-                    printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size);
-                    return -1;
-                }
-                inflateEnd(&strm);
-
-                if (bonus_size) {
-                    memcpy(expanded_source.data() + (expanded_len - bonus_size),
-                           bonus_data->data, bonus_size);
-                }
-            }
-
-            // Next, apply the bsdiff patch (in memory) to the uncompressed
-            // data.
-            std::vector<unsigned char> uncompressed_target_data;
-            if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len,
-                                    patch, patch_offset,
-                                    &uncompressed_target_data) != 0) {
-                return -1;
-            }
-
-            // Now compress the target data and append it to the output.
-
-            // we're done with the expanded_source data buffer, so we'll
-            // reuse that memory to receive the output of deflate.
-            if (expanded_source.size() < 32768U) {
-                expanded_source.resize(32768U);
-            }
-
-            {
-                std::vector<unsigned char>& temp_data = expanded_source;
-
-                // now the deflate stream
-                z_stream strm;
-                strm.zalloc = Z_NULL;
-                strm.zfree = Z_NULL;
-                strm.opaque = Z_NULL;
-                strm.avail_in = uncompressed_target_data.size();
-                strm.next_in = uncompressed_target_data.data();
-                int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
-                if (ret != Z_OK) {
-                    printf("failed to init uncompressed data deflation: %d\n", ret);
-                    return -1;
-                }
-                do {
-                    strm.avail_out = temp_data.size();
-                    strm.next_out = temp_data.data();
-                    ret = deflate(&strm, Z_FINISH);
-                    ssize_t have = temp_data.size() - strm.avail_out;
-
-                    if (sink(temp_data.data(), have, token) != have) {
-                        printf("failed to write %ld compressed bytes to output\n",
-                               (long)have);
-                        return -1;
-                    }
-                    if (ctx) SHA1_Update(ctx, temp_data.data(), have);
-                } while (ret != Z_STREAM_END);
-                deflateEnd(&strm);
-            }
-        } else {
-            printf("patch chunk %d is unknown type %d\n", i, type);
-            return -1;
+        // Because we've provided enough room to accommodate the output
+        // data, we expect one call to inflate() to suffice.
+        ret = inflate(&strm, Z_SYNC_FLUSH);
+        if (ret != Z_STREAM_END) {
+          printf("source inflation returned %d\n", ret);
+          return -1;
         }
-    }
+        // We should have filled the output buffer exactly, except
+        // for the bonus_size.
+        if (strm.avail_out != bonus_size) {
+          printf("source inflation short by %zu bytes\n", strm.avail_out - bonus_size);
+          return -1;
+        }
+        inflateEnd(&strm);
 
-    return 0;
+        if (bonus_size) {
+          memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0],
+                 bonus_size);
+        }
+      }
+
+      // Next, apply the bsdiff patch (in memory) to the uncompressed data.
+      std::vector<unsigned char> uncompressed_target_data;
+      // TODO(senj): Remove the only usage of ApplyBSDiffPatchMem here,
+      // replace it with ApplyBSDiffPatch with a custom sink function that
+      // wraps the given sink function to stream output to save memory.
+      if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, patch, patch_offset,
+                              &uncompressed_target_data) != 0) {
+        return -1;
+      }
+      if (uncompressed_target_data.size() != target_len) {
+        printf("expected target len to be %zu, but it's %zu\n", target_len,
+               uncompressed_target_data.size());
+        return -1;
+      }
+
+      // Now compress the target data and append it to the output.
+
+      // we're done with the expanded_source data buffer, so we'll
+      // reuse that memory to receive the output of deflate.
+      if (expanded_source.size() < 32768U) {
+        expanded_source.resize(32768U);
+      }
+
+      {
+        std::vector<unsigned char>& temp_data = expanded_source;
+
+        // now the deflate stream
+        z_stream strm;
+        strm.zalloc = Z_NULL;
+        strm.zfree = Z_NULL;
+        strm.opaque = Z_NULL;
+        strm.avail_in = uncompressed_target_data.size();
+        strm.next_in = uncompressed_target_data.data();
+        int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
+        if (ret != Z_OK) {
+          printf("failed to init uncompressed data deflation: %d\n", ret);
+          return -1;
+        }
+        do {
+          strm.avail_out = temp_data.size();
+          strm.next_out = temp_data.data();
+          ret = deflate(&strm, Z_FINISH);
+          ssize_t have = temp_data.size() - strm.avail_out;
+
+          if (sink(temp_data.data(), have, token) != have) {
+            printf("failed to write %zd compressed bytes to output\n", have);
+            return -1;
+          }
+          if (ctx) SHA1_Update(ctx, temp_data.data(), have);
+        } while (ret != Z_STREAM_END);
+        deflateEnd(&strm);
+      }
+    } else {
+      printf("patch chunk %d is unknown type %d\n", i, type);
+      return -1;
+    }
+  }
+
+  return 0;
 }
diff --git a/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
similarity index 85%
rename from applypatch/applypatch.h
rename to applypatch/include/applypatch/applypatch.h
index f392c55..4489dec 100644
--- a/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -17,11 +17,15 @@
 #ifndef _APPLYPATCH_H
 #define _APPLYPATCH_H
 
+#include <stdint.h>
 #include <sys/stat.h>
 
+#include <memory>
+#include <string>
 #include <vector>
 
-#include "openssl/sha.h"
+#include <openssl/sha.h>
+
 #include "edify/expr.h"
 
 struct FileContents {
@@ -39,33 +43,28 @@
 
 typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*);
 
-// applypatch.c
+// applypatch.cpp
 int ShowLicenses();
 size_t FreeSpaceForFile(const char* filename);
 int CacheSizeCheck(size_t bytes);
 int ParseSha1(const char* str, uint8_t* digest);
 
-int applypatch_flash(const char* source_filename, const char* target_filename,
-                     const char* target_sha1_str, size_t target_size);
 int applypatch(const char* source_filename,
                const char* target_filename,
                const char* target_sha1_str,
                size_t target_size,
-               int num_patches,
-               char** const patch_sha1_str,
-               Value** patch_data,
-               Value* bonus_data);
+               const std::vector<std::string>& patch_sha1_str,
+               const std::vector<std::unique_ptr<Value>>& patch_data,
+               const Value* bonus_data);
 int applypatch_check(const char* filename,
-                     int num_patches,
-                     char** const patch_sha1_str);
+                     const std::vector<std::string>& patch_sha1_str);
+int applypatch_flash(const char* source_filename, const char* target_filename,
+                     const char* target_sha1_str, size_t target_size);
 
 int LoadFileContents(const char* filename, FileContents* file);
 int SaveFileContents(const char* filename, const FileContents* file);
-void FreeFileContents(FileContents* file);
-int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str,
-                      int num_patches);
 
-// bsdiff.cpp
+// bspatch.cpp
 void ShowBSDiffLicense();
 int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size,
                      const Value* patch, ssize_t patch_offset,
diff --git a/applypatch/imgdiff.h b/applypatch/include/applypatch/imgdiff.h
similarity index 69%
rename from applypatch/imgdiff.h
rename to applypatch/include/applypatch/imgdiff.h
index f2069b4..22cbd4f 100644
--- a/applypatch/imgdiff.h
+++ b/applypatch/include/applypatch/imgdiff.h
@@ -14,17 +14,26 @@
  * limitations under the License.
  */
 
+#ifndef _APPLYPATCH_IMGDIFF_H
+#define _APPLYPATCH_IMGDIFF_H
+
+#include <stddef.h>
+
 // Image patch chunk types
-#define CHUNK_NORMAL   0
-#define CHUNK_GZIP     1   // version 1 only
-#define CHUNK_DEFLATE  2   // version 2 only
-#define CHUNK_RAW      3   // version 2 only
+#define CHUNK_NORMAL 0
+#define CHUNK_GZIP 1     // version 1 only
+#define CHUNK_DEFLATE 2  // version 2 only
+#define CHUNK_RAW 3      // version 2 only
 
 // The gzip header size is actually variable, but we currently don't
 // support gzipped data with any of the optional fields, so for now it
 // will always be ten bytes.  See RFC 1952 for the definition of the
 // gzip format.
-#define GZIP_HEADER_LEN   10
+static constexpr size_t GZIP_HEADER_LEN = 10;
 
 // The gzip footer size really is fixed.
-#define GZIP_FOOTER_LEN   8
+static constexpr size_t GZIP_FOOTER_LEN = 8;
+
+int imgdiff(int argc, const char** argv);
+
+#endif  // _APPLYPATCH_IMGDIFF_H
diff --git a/applypatch/include/applypatch/imgpatch.h b/applypatch/include/applypatch/imgpatch.h
index 64d9aa9..6549f79 100644
--- a/applypatch/include/applypatch/imgpatch.h
+++ b/applypatch/include/applypatch/imgpatch.h
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef _IMGPATCH_H
-#define _IMGPATCH_H
+#ifndef _APPLYPATCH_IMGPATCH_H
+#define _APPLYPATCH_IMGPATCH_H
 
-typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*);
+#include <sys/types.h>
+
+using SinkFn = ssize_t (*)(const unsigned char*, ssize_t, void*);
 
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const unsigned char* patch_data, ssize_t patch_size,
                     SinkFn sink, void* token);
 
-#endif  //_IMGPATCH_H
+#endif  // _APPLYPATCH_IMGPATCH_H
diff --git a/applypatch/libimgpatch.pc b/applypatch/libimgpatch.pc
new file mode 100644
index 0000000..e500293
--- /dev/null
+++ b/applypatch/libimgpatch.pc
@@ -0,0 +1,6 @@
+# This file is for libimgpatch in Chrome OS.
+
+Name: libimgpatch
+Description: Apply imgdiff patch
+Version: 0.0.1
+Libs: -limgpatch -lbz2 -lz
diff --git a/applypatch/utils.cpp b/applypatch/utils.cpp
deleted file mode 100644
index 4a80be7..0000000
--- a/applypatch/utils.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2009 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 agreed 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 <stdio.h>
-
-#include "utils.h"
-
-/** Write a 4-byte value to f in little-endian order. */
-void Write4(int value, FILE* f) {
-  fputc(value & 0xff, f);
-  fputc((value >> 8) & 0xff, f);
-  fputc((value >> 16) & 0xff, f);
-  fputc((value >> 24) & 0xff, f);
-}
-
-/** Write an 8-byte value to f in little-endian order. */
-void Write8(long long value, FILE* f) {
-  fputc(value & 0xff, f);
-  fputc((value >> 8) & 0xff, f);
-  fputc((value >> 16) & 0xff, f);
-  fputc((value >> 24) & 0xff, f);
-  fputc((value >> 32) & 0xff, f);
-  fputc((value >> 40) & 0xff, f);
-  fputc((value >> 48) & 0xff, f);
-  fputc((value >> 56) & 0xff, f);
-}
-
-int Read2(void* pv) {
-    unsigned char* p = reinterpret_cast<unsigned char*>(pv);
-    return (int)(((unsigned int)p[1] << 8) |
-                 (unsigned int)p[0]);
-}
-
-int Read4(void* pv) {
-    unsigned char* p = reinterpret_cast<unsigned char*>(pv);
-    return (int)(((unsigned int)p[3] << 24) |
-                 ((unsigned int)p[2] << 16) |
-                 ((unsigned int)p[1] << 8) |
-                 (unsigned int)p[0]);
-}
-
-long long Read8(void* pv) {
-    unsigned char* p = reinterpret_cast<unsigned char*>(pv);
-    return (long long)(((unsigned long long)p[7] << 56) |
-                       ((unsigned long long)p[6] << 48) |
-                       ((unsigned long long)p[5] << 40) |
-                       ((unsigned long long)p[4] << 32) |
-                       ((unsigned long long)p[3] << 24) |
-                       ((unsigned long long)p[2] << 16) |
-                       ((unsigned long long)p[1] << 8) |
-                       (unsigned long long)p[0]);
-}
diff --git a/applypatch/utils.h b/applypatch/utils.h
deleted file mode 100644
index bc97f17..0000000
--- a/applypatch/utils.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2009 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 agreed 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 _BUILD_TOOLS_APPLYPATCH_UTILS_H
-#define _BUILD_TOOLS_APPLYPATCH_UTILS_H
-
-#include <stdio.h>
-
-// Read and write little-endian values of various sizes.
-
-void Write4(int value, FILE* f);
-void Write8(long long value, FILE* f);
-int Read2(void* p);
-int Read4(void* p);
-long long Read8(void* p);
-
-#endif //  _BUILD_TOOLS_APPLYPATCH_UTILS_H
diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp
index e7aef78..285214f 100644
--- a/asn1_decoder.cpp
+++ b/asn1_decoder.cpp
@@ -14,178 +14,145 @@
  * limitations under the License.
  */
 
-#include <malloc.h>
-#include <stdint.h>
-#include <string.h>
-
 #include "asn1_decoder.h"
 
+#include <stdint.h>
 
-typedef struct asn1_context {
-    size_t length;
-    uint8_t* p;
-    int app_type;
-} asn1_context_t;
-
-
-static const int kMaskConstructed = 0xE0;
-static const int kMaskTag = 0x7F;
-static const int kMaskAppType = 0x1F;
-
-static const int kTagOctetString = 0x04;
-static const int kTagOid = 0x06;
-static const int kTagSequence = 0x30;
-static const int kTagSet = 0x31;
-static const int kTagConstructed = 0xA0;
-
-asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) {
-    asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t));
-    if (ctx == NULL) {
-        return NULL;
-    }
-    ctx->p = buffer;
-    ctx->length = length;
-    return ctx;
+int asn1_context::peek_byte() const {
+  if (length_ == 0) {
+    return -1;
+  }
+  return *p_;
 }
 
-void asn1_context_free(asn1_context_t* ctx) {
-    free(ctx);
+int asn1_context::get_byte() {
+  if (length_ == 0) {
+    return -1;
+  }
+
+  int byte = *p_;
+  p_++;
+  length_--;
+  return byte;
 }
 
-static inline int peek_byte(asn1_context_t* ctx) {
-    if (ctx->length <= 0) {
-        return -1;
-    }
-    return *ctx->p;
+bool asn1_context::skip_bytes(size_t num_skip) {
+  if (length_ < num_skip) {
+    return false;
+  }
+  p_ += num_skip;
+  length_ -= num_skip;
+  return true;
 }
 
-static inline int get_byte(asn1_context_t* ctx) {
-    if (ctx->length <= 0) {
-        return -1;
-    }
-    int byte = *ctx->p;
-    ctx->p++;
-    ctx->length--;
-    return byte;
-}
-
-static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) {
-    if (ctx->length < num_skip) {
-        return false;
-    }
-    ctx->p += num_skip;
-    ctx->length -= num_skip;
+bool asn1_context::decode_length(size_t* out_len) {
+  int num_octets = get_byte();
+  if (num_octets == -1) {
+    return false;
+  }
+  if ((num_octets & 0x80) == 0x00) {
+    *out_len = num_octets;
     return true;
-}
-
-static bool decode_length(asn1_context_t* ctx, size_t* out_len) {
-    int num_octets = get_byte(ctx);
-    if (num_octets == -1) {
-        return false;
+  }
+  num_octets &= kMaskTag;
+  if (static_cast<size_t>(num_octets) >= sizeof(size_t)) {
+    return false;
+  }
+  size_t length = 0;
+  for (int i = 0; i < num_octets; ++i) {
+    int byte = get_byte();
+    if (byte == -1) {
+      return false;
     }
-    if ((num_octets & 0x80) == 0x00) {
-        *out_len = num_octets;
-        return 1;
-    }
-    num_octets &= kMaskTag;
-    if ((size_t)num_octets >= sizeof(size_t)) {
-        return false;
-    }
-    size_t length = 0;
-    for (int i = 0; i < num_octets; ++i) {
-        int byte = get_byte(ctx);
-        if (byte == -1) {
-            return false;
-        }
-        length <<= 8;
-        length += byte;
-    }
-    *out_len = length;
-    return true;
+    length <<= 8;
+    length += byte;
+  }
+  *out_len = length;
+  return true;
 }
 
 /**
  * Returns the constructed type and advances the pointer. E.g. A0 -> 0
  */
-asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) {
-    int type = get_byte(ctx);
-    if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
-        return NULL;
-    }
+asn1_context* asn1_context::asn1_constructed_get() {
+  int type = get_byte();
+  if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  asn1_context* app_ctx = new asn1_context(p_, length);
+  app_ctx->app_type_ = type & kMaskAppType;
+  return app_ctx;
+}
+
+bool asn1_context::asn1_constructed_skip_all() {
+  int byte = peek_byte();
+  while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
+    skip_bytes(1);
     size_t length;
-    if (!decode_length(ctx, &length) || length > ctx->length) {
-        return NULL;
+    if (!decode_length(&length) || !skip_bytes(length)) {
+      return false;
     }
-    asn1_context_t* app_ctx = asn1_context_new(ctx->p, length);
-    app_ctx->app_type = type & kMaskAppType;
-    return app_ctx;
+    byte = peek_byte();
+  }
+  return byte != -1;
 }
 
-bool asn1_constructed_skip_all(asn1_context_t* ctx) {
-    int byte = peek_byte(ctx);
-    while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
-        skip_bytes(ctx, 1);
-        size_t length;
-        if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
-            return false;
-        }
-        byte = peek_byte(ctx);
-    }
-    return byte != -1;
+int asn1_context::asn1_constructed_type() const {
+  return app_type_;
 }
 
-int asn1_constructed_type(asn1_context_t* ctx) {
-    return ctx->app_type;
+asn1_context* asn1_context::asn1_sequence_get() {
+  if ((get_byte() & kMaskTag) != kTagSequence) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  return new asn1_context(p_, length);
 }
 
-asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) {
-    if ((get_byte(ctx) & kMaskTag) != kTagSequence) {
-        return NULL;
-    }
-    size_t length;
-    if (!decode_length(ctx, &length) || length > ctx->length) {
-        return NULL;
-    }
-    return asn1_context_new(ctx->p, length);
+asn1_context* asn1_context::asn1_set_get() {
+  if ((get_byte() & kMaskTag) != kTagSet) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  return new asn1_context(p_, length);
 }
 
-asn1_context_t* asn1_set_get(asn1_context_t* ctx) {
-    if ((get_byte(ctx) & kMaskTag) != kTagSet) {
-        return NULL;
-    }
-    size_t length;
-    if (!decode_length(ctx, &length) || length > ctx->length) {
-        return NULL;
-    }
-    return asn1_context_new(ctx->p, length);
+bool asn1_context::asn1_sequence_next() {
+  size_t length;
+  if (get_byte() == -1 || !decode_length(&length) || !skip_bytes(length)) {
+    return false;
+  }
+  return true;
 }
 
-bool asn1_sequence_next(asn1_context_t* ctx) {
-    size_t length;
-    if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
-        return false;
-    }
-    return true;
+bool asn1_context::asn1_oid_get(const uint8_t** oid, size_t* length) {
+  if (get_byte() != kTagOid) {
+    return false;
+  }
+  if (!decode_length(length) || *length == 0 || *length > length_) {
+    return false;
+  }
+  *oid = p_;
+  return true;
 }
 
-bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) {
-    if (get_byte(ctx) != kTagOid) {
-        return false;
-    }
-    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
-        return false;
-    }
-    *oid = ctx->p;
-    return true;
-}
-
-bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) {
-    if (get_byte(ctx) != kTagOctetString) {
-        return false;
-    }
-    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
-        return false;
-    }
-    *octet_string = ctx->p;
-    return true;
+bool asn1_context::asn1_octet_string_get(const uint8_t** octet_string, size_t* length) {
+  if (get_byte() != kTagOctetString) {
+    return false;
+  }
+  if (!decode_length(length) || *length == 0 || *length > length_) {
+    return false;
+  }
+  *octet_string = p_;
+  return true;
 }
diff --git a/asn1_decoder.h b/asn1_decoder.h
index b17141c..3e99211 100644
--- a/asn1_decoder.h
+++ b/asn1_decoder.h
@@ -14,23 +14,42 @@
  * limitations under the License.
  */
 
-
 #ifndef ASN1_DECODER_H_
 #define ASN1_DECODER_H_
 
 #include <stdint.h>
 
-typedef struct asn1_context asn1_context_t;
+class asn1_context {
+ public:
+  asn1_context(const uint8_t* buffer, size_t length) : p_(buffer), length_(length), app_type_(0) {}
+  int asn1_constructed_type() const;
+  asn1_context* asn1_constructed_get();
+  bool asn1_constructed_skip_all();
+  asn1_context* asn1_sequence_get();
+  asn1_context* asn1_set_get();
+  bool asn1_sequence_next();
+  bool asn1_oid_get(const uint8_t** oid, size_t* length);
+  bool asn1_octet_string_get(const uint8_t** octet_string, size_t* length);
 
-asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length);
-void asn1_context_free(asn1_context_t* ctx);
-asn1_context_t* asn1_constructed_get(asn1_context_t* ctx);
-bool asn1_constructed_skip_all(asn1_context_t* ctx);
-int asn1_constructed_type(asn1_context_t* ctx);
-asn1_context_t* asn1_sequence_get(asn1_context_t* ctx);
-asn1_context_t* asn1_set_get(asn1_context_t* ctx);
-bool asn1_sequence_next(asn1_context_t* seq);
-bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length);
-bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length);
+ private:
+  static constexpr int kMaskConstructed = 0xE0;
+  static constexpr int kMaskTag = 0x7F;
+  static constexpr int kMaskAppType = 0x1F;
+
+  static constexpr int kTagOctetString = 0x04;
+  static constexpr int kTagOid = 0x06;
+  static constexpr int kTagSequence = 0x30;
+  static constexpr int kTagSet = 0x31;
+  static constexpr int kTagConstructed = 0xA0;
+
+  int peek_byte() const;
+  int get_byte();
+  bool skip_bytes(size_t num_skip);
+  bool decode_length(size_t* out_len);
+
+  const uint8_t* p_;
+  size_t length_;
+  int app_type_;
+};
 
 #endif /* ASN1_DECODER_H_ */
diff --git a/bootloader_message/Android.mk b/bootloader_message/Android.mk
index 815ac67..a8c5081 100644
--- a/bootloader_message/Android.mk
+++ b/bootloader_message/Android.mk
@@ -19,6 +19,7 @@
 LOCAL_SRC_FILES := bootloader_message.cpp
 LOCAL_MODULE := libbootloader_message
 LOCAL_STATIC_LIBRARIES := libbase libfs_mgr
+LOCAL_CFLAGS := -Werror
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 include $(BUILD_STATIC_LIBRARY)
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index 9de7dff..d8086be 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -19,38 +19,24 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
-#include <sys/system_properties.h>
 
 #include <string>
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 #include <fs_mgr.h>
 
-static struct fstab* read_fstab(std::string* err) {
-  // The fstab path is always "/fstab.${ro.hardware}".
-  std::string fstab_path = "/fstab.";
-  char value[PROP_VALUE_MAX];
-  if (__system_property_get("ro.hardware", value) == 0) {
-    *err = "failed to get ro.hardware";
-    return nullptr;
-  }
-  fstab_path += value;
-  struct fstab* fstab = fs_mgr_read_fstab(fstab_path.c_str());
-  if (fstab == nullptr) {
-    *err = "failed to read " + fstab_path;
-  }
-  return fstab;
-}
-
 static std::string get_misc_blk_device(std::string* err) {
-  struct fstab* fstab = read_fstab(err);
-  if (fstab == nullptr) {
+  std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
+                                                             fs_mgr_free_fstab);
+  if (!fstab) {
+    *err = "failed to read default fstab";
     return "";
   }
-  fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+  fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc");
   if (record == nullptr) {
     *err = "failed to find /misc partition";
     return "";
@@ -81,26 +67,23 @@
   return ret == 0;
 }
 
-static bool read_misc_partition(void* p, size_t size, size_t offset, std::string* err) {
-  std::string misc_blk_device = get_misc_blk_device(err);
-  if (misc_blk_device.empty()) {
-    return false;
-  }
+static bool read_misc_partition(void* p, size_t size, const std::string& misc_blk_device,
+                                size_t offset, std::string* err) {
   if (!wait_for_device(misc_blk_device, err)) {
     return false;
   }
   android::base::unique_fd fd(open(misc_blk_device.c_str(), O_RDONLY));
-  if (fd.get() == -1) {
+  if (fd == -1) {
     *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
+  if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
     *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  if (!android::base::ReadFully(fd.get(), p, size)) {
+  if (!android::base::ReadFully(fd, p, size)) {
     *err = android::base::StringPrintf("failed to read %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
@@ -108,30 +91,25 @@
   return true;
 }
 
-static bool write_misc_partition(const void* p, size_t size, size_t offset, std::string* err) {
-  std::string misc_blk_device = get_misc_blk_device(err);
-  if (misc_blk_device.empty()) {
-    return false;
-  }
-  android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC));
-  if (fd.get() == -1) {
+static bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device,
+                                 size_t offset, std::string* err) {
+  android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY));
+  if (fd == -1) {
     *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
+  if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
     *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  if (!android::base::WriteFully(fd.get(), p, size)) {
+  if (!android::base::WriteFully(fd, p, size)) {
     *err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-
-  // TODO: O_SYNC and fsync duplicates each other?
-  if (fsync(fd.get()) == -1) {
+  if (fsync(fd) == -1) {
     *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
@@ -139,12 +117,32 @@
   return true;
 }
 
+bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device,
+                                  std::string* err) {
+  return read_misc_partition(boot, sizeof(*boot), misc_blk_device,
+                             BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
+}
+
 bool read_bootloader_message(bootloader_message* boot, std::string* err) {
-  return read_misc_partition(boot, sizeof(*boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return read_bootloader_message_from(boot, misc_blk_device, err);
+}
+
+bool write_bootloader_message_to(const bootloader_message& boot, const std::string& misc_blk_device,
+                                 std::string* err) {
+  return write_misc_partition(&boot, sizeof(boot), misc_blk_device,
+                              BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
 }
 
 bool write_bootloader_message(const bootloader_message& boot, std::string* err) {
-  return write_misc_partition(&boot, sizeof(boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return write_bootloader_message_to(boot, misc_blk_device, err);
 }
 
 bool clear_bootloader_message(std::string* err) {
@@ -165,16 +163,64 @@
   return write_bootloader_message(boot, err);
 }
 
+bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
+  bootloader_message boot;
+  if (!read_bootloader_message(&boot, err)) {
+    return false;
+  }
+
+  // Zero out the entire fields.
+  memset(boot.command, 0, sizeof(boot.command));
+  memset(boot.recovery, 0, sizeof(boot.recovery));
+
+  strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
+  strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
+  for (const auto& s : options) {
+    strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery));
+    if (s.back() != '\n') {
+      strlcat(boot.recovery, "\n", sizeof(boot.recovery));
+    }
+  }
+  return write_bootloader_message(boot, err);
+}
+
+bool write_reboot_bootloader(std::string* err) {
+  bootloader_message boot;
+  if (!read_bootloader_message(&boot, err)) {
+    return false;
+  }
+  if (boot.command[0] != '\0') {
+    *err = "Bootloader command pending.";
+    return false;
+  }
+  strlcpy(boot.command, "bootonce-bootloader", sizeof(boot.command));
+  return write_bootloader_message(boot, err);
+}
+
 bool read_wipe_package(std::string* package_data, size_t size, std::string* err) {
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
   package_data->resize(size);
-  return read_misc_partition(&(*package_data)[0], size, WIPE_PACKAGE_OFFSET_IN_MISC, err);
+  return read_misc_partition(&(*package_data)[0], size, misc_blk_device,
+                             WIPE_PACKAGE_OFFSET_IN_MISC, err);
 }
 
 bool write_wipe_package(const std::string& package_data, std::string* err) {
-  return write_misc_partition(package_data.data(), package_data.size(),
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return write_misc_partition(package_data.data(), package_data.size(), misc_blk_device,
                               WIPE_PACKAGE_OFFSET_IN_MISC, err);
 }
 
+extern "C" bool write_reboot_bootloader(void) {
+  std::string err;
+  return write_reboot_bootloader(&err);
+}
+
 extern "C" bool write_bootloader_message(const char* options) {
   std::string err;
   return write_bootloader_message({options}, &err);
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index c63aeca..bc5104d 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -17,18 +17,21 @@
 #ifndef _BOOTLOADER_MESSAGE_H
 #define _BOOTLOADER_MESSAGE_H
 
+#include <assert.h>
 #include <stddef.h>
+#include <stdint.h>
 
 // Spaces used by misc partition are as below:
-// 0   - 2K     Bootloader Message
-// 2K  - 16K    Used by Vendor's bootloader
+// 0   - 2K     For bootloader_message
+// 2K  - 16K    Used by Vendor's bootloader (the 2K - 4K range may be optionally used
+//              as bootloader_message_ab struct)
 // 16K - 64K    Used by uncrypt and recovery to store wipe_package for A/B devices
 // Note that these offsets are admitted by bootloader,recovery and uncrypt, so they
 // are not configurable without changing all of them.
 static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0;
 static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024;
 
-/* Bootloader Message
+/* Bootloader Message (2-KiB)
  *
  * This structure describes the content of a block in flash
  * that is used for recovery and the bootloader to talk to
@@ -39,8 +42,9 @@
  * It is also updated by the bootloader when firmware update
  * is complete (to boot into recovery for any final cleanup)
  *
- * The status field is written by the bootloader after the
- * completion of an "update-radio" or "update-hboot" command.
+ * The status field was used by the bootloader after the completion
+ * of an "update-radio" or "update-hboot" command, which has been
+ * deprecated since Froyo.
  *
  * The recovery field is only written by linux and used
  * for the system to send a message to recovery or the
@@ -51,12 +55,10 @@
  * package it is.  If the value is of the format "#/#" (eg, "1/3"),
  * the UI will add a simple indicator of that status.
  *
- * The slot_suffix field is used for A/B implementations where the
- * bootloader does not set the androidboot.ro.boot.slot_suffix kernel
- * commandline parameter. This is used by fs_mgr to mount /system and
- * other partitions with the slotselect flag set in fstab. A/B
- * implementations are free to use all 32 bytes and may store private
- * data past the first NUL-byte in this field.
+ * We used to have slot_suffix field for A/B boot control metadata in
+ * this struct, which gets unintentionally cleared by recovery or
+ * uncrypt. Move it into struct bootloader_message_ab to avoid the
+ * issue.
  */
 struct bootloader_message {
     char command[32];
@@ -69,23 +71,148 @@
     // stage string (for multistage packages) and possible future
     // expansion.
     char stage[32];
-    char slot_suffix[32];
-    char reserved[192];
+
+    // The 'reserved' field used to be 224 bytes when it was initially
+    // carved off from the 1024-byte recovery field. Bump it up to
+    // 1184-byte so that the entire bootloader_message struct rounds up
+    // to 2048-byte.
+    char reserved[1184];
 };
 
+/**
+ * We must be cautious when changing the bootloader_message struct size,
+ * because A/B-specific fields may end up with different offsets.
+ */
+#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus)
+static_assert(sizeof(struct bootloader_message) == 2048,
+              "struct bootloader_message size changes, which may break A/B devices");
+#endif
+
+/**
+ * The A/B-specific bootloader message structure (4-KiB).
+ *
+ * We separate A/B boot control metadata from the regular bootloader
+ * message struct and keep it here. Everything that's A/B-specific
+ * stays after struct bootloader_message, which should be managed by
+ * the A/B-bootloader or boot control HAL.
+ *
+ * The slot_suffix field is used for A/B implementations where the
+ * bootloader does not set the androidboot.ro.boot.slot_suffix kernel
+ * commandline parameter. This is used by fs_mgr to mount /system and
+ * other partitions with the slotselect flag set in fstab. A/B
+ * implementations are free to use all 32 bytes and may store private
+ * data past the first NUL-byte in this field. It is encouraged, but
+ * not mandatory, to use 'struct bootloader_control' described below.
+ */
+struct bootloader_message_ab {
+    struct bootloader_message message;
+    char slot_suffix[32];
+
+    // Round up the entire struct to 4096-byte.
+    char reserved[2016];
+};
+
+/**
+ * Be cautious about the struct size change, in case we put anything post
+ * bootloader_message_ab struct (b/29159185).
+ */
+#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus)
+static_assert(sizeof(struct bootloader_message_ab) == 4096,
+              "struct bootloader_message_ab size changes");
+#endif
+
+#define BOOT_CTRL_MAGIC   0x42414342 /* Bootloader Control AB */
+#define BOOT_CTRL_VERSION 1
+
+struct slot_metadata {
+    // Slot priority with 15 meaning highest priority, 1 lowest
+    // priority and 0 the slot is unbootable.
+    uint8_t priority : 4;
+    // Number of times left attempting to boot this slot.
+    uint8_t tries_remaining : 3;
+    // 1 if this slot has booted successfully, 0 otherwise.
+    uint8_t successful_boot : 1;
+    // 1 if this slot is corrupted from a dm-verity corruption, 0
+    // otherwise.
+    uint8_t verity_corrupted : 1;
+    // Reserved for further use.
+    uint8_t reserved : 7;
+} __attribute__((packed));
+
+/* Bootloader Control AB
+ *
+ * This struct can be used to manage A/B metadata. It is designed to
+ * be put in the 'slot_suffix' field of the 'bootloader_message'
+ * structure described above. It is encouraged to use the
+ * 'bootloader_control' structure to store the A/B metadata, but not
+ * mandatory.
+ */
+struct bootloader_control {
+    // NUL terminated active slot suffix.
+    char slot_suffix[4];
+    // Bootloader Control AB magic number (see BOOT_CTRL_MAGIC).
+    uint32_t magic;
+    // Version of struct being used (see BOOT_CTRL_VERSION).
+    uint8_t version;
+    // Number of slots being managed.
+    uint8_t nb_slot : 3;
+    // Number of times left attempting to boot recovery.
+    uint8_t recovery_tries_remaining : 3;
+    // Ensure 4-bytes alignment for slot_info field.
+    uint8_t reserved0[2];
+    // Per-slot information.  Up to 4 slots.
+    struct slot_metadata slot_info[4];
+    // Reserved for further use.
+    uint8_t reserved1[8];
+    // CRC32 of all 28 bytes preceding this field (little endian
+    // format).
+    uint32_t crc32_le;
+} __attribute__((packed));
+
+#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus)
+static_assert(sizeof(struct bootloader_control) ==
+              sizeof(((struct bootloader_message_ab *)0)->slot_suffix),
+              "struct bootloader_control has wrong size");
+#endif
+
 #ifdef __cplusplus
 
 #include <string>
 #include <vector>
 
+// Read bootloader message into boot. Error message will be set in err.
 bool read_bootloader_message(bootloader_message* boot, std::string* err);
+
+// Read bootloader message from the specified misc device into boot.
+bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device,
+                                  std::string* err);
+
+// Write bootloader message to BCB.
 bool write_bootloader_message(const bootloader_message& boot, std::string* err);
+
+// Write bootloader message to the specified BCB device.
+bool write_bootloader_message_to(const bootloader_message& boot,
+                                 const std::string& misc_blk_device, std::string* err);
+
+// Write bootloader message (boots into recovery with the options) to BCB. Will
+// set the command and recovery fields, and reset the rest.
 bool write_bootloader_message(const std::vector<std::string>& options, std::string* err);
+
+// Update bootloader message (boots into recovery with the options) to BCB. Will
+// only update the command and recovery fields.
+bool update_bootloader_message(const std::vector<std::string>& options, std::string* err);
+
+// Clear BCB.
 bool clear_bootloader_message(std::string* err);
 
-bool read_wipe_package(std::string* package_data, size_t size, std::string* err);
-bool write_wipe_package(const std::string& package_data, std::string* err);
+// Writes the reboot-bootloader reboot reason to the bootloader_message.
+bool write_reboot_bootloader(std::string* err);
 
+// Read the wipe package from BCB (from offset WIPE_PACKAGE_OFFSET_IN_MISC).
+bool read_wipe_package(std::string* package_data, size_t size, std::string* err);
+
+// Write the wipe package into BCB (to offset WIPE_PACKAGE_OFFSET_IN_MISC).
+bool write_wipe_package(const std::string& package_data, std::string* err);
 
 #else
 
@@ -93,6 +220,7 @@
 
 // C Interface.
 bool write_bootloader_message(const char* options);
+bool write_reboot_bootloader(void);
 
 #endif  // ifdef __cplusplus
 
diff --git a/common.h b/common.h
index de8b409..62fb132 100644
--- a/common.h
+++ b/common.h
@@ -17,27 +17,24 @@
 #ifndef RECOVERY_COMMON_H
 #define RECOVERY_COMMON_H
 
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdarg.h>
 
-#define LOGE(...) ui_print("E:" __VA_ARGS__)
-#define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__)
-#define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__)
-
-#if 0
-#define LOGV(...) fprintf(stdout, "V:" __VA_ARGS__)
-#define LOGD(...) fprintf(stdout, "D:" __VA_ARGS__)
-#else
-#define LOGV(...) do {} while (0)
-#define LOGD(...) do {} while (0)
-#endif
+#include <string>
 
 #define STRINGIFY(x) #x
 #define EXPAND(x) STRINGIFY(x)
 
+class RecoveryUI;
+
+extern RecoveryUI* ui;
 extern bool modified_flash;
-typedef struct fstab_rec Volume;
+
+// The current stage, e.g. "1/2".
+extern std::string stage;
+
+// The reason argument provided in "--reason=".
+extern const char* reason;
 
 // fopen a file, mounting volumes and making parent dirs as necessary.
 FILE* fopen_path(const char *path, const char *mode);
@@ -46,4 +43,6 @@
 
 bool is_ro_debuggable();
 
+bool reboot(const std::string& command);
+
 #endif  // RECOVERY_COMMON_H
diff --git a/device.cpp b/device.cpp
index e717ddd..6150186 100644
--- a/device.cpp
+++ b/device.cpp
@@ -60,7 +60,7 @@
   return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position];
 }
 
-int Device::HandleMenuKey(int key, int visible) {
+int Device::HandleMenuKey(int key, bool visible) {
   if (!visible) {
     return kNoAction;
   }
diff --git a/device.h b/device.h
index 5017782..639e2bf 100644
--- a/device.h
+++ b/device.h
@@ -20,96 +20,90 @@
 #include "ui.h"
 
 class Device {
-  public:
-    Device(RecoveryUI* ui) : ui_(ui) { }
-    virtual ~Device() { }
+ public:
+  explicit Device(RecoveryUI* ui) : ui_(ui) {}
+  virtual ~Device() {}
 
-    // Called to obtain the UI object that should be used to display
-    // the recovery user interface for this device.  You should not
-    // have called Init() on the UI object already, the caller will do
-    // that after this method returns.
-    virtual RecoveryUI* GetUI() { return ui_; }
+  // Called to obtain the UI object that should be used to display the recovery user interface for
+  // this device. You should not have called Init() on the UI object already, the caller will do
+  // that after this method returns.
+  virtual RecoveryUI* GetUI() {
+    return ui_;
+  }
 
-    // Called when recovery starts up (after the UI has been obtained
-    // and initialized and after the arguments have been parsed, but
-    // before anything else).
-    virtual void StartRecovery() { };
+  // Called when recovery starts up (after the UI has been obtained and initialized and after the
+  // arguments have been parsed, but before anything else).
+  virtual void StartRecovery() {};
 
-    // Called from the main thread when recovery is at the main menu
-    // and waiting for input, and a key is pressed.  (Note that "at"
-    // the main menu does not necessarily mean the menu is visible;
-    // recovery will be at the main menu with it invisible after an
-    // unsuccessful operation [ie OTA package failure], or if recovery
-    // is started with no command.)
-    //
-    // key is the code of the key just pressed.  (You can call
-    // IsKeyPressed() on the RecoveryUI object you returned from GetUI
-    // if you want to find out if other keys are held down.)
-    //
-    // visible is true if the menu is visible.
-    //
-    // Return one of the defined constants below in order to:
-    //
-    //   - move the menu highlight (kHighlight{Up,Down})
-    //   - invoke the highlighted item (kInvokeItem)
-    //   - do nothing (kNoAction)
-    //   - invoke a specific action (a menu position: any non-negative number)
-    virtual int HandleMenuKey(int key, int visible);
+  // Called from the main thread when recovery is at the main menu and waiting for input, and a key
+  // is pressed. (Note that "at" the main menu does not necessarily mean the menu is visible;
+  // recovery will be at the main menu with it invisible after an unsuccessful operation [ie OTA
+  // package failure], or if recovery is started with no command.)
+  //
+  // 'key' is the code of the key just pressed. (You can call IsKeyPressed() on the RecoveryUI
+  // object you returned from GetUI if you want to find out if other keys are held down.)
+  //
+  // 'visible' is true if the menu is visible.
+  //
+  // Returns one of the defined constants below in order to:
+  //
+  //   - move the menu highlight (kHighlight{Up,Down})
+  //   - invoke the highlighted item (kInvokeItem)
+  //   - do nothing (kNoAction)
+  //   - invoke a specific action (a menu position: any non-negative number)
+  virtual int HandleMenuKey(int key, bool visible);
 
-    enum BuiltinAction {
-        NO_ACTION = 0,
-        REBOOT = 1,
-        APPLY_SDCARD = 2,
-        // APPLY_CACHE was 3.
-        APPLY_ADB_SIDELOAD = 4,
-        WIPE_DATA = 5,
-        WIPE_CACHE = 6,
-        REBOOT_BOOTLOADER = 7,
-        SHUTDOWN = 8,
-        VIEW_RECOVERY_LOGS = 9,
-        MOUNT_SYSTEM = 10,
-        RUN_GRAPHICS_TEST = 11,
-    };
+  enum BuiltinAction {
+    NO_ACTION = 0,
+    REBOOT = 1,
+    APPLY_SDCARD = 2,
+    // APPLY_CACHE was 3.
+    APPLY_ADB_SIDELOAD = 4,
+    WIPE_DATA = 5,
+    WIPE_CACHE = 6,
+    REBOOT_BOOTLOADER = 7,
+    SHUTDOWN = 8,
+    VIEW_RECOVERY_LOGS = 9,
+    MOUNT_SYSTEM = 10,
+    RUN_GRAPHICS_TEST = 11,
+  };
 
-    // Return the list of menu items (an array of strings,
-    // NULL-terminated).  The menu_position passed to InvokeMenuItem
-    // will correspond to the indexes into this array.
-    virtual const char* const* GetMenuItems();
+  // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed
+  // to InvokeMenuItem will correspond to the indexes into this array.
+  virtual const char* const* GetMenuItems();
 
-    // Perform a recovery action selected from the menu.
-    // 'menu_position' will be the item number of the selected menu
-    // item, or a non-negative number returned from
-    // device_handle_key().  The menu will be hidden when this is
-    // called; implementations can call ui_print() to print
-    // information to the screen.  If the menu position is one of the
-    // builtin actions, you can just return the corresponding enum
-    // value.  If it is an action specific to your device, you
-    // actually perform it here and return NO_ACTION.
-    virtual BuiltinAction InvokeMenuItem(int menu_position);
+  // Perform a recovery action selected from the menu. 'menu_position' will be the item number of
+  // the selected menu item, or a non-negative number returned from HandleMenuKey(). The menu will
+  // be hidden when this is called; implementations can call ui_print() to print information to the
+  // screen. If the menu position is one of the builtin actions, you can just return the
+  // corresponding enum value. If it is an action specific to your device, you actually perform it
+  // here and return NO_ACTION.
+  virtual BuiltinAction InvokeMenuItem(int menu_position);
 
-    static const int kNoAction = -1;
-    static const int kHighlightUp = -2;
-    static const int kHighlightDown = -3;
-    static const int kInvokeItem = -4;
+  static const int kNoAction = -1;
+  static const int kHighlightUp = -2;
+  static const int kHighlightDown = -3;
+  static const int kInvokeItem = -4;
 
-    // Called before and after we do a wipe data/factory reset operation,
-    // either via a reboot from the main system with the --wipe_data flag,
-    // or when the user boots into recovery image manually and selects the
-    // option from the menu, to perform whatever device-specific wiping
-    // actions are needed.
-    // Return true on success; returning false from PreWipeData will prevent
-    // the regular wipe, and returning false from PostWipeData will cause
-    // the wipe to be considered a failure.
-    virtual bool PreWipeData() { return true; }
-    virtual bool PostWipeData() { return true; }
+  // Called before and after we do a wipe data/factory reset operation, either via a reboot from the
+  // main system with the --wipe_data flag, or when the user boots into recovery image manually and
+  // selects the option from the menu, to perform whatever device-specific wiping actions as needed.
+  // Returns true on success; returning false from PreWipeData will prevent the regular wipe, and
+  // returning false from PostWipeData will cause the wipe to be considered a failure.
+  virtual bool PreWipeData() {
+    return true;
+  }
 
-  private:
-    RecoveryUI* ui_;
+  virtual bool PostWipeData() {
+    return true;
+  }
+
+ private:
+  RecoveryUI* ui_;
 };
 
-// The device-specific library must define this function (or the
-// default one will be used, if there is no device-specific library).
-// It returns the Device object that recovery should use.
+// The device-specific library must define this function (or the default one will be used, if there
+// is no device-specific library). It returns the Device object that recovery should use.
 Device* make_device();
 
 #endif  // _DEVICE_H
diff --git a/edify/Android.mk b/edify/Android.mk
index 71cf765..d8058c1 100644
--- a/edify/Android.mk
+++ b/edify/Android.mk
@@ -1,23 +1,36 @@
 # Copyright 2009 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 agreed 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.
 
 LOCAL_PATH := $(call my-dir)
 
 edify_src_files := \
-	lexer.ll \
-	parser.yy \
-	expr.cpp
+    lexer.ll \
+    parser.yy \
+    expr.cpp
 
 #
-# Build the host-side command line tool
+# Build the host-side command line tool (host executable)
 #
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
-		$(edify_src_files) \
-		main.cpp
+    $(edify_src_files) \
+    edify_parser.cpp
 
+LOCAL_CFLAGS := -Werror
 LOCAL_CPPFLAGS := -g -O0
-LOCAL_MODULE := edify
+LOCAL_MODULE := edify_parser
 LOCAL_YACCFLAGS := -v
 LOCAL_CPPFLAGS += -Wno-unused-parameter
 LOCAL_CPPFLAGS += -Wno-deprecated-register
@@ -28,12 +41,13 @@
 include $(BUILD_HOST_EXECUTABLE)
 
 #
-# Build the device-side library
+# Build the device-side library (static library)
 #
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(edify_src_files)
 
+LOCAL_CFLAGS := -Werror
 LOCAL_CPPFLAGS := -Wno-unused-parameter
 LOCAL_CPPFLAGS += -Wno-deprecated-register
 LOCAL_MODULE := libedify
diff --git a/edify/README b/edify/README.md
similarity index 99%
rename from edify/README
rename to edify/README.md
index 810455c..b3330e2 100644
--- a/edify/README
+++ b/edify/README.md
@@ -1,3 +1,6 @@
+edify
+=====
+
 Update scripts (from donut onwards) are written in a new little
 scripting language ("edify") that is superficially somewhat similar to
 the old one ("amend").  This is a brief overview of the new language.
diff --git a/edify/edify_parser.cpp b/edify/edify_parser.cpp
new file mode 100644
index 0000000..f1b5628
--- /dev/null
+++ b/edify/edify_parser.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009 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 agreed 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.
+ */
+
+/**
+ * This is a host-side tool for validating a given edify script file.
+ *
+ * We used to have edify test cases here, which have been moved to
+ * tests/component/edify_test.cpp.
+ *
+ * Caveat: It doesn't recognize functions defined through updater, which
+ * makes the tool less useful. We should either extend the tool or remove it.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <android-base/file.h>
+
+#include "expr.h"
+
+static void ExprDump(int depth, const std::unique_ptr<Expr>& n, const std::string& script) {
+    printf("%*s", depth*2, "");
+    printf("%s %p (%d-%d) \"%s\"\n",
+           n->name.c_str(), n->fn, n->start, n->end,
+           script.substr(n->start, n->end - n->start).c_str());
+    for (size_t i = 0; i < n->argv.size(); ++i) {
+        ExprDump(depth+1, n->argv[i], script);
+    }
+}
+
+int main(int argc, char** argv) {
+    RegisterBuiltins();
+
+    if (argc != 2) {
+        printf("Usage: %s <edify script>\n", argv[0]);
+        return 1;
+    }
+
+    std::string buffer;
+    if (!android::base::ReadFileToString(argv[1], &buffer)) {
+        printf("%s: failed to read %s: %s\n", argv[0], argv[1], strerror(errno));
+        return 1;
+    }
+
+    std::unique_ptr<Expr> root;
+    int error_count = 0;
+    int error = parse_string(buffer.data(), &root, &error_count);
+    printf("parse returned %d; %d errors encountered\n", error, error_count);
+    if (error == 0 || error_count > 0) {
+
+        ExprDump(0, root, buffer);
+
+        State state(buffer, nullptr);
+        std::string result;
+        if (!Evaluate(&state, root, &result)) {
+            printf("result was NULL, message is: %s\n",
+                   (state.errmsg.empty() ? "(NULL)" : state.errmsg.c_str()));
+        } else {
+            printf("result is [%s]\n", result.c_str());
+        }
+    }
+    return 0;
+}
diff --git a/edify/expr.cpp b/edify/expr.cpp
index cc14fbe..54ab332 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -14,201 +14,172 @@
  * limitations under the License.
  */
 
-#include <string.h>
-#include <stdbool.h>
+#include "expr.h"
+
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <stdarg.h>
+#include <string.h>
 #include <unistd.h>
 
+#include <memory>
 #include <string>
+#include <unordered_map>
+#include <vector>
 
+#include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
-#include "expr.h"
-
 // Functions should:
 //
 //    - return a malloc()'d string
-//    - if Evaluate() on any argument returns NULL, return NULL.
+//    - if Evaluate() on any argument returns nullptr, return nullptr.
 
-int BooleanString(const char* s) {
-    return s[0] != '\0';
+static bool BooleanString(const std::string& s) {
+    return !s.empty();
 }
 
-char* Evaluate(State* state, Expr* expr) {
-    Value* v = expr->fn(expr->name, state, expr->argc, expr->argv);
-    if (v == NULL) return NULL;
+bool Evaluate(State* state, const std::unique_ptr<Expr>& expr, std::string* result) {
+    if (result == nullptr) {
+        return false;
+    }
+
+    std::unique_ptr<Value> v(expr->fn(expr->name.c_str(), state, expr->argv));
+    if (!v) {
+        return false;
+    }
     if (v->type != VAL_STRING) {
         ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type);
-        FreeValue(v);
-        return NULL;
+        return false;
     }
-    char* result = v->data;
-    free(v);
-    return result;
+
+    *result = v->data;
+    return true;
 }
 
-Value* EvaluateValue(State* state, Expr* expr) {
-    return expr->fn(expr->name, state, expr->argc, expr->argv);
+Value* EvaluateValue(State* state, const std::unique_ptr<Expr>& expr) {
+    return expr->fn(expr->name.c_str(), state, expr->argv);
 }
 
-Value* StringValue(char* str) {
-    if (str == NULL) return NULL;
-    Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value)));
-    v->type = VAL_STRING;
-    v->size = strlen(str);
-    v->data = str;
-    return v;
-}
-
-void FreeValue(Value* v) {
-    if (v == NULL) return;
-    free(v->data);
-    free(v);
-}
-
-Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc == 0) {
-        return StringValue(strdup(""));
+Value* StringValue(const char* str) {
+    if (str == nullptr) {
+        return nullptr;
     }
-    char** strings = reinterpret_cast<char**>(malloc(argc * sizeof(char*)));
-    int i;
-    for (i = 0; i < argc; ++i) {
-        strings[i] = NULL;
+    return new Value(VAL_STRING, str);
+}
+
+Value* StringValue(const std::string& str) {
+    return StringValue(str.c_str());
+}
+
+Value* ConcatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.empty()) {
+        return StringValue("");
     }
-    char* result = NULL;
-    int length = 0;
-    for (i = 0; i < argc; ++i) {
-        strings[i] = Evaluate(state, argv[i]);
-        if (strings[i] == NULL) {
-            goto done;
+    std::string result;
+    for (size_t i = 0; i < argv.size(); ++i) {
+        std::string str;
+        if (!Evaluate(state, argv[i], &str)) {
+            return nullptr;
         }
-        length += strlen(strings[i]);
+        result += str;
     }
 
-    result = reinterpret_cast<char*>(malloc(length+1));
-    int p;
-    p = 0;
-    for (i = 0; i < argc; ++i) {
-        strcpy(result+p, strings[i]);
-        p += strlen(strings[i]);
-    }
-    result[p] = '\0';
-
-  done:
-    for (i = 0; i < argc; ++i) {
-        free(strings[i]);
-    }
-    free(strings);
     return StringValue(result);
 }
 
-Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2 && argc != 3) {
-        free(state->errmsg);
-        state->errmsg = strdup("ifelse expects 2 or 3 arguments");
-        return NULL;
-    }
-    char* cond = Evaluate(state, argv[0]);
-    if (cond == NULL) {
-        return NULL;
+Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2 && argv.size() != 3) {
+        state->errmsg = "ifelse expects 2 or 3 arguments";
+        return nullptr;
     }
 
-    if (BooleanString(cond) == true) {
-        free(cond);
-        return EvaluateValue(state, argv[1]);
-    } else {
-        if (argc == 3) {
-            free(cond);
-            return EvaluateValue(state, argv[2]);
-        } else {
-            return StringValue(cond);
-        }
+    std::string cond;
+    if (!Evaluate(state, argv[0], &cond)) {
+        return nullptr;
     }
+
+    if (!cond.empty()) {
+        return EvaluateValue(state, argv[1]);
+    } else if (argv.size() == 3) {
+        return EvaluateValue(state, argv[2]);
+    }
+
+    return StringValue("");
 }
 
-Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* msg = NULL;
-    if (argc > 0) {
-        msg = Evaluate(state, argv[0]);
-    }
-    free(state->errmsg);
-    if (msg) {
+Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::string msg;
+    if (!argv.empty() && Evaluate(state, argv[0], &msg)) {
         state->errmsg = msg;
     } else {
-        state->errmsg = strdup("called abort()");
+        state->errmsg = "called abort()";
     }
-    return NULL;
+    return nullptr;
 }
 
-Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]) {
-    int i;
-    for (i = 0; i < argc; ++i) {
-        char* v = Evaluate(state, argv[i]);
-        if (v == NULL) {
-            return NULL;
+Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    for (size_t i = 0; i < argv.size(); ++i) {
+        std::string result;
+        if (!Evaluate(state, argv[i], &result)) {
+            return nullptr;
         }
-        int b = BooleanString(v);
-        free(v);
-        if (!b) {
-            int prefix_len;
+        if (result.empty()) {
             int len = argv[i]->end - argv[i]->start;
-            char* err_src = reinterpret_cast<char*>(malloc(len + 20));
-            strcpy(err_src, "assert failed: ");
-            prefix_len = strlen(err_src);
-            memcpy(err_src + prefix_len, state->script + argv[i]->start, len);
-            err_src[prefix_len + len] = '\0';
-            free(state->errmsg);
-            state->errmsg = err_src;
-            return NULL;
+            state->errmsg = "assert failed: " + state->script.substr(argv[i]->start, len);
+            return nullptr;
         }
     }
-    return StringValue(strdup(""));
+    return StringValue("");
 }
 
-Value* SleepFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* val = Evaluate(state, argv[0]);
-    if (val == NULL) {
-        return NULL;
+Value* SleepFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::string val;
+    if (!Evaluate(state, argv[0], &val)) {
+        return nullptr;
     }
-    int v = strtol(val, NULL, 10);
+
+    int v;
+    if (!android::base::ParseInt(val.c_str(), &v, 0)) {
+        return nullptr;
+    }
     sleep(v);
+
     return StringValue(val);
 }
 
-Value* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) {
-    int i;
-    for (i = 0; i < argc; ++i) {
-        char* v = Evaluate(state, argv[i]);
-        if (v == NULL) {
-            return NULL;
+Value* StdoutFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    for (size_t i = 0; i < argv.size(); ++i) {
+        std::string v;
+        if (!Evaluate(state, argv[i], &v)) {
+            return nullptr;
         }
-        fputs(v, stdout);
-        free(v);
+        fputs(v.c_str(), stdout);
     }
-    return StringValue(strdup(""));
+    return StringValue("");
 }
 
 Value* LogicalAndFn(const char* name, State* state,
-                   int argc, Expr* argv[]) {
-    char* left = Evaluate(state, argv[0]);
-    if (left == NULL) return NULL;
-    if (BooleanString(left) == true) {
-        free(left);
+                    const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::string left;
+    if (!Evaluate(state, argv[0], &left)) {
+        return nullptr;
+    }
+    if (BooleanString(left)) {
         return EvaluateValue(state, argv[1]);
     } else {
-        return StringValue(left);
+        return StringValue("");
     }
 }
 
 Value* LogicalOrFn(const char* name, State* state,
-                   int argc, Expr* argv[]) {
-    char* left = Evaluate(state, argv[0]);
-    if (left == NULL) return NULL;
-    if (BooleanString(left) == false) {
-        free(left);
+                   const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::string left;
+    if (!Evaluate(state, argv[0], &left)) {
+        return nullptr;
+    }
+    if (!BooleanString(left)) {
         return EvaluateValue(state, argv[1]);
     } else {
         return StringValue(left);
@@ -216,174 +187,144 @@
 }
 
 Value* LogicalNotFn(const char* name, State* state,
-                    int argc, Expr* argv[]) {
-    char* val = Evaluate(state, argv[0]);
-    if (val == NULL) return NULL;
-    bool bv = BooleanString(val);
-    free(val);
-    return StringValue(strdup(bv ? "" : "t"));
+                    const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::string val;
+    if (!Evaluate(state, argv[0], &val)) {
+        return nullptr;
+    }
+
+    return StringValue(BooleanString(val) ? "" : "t");
 }
 
 Value* SubstringFn(const char* name, State* state,
-                   int argc, Expr* argv[]) {
-    char* needle = Evaluate(state, argv[0]);
-    if (needle == NULL) return NULL;
-    char* haystack = Evaluate(state, argv[1]);
-    if (haystack == NULL) {
-        free(needle);
-        return NULL;
+                   const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::string needle;
+    if (!Evaluate(state, argv[0], &needle)) {
+        return nullptr;
     }
 
-    char* result = strdup(strstr(haystack, needle) ? "t" : "");
-    free(needle);
-    free(haystack);
+    std::string haystack;
+    if (!Evaluate(state, argv[1], &haystack)) {
+        return nullptr;
+    }
+
+    std::string result = (haystack.find(needle) != std::string::npos) ? "t" : "";
     return StringValue(result);
 }
 
-Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* left = Evaluate(state, argv[0]);
-    if (left == NULL) return NULL;
-    char* right = Evaluate(state, argv[1]);
-    if (right == NULL) {
-        free(left);
-        return NULL;
+Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::string left;
+    if (!Evaluate(state, argv[0], &left)) {
+        return nullptr;
+    }
+    std::string right;
+    if (!Evaluate(state, argv[1], &right)) {
+        return nullptr;
     }
 
-    char* result = strdup(strcmp(left, right) == 0 ? "t" : "");
-    free(left);
-    free(right);
+    const char* result = (left == right) ? "t" : "";
     return StringValue(result);
 }
 
-Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* left = Evaluate(state, argv[0]);
-    if (left == NULL) return NULL;
-    char* right = Evaluate(state, argv[1]);
-    if (right == NULL) {
-        free(left);
-        return NULL;
+Value* InequalityFn(const char* name, State* state,
+                    const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::string left;
+    if (!Evaluate(state, argv[0], &left)) {
+        return nullptr;
+    }
+    std::string right;
+    if (!Evaluate(state, argv[1], &right)) {
+        return nullptr;
     }
 
-    char* result = strdup(strcmp(left, right) != 0 ? "t" : "");
-    free(left);
-    free(right);
+    const char* result = (left != right) ? "t" : "";
     return StringValue(result);
 }
 
-Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) {
-    Value* left = EvaluateValue(state, argv[0]);
-    if (left == NULL) return NULL;
-    FreeValue(left);
+Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    std::unique_ptr<Value> left(EvaluateValue(state, argv[0]));
+    if (!left) {
+        return nullptr;
+    }
     return EvaluateValue(state, argv[1]);
 }
 
-Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2) {
-        free(state->errmsg);
-        state->errmsg = strdup("less_than_int expects 2 arguments");
-        return NULL;
+Value* LessThanIntFn(const char* name, State* state,
+                     const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2) {
+        state->errmsg = "less_than_int expects 2 arguments";
+        return nullptr;
     }
 
-    char* left;
-    char* right;
-    if (ReadArgs(state, argv, 2, &left, &right) < 0) return NULL;
-
-    bool result = false;
-    char* end;
-
-    long l_int = strtol(left, &end, 10);
-    if (left[0] == '\0' || *end != '\0') {
-        goto done;
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+        return nullptr;
     }
 
-    long r_int;
-    r_int = strtol(right, &end, 10);
-    if (right[0] == '\0' || *end != '\0') {
-        goto done;
+    // Parse up to at least long long or 64-bit integers.
+    int64_t l_int;
+    if (!android::base::ParseInt(args[0].c_str(), &l_int)) {
+        state->errmsg = "failed to parse int in " + args[0];
+        return nullptr;
     }
 
-    result = l_int < r_int;
+    int64_t r_int;
+    if (!android::base::ParseInt(args[1].c_str(), &r_int)) {
+        state->errmsg = "failed to parse int in " + args[1];
+        return nullptr;
+    }
 
-  done:
-    free(left);
-    free(right);
-    return StringValue(strdup(result ? "t" : ""));
+    return StringValue(l_int < r_int ? "t" : "");
 }
 
 Value* GreaterThanIntFn(const char* name, State* state,
-                        int argc, Expr* argv[]) {
-    if (argc != 2) {
-        free(state->errmsg);
-        state->errmsg = strdup("greater_than_int expects 2 arguments");
-        return NULL;
+                        const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2) {
+        state->errmsg = "greater_than_int expects 2 arguments";
+        return nullptr;
     }
 
-    Expr* temp[2];
-    temp[0] = argv[1];
-    temp[1] = argv[0];
-
-    return LessThanIntFn(name, state, 2, temp);
-}
-
-Value* Literal(const char* name, State* state, int argc, Expr* argv[]) {
-    return StringValue(strdup(name));
-}
-
-Expr* Build(Function fn, YYLTYPE loc, int count, ...) {
-    va_list v;
-    va_start(v, count);
-    Expr* e = reinterpret_cast<Expr*>(malloc(sizeof(Expr)));
-    e->fn = fn;
-    e->name = "(operator)";
-    e->argc = count;
-    e->argv = reinterpret_cast<Expr**>(malloc(count * sizeof(Expr*)));
-    int i;
-    for (i = 0; i < count; ++i) {
-        e->argv[i] = va_arg(v, Expr*);
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+        return nullptr;
     }
-    va_end(v);
-    e->start = loc.start;
-    e->end = loc.end;
-    return e;
+
+    // Parse up to at least long long or 64-bit integers.
+    int64_t l_int;
+    if (!android::base::ParseInt(args[0].c_str(), &l_int)) {
+        state->errmsg = "failed to parse int in " + args[0];
+        return nullptr;
+    }
+
+    int64_t r_int;
+    if (!android::base::ParseInt(args[1].c_str(), &r_int)) {
+        state->errmsg = "failed to parse int in " + args[1];
+        return nullptr;
+    }
+
+    return StringValue(l_int > r_int ? "t" : "");
+}
+
+Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    return StringValue(name);
 }
 
 // -----------------------------------------------------------------
 //   the function table
 // -----------------------------------------------------------------
 
-static int fn_entries = 0;
-static int fn_size = 0;
-NamedFunction* fn_table = NULL;
+static std::unordered_map<std::string, Function> fn_table;
 
-void RegisterFunction(const char* name, Function fn) {
-    if (fn_entries >= fn_size) {
-        fn_size = fn_size*2 + 1;
-        fn_table = reinterpret_cast<NamedFunction*>(realloc(fn_table, fn_size * sizeof(NamedFunction)));
+void RegisterFunction(const std::string& name, Function fn) {
+    fn_table[name] = fn;
+}
+
+Function FindFunction(const std::string& name) {
+    if (fn_table.find(name) == fn_table.end()) {
+        return nullptr;
+    } else {
+        return fn_table[name];
     }
-    fn_table[fn_entries].name = name;
-    fn_table[fn_entries].fn = fn;
-    ++fn_entries;
-}
-
-static int fn_entry_compare(const void* a, const void* b) {
-    const char* na = ((const NamedFunction*)a)->name;
-    const char* nb = ((const NamedFunction*)b)->name;
-    return strcmp(na, nb);
-}
-
-void FinishRegistration() {
-    qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare);
-}
-
-Function FindFunction(const char* name) {
-    NamedFunction key;
-    key.name = name;
-    NamedFunction* nf = reinterpret_cast<NamedFunction*>(bsearch(&key, fn_table, fn_entries,
-            sizeof(NamedFunction), fn_entry_compare));
-    if (nf == NULL) {
-        return NULL;
-    }
-    return nf->fn;
 }
 
 void RegisterBuiltins() {
@@ -404,106 +345,56 @@
 //   convenience methods for functions
 // -----------------------------------------------------------------
 
-// Evaluate the expressions in argv, giving 'count' char* (the ... is
-// zero or more char** to put them in).  If any expression evaluates
-// to NULL, free the rest and return -1.  Return 0 on success.
-int ReadArgs(State* state, Expr* argv[], int count, ...) {
-    char** args = reinterpret_cast<char**>(malloc(count * sizeof(char*)));
-    va_list v;
-    va_start(v, count);
-    int i;
-    for (i = 0; i < count; ++i) {
-        args[i] = Evaluate(state, argv[i]);
-        if (args[i] == NULL) {
-            va_end(v);
-            int j;
-            for (j = 0; j < i; ++j) {
-                free(args[j]);
-            }
-            free(args);
-            return -1;
-        }
-        *(va_arg(v, char**)) = args[i];
-    }
-    va_end(v);
-    free(args);
-    return 0;
+// Evaluate the expressions in argv, and put the results of strings in args. If any expression
+// evaluates to nullptr, return false. Return true on success.
+bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+              std::vector<std::string>* args) {
+    return ReadArgs(state, argv, args, 0, argv.size());
 }
 
-// Evaluate the expressions in argv, giving 'count' Value* (the ... is
-// zero or more Value** to put them in).  If any expression evaluates
-// to NULL, free the rest and return -1.  Return 0 on success.
-int ReadValueArgs(State* state, Expr* argv[], int count, ...) {
-    Value** args = reinterpret_cast<Value**>(malloc(count * sizeof(Value*)));
-    va_list v;
-    va_start(v, count);
-    int i;
-    for (i = 0; i < count; ++i) {
-        args[i] = EvaluateValue(state, argv[i]);
-        if (args[i] == NULL) {
-            va_end(v);
-            int j;
-            for (j = 0; j < i; ++j) {
-                FreeValue(args[j]);
-            }
-            free(args);
-            return -1;
-        }
-        *(va_arg(v, Value**)) = args[i];
+bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+              std::vector<std::string>* args, size_t start, size_t len) {
+    if (args == nullptr) {
+        return false;
     }
-    va_end(v);
-    free(args);
-    return 0;
+    if (start + len > argv.size()) {
+        return false;
+    }
+    for (size_t i = start; i < start + len; ++i) {
+        std::string var;
+        if (!Evaluate(state, argv[i], &var)) {
+            args->clear();
+            return false;
+        }
+        args->push_back(var);
+    }
+    return true;
 }
 
-// Evaluate the expressions in argv, returning an array of char*
-// results.  If any evaluate to NULL, free the rest and return NULL.
-// The caller is responsible for freeing the returned array and the
-// strings it contains.
-char** ReadVarArgs(State* state, int argc, Expr* argv[]) {
-    char** args = (char**)malloc(argc * sizeof(char*));
-    int i = 0;
-    for (i = 0; i < argc; ++i) {
-        args[i] = Evaluate(state, argv[i]);
-        if (args[i] == NULL) {
-            int j;
-            for (j = 0; j < i; ++j) {
-                free(args[j]);
-            }
-            free(args);
-            return NULL;
-        }
-    }
-    return args;
+// Evaluate the expressions in argv, and put the results of Value* in args. If any expression
+// evaluate to nullptr, return false. Return true on success.
+bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+                   std::vector<std::unique_ptr<Value>>* args) {
+    return ReadValueArgs(state, argv, args, 0, argv.size());
 }
 
-// Evaluate the expressions in argv, returning an array of Value*
-// results.  If any evaluate to NULL, free the rest and return NULL.
-// The caller is responsible for freeing the returned array and the
-// Values it contains.
-Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]) {
-    Value** args = (Value**)malloc(argc * sizeof(Value*));
-    int i = 0;
-    for (i = 0; i < argc; ++i) {
-        args[i] = EvaluateValue(state, argv[i]);
-        if (args[i] == NULL) {
-            int j;
-            for (j = 0; j < i; ++j) {
-                FreeValue(args[j]);
-            }
-            free(args);
-            return NULL;
-        }
+bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+                   std::vector<std::unique_ptr<Value>>* args, size_t start, size_t len) {
+    if (args == nullptr) {
+        return false;
     }
-    return args;
-}
-
-static void ErrorAbortV(State* state, const char* format, va_list ap) {
-    std::string buffer;
-    android::base::StringAppendV(&buffer, format, ap);
-    free(state->errmsg);
-    state->errmsg = strdup(buffer.c_str());
-    return;
+    if (len == 0 || start + len > argv.size()) {
+        return false;
+    }
+    for (size_t i = start; i < start + len; ++i) {
+        std::unique_ptr<Value> v(EvaluateValue(state, argv[i]));
+        if (!v) {
+            args->clear();
+            return false;
+        }
+        args->push_back(std::move(v));
+    }
+    return true;
 }
 
 // Use printf-style arguments to compose an error message to put into
@@ -511,7 +402,7 @@
 Value* ErrorAbort(State* state, const char* format, ...) {
     va_list ap;
     va_start(ap, format);
-    ErrorAbortV(state, format, ap);
+    android::base::StringAppendV(&state->errmsg, format, ap);
     va_end(ap);
     return nullptr;
 }
@@ -519,8 +410,14 @@
 Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) {
     va_list ap;
     va_start(ap, format);
-    ErrorAbortV(state, format, ap);
+    android::base::StringAppendV(&state->errmsg, format, ap);
     va_end(ap);
     state->cause_code = cause_code;
     return nullptr;
 }
+
+State::State(const std::string& script, void* cookie) :
+    script(script),
+    cookie(cookie) {
+}
+
diff --git a/edify/expr.h b/edify/expr.h
index 8863479..4838d20 100644
--- a/edify/expr.h
+++ b/edify/expr.h
@@ -19,27 +19,26 @@
 
 #include <unistd.h>
 
+#include <memory>
+#include <string>
+#include <vector>
+
 #include "error_code.h"
-#include "yydefs.h"
 
-#define MAX_STRING_LEN 1024
+struct State {
+    State(const std::string& script, void* cookie);
 
-typedef struct Expr Expr;
+    // The source of the original script.
+    const std::string& script;
 
-typedef struct {
     // Optional pointer to app-specific data; the core of edify never
     // uses this value.
     void* cookie;
 
-    // The source of the original script.  Must be NULL-terminated,
-    // and in writable memory (Evaluate may make temporary changes to
-    // it but will restore it when done).
-    char* script;
-
     // The error message (if any) returned if the evaluation aborts.
-    // Should be NULL initially, will be either NULL or a malloc'd
-    // pointer after Evaluate() returns.
-    char* errmsg;
+    // Should be empty initially, will be either empty or a string that
+    // Evaluate() returns.
+    std::string errmsg;
 
     // error code indicates the type of failure (e.g. failure to update system image)
     // during the OTA process.
@@ -50,117 +49,95 @@
     CauseCode cause_code = kNoCause;
 
     bool is_retry = false;
-
-} State;
-
-#define VAL_STRING  1  // data will be NULL-terminated; size doesn't count null
-#define VAL_BLOB    2
-
-typedef struct {
-    int type;
-    ssize_t size;
-    char* data;
-} Value;
-
-typedef Value* (*Function)(const char* name, State* state,
-                           int argc, Expr* argv[]);
-
-struct Expr {
-    Function fn;
-    const char* name;
-    int argc;
-    Expr** argv;
-    int start, end;
 };
 
-// Take one of the Expr*s passed to the function as an argument,
-// evaluate it, return the resulting Value.  The caller takes
-// ownership of the returned Value.
-Value* EvaluateValue(State* state, Expr* expr);
+enum ValueType {
+    VAL_INVALID = -1,
+    VAL_STRING = 1,
+    VAL_BLOB = 2,
+};
 
-// Take one of the Expr*s passed to the function as an argument,
-// evaluate it, assert that it is a string, and return the resulting
-// char*.  The caller takes ownership of the returned char*.  This is
-// a convenience function for older functions that want to deal only
-// with strings.
-char* Evaluate(State* state, Expr* expr);
+struct Value {
+    ValueType type;
+    std::string data;
+
+    Value(ValueType type, const std::string& str) :
+        type(type),
+        data(str) {}
+};
+
+struct Expr;
+
+using Function = Value* (*)(const char* name, State* state,
+                            const std::vector<std::unique_ptr<Expr>>& argv);
+
+struct Expr {
+  Function fn;
+  std::string name;
+  std::vector<std::unique_ptr<Expr>> argv;
+  int start, end;
+
+  Expr(Function fn, const std::string& name, int start, int end) :
+    fn(fn),
+    name(name),
+    start(start),
+    end(end) {}
+};
+
+// Evaluate the input expr, return the resulting Value.
+Value* EvaluateValue(State* state, const std::unique_ptr<Expr>& expr);
+
+// Evaluate the input expr, assert that it is a string, and update the result parameter. This
+// function returns true if the evaluation succeeds. This is a convenience function for older
+// functions that want to deal only with strings.
+bool Evaluate(State* state, const std::unique_ptr<Expr>& expr, std::string* result);
 
 // Glue to make an Expr out of a literal.
-Value* Literal(const char* name, State* state, int argc, Expr* argv[]);
+Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
 
 // Functions corresponding to various syntactic sugar operators.
 // ("concat" is also available as a builtin function, to concatenate
 // more than two strings.)
-Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* SubstringFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]);
-
-// Convenience function for building expressions with a fixed number
-// of arguments.
-Expr* Build(Function fn, YYLTYPE loc, int count, ...);
+Value* ConcatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* LogicalAndFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* LogicalOrFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* LogicalNotFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* SubstringFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* InequalityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
 
 // Global builtins, registered by RegisterBuiltins().
-Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]);
-
-
-// For setting and getting the global error string (when returning
-// NULL from a function).
-void SetError(const char* message);  // makes a copy
-const char* GetError();              // retains ownership
-void ClearError();
-
-
-typedef struct {
-  const char* name;
-  Function fn;
-} NamedFunction;
+Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
 
 // Register a new function.  The same Function may be registered under
 // multiple names, but a given name should only be used once.
-void RegisterFunction(const char* name, Function fn);
+void RegisterFunction(const std::string& name, Function fn);
 
 // Register all the builtins.
 void RegisterBuiltins();
 
-// Call this after all calls to RegisterFunction() but before parsing
-// any scripts to finish building the function table.
-void FinishRegistration();
-
 // Find the Function for a given name; return NULL if no such function
 // exists.
-Function FindFunction(const char* name);
-
+Function FindFunction(const std::string& name);
 
 // --- convenience functions for use in functions ---
 
-// Evaluate the expressions in argv, giving 'count' char* (the ... is
-// zero or more char** to put them in).  If any expression evaluates
-// to NULL, free the rest and return -1.  Return 0 on success.
-int ReadArgs(State* state, Expr* argv[], int count, ...);
+// Evaluate the expressions in argv, and put the results of strings in args. If any expression
+// evaluates to nullptr, return false. Return true on success.
+bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+              std::vector<std::string>* args);
+bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+              std::vector<std::string>* args, size_t start, size_t len);
 
-// Evaluate the expressions in argv, giving 'count' Value* (the ... is
-// zero or more Value** to put them in).  If any expression evaluates
-// to NULL, free the rest and return -1.  Return 0 on success.
-int ReadValueArgs(State* state, Expr* argv[], int count, ...);
-
-// Evaluate the expressions in argv, returning an array of char*
-// results.  If any evaluate to NULL, free the rest and return NULL.
-// The caller is responsible for freeing the returned array and the
-// strings it contains.
-char** ReadVarArgs(State* state, int argc, Expr* argv[]);
-
-// Evaluate the expressions in argv, returning an array of Value*
-// results.  If any evaluate to NULL, free the rest and return NULL.
-// The caller is responsible for freeing the returned array and the
-// Values it contains.
-Value** ReadValueVarArgs(State* state, int argc, Expr* argv[]);
+// Evaluate the expressions in argv, and put the results of Value* in args. If any
+// expression evaluate to nullptr, return false. Return true on success.
+bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+                   std::vector<std::unique_ptr<Value>>* args);
+bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+                   std::vector<std::unique_ptr<Value>>* args, size_t start, size_t len);
 
 // Use printf-style arguments to compose an error message to put into
 // *state.  Returns NULL.
@@ -172,12 +149,11 @@
 Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...)
     __attribute__((format(printf, 3, 4)));
 
-// Wrap a string into a Value, taking ownership of the string.
-Value* StringValue(char* str);
+// Copying the string into a Value.
+Value* StringValue(const char* str);
 
-// Free a Value object.
-void FreeValue(Value* v);
+Value* StringValue(const std::string& str);
 
-int parse_string(const char* str, Expr** root, int* error_count);
+int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count);
 
 #endif  // _EXPRESSION_H
diff --git a/edify/main.cpp b/edify/main.cpp
deleted file mode 100644
index 6007a3d..0000000
--- a/edify/main.cpp
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2009 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 agreed 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <string>
-
-#include "expr.h"
-#include "parser.h"
-
-extern int yyparse(Expr** root, int* error_count);
-
-int expect(const char* expr_str, const char* expected, int* errors) {
-    Expr* e;
-    char* result;
-
-    printf(".");
-
-    int error_count = parse_string(expr_str, &e, &error_count);
-    if (error_count > 0) {
-        printf("error parsing \"%s\" (%d errors)\n",
-               expr_str, error_count);
-        ++*errors;
-        return 0;
-    }
-
-    State state;
-    state.cookie = NULL;
-    state.script = strdup(expr_str);
-    state.errmsg = NULL;
-
-    result = Evaluate(&state, e);
-    free(state.errmsg);
-    free(state.script);
-    if (result == NULL && expected != NULL) {
-        printf("error evaluating \"%s\"\n", expr_str);
-        ++*errors;
-        return 0;
-    }
-
-    if (result == NULL && expected == NULL) {
-        return 1;
-    }
-
-    if (strcmp(result, expected) != 0) {
-        printf("evaluating \"%s\": expected \"%s\", got \"%s\"\n",
-               expr_str, expected, result);
-        ++*errors;
-        free(result);
-        return 0;
-    }
-
-    free(result);
-    return 1;
-}
-
-int test() {
-    int errors = 0;
-
-    expect("a", "a", &errors);
-    expect("\"a\"", "a", &errors);
-    expect("\"\\x61\"", "a", &errors);
-    expect("# this is a comment\n"
-           "  a\n"
-           "   \n",
-           "a", &errors);
-
-
-    // sequence operator
-    expect("a; b; c", "c", &errors);
-
-    // string concat operator
-    expect("a + b", "ab", &errors);
-    expect("a + \n \"b\"", "ab", &errors);
-    expect("a + b +\nc\n", "abc", &errors);
-
-    // string concat function
-    expect("concat(a, b)", "ab", &errors);
-    expect("concat(a,\n \"b\")", "ab", &errors);
-    expect("concat(a + b,\nc,\"d\")", "abcd", &errors);
-    expect("\"concat\"(a + b,\nc,\"d\")", "abcd", &errors);
-
-    // logical and
-    expect("a && b", "b", &errors);
-    expect("a && \"\"", "", &errors);
-    expect("\"\" && b", "", &errors);
-    expect("\"\" && \"\"", "", &errors);
-    expect("\"\" && abort()", "", &errors);   // test short-circuiting
-    expect("t && abort()", NULL, &errors);
-
-    // logical or
-    expect("a || b", "a", &errors);
-    expect("a || \"\"", "a", &errors);
-    expect("\"\" || b", "b", &errors);
-    expect("\"\" || \"\"", "", &errors);
-    expect("a || abort()", "a", &errors);     // test short-circuiting
-    expect("\"\" || abort()", NULL, &errors);
-
-    // logical not
-    expect("!a", "", &errors);
-    expect("! \"\"", "t", &errors);
-    expect("!!a", "t", &errors);
-
-    // precedence
-    expect("\"\" == \"\" && b", "b", &errors);
-    expect("a + b == ab", "t", &errors);
-    expect("ab == a + b", "t", &errors);
-    expect("a + (b == ab)", "a", &errors);
-    expect("(ab == a) + b", "b", &errors);
-
-    // substring function
-    expect("is_substring(cad, abracadabra)", "t", &errors);
-    expect("is_substring(abrac, abracadabra)", "t", &errors);
-    expect("is_substring(dabra, abracadabra)", "t", &errors);
-    expect("is_substring(cad, abracxadabra)", "", &errors);
-    expect("is_substring(abrac, axbracadabra)", "", &errors);
-    expect("is_substring(dabra, abracadabrxa)", "", &errors);
-
-    // ifelse function
-    expect("ifelse(t, yes, no)", "yes", &errors);
-    expect("ifelse(!t, yes, no)", "no", &errors);
-    expect("ifelse(t, yes, abort())", "yes", &errors);
-    expect("ifelse(!t, abort(), no)", "no", &errors);
-
-    // if "statements"
-    expect("if t then yes else no endif", "yes", &errors);
-    expect("if \"\" then yes else no endif", "no", &errors);
-    expect("if \"\" then yes endif", "", &errors);
-    expect("if \"\"; t then yes endif", "yes", &errors);
-
-    // numeric comparisons
-    expect("less_than_int(3, 14)", "t", &errors);
-    expect("less_than_int(14, 3)", "", &errors);
-    expect("less_than_int(x, 3)", "", &errors);
-    expect("less_than_int(3, x)", "", &errors);
-    expect("greater_than_int(3, 14)", "", &errors);
-    expect("greater_than_int(14, 3)", "t", &errors);
-    expect("greater_than_int(x, 3)", "", &errors);
-    expect("greater_than_int(3, x)", "", &errors);
-
-    // big string
-    expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str(), &errors);
-
-    printf("\n");
-
-    return errors;
-}
-
-void ExprDump(int depth, Expr* n, char* script) {
-    printf("%*s", depth*2, "");
-    char temp = script[n->end];
-    script[n->end] = '\0';
-    printf("%s %p (%d-%d) \"%s\"\n",
-           n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end,
-           script+n->start);
-    script[n->end] = temp;
-    int i;
-    for (i = 0; i < n->argc; ++i) {
-        ExprDump(depth+1, n->argv[i], script);
-    }
-}
-
-int main(int argc, char** argv) {
-    RegisterBuiltins();
-    FinishRegistration();
-
-    if (argc == 1) {
-        return test() != 0;
-    }
-
-    FILE* f = fopen(argv[1], "r");
-    if (f == NULL) {
-        printf("%s: %s: No such file or directory\n", argv[0], argv[1]);
-        return 1;
-    }
-    char buffer[8192];
-    int size = fread(buffer, 1, 8191, f);
-    fclose(f);
-    buffer[size] = '\0';
-
-    Expr* root;
-    int error_count = 0;
-    int error = parse_string(buffer, &root, &error_count);
-    printf("parse returned %d; %d errors encountered\n", error, error_count);
-    if (error == 0 || error_count > 0) {
-
-        ExprDump(0, root, buffer);
-
-        State state;
-        state.cookie = NULL;
-        state.script = buffer;
-        state.errmsg = NULL;
-
-        char* result = Evaluate(&state, root);
-        if (result == NULL) {
-            printf("result was NULL, message is: %s\n",
-                   (state.errmsg == NULL ? "(NULL)" : state.errmsg));
-            free(state.errmsg);
-        } else {
-            printf("result is [%s]\n", result);
-        }
-    }
-    return 0;
-}
diff --git a/edify/parser.yy b/edify/parser.yy
index 098a637..97205fe 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -19,6 +19,10 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <memory>
+#include <string>
+#include <vector>
+
 #include "expr.h"
 #include "yydefs.h"
 #include "parser.h"
@@ -26,13 +30,26 @@
 extern int gLine;
 extern int gColumn;
 
-void yyerror(Expr** root, int* error_count, const char* s);
-int yyparse(Expr** root, int* error_count);
+void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s);
+int yyparse(std::unique_ptr<Expr>* root, int* error_count);
 
 struct yy_buffer_state;
 void yy_switch_to_buffer(struct yy_buffer_state* new_buffer);
 struct yy_buffer_state* yy_scan_string(const char* yystr);
 
+// Convenience function for building expressions with a fixed number
+// of arguments.
+static Expr* Build(Function fn, YYLTYPE loc, size_t count, ...) {
+    va_list v;
+    va_start(v, count);
+    Expr* e = new Expr(fn, "(operator)", loc.start, loc.end);
+    for (size_t i = 0; i < count; ++i) {
+        e->argv.emplace_back(va_arg(v, Expr*));
+    }
+    va_end(v);
+    return e;
+}
+
 %}
 
 %locations
@@ -40,10 +57,7 @@
 %union {
     char* str;
     Expr* expr;
-    struct {
-        int argc;
-        Expr** argv;
-    } args;
+    std::vector<std::unique_ptr<Expr>>* args;
 }
 
 %token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF
@@ -51,7 +65,10 @@
 %type <expr> expr
 %type <args> arglist
 
-%parse-param {Expr** root}
+%destructor { delete $$; } expr
+%destructor { delete $$; } arglist
+
+%parse-param {std::unique_ptr<Expr>* root}
 %parse-param {int* error_count}
 %error-verbose
 
@@ -66,17 +83,11 @@
 
 %%
 
-input:  expr           { *root = $1; }
+input:  expr           { root->reset($1); }
 ;
 
 expr:  STRING {
-    $$ = reinterpret_cast<Expr*>(malloc(sizeof(Expr)));
-    $$->fn = Literal;
-    $$->name = $1;
-    $$->argc = 0;
-    $$->argv = NULL;
-    $$->start = @$.start;
-    $$->end = @$.end;
+    $$ = new Expr(Literal, $1, @$.start, @$.end);
 }
 |  '(' expr ')'                      { $$ = $2; $$->start=@$.start; $$->end=@$.end; }
 |  expr ';'                          { $$ = $1; $$->start=@1.start; $$->end=@1.end; }
@@ -91,41 +102,32 @@
 |  IF expr THEN expr ENDIF           { $$ = Build(IfElseFn, @$, 2, $2, $4); }
 |  IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); }
 | STRING '(' arglist ')' {
-    $$ = reinterpret_cast<Expr*>(malloc(sizeof(Expr)));
-    $$->fn = FindFunction($1);
-    if ($$->fn == NULL) {
-        char buffer[256];
-        snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1);
-        yyerror(root, error_count, buffer);
+    Function fn = FindFunction($1);
+    if (fn == nullptr) {
+        std::string msg = "unknown function \"" + std::string($1) + "\"";
+        yyerror(root, error_count, msg.c_str());
         YYERROR;
     }
-    $$->name = $1;
-    $$->argc = $3.argc;
-    $$->argv = $3.argv;
-    $$->start = @$.start;
-    $$->end = @$.end;
+    $$ = new Expr(fn, $1, @$.start, @$.end);
+    $$->argv = std::move(*$3);
 }
 ;
 
 arglist:    /* empty */ {
-    $$.argc = 0;
-    $$.argv = NULL;
+    $$ = new std::vector<std::unique_ptr<Expr>>;
 }
 | expr {
-    $$.argc = 1;
-    $$.argv = reinterpret_cast<Expr**>(malloc(sizeof(Expr*)));
-    $$.argv[0] = $1;
+    $$ = new std::vector<std::unique_ptr<Expr>>;
+    $$->emplace_back($1);
 }
 | arglist ',' expr {
-    $$.argc = $1.argc + 1;
-    $$.argv = reinterpret_cast<Expr**>(realloc($$.argv, $$.argc * sizeof(Expr*)));
-    $$.argv[$$.argc-1] = $3;
+    $$->push_back(std::unique_ptr<Expr>($3));
 }
 ;
 
 %%
 
-void yyerror(Expr** root, int* error_count, const char* s) {
+void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s) {
   if (strlen(s) == 0) {
     s = "syntax error";
   }
@@ -133,7 +135,7 @@
   ++*error_count;
 }
 
-int parse_string(const char* str, Expr** root, int* error_count) {
+int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count) {
     yy_switch_to_buffer(yy_scan_string(str));
     return yyparse(root, error_count);
 }
diff --git a/error_code.h b/error_code.h
index dfea0eb..cde4ee6 100644
--- a/error_code.h
+++ b/error_code.h
@@ -18,52 +18,54 @@
 #define _ERROR_CODE_H_
 
 enum ErrorCode {
-    kNoError = -1,
-    kLowBattery = 20,
-    kZipVerificationFailure,
-    kZipOpenFailure,
-    kBootreasonInBlacklist
+  kNoError = -1,
+  kLowBattery = 20,
+  kZipVerificationFailure,
+  kZipOpenFailure,
+  kBootreasonInBlacklist,
+  kPackageCompatibilityFailure,
 };
 
 enum CauseCode {
-    kNoCause = -1,
-    kArgsParsingFailure = 100,
-    kStashCreationFailure,
-    kFileOpenFailure,
-    kLseekFailure,
-    kFreadFailure,
-    kFwriteFailure,
-    kFsyncFailure,
-    kLibfecFailure,
-    kFileGetPropFailure,
-    kFileRenameFailure,
-    kSymlinkFailure,
-    kSetMetadataFailure,
-    kTune2FsFailure,
-    kRebootFailure,
-    kVendorFailure = 200
+  kNoCause = -1,
+  kArgsParsingFailure = 100,
+  kStashCreationFailure,
+  kFileOpenFailure,
+  kLseekFailure,
+  kFreadFailure,
+  kFwriteFailure,
+  kFsyncFailure,
+  kLibfecFailure,
+  kFileGetPropFailure,
+  kFileRenameFailure,
+  kSymlinkFailure,
+  kSetMetadataFailure,
+  kTune2FsFailure,
+  kRebootFailure,
+  kPackageExtractFileFailure,
+  kVendorFailure = 200
 };
 
 enum UncryptErrorCode {
-    kUncryptNoError = -1,
-    kUncryptErrorPlaceholder = 50,
-    kUncryptTimeoutError = 100,
-    kUncryptFileRemoveError,
-    kUncryptFileOpenError,
-    kUncryptSocketOpenError,
-    kUncryptSocketWriteError,
-    kUncryptSocketListenError,
-    kUncryptSocketAcceptError,
-    kUncryptFstabReadError,
-    kUncryptFileStatError,
-    kUncryptBlockOpenError,
-    kUncryptIoctlError,
-    kUncryptReadError,
-    kUncryptWriteError,
-    kUncryptFileSyncError,
-    kUncryptFileCloseError,
-    kUncryptFileRenameError,
-    kUncryptPackageMissingError,
+  kUncryptNoError = -1,
+  kUncryptErrorPlaceholder = 50,
+  kUncryptTimeoutError = 100,
+  kUncryptFileRemoveError,
+  kUncryptFileOpenError,
+  kUncryptSocketOpenError,
+  kUncryptSocketWriteError,
+  kUncryptSocketListenError,
+  kUncryptSocketAcceptError,
+  kUncryptFstabReadError,
+  kUncryptFileStatError,
+  kUncryptBlockOpenError,
+  kUncryptIoctlError,
+  kUncryptReadError,
+  kUncryptWriteError,
+  kUncryptFileSyncError,
+  kUncryptFileCloseError,
+  kUncryptFileRenameError,
+  kUncryptPackageMissingError,
 };
 
-#endif
+#endif // _ERROR_CODE_H_
diff --git a/etc/META-INF/com/google/android/update-script b/etc/META-INF/com/google/android/update-script
deleted file mode 100644
index b091b19..0000000
--- a/etc/META-INF/com/google/android/update-script
+++ /dev/null
@@ -1,8 +0,0 @@
-assert compatible_with("0.1") == "true"
-assert file_contains("SYSTEM:build.prop", "ro.product.device=dream") == "true" || file_contains("SYSTEM:build.prop", "ro.build.product=dream") == "true"
-assert file_contains("RECOVERY:default.prop", "ro.product.device=dream") == "true" || file_contains("RECOVERY:default.prop", "ro.build.product=dream") == "true"
-assert getprop("ro.product.device") == "dream"
-format BOOT:
-format SYSTEM:
-copy_dir PACKAGE:system SYSTEM:
-write_raw_image PACKAGE:boot.img BOOT:
diff --git a/etc/init.rc b/etc/init.rc
index 5915b8d..d8121cc 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -5,7 +5,6 @@
     restorecon /postinstall
 
     start ueventd
-    start healthd
 
 on init
     export ANDROID_ROOT /system
@@ -14,6 +13,9 @@
 
     symlink /system/etc /etc
 
+    mount cgroup none /acct cpuacct
+    mkdir /acct/uid
+
     mkdir /sdcard
     mkdir /system
     mkdir /data
@@ -28,6 +30,7 @@
     write /proc/sys/vm/max_map_count 1000000
 
 on fs
+    write /sys/class/android_usb/android0/f_ffs/aliases adb
     mkdir /dev/usb-ffs 0770 shell shell
     mkdir /dev/usb-ffs/adb 0770 shell shell
     mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
@@ -35,7 +38,6 @@
     write /sys/class/android_usb/android0/enable 0
     write /sys/class/android_usb/android0/idVendor 18D1
     write /sys/class/android_usb/android0/idProduct D001
-    write /sys/class/android_usb/android0/f_ffs/aliases adb
     write /sys/class/android_usb/android0/functions adb
     write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
     write /sys/class/android_usb/android0/iProduct ${ro.product.model}
@@ -73,16 +75,13 @@
     trigger early-boot
     trigger boot
 
-on property:sys.powerctl=*
-   powerctl ${sys.powerctl}
-
 service ueventd /sbin/ueventd
     critical
     seclabel u:r:ueventd:s0
 
-service healthd /sbin/healthd -r
+service charger /charger -r
     critical
-    seclabel u:r:healthd:s0
+    seclabel u:r:charger:s0
 
 service recovery /sbin/recovery
     seclabel u:r:recovery:s0
diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp
index df96312..b0ecf96 100644
--- a/fuse_sdcard_provider.cpp
+++ b/fuse_sdcard_provider.cpp
@@ -23,6 +23,8 @@
 #include <unistd.h>
 #include <fcntl.h>
 
+#include <android-base/file.h>
+
 #include "fuse_sideload.h"
 
 struct file_data {
@@ -41,14 +43,9 @@
         return -EIO;
     }
 
-    while (fetch_size > 0) {
-        ssize_t r = TEMP_FAILURE_RETRY(read(fd->fd, buffer, fetch_size));
-        if (r == -1) {
-            fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno));
-            return -EIO;
-        }
-        fetch_size -= r;
-        buffer += r;
+    if (!android::base::ReadFully(fd->fd, buffer, fetch_size)) {
+        fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno));
+        return -EIO;
     }
 
     return 0;
diff --git a/install.cpp b/install.cpp
index e144d9b..ffeba2e 100644
--- a/install.cpp
+++ b/install.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "install.h"
+
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -24,48 +26,53 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <chrono>
+#include <condition_variable>
+#include <functional>
 #include <limits>
 #include <map>
+#include <mutex>
 #include <string>
+#include <thread>
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parsedouble.h>
 #include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <cutils/properties.h>
+#include <vintf/VintfObjectRecovery.h>
+#include <ziparchive/zip_archive.h>
 
 #include "common.h"
 #include "error_code.h"
-#include "install.h"
 #include "minui/minui.h"
-#include "minzip/SysUtil.h"
-#include "minzip/Zip.h"
-#include "mtdutils/mounts.h"
-#include "mtdutils/mtdutils.h"
+#include "otautil/SysUtil.h"
+#include "otautil/ThermalUtil.h"
 #include "roots.h"
 #include "ui.h"
 #include "verifier.h"
 
-extern RecoveryUI* ui;
+using namespace std::chrono_literals;
 
-#define ASSUMED_UPDATE_BINARY_NAME  "META-INF/com/google/android/update-binary"
-static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
-static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
 #define PUBLIC_KEYS_FILE "/res/keys"
 static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
 static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
 
 // Default allocation of progress bar segments to operations
-static const int VERIFICATION_PROGRESS_TIME = 60;
-static const float VERIFICATION_PROGRESS_FRACTION = 0.25;
-static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
-static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
+static constexpr int VERIFICATION_PROGRESS_TIME = 60;
+static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
+static constexpr float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
+static constexpr float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
+
+static std::condition_variable finish_log_temperature;
 
 // This function parses and returns the build.version.incremental
-static int parse_build_number(std::string str) {
-    size_t pos = str.find("=");
+static int parse_build_number(const std::string& str) {
+    size_t pos = str.find('=');
     if (pos != std::string::npos) {
         std::string num_string = android::base::Trim(str.substr(pos+1));
         int build_number;
@@ -74,27 +81,33 @@
         }
     }
 
-    LOGE("Failed to parse build number in %s.\n", str.c_str());
+    LOG(ERROR) << "Failed to parse build number in " << str;
     return -1;
 }
 
-bool read_metadata_from_package(ZipArchive* zip, std::string* meta_data) {
-    const ZipEntry* meta_entry = mzFindZipEntry(zip, METADATA_PATH);
-    if (meta_entry == nullptr) {
-        LOGE("Failed to find %s in update package.\n", METADATA_PATH);
+bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data) {
+    ZipString metadata_path(METADATA_PATH);
+    ZipEntry meta_entry;
+    if (meta_data == nullptr) {
+        LOG(ERROR) << "string* meta_data can't be nullptr";
+        return false;
+    }
+    if (FindEntry(zip, metadata_path, &meta_entry) != 0) {
+        LOG(ERROR) << "Failed to find " << METADATA_PATH << " in update package";
         return false;
     }
 
-    meta_data->resize(meta_entry->uncompLen, '\0');
-    if (!mzReadZipEntry(zip, meta_entry, &(*meta_data)[0], meta_entry->uncompLen)) {
-        LOGE("Failed to read metadata in update package.\n");
+    meta_data->resize(meta_entry.uncompressed_length, '\0');
+    if (ExtractToMemory(zip, &meta_entry, reinterpret_cast<uint8_t*>(&(*meta_data)[0]),
+                        meta_entry.uncompressed_length) != 0) {
+        LOG(ERROR) << "Failed to read metadata in update package";
         return false;
     }
     return true;
 }
 
 // Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
-static void read_source_target_build(ZipArchive* zip, std::vector<std::string>& log_buffer) {
+static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>& log_buffer) {
     std::string meta_data;
     if (!read_metadata_from_package(zip, &meta_data)) {
         return;
@@ -121,348 +134,438 @@
     }
 }
 
-// Extract the update binary from the open zip archive |zip| located at |path|
-// and store into |cmd| the command line that should be called. The |status_fd|
-// is the file descriptor the child process should use to report back the
-// progress of the update.
-static int
-update_binary_command(const char* path, ZipArchive* zip, int retry_count,
-                      int status_fd, std::vector<std::string>* cmd);
+// Extract the update binary from the open zip archive |zip| located at |path| and store into |cmd|
+// the command line that should be called. The |status_fd| is the file descriptor the child process
+// should use to report back the progress of the update.
+int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
+                          int status_fd, std::vector<std::string>* cmd);
 
 #ifdef AB_OTA_UPDATER
 
 // Parses the metadata of the OTA package in |zip| and checks whether we are
 // allowed to accept this A/B package. Downgrading is not allowed unless
 // explicitly enabled in the package and only for incremental packages.
-static int check_newer_ab_build(ZipArchive* zip)
-{
-    std::string metadata_str;
-    if (!read_metadata_from_package(zip, &metadata_str)) {
-        return INSTALL_CORRUPT;
+static int check_newer_ab_build(ZipArchiveHandle zip) {
+  std::string metadata_str;
+  if (!read_metadata_from_package(zip, &metadata_str)) {
+    return INSTALL_CORRUPT;
+  }
+  std::map<std::string, std::string> metadata;
+  for (const std::string& line : android::base::Split(metadata_str, "\n")) {
+    size_t eq = line.find('=');
+    if (eq != std::string::npos) {
+      metadata[line.substr(0, eq)] = line.substr(eq + 1);
     }
-    std::map<std::string, std::string> metadata;
-    for (const std::string& line : android::base::Split(metadata_str, "\n")) {
-        size_t eq = line.find('=');
-        if (eq != std::string::npos) {
-            metadata[line.substr(0, eq)] = line.substr(eq + 1);
-        }
-    }
-    char value[PROPERTY_VALUE_MAX];
+  }
 
-    property_get("ro.product.device", value, "");
-    const std::string& pkg_device = metadata["pre-device"];
-    if (pkg_device != value || pkg_device.empty()) {
-        LOGE("Package is for product %s but expected %s\n",
-             pkg_device.c_str(), value);
-        return INSTALL_ERROR;
-    }
+  std::string value = android::base::GetProperty("ro.product.device", "");
+  const std::string& pkg_device = metadata["pre-device"];
+  if (pkg_device != value || pkg_device.empty()) {
+    LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value;
+    return INSTALL_ERROR;
+  }
 
-    // We allow the package to not have any serialno, but if it has a non-empty
-    // value it should match.
-    property_get("ro.serialno", value, "");
-    const std::string& pkg_serial_no = metadata["serialno"];
-    if (!pkg_serial_no.empty() && pkg_serial_no != value) {
-        LOGE("Package is for serial %s\n", pkg_serial_no.c_str());
-        return INSTALL_ERROR;
-    }
+  // We allow the package to not have any serialno, but if it has a non-empty
+  // value it should match.
+  value = android::base::GetProperty("ro.serialno", "");
+  const std::string& pkg_serial_no = metadata["serialno"];
+  if (!pkg_serial_no.empty() && pkg_serial_no != value) {
+    LOG(ERROR) << "Package is for serial " << pkg_serial_no;
+    return INSTALL_ERROR;
+  }
 
-    if (metadata["ota-type"] != "AB") {
-        LOGE("Package is not A/B\n");
-        return INSTALL_ERROR;
-    }
+  if (metadata["ota-type"] != "AB") {
+    LOG(ERROR) << "Package is not A/B";
+    return INSTALL_ERROR;
+  }
 
-    // Incremental updates should match the current build.
-    property_get("ro.build.version.incremental", value, "");
-    const std::string& pkg_pre_build = metadata["pre-build-incremental"];
-    if (!pkg_pre_build.empty() && pkg_pre_build != value) {
-        LOGE("Package is for source build %s but expected %s\n",
-             pkg_pre_build.c_str(), value);
-        return INSTALL_ERROR;
-    }
-    property_get("ro.build.fingerprint", value, "");
-    const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
-    if (!pkg_pre_build_fingerprint.empty() &&
-        pkg_pre_build_fingerprint != value) {
-        LOGE("Package is for source build %s but expected %s\n",
-             pkg_pre_build_fingerprint.c_str(), value);
-        return INSTALL_ERROR;
-    }
+  // Incremental updates should match the current build.
+  value = android::base::GetProperty("ro.build.version.incremental", "");
+  const std::string& pkg_pre_build = metadata["pre-build-incremental"];
+  if (!pkg_pre_build.empty() && pkg_pre_build != value) {
+    LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value;
+    return INSTALL_ERROR;
+  }
 
-    // Check for downgrade version.
-    int64_t build_timestampt = property_get_int64(
-            "ro.build.date.utc", std::numeric_limits<int64_t>::max());
-    int64_t pkg_post_timespampt = 0;
-    // We allow to full update to the same version we are running, in case there
-    // is a problem with the current copy of that version.
-    if (metadata["post-timestamp"].empty() ||
-        !android::base::ParseInt(metadata["post-timestamp"].c_str(),
-                                 &pkg_post_timespampt) ||
-        pkg_post_timespampt < build_timestampt) {
-        if (metadata["ota-downgrade"] != "yes") {
-            LOGE("Update package is older than the current build, expected a "
-                 "build newer than timestamp %" PRIu64 " but package has "
-                 "timestamp %" PRIu64 " and downgrade not allowed.\n",
-                 build_timestampt, pkg_post_timespampt);
-            return INSTALL_ERROR;
-        }
-        if (pkg_pre_build_fingerprint.empty()) {
-            LOGE("Downgrade package must have a pre-build version set, not "
-                 "allowed.\n");
-            return INSTALL_ERROR;
-        }
-    }
+  value = android::base::GetProperty("ro.build.fingerprint", "");
+  const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
+  if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) {
+    LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
+               << value;
+    return INSTALL_ERROR;
+  }
 
-    return 0;
+  // Check for downgrade version.
+  int64_t build_timestamp =
+      android::base::GetIntProperty("ro.build.date.utc", std::numeric_limits<int64_t>::max());
+  int64_t pkg_post_timestamp = 0;
+  // We allow to full update to the same version we are running, in case there
+  // is a problem with the current copy of that version.
+  if (metadata["post-timestamp"].empty() ||
+      !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) ||
+      pkg_post_timestamp < build_timestamp) {
+    if (metadata["ota-downgrade"] != "yes") {
+      LOG(ERROR) << "Update package is older than the current build, expected a build "
+                    "newer than timestamp "
+                 << build_timestamp << " but package has timestamp " << pkg_post_timestamp
+                 << " and downgrade not allowed.";
+      return INSTALL_ERROR;
+    }
+    if (pkg_pre_build_fingerprint.empty()) {
+      LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
+      return INSTALL_ERROR;
+    }
+  }
+
+  return 0;
 }
 
-static int
-update_binary_command(const char* path, ZipArchive* zip, int retry_count,
-                      int status_fd, std::vector<std::string>* cmd)
-{
-    int ret = check_newer_ab_build(zip);
-    if (ret) {
-        return ret;
-    }
+int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
+                          int status_fd, std::vector<std::string>* cmd) {
+  CHECK(cmd != nullptr);
+  int ret = check_newer_ab_build(zip);
+  if (ret != 0) {
+    return ret;
+  }
 
-    // For A/B updates we extract the payload properties to a buffer and obtain
-    // the RAW payload offset in the zip file.
-    const ZipEntry* properties_entry =
-            mzFindZipEntry(zip, AB_OTA_PAYLOAD_PROPERTIES);
-    if (!properties_entry) {
-        LOGE("Can't find %s\n", AB_OTA_PAYLOAD_PROPERTIES);
-        return INSTALL_CORRUPT;
-    }
-    std::vector<unsigned char> payload_properties(
-            mzGetZipEntryUncompLen(properties_entry));
-    if (!mzExtractZipEntryToBuffer(zip, properties_entry,
-                                   payload_properties.data())) {
-        LOGE("Can't extract %s\n", AB_OTA_PAYLOAD_PROPERTIES);
-        return INSTALL_CORRUPT;
-    }
+  // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
+  // in the zip file.
+  static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
+  ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES);
+  ZipEntry properties_entry;
+  if (FindEntry(zip, property_name, &properties_entry) != 0) {
+    LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES;
+    return INSTALL_CORRUPT;
+  }
+  uint32_t properties_entry_length = properties_entry.uncompressed_length;
+  std::vector<uint8_t> payload_properties(properties_entry_length);
+  int32_t err =
+      ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length);
+  if (err != 0) {
+    LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err);
+    return INSTALL_CORRUPT;
+  }
 
-    const ZipEntry* payload_entry = mzFindZipEntry(zip, AB_OTA_PAYLOAD);
-    if (!payload_entry) {
-        LOGE("Can't find %s\n", AB_OTA_PAYLOAD);
-        return INSTALL_CORRUPT;
-    }
-    long payload_offset = mzGetZipEntryOffset(payload_entry);
-    *cmd = {
-        "/sbin/update_engine_sideload",
-        android::base::StringPrintf("--payload=file://%s", path),
-        android::base::StringPrintf("--offset=%ld", payload_offset),
-        "--headers=" + std::string(payload_properties.begin(),
-                                   payload_properties.end()),
-        android::base::StringPrintf("--status_fd=%d", status_fd),
-    };
-    return 0;
+  static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
+  ZipString payload_name(AB_OTA_PAYLOAD);
+  ZipEntry payload_entry;
+  if (FindEntry(zip, payload_name, &payload_entry) != 0) {
+    LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD;
+    return INSTALL_CORRUPT;
+  }
+  long payload_offset = payload_entry.offset;
+  *cmd = {
+    "/sbin/update_engine_sideload",
+    "--payload=file://" + path,
+    android::base::StringPrintf("--offset=%ld", payload_offset),
+    "--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
+    android::base::StringPrintf("--status_fd=%d", status_fd),
+  };
+  return 0;
 }
 
 #else  // !AB_OTA_UPDATER
 
-static int
-update_binary_command(const char* path, ZipArchive* zip, int retry_count,
-                      int status_fd, std::vector<std::string>* cmd)
-{
-    // On traditional updates we extract the update binary from the package.
-    const ZipEntry* binary_entry =
-            mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
-    if (binary_entry == NULL) {
-        return INSTALL_CORRUPT;
-    }
+int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
+                          int status_fd, std::vector<std::string>* cmd) {
+  CHECK(cmd != nullptr);
 
-    const char* binary = "/tmp/update_binary";
-    unlink(binary);
-    int fd = creat(binary, 0755);
-    if (fd < 0) {
-        LOGE("Can't make %s\n", binary);
-        return INSTALL_ERROR;
-    }
-    bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
-    close(fd);
+  // On traditional updates we extract the update binary from the package.
+  static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
+  ZipString binary_name(UPDATE_BINARY_NAME);
+  ZipEntry binary_entry;
+  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
+    LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
+    return INSTALL_CORRUPT;
+  }
 
-    if (!ok) {
-        LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
-        return INSTALL_ERROR;
-    }
+  const char* binary = "/tmp/update_binary";
+  unlink(binary);
+  int fd = creat(binary, 0755);
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to create " << binary;
+    return INSTALL_ERROR;
+  }
 
-    *cmd = {
-        binary,
-        EXPAND(RECOVERY_API_VERSION),   // defined in Android.mk
-        std::to_string(status_fd),
-        path,
-    };
-    if (retry_count > 0)
-        cmd->push_back("retry");
-    return 0;
+  int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
+  close(fd);
+  if (error != 0) {
+    LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
+    return INSTALL_ERROR;
+  }
+
+  *cmd = {
+    binary,
+    EXPAND(RECOVERY_API_VERSION),  // defined in Android.mk
+    std::to_string(status_fd),
+    path,
+  };
+  if (retry_count > 0) {
+    cmd->push_back("retry");
+  }
+  return 0;
 }
 #endif  // !AB_OTA_UPDATER
 
+static void log_max_temperature(int* max_temperature) {
+  CHECK(max_temperature != nullptr);
+  std::mutex mtx;
+  std::unique_lock<std::mutex> lck(mtx);
+  while (finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) {
+    *max_temperature = std::max(*max_temperature, GetMaxValueFromThermalZone());
+  }
+}
+
 // If the package contains an update binary, extract it and run it.
-static int
-try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache,
-                  std::vector<std::string>& log_buffer, int retry_count)
-{
-    read_source_target_build(zip, log_buffer);
+static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
+                             std::vector<std::string>& log_buffer, int retry_count,
+                             int* max_temperature) {
+  read_source_target_build(zip, log_buffer);
 
-    int pipefd[2];
-    pipe(pipefd);
+  int pipefd[2];
+  pipe(pipefd);
 
-    std::vector<std::string> args;
-    int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
-    mzCloseZipArchive(zip);
-    if (ret) {
-        close(pipefd[0]);
-        close(pipefd[1]);
-        return ret;
-    }
-
-    // When executing the update binary contained in the package, the
-    // arguments passed are:
-    //
-    //   - the version number for this interface
-    //
-    //   - an fd to which the program can write in order to update the
-    //     progress bar.  The program can write single-line commands:
-    //
-    //        progress <frac> <secs>
-    //            fill up the next <frac> part of of the progress bar
-    //            over <secs> seconds.  If <secs> is zero, use
-    //            set_progress commands to manually control the
-    //            progress of this segment of the bar.
-    //
-    //        set_progress <frac>
-    //            <frac> should be between 0.0 and 1.0; sets the
-    //            progress bar within the segment defined by the most
-    //            recent progress command.
-    //
-    //        firmware <"hboot"|"radio"> <filename>
-    //            arrange to install the contents of <filename> in the
-    //            given partition on reboot.
-    //
-    //            (API v2: <filename> may start with "PACKAGE:" to
-    //            indicate taking a file from the OTA package.)
-    //
-    //            (API v3: this command no longer exists.)
-    //
-    //        ui_print <string>
-    //            display <string> on the screen.
-    //
-    //        wipe_cache
-    //            a wipe of cache will be performed following a successful
-    //            installation.
-    //
-    //        clear_display
-    //            turn off the text display.
-    //
-    //        enable_reboot
-    //            packages can explicitly request that they want the user
-    //            to be able to reboot during installation (useful for
-    //            debugging packages that don't exit).
-    //
-    //   - the name of the package zip file.
-    //
-    //   - an optional argument "retry" if this update is a retry of a failed
-    //   update attempt.
-    //
-
-    // Convert the vector to a NULL-terminated char* array suitable for execv.
-    const char* chr_args[args.size() + 1];
-    chr_args[args.size()] = NULL;
-    for (size_t i = 0; i < args.size(); i++) {
-        chr_args[i] = args[i].c_str();
-    }
-
-    pid_t pid = fork();
-
-    if (pid == -1) {
-        close(pipefd[0]);
-        close(pipefd[1]);
-        LOGE("Failed to fork update binary: %s\n", strerror(errno));
-        return INSTALL_ERROR;
-    }
-
-    if (pid == 0) {
-        umask(022);
-        close(pipefd[0]);
-        execv(chr_args[0], const_cast<char**>(chr_args));
-        fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
-        _exit(-1);
-    }
+  std::vector<std::string> args;
+  int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
+  if (ret) {
+    close(pipefd[0]);
     close(pipefd[1]);
+    return ret;
+  }
 
-    *wipe_cache = false;
-    bool retry_update = false;
+  // When executing the update binary contained in the package, the
+  // arguments passed are:
+  //
+  //   - the version number for this interface
+  //
+  //   - an FD to which the program can write in order to update the
+  //     progress bar.  The program can write single-line commands:
+  //
+  //        progress <frac> <secs>
+  //            fill up the next <frac> part of of the progress bar
+  //            over <secs> seconds.  If <secs> is zero, use
+  //            set_progress commands to manually control the
+  //            progress of this segment of the bar.
+  //
+  //        set_progress <frac>
+  //            <frac> should be between 0.0 and 1.0; sets the
+  //            progress bar within the segment defined by the most
+  //            recent progress command.
+  //
+  //        ui_print <string>
+  //            display <string> on the screen.
+  //
+  //        wipe_cache
+  //            a wipe of cache will be performed following a successful
+  //            installation.
+  //
+  //        clear_display
+  //            turn off the text display.
+  //
+  //        enable_reboot
+  //            packages can explicitly request that they want the user
+  //            to be able to reboot during installation (useful for
+  //            debugging packages that don't exit).
+  //
+  //        retry_update
+  //            updater encounters some issue during the update. It requests
+  //            a reboot to retry the same package automatically.
+  //
+  //        log <string>
+  //            updater requests logging the string (e.g. cause of the
+  //            failure).
+  //
+  //   - the name of the package zip file.
+  //
+  //   - an optional argument "retry" if this update is a retry of a failed
+  //   update attempt.
+  //
 
-    char buffer[1024];
-    FILE* from_child = fdopen(pipefd[0], "r");
-    while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
-        char* command = strtok(buffer, " \n");
-        if (command == NULL) {
-            continue;
-        } else if (strcmp(command, "progress") == 0) {
-            char* fraction_s = strtok(NULL, " \n");
-            char* seconds_s = strtok(NULL, " \n");
+  // Convert the vector to a NULL-terminated char* array suitable for execv.
+  const char* chr_args[args.size() + 1];
+  chr_args[args.size()] = nullptr;
+  for (size_t i = 0; i < args.size(); i++) {
+    chr_args[i] = args[i].c_str();
+  }
 
-            float fraction = strtof(fraction_s, NULL);
-            int seconds = strtol(seconds_s, NULL, 10);
+  pid_t pid = fork();
 
-            ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
-        } else if (strcmp(command, "set_progress") == 0) {
-            char* fraction_s = strtok(NULL, " \n");
-            float fraction = strtof(fraction_s, NULL);
-            ui->SetProgress(fraction);
-        } else if (strcmp(command, "ui_print") == 0) {
-            char* str = strtok(NULL, "\n");
-            if (str) {
-                ui->PrintOnScreenOnly("%s", str);
-            } else {
-                ui->PrintOnScreenOnly("\n");
-            }
-            fflush(stdout);
-        } else if (strcmp(command, "wipe_cache") == 0) {
-            *wipe_cache = true;
-        } else if (strcmp(command, "clear_display") == 0) {
-            ui->SetBackground(RecoveryUI::NONE);
-        } else if (strcmp(command, "enable_reboot") == 0) {
-            // packages can explicitly request that they want the user
-            // to be able to reboot during installation (useful for
-            // debugging packages that don't exit).
-            ui->SetEnableReboot(true);
-        } else if (strcmp(command, "retry_update") == 0) {
-            retry_update = true;
-        } else if (strcmp(command, "log") == 0) {
-            // Save the logging request from updater and write to
-            // last_install later.
-            log_buffer.push_back(std::string(strtok(NULL, "\n")));
-        } else {
-            LOGE("unknown command [%s]\n", command);
-        }
+  if (pid == -1) {
+    close(pipefd[0]);
+    close(pipefd[1]);
+    PLOG(ERROR) << "Failed to fork update binary";
+    return INSTALL_ERROR;
+  }
+
+  if (pid == 0) {
+    umask(022);
+    close(pipefd[0]);
+    execv(chr_args[0], const_cast<char**>(chr_args));
+    // Bug: 34769056
+    // We shouldn't use LOG/PLOG in the forked process, since they may cause
+    // the child process to hang. This deadlock results from an improperly
+    // copied mutex in the ui functions.
+    fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
+    _exit(EXIT_FAILURE);
+  }
+  close(pipefd[1]);
+
+  std::thread temperature_logger(log_max_temperature, max_temperature);
+
+  *wipe_cache = false;
+  bool retry_update = false;
+
+  char buffer[1024];
+  FILE* from_child = fdopen(pipefd[0], "r");
+  while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
+    std::string line(buffer);
+    size_t space = line.find_first_of(" \n");
+    std::string command(line.substr(0, space));
+    if (command.empty()) continue;
+
+    // Get rid of the leading and trailing space and/or newline.
+    std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space));
+
+    if (command == "progress") {
+      std::vector<std::string> tokens = android::base::Split(args, " ");
+      double fraction;
+      int seconds;
+      if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) &&
+          android::base::ParseInt(tokens[1], &seconds)) {
+        ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds);
+      } else {
+        LOG(ERROR) << "invalid \"progress\" parameters: " << line;
+      }
+    } else if (command == "set_progress") {
+      std::vector<std::string> tokens = android::base::Split(args, " ");
+      double fraction;
+      if (tokens.size() == 1 && android::base::ParseDouble(tokens[0].c_str(), &fraction)) {
+        ui->SetProgress(fraction);
+      } else {
+        LOG(ERROR) << "invalid \"set_progress\" parameters: " << line;
+      }
+    } else if (command == "ui_print") {
+      ui->PrintOnScreenOnly("%s\n", args.c_str());
+      fflush(stdout);
+    } else if (command == "wipe_cache") {
+      *wipe_cache = true;
+    } else if (command == "clear_display") {
+      ui->SetBackground(RecoveryUI::NONE);
+    } else if (command == "enable_reboot") {
+      // packages can explicitly request that they want the user
+      // to be able to reboot during installation (useful for
+      // debugging packages that don't exit).
+      ui->SetEnableReboot(true);
+    } else if (command == "retry_update") {
+      retry_update = true;
+    } else if (command == "log") {
+      if (!args.empty()) {
+        // Save the logging request from updater and write to last_install later.
+        log_buffer.push_back(args);
+      } else {
+        LOG(ERROR) << "invalid \"log\" parameters: " << line;
+      }
+    } else {
+      LOG(ERROR) << "unknown command [" << command << "]";
     }
-    fclose(from_child);
+  }
+  fclose(from_child);
 
-    int status;
-    waitpid(pid, &status, 0);
-    if (retry_update) {
-        return INSTALL_RETRY;
-    }
-    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-        LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
-        return INSTALL_ERROR;
-    }
+  int status;
+  waitpid(pid, &status, 0);
 
-    return INSTALL_SUCCESS;
+  finish_log_temperature.notify_one();
+  temperature_logger.join();
+
+  if (retry_update) {
+    return INSTALL_RETRY;
+  }
+  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+    LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")";
+    return INSTALL_ERROR;
+  }
+
+  return INSTALL_SUCCESS;
+}
+
+// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
+// entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA
+// package.
+bool verify_package_compatibility(ZipArchiveHandle package_zip) {
+  LOG(INFO) << "Verifying package compatibility...";
+
+  static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip";
+  ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY);
+  ZipEntry compatibility_entry;
+  if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) {
+    LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry";
+    return true;
+  }
+
+  std::string zip_content(compatibility_entry.uncompressed_length, '\0');
+  int32_t ret;
+  if ((ret = ExtractToMemory(package_zip, &compatibility_entry,
+                             reinterpret_cast<uint8_t*>(&zip_content[0]),
+                             compatibility_entry.uncompressed_length)) != 0) {
+    LOG(ERROR) << "Failed to read " << COMPATIBILITY_ZIP_ENTRY << ": " << ErrorCodeString(ret);
+    return false;
+  }
+
+  ZipArchiveHandle zip_handle;
+  ret = OpenArchiveFromMemory(static_cast<void*>(const_cast<char*>(zip_content.data())),
+                              zip_content.size(), COMPATIBILITY_ZIP_ENTRY, &zip_handle);
+  if (ret != 0) {
+    LOG(ERROR) << "Failed to OpenArchiveFromMemory: " << ErrorCodeString(ret);
+    return false;
+  }
+
+  // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
+  void* cookie;
+  ret = StartIteration(zip_handle, &cookie, nullptr, nullptr);
+  if (ret != 0) {
+    LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret);
+    CloseArchive(zip_handle);
+    return false;
+  }
+  std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+
+  std::vector<std::string> compatibility_info;
+  ZipEntry info_entry;
+  ZipString info_name;
+  while (Next(cookie, &info_entry, &info_name) == 0) {
+    std::string content(info_entry.uncompressed_length, '\0');
+    int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]),
+                                  info_entry.uncompressed_length);
+    if (ret != 0) {
+      LOG(ERROR) << "Failed to read " << info_name.name << ": " << ErrorCodeString(ret);
+      CloseArchive(zip_handle);
+      return false;
+    }
+    compatibility_info.emplace_back(std::move(content));
+  }
+  CloseArchive(zip_handle);
+
+  // VintfObjectRecovery::CheckCompatibility returns zero on success.
+  std::string err;
+  int result = android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err);
+  if (result == 0) {
+    return true;
+  }
+
+  LOG(ERROR) << "Failed to verify package compatibility (result " << result << "): " << err;
+  return false;
 }
 
 static int
 really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
-                       std::vector<std::string>& log_buffer, int retry_count)
+                       std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
 {
     ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
     ui->Print("Finding update package...\n");
     // Give verification half the progress bar...
     ui->SetProgressType(RecoveryUI::DETERMINATE);
     ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
-    LOGI("Update location: %s\n", path);
+    LOG(INFO) << "Update location: " << path;
 
     // Map the update package into memory.
     ui->Print("Opening update package...\n");
@@ -477,7 +580,7 @@
 
     MemMapping map;
     if (sysMapFile(path, &map) != 0) {
-        LOGE("failed to map file\n");
+        LOG(ERROR) << "failed to map file";
         return INSTALL_CORRUPT;
     }
 
@@ -489,28 +592,37 @@
     }
 
     // Try to open the package.
-    ZipArchive zip;
-    int err = mzOpenZipArchive(map.addr, map.length, &zip);
+    ZipArchiveHandle zip;
+    int err = OpenArchiveFromMemory(map.addr, map.length, path, &zip);
     if (err != 0) {
-        LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
+        LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
         log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
 
         sysReleaseMap(&map);
+        CloseArchive(zip);
         return INSTALL_CORRUPT;
     }
 
+    // Additionally verify the compatibility of the package.
+    if (!verify_package_compatibility(zip)) {
+      log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
+      sysReleaseMap(&map);
+      CloseArchive(zip);
+      return INSTALL_CORRUPT;
+    }
+
     // Verify and install the contents of the package.
     ui->Print("Installing update...\n");
     if (retry_count > 0) {
         ui->Print("Retry attempt: %d\n", retry_count);
     }
     ui->SetEnableReboot(false);
-    int result = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count);
+    int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
     ui->SetEnableReboot(true);
     ui->Print("\n");
 
     sysReleaseMap(&map);
-
+    CloseArchive(zip);
     return result;
 }
 
@@ -521,30 +633,38 @@
     modified_flash = true;
     auto start = std::chrono::system_clock::now();
 
+    int start_temperature = GetMaxValueFromThermalZone();
+    int max_temperature = start_temperature;
+
     int result;
     std::vector<std::string> log_buffer;
     if (setup_install_mounts() != 0) {
-        LOGE("failed to set up expected mounts for install; aborting\n");
+        LOG(ERROR) << "failed to set up expected mounts for install; aborting";
         result = INSTALL_ERROR;
     } else {
-        result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count);
+        result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count,
+                                        &max_temperature);
     }
 
     // Measure the time spent to apply OTA update in seconds.
     std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
     int time_total = static_cast<int>(duration.count());
 
-    if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
-        LOGW("Can't mount %s\n", UNCRYPT_STATUS);
-    } else {
+    bool has_cache = volume_for_path("/cache") != nullptr;
+    // Skip logging the uncrypt_status on devices without /cache.
+    if (has_cache) {
+      if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
+        LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS;
+      } else {
         std::string uncrypt_status;
         if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) {
-            LOGW("failed to read uncrypt status: %s\n", strerror(errno));
+          PLOG(WARNING) << "failed to read uncrypt status";
         } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) {
-            LOGW("corrupted uncrypt_status: %s: %s\n", uncrypt_status.c_str(), strerror(errno));
+          LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status;
         } else {
-            log_buffer.push_back(android::base::Trim(uncrypt_status));
+          log_buffer.push_back(android::base::Trim(uncrypt_status));
         }
+      }
     }
 
     // The first two lines need to be the package name and install result.
@@ -554,36 +674,50 @@
         "time_total: " + std::to_string(time_total),
         "retry: " + std::to_string(retry_count),
     };
+
+    int end_temperature = GetMaxValueFromThermalZone();
+    max_temperature = std::max(end_temperature, max_temperature);
+    if (start_temperature > 0) {
+      log_buffer.push_back("temperature_start: " + std::to_string(start_temperature));
+    }
+    if (end_temperature > 0) {
+      log_buffer.push_back("temperature_end: " + std::to_string(end_temperature));
+    }
+    if (max_temperature > 0) {
+      log_buffer.push_back("temperature_max: " + std::to_string(max_temperature));
+    }
+
     std::string log_content = android::base::Join(log_header, "\n") + "\n" +
-            android::base::Join(log_buffer, "\n");
+            android::base::Join(log_buffer, "\n") + "\n";
     if (!android::base::WriteStringToFile(log_content, install_file)) {
-        LOGE("failed to write %s: %s\n", install_file, strerror(errno));
+        PLOG(ERROR) << "failed to write " << install_file;
     }
 
     // Write a copy into last_log.
-    LOGI("%s\n", log_content.c_str());
+    LOG(INFO) << log_content;
 
     return result;
 }
 
 bool verify_package(const unsigned char* package_data, size_t package_size) {
-    std::vector<Certificate> loadedKeys;
-    if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
-        LOGE("Failed to load keys\n");
-        return false;
-    }
-    LOGI("%zu key(s) loaded from %s\n", loadedKeys.size(), PUBLIC_KEYS_FILE);
+  std::vector<Certificate> loadedKeys;
+  if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
+    LOG(ERROR) << "Failed to load keys";
+    return false;
+  }
+  LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE;
 
-    // Verify package.
-    ui->Print("Verifying update package...\n");
-    auto t0 = std::chrono::system_clock::now();
-    int err = verify_file(const_cast<unsigned char*>(package_data), package_size, loadedKeys);
-    std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
-    ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
-    if (err != VERIFY_SUCCESS) {
-        LOGE("Signature verification failed\n");
-        LOGE("error: %d\n", kZipVerificationFailure);
-        return false;
-    }
-    return true;
+  // Verify package.
+  ui->Print("Verifying update package...\n");
+  auto t0 = std::chrono::system_clock::now();
+  int err = verify_file(package_data, package_size, loadedKeys,
+                        std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
+  std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
+  ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
+  if (err != VERIFY_SUCCESS) {
+    LOG(ERROR) << "Signature verification failed";
+    LOG(ERROR) << "error: " << kZipVerificationFailure;
+    return false;
+  }
+  return true;
 }
diff --git a/install.h b/install.h
index 14de225..68f0a8d 100644
--- a/install.h
+++ b/install.h
@@ -18,12 +18,11 @@
 #define RECOVERY_INSTALL_H_
 
 #include <string>
-
-#include "common.h"
-#include "minzip/Zip.h"
+#include <ziparchive/zip_archive.h>
 
 enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED,
         INSTALL_RETRY };
+
 // Install the package specified by root_path.  If INSTALL_SUCCESS is
 // returned and *wipe_cache is true on exit, caller should wipe the
 // cache partition.
@@ -36,6 +35,10 @@
 
 // Read meta data file of the package, write its content in the string pointed by meta_data.
 // Return true if succeed, otherwise return false.
-bool read_metadata_from_package(ZipArchive* zip, std::string* meta_data);
+bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data);
+
+// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
+// entry doesn't exist.
+bool verify_package_compatibility(ZipArchiveHandle package_zip);
 
 #endif  // RECOVERY_INSTALL_H_
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
index 3db3b41..7eef13e 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -11,9 +11,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
-    adb_main.cpp \
     fuse_adb_provider.cpp \
-    services.cpp \
+    minadbd.cpp \
+    minadbd_services.cpp \
 
 LOCAL_CLANG := true
 LOCAL_MODULE := libminadbd
@@ -21,7 +21,7 @@
 LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration
 LOCAL_C_INCLUDES := bootable/recovery system/core/adb
 LOCAL_WHOLE_STATIC_LIBRARIES := libadbd
-LOCAL_STATIC_LIBRARIES := libbase
+LOCAL_STATIC_LIBRARIES := libcrypto libbase
 
 include $(BUILD_STATIC_LIBRARY)
 
diff --git a/minadbd/README.txt b/minadbd/README.md
similarity index 79%
rename from minadbd/README.txt
rename to minadbd/README.md
index e69dc87..5a0a067 100644
--- a/minadbd/README.txt
+++ b/minadbd/README.md
@@ -3,6 +3,6 @@
 
   - all services removed
   - all host mode support removed
-  - sideload_service() added; this is the only service supported.  It
+  - `sideload_service()` added; this is the only service supported. It
     receives a single blob of data, writes it to a fixed filename, and
     makes the process exit.
diff --git a/minadbd/fuse_adb_provider.cpp b/minadbd/fuse_adb_provider.cpp
index d71807d..0f4c256 100644
--- a/minadbd/fuse_adb_provider.cpp
+++ b/minadbd/fuse_adb_provider.cpp
@@ -19,8 +19,6 @@
 #include <string.h>
 #include <errno.h>
 
-#include "sysdeps.h"
-
 #include "adb.h"
 #include "adb_io.h"
 #include "fuse_adb_provider.h"
diff --git a/minadbd/adb_main.cpp b/minadbd/minadbd.cpp
similarity index 91%
rename from minadbd/adb_main.cpp
rename to minadbd/minadbd.cpp
index 0694280..349189c 100644
--- a/minadbd/adb_main.cpp
+++ b/minadbd/minadbd.cpp
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
+#include "minadbd.h"
+
 #include <errno.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 
-#include "sysdeps.h"
-
 #include "adb.h"
 #include "adb_auth.h"
 #include "transport.h"
 
-int adb_server_main(int is_daemon, int server_port, int /* reply_fd */) {
+int minadbd_main() {
     adb_device_banner = "sideload";
 
     signal(SIGPIPE, SIG_IGN);
diff --git a/minzip/inline_magic.h b/minadbd/minadbd.h
similarity index 68%
rename from minzip/inline_magic.h
rename to minadbd/minadbd.h
index 59c659f..3570a5d 100644
--- a/minzip/inline_magic.h
+++ b/minadbd/minadbd.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -14,13 +14,9 @@
  * limitations under the License.
  */
 
-#ifndef MINZIP_INLINE_MAGIC_H_
-#define MINZIP_INLINE_MAGIC_H_
+#ifndef MINADBD_H__
+#define MINADBD_H__
 
-#ifndef MINZIP_GENERATE_INLINES
-#define INLINE extern inline __attribute((__gnu_inline__))
-#else
-#define INLINE
+int minadbd_main();
+
 #endif
-
-#endif  // MINZIP_INLINE_MAGIC_H_
diff --git a/minadbd/services.cpp b/minadbd/minadbd_services.cpp
similarity index 96%
rename from minadbd/services.cpp
rename to minadbd/minadbd_services.cpp
index 658a43f..426d982 100644
--- a/minadbd/services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -21,11 +21,10 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "sysdeps.h"
-
 #include "adb.h"
 #include "fdevent.h"
 #include "fuse_adb_provider.h"
+#include "sysdeps.h"
 
 typedef struct stinfo stinfo;
 
@@ -62,12 +61,12 @@
 
 static int create_service_thread(void (*func)(int, void *), void *cookie) {
     int s[2];
-    if(adb_socketpair(s)) {
+    if (adb_socketpair(s)) {
         printf("cannot create service socket pair\n");
         return -1;
     }
 
-    stinfo* sti = reinterpret_cast<stinfo*>(malloc(sizeof(stinfo)));
+    stinfo* sti = static_cast<stinfo*>(malloc(sizeof(stinfo)));
     if(sti == 0) fatal("cannot allocate stinfo");
     sti->func = func;
     sti->cookie = cookie;
diff --git a/minui/Android.mk b/minui/Android.mk
index 3057f45..4dfc65f 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -1,3 +1,17 @@
+# Copyright (C) 2007 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 agreed 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.
+
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
@@ -9,14 +23,21 @@
     graphics_fbdev.cpp \
     resources.cpp \
 
-LOCAL_WHOLE_STATIC_LIBRARIES += libadf
-LOCAL_WHOLE_STATIC_LIBRARIES += libdrm
-LOCAL_STATIC_LIBRARIES += libpng
+LOCAL_WHOLE_STATIC_LIBRARIES := \
+    libadf \
+    libdrm \
+    libsync_recovery
+
+LOCAL_STATIC_LIBRARIES := \
+    libpng \
+    libbase
+
+LOCAL_CFLAGS := -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 
 LOCAL_MODULE := libminui
 
-LOCAL_CLANG := true
-
 # This used to compare against values in double-quotes (which are just
 # ordinary characters in this context).  Strip double-quotes from the
 # value so that either will work.
@@ -41,8 +62,13 @@
 
 # Used by OEMs for factory test images.
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_MODULE := libminui
 LOCAL_WHOLE_STATIC_LIBRARIES += libminui
-LOCAL_SHARED_LIBRARIES := libpng
+LOCAL_SHARED_LIBRARIES := \
+    libpng \
+    libbase
+
+LOCAL_CFLAGS := -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 include $(BUILD_SHARED_LIBRARY)
diff --git a/minui/events.cpp b/minui/events.cpp
index 3b2262a..0e1fd44 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -15,17 +15,18 @@
  */
 
 #include <dirent.h>
-#include <errno.h>
 #include <fcntl.h>
+#include <linux/input.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/epoll.h>
+#include <sys/ioctl.h>
 #include <unistd.h>
 
-#include <linux/input.h>
+#include <functional>
 
-#include "minui.h"
+#include "minui/minui.h"
 
 #define MAX_DEVICES 16
 #define MAX_MISC_FDS 16
@@ -34,9 +35,8 @@
 #define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
 
 struct fd_info {
-    int fd;
-    ev_callback cb;
-    void* data;
+  int fd;
+  ev_callback cb;
 };
 
 static int g_epoll_fd;
@@ -49,92 +49,91 @@
 static unsigned ev_dev_count = 0;
 static unsigned ev_misc_count = 0;
 
-static bool test_bit(size_t bit, unsigned long* array) {
+static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
     return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
 }
 
-int ev_init(ev_callback input_cb, void* data) {
-    bool epollctlfail = false;
+int ev_init(ev_callback input_cb) {
+  bool epollctlfail = false;
 
-    g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
-    if (g_epoll_fd == -1) {
-        return -1;
+  g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
+  if (g_epoll_fd == -1) {
+    return -1;
+  }
+
+  DIR* dir = opendir("/dev/input");
+  if (dir != NULL) {
+    dirent* de;
+    while ((de = readdir(dir))) {
+      // Use unsigned long to match ioctl's parameter type.
+      unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
+
+      //            fprintf(stderr,"/dev/input/%s\n", de->d_name);
+      if (strncmp(de->d_name, "event", 5)) continue;
+      int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
+      if (fd == -1) continue;
+
+      // Read the evbits of the input device.
+      if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+        close(fd);
+        continue;
+      }
+
+      // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
+      if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
+        close(fd);
+        continue;
+      }
+
+      epoll_event ev;
+      ev.events = EPOLLIN | EPOLLWAKEUP;
+      ev.data.ptr = &ev_fdinfo[ev_count];
+      if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
+        close(fd);
+        epollctlfail = true;
+        continue;
+      }
+
+      ev_fdinfo[ev_count].fd = fd;
+      ev_fdinfo[ev_count].cb = std::move(input_cb);
+      ev_count++;
+      ev_dev_count++;
+      if (ev_dev_count == MAX_DEVICES) break;
     }
 
-    DIR* dir = opendir("/dev/input");
-    if (dir != NULL) {
-        dirent* de;
-        while ((de = readdir(dir))) {
-            unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
+    closedir(dir);
+  }
 
-//            fprintf(stderr,"/dev/input/%s\n", de->d_name);
-            if (strncmp(de->d_name, "event", 5)) continue;
-            int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
-            if (fd == -1) continue;
+  if (epollctlfail && !ev_count) {
+    close(g_epoll_fd);
+    g_epoll_fd = -1;
+    return -1;
+  }
 
-            // Read the evbits of the input device.
-            if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
-                close(fd);
-                continue;
-            }
-
-            // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
-            if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
-                close(fd);
-                continue;
-            }
-
-            epoll_event ev;
-            ev.events = EPOLLIN | EPOLLWAKEUP;
-            ev.data.ptr = &ev_fdinfo[ev_count];
-            if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
-                close(fd);
-                epollctlfail = true;
-                continue;
-            }
-
-            ev_fdinfo[ev_count].fd = fd;
-            ev_fdinfo[ev_count].cb = input_cb;
-            ev_fdinfo[ev_count].data = data;
-            ev_count++;
-            ev_dev_count++;
-            if (ev_dev_count == MAX_DEVICES) break;
-        }
-
-        closedir(dir);
-    }
-
-    if (epollctlfail && !ev_count) {
-        close(g_epoll_fd);
-        g_epoll_fd = -1;
-        return -1;
-    }
-
-    return 0;
+  return 0;
 }
 
 int ev_get_epollfd(void) {
     return g_epoll_fd;
 }
 
-int ev_add_fd(int fd, ev_callback cb, void* data) {
-    if (ev_misc_count == MAX_MISC_FDS || cb == NULL) {
-        return -1;
-    }
+int ev_add_fd(int fd, ev_callback cb) {
+  if (ev_misc_count == MAX_MISC_FDS || cb == NULL) {
+    return -1;
+  }
 
-    epoll_event ev;
-    ev.events = EPOLLIN | EPOLLWAKEUP;
-    ev.data.ptr = (void *)&ev_fdinfo[ev_count];
-    int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
-    if (!ret) {
-        ev_fdinfo[ev_count].fd = fd;
-        ev_fdinfo[ev_count].cb = cb;
-        ev_fdinfo[ev_count].data = data;
-        ev_count++;
-        ev_misc_count++;
-    }
+  epoll_event ev;
+  ev.events = EPOLLIN | EPOLLWAKEUP;
+  ev.data.ptr = static_cast<void*>(&ev_fdinfo[ev_count]);
+  int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
+  if (!ret) {
+    ev_fdinfo[ev_count].fd = fd;
+    ev_fdinfo[ev_count].cb = std::move(cb);
+    ev_count++;
+    ev_misc_count++;
+  }
 
-    return ret;
+  return ret;
 }
 
 void ev_exit(void) {
@@ -155,13 +154,13 @@
 }
 
 void ev_dispatch(void) {
-    for (int n = 0; n < npolledevents; n++) {
-        fd_info* fdi = reinterpret_cast<fd_info*>(polledevents[n].data.ptr);
-        ev_callback cb = fdi->cb;
-        if (cb) {
-            cb(fdi->fd, polledevents[n].events, fdi->data);
-        }
+  for (int n = 0; n < npolledevents; n++) {
+    fd_info* fdi = static_cast<fd_info*>(polledevents[n].data.ptr);
+    const ev_callback& cb = fdi->cb;
+    if (cb) {
+      cb(fdi->fd, polledevents[n].events);
     }
+  }
 }
 
 int ev_get_input(int fd, uint32_t epevents, input_event* ev) {
@@ -174,37 +173,39 @@
     return -1;
 }
 
-int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data) {
-    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
-    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];
+int ev_sync_key_state(const ev_set_key_callback& set_key_cb) {
+  // Use unsigned long to match ioctl's parameter type.
+  unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];    // NOLINT
+  unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];  // NOLINT
 
-    for (size_t i = 0; i < ev_dev_count; ++i) {
-        memset(ev_bits, 0, sizeof(ev_bits));
-        memset(key_bits, 0, sizeof(key_bits));
+  for (size_t i = 0; i < ev_dev_count; ++i) {
+    memset(ev_bits, 0, sizeof(ev_bits));
+    memset(key_bits, 0, sizeof(key_bits));
 
-        if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
-            continue;
-        }
-        if (!test_bit(EV_KEY, ev_bits)) {
-            continue;
-        }
-        if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) {
-            continue;
-        }
-
-        for (int code = 0; code <= KEY_MAX; code++) {
-            if (test_bit(code, key_bits)) {
-                set_key_cb(code, 1, data);
-            }
-        }
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+      continue;
+    }
+    if (!test_bit(EV_KEY, ev_bits)) {
+      continue;
+    }
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) {
+      continue;
     }
 
-    return 0;
+    for (int code = 0; code <= KEY_MAX; code++) {
+      if (test_bit(code, key_bits)) {
+        set_key_cb(code, 1);
+      }
+    }
+  }
+
+  return 0;
 }
 
-void ev_iterate_available_keys(std::function<void(int)> f) {
-    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
-    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];
+void ev_iterate_available_keys(const std::function<void(int)>& f) {
+    // Use unsigned long to match ioctl's parameter type.
+    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
+    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT
 
     for (size_t i = 0; i < ev_dev_count; ++i) {
         memset(ev_bits, 0, sizeof(ev_bits));
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index ab56a6f..3bfce11 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -14,29 +14,22 @@
  * limitations under the License.
  */
 
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <fcntl.h>
-#include <stdio.h>
-
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <sys/types.h>
-
-#include <linux/fb.h>
-#include <linux/kd.h>
-
-#include <time.h>
-
-#include "font_10x18.h"
-#include "minui.h"
 #include "graphics.h"
 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <memory>
+
+#include "font_10x18.h"
+#include "graphics_adf.h"
+#include "graphics_drm.h"
+#include "graphics_fbdev.h"
+#include "minui/minui.h"
+
 static GRFont* gr_font = NULL;
-static minui_backend* gr_backend = NULL;
+static MinuiBackend* gr_backend = nullptr;
 
 static int overscan_percent = OVERSCAN_PERCENT;
 static int overscan_offset_x = 0;
@@ -265,7 +258,7 @@
 }
 
 int gr_init_font(const char* name, GRFont** dest) {
-    GRFont* font = reinterpret_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
+    GRFont* font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
     if (font == nullptr) {
         return -1;
     }
@@ -298,15 +291,15 @@
 
 
     // fall back to the compiled-in font.
-    gr_font = reinterpret_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
-    gr_font->texture = reinterpret_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
+    gr_font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
+    gr_font->texture = static_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
     gr_font->texture->width = font.width;
     gr_font->texture->height = font.height;
     gr_font->texture->row_bytes = font.width;
     gr_font->texture->pixel_bytes = 1;
 
-    unsigned char* bits = reinterpret_cast<unsigned char*>(malloc(font.width * font.height));
-    gr_font->texture->data = reinterpret_cast<unsigned char*>(bits);
+    unsigned char* bits = static_cast<unsigned char*>(malloc(font.width * font.height));
+    gr_font->texture->data = bits;
 
     unsigned char data;
     unsigned char* in = font.rundata;
@@ -319,109 +312,53 @@
     gr_font->char_height = font.char_height;
 }
 
-#if 0
-// Exercises many of the gr_*() functions; useful for testing.
-static void gr_test() {
-    GRSurface** images;
-    int frames;
-    int result = res_create_multi_surface("icon_installing", &frames, &images);
-    if (result < 0) {
-        printf("create surface %d\n", result);
-        gr_exit();
-        return;
-    }
-
-    time_t start = time(NULL);
-    int x;
-    for (x = 0; x <= 1200; ++x) {
-        if (x < 400) {
-            gr_color(0, 0, 0, 255);
-        } else {
-            gr_color(0, (x-400)%128, 0, 255);
-        }
-        gr_clear();
-
-        gr_color(255, 0, 0, 255);
-        GRSurface* frame = images[x%frames];
-        gr_blit(frame, 0, 0, frame->width, frame->height, x, 0);
-
-        gr_color(255, 0, 0, 128);
-        gr_fill(400, 150, 600, 350);
-
-        gr_color(255, 255, 255, 255);
-        gr_text(500, 225, "hello, world!", 0);
-        gr_color(255, 255, 0, 128);
-        gr_text(300+x, 275, "pack my box with five dozen liquor jugs", 1);
-
-        gr_color(0, 0, 255, 128);
-        gr_fill(gr_draw->width - 200 - x, 300, gr_draw->width - x, 500);
-
-        gr_draw = gr_backend->flip(gr_backend);
-    }
-    printf("getting end time\n");
-    time_t end = time(NULL);
-    printf("got end time\n");
-    printf("start %ld end %ld\n", (long)start, (long)end);
-    if (end > start) {
-        printf("%.2f fps\n", ((double)x) / (end-start));
-    }
-}
-#endif
-
 void gr_flip() {
-    gr_draw = gr_backend->flip(gr_backend);
+  gr_draw = gr_backend->Flip();
 }
 
-int gr_init(void)
-{
-    gr_init_font();
+int gr_init() {
+  gr_init_font();
 
-    gr_backend = open_adf();
-    if (gr_backend) {
-        gr_draw = gr_backend->init(gr_backend);
-        if (!gr_draw) {
-            gr_backend->exit(gr_backend);
-        }
-    }
+  auto backend = std::unique_ptr<MinuiBackend>{ std::make_unique<MinuiBackendAdf>() };
+  gr_draw = backend->Init();
 
-    if (!gr_draw) {
-        gr_backend = open_drm();
-        gr_draw = gr_backend->init(gr_backend);
-    }
+  if (!gr_draw) {
+    backend = std::make_unique<MinuiBackendDrm>();
+    gr_draw = backend->Init();
+  }
 
-    if (!gr_draw) {
-        gr_backend = open_fbdev();
-        gr_draw = gr_backend->init(gr_backend);
-        if (gr_draw == NULL) {
-            return -1;
-        }
-    }
+  if (!gr_draw) {
+    backend = std::make_unique<MinuiBackendFbdev>();
+    gr_draw = backend->Init();
+  }
 
-    overscan_offset_x = gr_draw->width * overscan_percent / 100;
-    overscan_offset_y = gr_draw->height * overscan_percent / 100;
+  if (!gr_draw) {
+    return -1;
+  }
 
-    gr_flip();
-    gr_flip();
+  gr_backend = backend.release();
 
-    return 0;
+  overscan_offset_x = gr_draw->width * overscan_percent / 100;
+  overscan_offset_y = gr_draw->height * overscan_percent / 100;
+
+  gr_flip();
+  gr_flip();
+
+  return 0;
 }
 
-void gr_exit(void)
-{
-    gr_backend->exit(gr_backend);
+void gr_exit() {
+  delete gr_backend;
 }
 
-int gr_fb_width(void)
-{
-    return gr_draw->width - 2*overscan_offset_x;
+int gr_fb_width() {
+  return gr_draw->width - 2 * overscan_offset_x;
 }
 
-int gr_fb_height(void)
-{
-    return gr_draw->height - 2*overscan_offset_y;
+int gr_fb_height() {
+  return gr_draw->height - 2 * overscan_offset_y;
 }
 
-void gr_fb_blank(bool blank)
-{
-    gr_backend->blank(gr_backend, blank);
+void gr_fb_blank(bool blank) {
+  gr_backend->Blank(blank);
 }
diff --git a/minui/graphics.h b/minui/graphics.h
index 52968eb..3c45a40 100644
--- a/minui/graphics.h
+++ b/minui/graphics.h
@@ -17,27 +17,22 @@
 #ifndef _GRAPHICS_H_
 #define _GRAPHICS_H_
 
-#include "minui.h"
+#include "minui/minui.h"
 
-// TODO: lose the function pointers.
-struct minui_backend {
-    // Initializes the backend and returns a GRSurface* to draw into.
-    GRSurface* (*init)(minui_backend*);
+class MinuiBackend {
+ public:
+  // Initializes the backend and returns a GRSurface* to draw into.
+  virtual GRSurface* Init() = 0;
 
-    // Causes the current drawing surface (returned by the most recent
-    // call to flip() or init()) to be displayed, and returns a new
-    // drawing surface.
-    GRSurface* (*flip)(minui_backend*);
+  // Causes the current drawing surface (returned by the most recent call to Flip() or Init()) to
+  // be displayed, and returns a new drawing surface.
+  virtual GRSurface* Flip() = 0;
 
-    // Blank (or unblank) the screen.
-    void (*blank)(minui_backend*, bool);
+  // Blank (or unblank) the screen.
+  virtual void Blank(bool) = 0;
 
-    // Device cleanup when drawing is done.
-    void (*exit)(minui_backend*);
+  // Device cleanup when drawing is done.
+  virtual ~MinuiBackend() {};
 };
 
-minui_backend* open_fbdev();
-minui_backend* open_adf();
-minui_backend* open_drm();
-
-#endif
+#endif  // _GRAPHICS_H_
diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp
index 5d0867f..1b15a04 100644
--- a/minui/graphics_adf.cpp
+++ b/minui/graphics_adf.cpp
@@ -14,236 +14,184 @@
  * limitations under the License.
  */
 
+#include "graphics_adf.h"
+
 #include <errno.h>
 #include <fcntl.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
+#include <sys/mman.h>
 #include <unistd.h>
 
-#include <sys/cdefs.h>
-#include <sys/mman.h>
-
 #include <adf/adf.h>
+#include <sync/sync.h>
 
-#include "graphics.h"
+#include "minui/minui.h"
 
-struct adf_surface_pdata {
-    GRSurface base;
-    int fd;
-    __u32 offset;
-    __u32 pitch;
-};
+MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), n_surfaces(0), surfaces() {}
 
-struct adf_pdata {
-    minui_backend base;
-    int intf_fd;
-    adf_id_t eng_id;
-    __u32 format;
+int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) {
+  *surf = {};
+  surf->fence_fd = -1;
+  surf->fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
+                                               &surf->offset, &surf->pitch);
+  if (surf->fd < 0) {
+    return surf->fd;
+  }
 
-    unsigned int current_surface;
-    unsigned int n_surfaces;
-    adf_surface_pdata surfaces[2];
-};
+  surf->width = mode->hdisplay;
+  surf->height = mode->vdisplay;
+  surf->row_bytes = surf->pitch;
+  surf->pixel_bytes = (format == DRM_FORMAT_RGB565) ? 2 : 4;
 
-static GRSurface* adf_flip(minui_backend *backend);
-static void adf_blank(minui_backend *backend, bool blank);
+  surf->data = static_cast<uint8_t*>(
+      mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset));
+  if (surf->data == MAP_FAILED) {
+    int saved_errno = errno;
+    close(surf->fd);
+    return -saved_errno;
+  }
 
-static int adf_surface_init(adf_pdata *pdata, drm_mode_modeinfo *mode, adf_surface_pdata *surf) {
-    memset(surf, 0, sizeof(*surf));
-
-    surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay,
-            mode->vdisplay, pdata->format, &surf->offset, &surf->pitch);
-    if (surf->fd < 0)
-        return surf->fd;
-
-    surf->base.width = mode->hdisplay;
-    surf->base.height = mode->vdisplay;
-    surf->base.row_bytes = surf->pitch;
-    surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4;
-
-    surf->base.data = reinterpret_cast<uint8_t*>(mmap(NULL,
-                                                      surf->pitch * surf->base.height, PROT_WRITE,
-                                                      MAP_SHARED, surf->fd, surf->offset));
-    if (surf->base.data == MAP_FAILED) {
-        close(surf->fd);
-        return -errno;
-    }
-
-    return 0;
+  return 0;
 }
 
-static int adf_interface_init(adf_pdata *pdata)
-{
-    adf_interface_data intf_data;
-    int ret = 0;
-    int err;
+int MinuiBackendAdf::InterfaceInit() {
+  adf_interface_data intf_data;
+  int err = adf_get_interface_data(intf_fd, &intf_data);
+  if (err < 0) return err;
 
-    err = adf_get_interface_data(pdata->intf_fd, &intf_data);
-    if (err < 0)
-        return err;
+  int ret = 0;
+  err = SurfaceInit(&intf_data.current_mode, &surfaces[0]);
+  if (err < 0) {
+    fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
+    ret = err;
+    goto done;
+  }
 
-    err = adf_surface_init(pdata, &intf_data.current_mode, &pdata->surfaces[0]);
-    if (err < 0) {
-        fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
-        ret = err;
-        goto done;
-    }
-
-    err = adf_surface_init(pdata, &intf_data.current_mode,
-            &pdata->surfaces[1]);
-    if (err < 0) {
-        fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
-        memset(&pdata->surfaces[1], 0, sizeof(pdata->surfaces[1]));
-        pdata->n_surfaces = 1;
-    } else {
-        pdata->n_surfaces = 2;
-    }
+  err = SurfaceInit(&intf_data.current_mode, &surfaces[1]);
+  if (err < 0) {
+    fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
+    surfaces[1] = {};
+    n_surfaces = 1;
+  } else {
+    n_surfaces = 2;
+  }
 
 done:
-    adf_free_interface_data(&intf_data);
-    return ret;
+  adf_free_interface_data(&intf_data);
+  return ret;
 }
 
-static int adf_device_init(adf_pdata *pdata, adf_device *dev)
-{
-    adf_id_t intf_id;
-    int intf_fd;
-    int err;
+int MinuiBackendAdf::DeviceInit(adf_device* dev) {
+  adf_id_t intf_id;
+  int err = adf_find_simple_post_configuration(dev, &format, 1, &intf_id, &eng_id);
+  if (err < 0) return err;
 
-    err = adf_find_simple_post_configuration(dev, &pdata->format, 1, &intf_id,
-            &pdata->eng_id);
-    if (err < 0)
-        return err;
+  err = adf_device_attach(dev, eng_id, intf_id);
+  if (err < 0 && err != -EALREADY) return err;
 
-    err = adf_device_attach(dev, pdata->eng_id, intf_id);
-    if (err < 0 && err != -EALREADY)
-        return err;
+  intf_fd = adf_interface_open(dev, intf_id, O_RDWR);
+  if (intf_fd < 0) return intf_fd;
 
-    pdata->intf_fd = adf_interface_open(dev, intf_id, O_RDWR);
-    if (pdata->intf_fd < 0)
-        return pdata->intf_fd;
+  err = InterfaceInit();
+  if (err < 0) {
+    close(intf_fd);
+    intf_fd = -1;
+  }
 
-    err = adf_interface_init(pdata);
-    if (err < 0) {
-        close(pdata->intf_fd);
-        pdata->intf_fd = -1;
-    }
-
-    return err;
+  return err;
 }
 
-static GRSurface* adf_init(minui_backend *backend)
-{
-    adf_pdata *pdata = (adf_pdata *)backend;
-    adf_id_t *dev_ids = NULL;
-    ssize_t n_dev_ids, i;
-    GRSurface* ret;
-
+GRSurface* MinuiBackendAdf::Init() {
 #if defined(RECOVERY_ABGR)
-    pdata->format = DRM_FORMAT_ABGR8888;
+  format = DRM_FORMAT_ABGR8888;
 #elif defined(RECOVERY_BGRA)
-    pdata->format = DRM_FORMAT_BGRA8888;
+  format = DRM_FORMAT_BGRA8888;
 #elif defined(RECOVERY_RGBX)
-    pdata->format = DRM_FORMAT_RGBX8888;
+  format = DRM_FORMAT_RGBX8888;
 #else
-    pdata->format = DRM_FORMAT_RGB565;
+  format = DRM_FORMAT_RGB565;
 #endif
 
-    n_dev_ids = adf_devices(&dev_ids);
-    if (n_dev_ids == 0) {
-        return NULL;
-    } else if (n_dev_ids < 0) {
-        fprintf(stderr, "enumerating adf devices failed: %s\n",
-                strerror(-n_dev_ids));
-        return NULL;
+  adf_id_t* dev_ids = nullptr;
+  ssize_t n_dev_ids = adf_devices(&dev_ids);
+  if (n_dev_ids == 0) {
+    return nullptr;
+  } else if (n_dev_ids < 0) {
+    fprintf(stderr, "enumerating adf devices failed: %s\n", strerror(-n_dev_ids));
+    return nullptr;
+  }
+
+  intf_fd = -1;
+
+  for (ssize_t i = 0; i < n_dev_ids && intf_fd < 0; i++) {
+    int err = adf_device_open(dev_ids[i], O_RDWR, &dev);
+    if (err < 0) {
+      fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i], strerror(-err));
+      continue;
     }
 
-    pdata->intf_fd = -1;
+    err = DeviceInit(&dev);
+    if (err < 0) {
+      fprintf(stderr, "initializing adf device %u failed: %s\n", dev_ids[i], strerror(-err));
+      adf_device_close(&dev);
+    }
+  }
 
-    for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) {
-        adf_device dev;
+  free(dev_ids);
 
-        int err = adf_device_open(dev_ids[i], O_RDWR, &dev);
-        if (err < 0) {
-            fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i],
-                    strerror(-err));
-            continue;
-        }
+  if (intf_fd < 0) return nullptr;
 
-        err = adf_device_init(pdata, &dev);
-        if (err < 0)
-            fprintf(stderr, "initializing adf device %u failed: %s\n",
-                    dev_ids[i], strerror(-err));
+  GRSurface* ret = Flip();
 
-        adf_device_close(&dev);
+  Blank(true);
+  Blank(false);
+
+  return ret;
+}
+
+void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) {
+  static constexpr unsigned int warningTimeout = 3000;
+
+  if (surf == nullptr) return;
+
+  if (surf->fence_fd >= 0) {
+    int err = sync_wait(surf->fence_fd, warningTimeout);
+    if (err < 0) {
+      perror("adf sync fence wait error\n");
     }
 
-    free(dev_ids);
-
-    if (pdata->intf_fd < 0)
-        return NULL;
-
-    ret = adf_flip(backend);
-
-    adf_blank(backend, true);
-    adf_blank(backend, false);
-
-    return ret;
+    close(surf->fence_fd);
+    surf->fence_fd = -1;
+  }
 }
 
-static GRSurface* adf_flip(minui_backend *backend)
-{
-    adf_pdata *pdata = (adf_pdata *)backend;
-    adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface];
+GRSurface* MinuiBackendAdf::Flip() {
+  GRSurfaceAdf* surf = &surfaces[current_surface];
 
-    int fence_fd = adf_interface_simple_post(pdata->intf_fd, pdata->eng_id,
-            surf->base.width, surf->base.height, pdata->format, surf->fd,
-            surf->offset, surf->pitch, -1);
-    if (fence_fd >= 0)
-        close(fence_fd);
+  int fence_fd = adf_interface_simple_post(intf_fd, eng_id, surf->width, surf->height, format,
+                                           surf->fd, surf->offset, surf->pitch, -1);
+  if (fence_fd >= 0) surf->fence_fd = fence_fd;
 
-    pdata->current_surface = (pdata->current_surface + 1) % pdata->n_surfaces;
-    return &pdata->surfaces[pdata->current_surface].base;
+  current_surface = (current_surface + 1) % n_surfaces;
+  Sync(&surfaces[current_surface]);
+  return &surfaces[current_surface];
 }
 
-static void adf_blank(minui_backend *backend, bool blank)
-{
-    adf_pdata *pdata = (adf_pdata *)backend;
-    adf_interface_blank(pdata->intf_fd,
-            blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
+void MinuiBackendAdf::Blank(bool blank) {
+  adf_interface_blank(intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
 }
 
-static void adf_surface_destroy(adf_surface_pdata *surf)
-{
-    munmap(surf->base.data, surf->pitch * surf->base.height);
-    close(surf->fd);
+void MinuiBackendAdf::SurfaceDestroy(GRSurfaceAdf* surf) {
+  munmap(surf->data, surf->pitch * surf->height);
+  close(surf->fence_fd);
+  close(surf->fd);
 }
 
-static void adf_exit(minui_backend *backend)
-{
-    adf_pdata *pdata = (adf_pdata *)backend;
-    unsigned int i;
-
-    for (i = 0; i < pdata->n_surfaces; i++)
-        adf_surface_destroy(&pdata->surfaces[i]);
-    if (pdata->intf_fd >= 0)
-        close(pdata->intf_fd);
-    free(pdata);
-}
-
-minui_backend *open_adf()
-{
-    adf_pdata* pdata = reinterpret_cast<adf_pdata*>(calloc(1, sizeof(*pdata)));
-    if (!pdata) {
-        perror("allocating adf backend failed");
-        return NULL;
-    }
-
-    pdata->base.init = adf_init;
-    pdata->base.flip = adf_flip;
-    pdata->base.blank = adf_blank;
-    pdata->base.exit = adf_exit;
-    return &pdata->base;
+MinuiBackendAdf::~MinuiBackendAdf() {
+  adf_device_close(&dev);
+  for (unsigned int i = 0; i < n_surfaces; i++) {
+    SurfaceDestroy(&surfaces[i]);
+  }
+  if (intf_fd >= 0) close(intf_fd);
 }
diff --git a/minui/graphics_adf.h b/minui/graphics_adf.h
new file mode 100644
index 0000000..2f019ed
--- /dev/null
+++ b/minui/graphics_adf.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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 agreed 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 _GRAPHICS_ADF_H_
+#define _GRAPHICS_ADF_H_
+
+#include <adf/adf.h>
+
+#include "graphics.h"
+
+class GRSurfaceAdf : public GRSurface {
+ private:
+  int fence_fd;
+  int fd;
+  __u32 offset;
+  __u32 pitch;
+
+  friend class MinuiBackendAdf;
+};
+
+class MinuiBackendAdf : public MinuiBackend {
+ public:
+  GRSurface* Init() override;
+  GRSurface* Flip() override;
+  void Blank(bool) override;
+  ~MinuiBackendAdf() override;
+  MinuiBackendAdf();
+
+ private:
+  int SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf);
+  int InterfaceInit();
+  int DeviceInit(adf_device* dev);
+  void SurfaceDestroy(GRSurfaceAdf* surf);
+  void Sync(GRSurfaceAdf* surf);
+
+  int intf_fd;
+  adf_id_t eng_id;
+  __u32 format;
+  adf_device dev;
+  unsigned int current_surface;
+  unsigned int n_surfaces;
+  GRSurfaceAdf surfaces[2];
+};
+
+#endif  // _GRAPHICS_ADF_H_
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index 03e33b7..e7d4b38 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -14,463 +14,382 @@
  * limitations under the License.
  */
 
-#include <drm_fourcc.h>
+#include "graphics_drm.h"
+
 #include <fcntl.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
-#include <sys/cdefs.h>
-#include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/types.h>
 #include <unistd.h>
+
+#include <drm_fourcc.h>
 #include <xf86drm.h>
 #include <xf86drmMode.h>
 
-#include "minui.h"
-#include "graphics.h"
+#include "minui/minui.h"
 
 #define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
 
-struct drm_surface {
-    GRSurface base;
-    uint32_t fb_id;
-    uint32_t handle;
-};
+MinuiBackendDrm::MinuiBackendDrm()
+    : GRSurfaceDrms(), main_monitor_crtc(nullptr), main_monitor_connector(nullptr), drm_fd(-1) {}
 
-static drm_surface *drm_surfaces[2];
-static int current_buffer;
-
-static drmModeCrtc *main_monitor_crtc;
-static drmModeConnector *main_monitor_connector;
-
-static int drm_fd = -1;
-
-static void drm_disable_crtc(int drm_fd, drmModeCrtc *crtc) {
-    if (crtc) {
-        drmModeSetCrtc(drm_fd, crtc->crtc_id,
-                       0, // fb_id
-                       0, 0,  // x,y
-                       NULL,  // connectors
-                       0,     // connector_count
-                       NULL); // mode
-    }
+void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) {
+  if (crtc) {
+    drmModeSetCrtc(drm_fd, crtc->crtc_id,
+                   0,         // fb_id
+                   0, 0,      // x,y
+                   nullptr,   // connectors
+                   0,         // connector_count
+                   nullptr);  // mode
+  }
 }
 
-static void drm_enable_crtc(int drm_fd, drmModeCrtc *crtc,
-                            struct drm_surface *surface) {
-    int32_t ret;
+void MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface) {
+  int32_t ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0,  // x,y
+                               &main_monitor_connector->connector_id,
+                               1,  // connector_count
+                               &main_monitor_crtc->mode);
 
-    ret = drmModeSetCrtc(drm_fd, crtc->crtc_id,
-                         surface->fb_id,
-                         0, 0,  // x,y
-                         &main_monitor_connector->connector_id,
-                         1,  // connector_count
-                         &main_monitor_crtc->mode);
-
-    if (ret)
-        printf("drmModeSetCrtc failed ret=%d\n", ret);
+  if (ret) {
+    printf("drmModeSetCrtc failed ret=%d\n", ret);
+  }
 }
 
-static void drm_blank(minui_backend* backend __unused, bool blank) {
-    if (blank)
-        drm_disable_crtc(drm_fd, main_monitor_crtc);
-    else
-        drm_enable_crtc(drm_fd, main_monitor_crtc,
-                        drm_surfaces[current_buffer]);
+void MinuiBackendDrm::Blank(bool blank) {
+  if (blank) {
+    DrmDisableCrtc(drm_fd, main_monitor_crtc);
+  } else {
+    DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
+  }
 }
 
-static void drm_destroy_surface(struct drm_surface *surface) {
-    struct drm_gem_close gem_close;
-    int ret;
+void MinuiBackendDrm::DrmDestroySurface(GRSurfaceDrm* surface) {
+  if (!surface) return;
 
-    if(!surface)
-        return;
+  if (surface->data) {
+    munmap(surface->data, surface->row_bytes * surface->height);
+  }
 
-    if (surface->base.data)
-        munmap(surface->base.data,
-               surface->base.row_bytes * surface->base.height);
-
-    if (surface->fb_id) {
-        ret = drmModeRmFB(drm_fd, surface->fb_id);
-        if (ret)
-            printf("drmModeRmFB failed ret=%d\n", ret);
+  if (surface->fb_id) {
+    int ret = drmModeRmFB(drm_fd, surface->fb_id);
+    if (ret) {
+      printf("drmModeRmFB failed ret=%d\n", ret);
     }
+  }
 
-    if (surface->handle) {
-        memset(&gem_close, 0, sizeof(gem_close));
-        gem_close.handle = surface->handle;
+  if (surface->handle) {
+    drm_gem_close gem_close = {};
+    gem_close.handle = surface->handle;
 
-        ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
-        if (ret)
-            printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
+    int ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
+    if (ret) {
+      printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
     }
+  }
 
-    free(surface);
+  delete surface;
 }
 
 static int drm_format_to_bpp(uint32_t format) {
-    switch(format) {
-        case DRM_FORMAT_ABGR8888:
-        case DRM_FORMAT_BGRA8888:
-        case DRM_FORMAT_RGBX8888:
-        case DRM_FORMAT_BGRX8888:
-        case DRM_FORMAT_XBGR8888:
-        case DRM_FORMAT_XRGB8888:
-            return 32;
-        case DRM_FORMAT_RGB565:
-            return 16;
-        default:
-            printf("Unknown format %d\n", format);
-            return 32;
-    }
+  switch (format) {
+    case DRM_FORMAT_ABGR8888:
+    case DRM_FORMAT_BGRA8888:
+    case DRM_FORMAT_RGBX8888:
+    case DRM_FORMAT_BGRX8888:
+    case DRM_FORMAT_XBGR8888:
+    case DRM_FORMAT_XRGB8888:
+      return 32;
+    case DRM_FORMAT_RGB565:
+      return 16;
+    default:
+      printf("Unknown format %d\n", format);
+      return 32;
+  }
 }
 
-static drm_surface *drm_create_surface(int width, int height) {
-    struct drm_surface *surface;
-    struct drm_mode_create_dumb create_dumb;
-    uint32_t format;
-    int ret;
+GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) {
+  GRSurfaceDrm* surface = new GRSurfaceDrm;
+  *surface = {};
 
-    surface = (struct drm_surface*)calloc(1, sizeof(*surface));
-    if (!surface) {
-        printf("Can't allocate memory\n");
-        return NULL;
-    }
-
+  uint32_t format;
 #if defined(RECOVERY_ABGR)
-    format = DRM_FORMAT_RGBA8888;
+  format = DRM_FORMAT_RGBA8888;
 #elif defined(RECOVERY_BGRA)
-    format = DRM_FORMAT_ARGB8888;
+  format = DRM_FORMAT_ARGB8888;
 #elif defined(RECOVERY_RGBX)
-    format = DRM_FORMAT_XBGR8888;
+  format = DRM_FORMAT_XBGR8888;
 #else
-    format = DRM_FORMAT_RGB565;
+  format = DRM_FORMAT_RGB565;
 #endif
 
-    memset(&create_dumb, 0, sizeof(create_dumb));
-    create_dumb.height = height;
-    create_dumb.width = width;
-    create_dumb.bpp = drm_format_to_bpp(format);
-    create_dumb.flags = 0;
+  drm_mode_create_dumb create_dumb = {};
+  create_dumb.height = height;
+  create_dumb.width = width;
+  create_dumb.bpp = drm_format_to_bpp(format);
+  create_dumb.flags = 0;
 
-    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
-    if (ret) {
-        printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n",ret);
-        drm_destroy_surface(surface);
-        return NULL;
-    }
-    surface->handle = create_dumb.handle;
+  int ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
+  if (ret) {
+    printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n", ret);
+    DrmDestroySurface(surface);
+    return nullptr;
+  }
+  surface->handle = create_dumb.handle;
 
-    uint32_t handles[4], pitches[4], offsets[4];
+  uint32_t handles[4], pitches[4], offsets[4];
 
-    handles[0] = surface->handle;
-    pitches[0] = create_dumb.pitch;
-    offsets[0] = 0;
+  handles[0] = surface->handle;
+  pitches[0] = create_dumb.pitch;
+  offsets[0] = 0;
 
-    ret = drmModeAddFB2(drm_fd, width, height,
-            format, handles, pitches, offsets,
-            &(surface->fb_id), 0);
-    if (ret) {
-        printf("drmModeAddFB2 failed ret=%d\n", ret);
-        drm_destroy_surface(surface);
-        return NULL;
-    }
+  ret =
+      drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &(surface->fb_id), 0);
+  if (ret) {
+    printf("drmModeAddFB2 failed ret=%d\n", ret);
+    DrmDestroySurface(surface);
+    return nullptr;
+  }
 
-    struct drm_mode_map_dumb map_dumb;
-    memset(&map_dumb, 0, sizeof(map_dumb));
-    map_dumb.handle = create_dumb.handle;
-    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
-    if (ret) {
-        printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n",ret);
-        drm_destroy_surface(surface);
-        return NULL;;
-    }
+  drm_mode_map_dumb map_dumb = {};
+  map_dumb.handle = create_dumb.handle;
+  ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
+  if (ret) {
+    printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n", ret);
+    DrmDestroySurface(surface);
+    return nullptr;
+  }
 
-    surface->base.height = height;
-    surface->base.width = width;
-    surface->base.row_bytes = create_dumb.pitch;
-    surface->base.pixel_bytes = create_dumb.bpp / 8;
-    surface->base.data = (unsigned char*)
-                         mmap(NULL,
-                              surface->base.height * surface->base.row_bytes,
-                              PROT_READ | PROT_WRITE, MAP_SHARED,
-                              drm_fd, map_dumb.offset);
-    if (surface->base.data == MAP_FAILED) {
-        perror("mmap() failed");
-        drm_destroy_surface(surface);
-        return NULL;
-    }
+  surface->height = height;
+  surface->width = width;
+  surface->row_bytes = create_dumb.pitch;
+  surface->pixel_bytes = create_dumb.bpp / 8;
+  surface->data = static_cast<unsigned char*>(mmap(nullptr, surface->height * surface->row_bytes,
+                                                   PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd,
+                                                   map_dumb.offset));
+  if (surface->data == MAP_FAILED) {
+    perror("mmap() failed");
+    DrmDestroySurface(surface);
+    return nullptr;
+  }
 
-    return surface;
+  return surface;
 }
 
-static drmModeCrtc *find_crtc_for_connector(int fd,
-                            drmModeRes *resources,
-                            drmModeConnector *connector) {
-    int i, j;
-    drmModeEncoder *encoder;
-    int32_t crtc;
+static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources,
+                                            drmModeConnector* connector) {
+  // Find the encoder. If we already have one, just use it.
+  drmModeEncoder* encoder;
+  if (connector->encoder_id) {
+    encoder = drmModeGetEncoder(fd, connector->encoder_id);
+  } else {
+    encoder = nullptr;
+  }
 
-    /*
-     * Find the encoder. If we already have one, just use it.
-     */
-    if (connector->encoder_id)
-        encoder = drmModeGetEncoder(fd, connector->encoder_id);
-    else
-        encoder = NULL;
+  int32_t crtc;
+  if (encoder && encoder->crtc_id) {
+    crtc = encoder->crtc_id;
+    drmModeFreeEncoder(encoder);
+    return drmModeGetCrtc(fd, crtc);
+  }
 
-    if (encoder && encoder->crtc_id) {
-        crtc = encoder->crtc_id;
+  // Didn't find anything, try to find a crtc and encoder combo.
+  crtc = -1;
+  for (int i = 0; i < connector->count_encoders; i++) {
+    encoder = drmModeGetEncoder(fd, connector->encoders[i]);
+
+    if (encoder) {
+      for (int j = 0; j < resources->count_crtcs; j++) {
+        if (!(encoder->possible_crtcs & (1 << j))) continue;
+        crtc = resources->crtcs[j];
+        break;
+      }
+      if (crtc >= 0) {
         drmModeFreeEncoder(encoder);
         return drmModeGetCrtc(fd, crtc);
+      }
     }
+  }
 
-    /*
-     * Didn't find anything, try to find a crtc and encoder combo.
-     */
-    crtc = -1;
-    for (i = 0; i < connector->count_encoders; i++) {
-        encoder = drmModeGetEncoder(fd, connector->encoders[i]);
-
-        if (encoder) {
-            for (j = 0; j < resources->count_crtcs; j++) {
-                if (!(encoder->possible_crtcs & (1 << j)))
-                    continue;
-                crtc = resources->crtcs[j];
-                break;
-            }
-            if (crtc >= 0) {
-                drmModeFreeEncoder(encoder);
-                return drmModeGetCrtc(fd, crtc);
-            }
-        }
-    }
-
-    return NULL;
+  return nullptr;
 }
 
-static drmModeConnector *find_used_connector_by_type(int fd,
-                                 drmModeRes *resources,
-                                 unsigned type) {
-    int i;
-    for (i = 0; i < resources->count_connectors; i++) {
-        drmModeConnector *connector;
-
-        connector = drmModeGetConnector(fd, resources->connectors[i]);
-        if (connector) {
-            if ((connector->connector_type == type) &&
-                    (connector->connection == DRM_MODE_CONNECTED) &&
-                    (connector->count_modes > 0))
-                return connector;
-
-            drmModeFreeConnector(connector);
-        }
+static drmModeConnector* find_used_connector_by_type(int fd, drmModeRes* resources, unsigned type) {
+  for (int i = 0; i < resources->count_connectors; i++) {
+    drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]);
+    if (connector) {
+      if ((connector->connector_type == type) && (connector->connection == DRM_MODE_CONNECTED) &&
+          (connector->count_modes > 0)) {
+        return connector;
+      }
+      drmModeFreeConnector(connector);
     }
-    return NULL;
+  }
+  return nullptr;
 }
 
-static drmModeConnector *find_first_connected_connector(int fd,
-                             drmModeRes *resources) {
-    int i;
-    for (i = 0; i < resources->count_connectors; i++) {
-        drmModeConnector *connector;
+static drmModeConnector* find_first_connected_connector(int fd, drmModeRes* resources) {
+  for (int i = 0; i < resources->count_connectors; i++) {
+    drmModeConnector* connector;
 
-        connector = drmModeGetConnector(fd, resources->connectors[i]);
-        if (connector) {
-            if ((connector->count_modes > 0) &&
-                    (connector->connection == DRM_MODE_CONNECTED))
-                return connector;
+    connector = drmModeGetConnector(fd, resources->connectors[i]);
+    if (connector) {
+      if ((connector->count_modes > 0) && (connector->connection == DRM_MODE_CONNECTED))
+        return connector;
 
-            drmModeFreeConnector(connector);
-        }
+      drmModeFreeConnector(connector);
     }
-    return NULL;
+  }
+  return nullptr;
 }
 
-static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources,
-        uint32_t *mode_index) {
-    unsigned i = 0;
-    int modes;
-    /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
-    unsigned kConnectorPriority[] = {
-        DRM_MODE_CONNECTOR_LVDS,
-        DRM_MODE_CONNECTOR_eDP,
-        DRM_MODE_CONNECTOR_DSI,
-    };
+drmModeConnector* MinuiBackendDrm::FindMainMonitor(int fd, drmModeRes* resources,
+                                                   uint32_t* mode_index) {
+  /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
+  static constexpr unsigned kConnectorPriority[] = {
+    DRM_MODE_CONNECTOR_LVDS,
+    DRM_MODE_CONNECTOR_eDP,
+    DRM_MODE_CONNECTOR_DSI,
+  };
 
-    drmModeConnector *main_monitor_connector = NULL;
-    do {
-        main_monitor_connector = find_used_connector_by_type(fd,
-                                         resources,
-                                         kConnectorPriority[i]);
-        i++;
-    } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
+  drmModeConnector* main_monitor_connector = nullptr;
+  unsigned i = 0;
+  do {
+    main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]);
+    i++;
+  } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
 
-    /* If we didn't find a connector, grab the first one that is connected. */
-    if (!main_monitor_connector)
-        main_monitor_connector =
-                find_first_connected_connector(fd, resources);
+  /* If we didn't find a connector, grab the first one that is connected. */
+  if (!main_monitor_connector) {
+    main_monitor_connector = find_first_connected_connector(fd, resources);
+  }
 
-    /* If we still didn't find a connector, give up and return. */
-    if (!main_monitor_connector)
-        return NULL;
+  /* If we still didn't find a connector, give up and return. */
+  if (!main_monitor_connector) return nullptr;
 
-    *mode_index = 0;
-    for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
-        if (main_monitor_connector->modes[modes].type &
-                DRM_MODE_TYPE_PREFERRED) {
-            *mode_index = modes;
-            break;
-        }
+  *mode_index = 0;
+  for (int modes = 0; modes < main_monitor_connector->count_modes; modes++) {
+    if (main_monitor_connector->modes[modes].type & DRM_MODE_TYPE_PREFERRED) {
+      *mode_index = modes;
+      break;
     }
+  }
 
-    return main_monitor_connector;
+  return main_monitor_connector;
 }
 
-static void disable_non_main_crtcs(int fd,
-                    drmModeRes *resources,
-                    drmModeCrtc* main_crtc) {
-    int i;
-    drmModeCrtc* crtc;
-
-    for (i = 0; i < resources->count_connectors; i++) {
-        drmModeConnector *connector;
-
-        connector = drmModeGetConnector(fd, resources->connectors[i]);
-        crtc = find_crtc_for_connector(fd, resources, connector);
-        if (crtc->crtc_id != main_crtc->crtc_id)
-            drm_disable_crtc(fd, crtc);
-        drmModeFreeCrtc(crtc);
+void MinuiBackendDrm::DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc) {
+  for (int i = 0; i < resources->count_connectors; i++) {
+    drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]);
+    drmModeCrtc* crtc = find_crtc_for_connector(fd, resources, connector);
+    if (crtc->crtc_id != main_crtc->crtc_id) {
+      DrmDisableCrtc(fd, crtc);
     }
+    drmModeFreeCrtc(crtc);
+  }
 }
 
-static GRSurface* drm_init(minui_backend* backend __unused) {
-    drmModeRes *res = NULL;
-    uint32_t selected_mode;
-    char *dev_name;
-    int width, height;
-    int ret, i;
+GRSurface* MinuiBackendDrm::Init() {
+  drmModeRes* res = nullptr;
 
-    /* Consider DRM devices in order. */
-    for (i = 0; i < DRM_MAX_MINOR; i++) {
-        uint64_t cap = 0;
+  /* Consider DRM devices in order. */
+  for (int i = 0; i < DRM_MAX_MINOR; i++) {
+    char* dev_name;
+    int ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
+    if (ret < 0) continue;
 
-        ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
-        if (ret < 0)
-            continue;
+    drm_fd = open(dev_name, O_RDWR, 0);
+    free(dev_name);
+    if (drm_fd < 0) continue;
 
-        drm_fd = open(dev_name, O_RDWR, 0);
-        free(dev_name);
-        if (drm_fd < 0)
-            continue;
-
-        /* We need dumb buffers. */
-        ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
-        if (ret || cap == 0) {
-            close(drm_fd);
-            continue;
-        }
-
-        res = drmModeGetResources(drm_fd);
-        if (!res) {
-            close(drm_fd);
-            continue;
-        }
-
-        /* Use this device if it has at least one connected monitor. */
-        if (res->count_crtcs > 0 && res->count_connectors > 0)
-            if (find_first_connected_connector(drm_fd, res))
-                break;
-
-        drmModeFreeResources(res);
-        close(drm_fd);
-        res = NULL;
+    uint64_t cap = 0;
+    /* We need dumb buffers. */
+    ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
+    if (ret || cap == 0) {
+      close(drm_fd);
+      continue;
     }
 
-    if (drm_fd < 0 || res == NULL) {
-        perror("cannot find/open a drm device");
-        return NULL;
+    res = drmModeGetResources(drm_fd);
+    if (!res) {
+      close(drm_fd);
+      continue;
     }
 
-    main_monitor_connector = find_main_monitor(drm_fd,
-            res, &selected_mode);
-
-    if (!main_monitor_connector) {
-        printf("main_monitor_connector not found\n");
-        drmModeFreeResources(res);
-        close(drm_fd);
-        return NULL;
+    /* Use this device if it has at least one connected monitor. */
+    if (res->count_crtcs > 0 && res->count_connectors > 0) {
+      if (find_first_connected_connector(drm_fd, res)) break;
     }
 
-    main_monitor_crtc = find_crtc_for_connector(drm_fd, res,
-                                                main_monitor_connector);
-
-    if (!main_monitor_crtc) {
-        printf("main_monitor_crtc not found\n");
-        drmModeFreeResources(res);
-        close(drm_fd);
-        return NULL;
-    }
-
-    disable_non_main_crtcs(drm_fd,
-                           res, main_monitor_crtc);
-
-    main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
-
-    width = main_monitor_crtc->mode.hdisplay;
-    height = main_monitor_crtc->mode.vdisplay;
-
     drmModeFreeResources(res);
-
-    drm_surfaces[0] = drm_create_surface(width, height);
-    drm_surfaces[1] = drm_create_surface(width, height);
-    if (!drm_surfaces[0] || !drm_surfaces[1]) {
-        drm_destroy_surface(drm_surfaces[0]);
-        drm_destroy_surface(drm_surfaces[1]);
-        drmModeFreeResources(res);
-        close(drm_fd);
-        return NULL;
-    }
-
-    current_buffer = 0;
-
-    drm_enable_crtc(drm_fd, main_monitor_crtc, drm_surfaces[1]);
-
-    return &(drm_surfaces[0]->base);
-}
-
-static GRSurface* drm_flip(minui_backend* backend __unused) {
-    int ret;
-
-    ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
-                          drm_surfaces[current_buffer]->fb_id, 0, NULL);
-    if (ret < 0) {
-        printf("drmModePageFlip failed ret=%d\n", ret);
-        return NULL;
-    }
-    current_buffer = 1 - current_buffer;
-    return &(drm_surfaces[current_buffer]->base);
-}
-
-static void drm_exit(minui_backend* backend __unused) {
-    drm_disable_crtc(drm_fd, main_monitor_crtc);
-    drm_destroy_surface(drm_surfaces[0]);
-    drm_destroy_surface(drm_surfaces[1]);
-    drmModeFreeCrtc(main_monitor_crtc);
-    drmModeFreeConnector(main_monitor_connector);
     close(drm_fd);
-    drm_fd = -1;
+    res = nullptr;
+  }
+
+  if (drm_fd < 0 || res == nullptr) {
+    perror("cannot find/open a drm device");
+    return nullptr;
+  }
+
+  uint32_t selected_mode;
+  main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode);
+
+  if (!main_monitor_connector) {
+    printf("main_monitor_connector not found\n");
+    drmModeFreeResources(res);
+    close(drm_fd);
+    return nullptr;
+  }
+
+  main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector);
+
+  if (!main_monitor_crtc) {
+    printf("main_monitor_crtc not found\n");
+    drmModeFreeResources(res);
+    close(drm_fd);
+    return nullptr;
+  }
+
+  DisableNonMainCrtcs(drm_fd, res, main_monitor_crtc);
+
+  main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
+
+  int width = main_monitor_crtc->mode.hdisplay;
+  int height = main_monitor_crtc->mode.vdisplay;
+
+  drmModeFreeResources(res);
+
+  GRSurfaceDrms[0] = DrmCreateSurface(width, height);
+  GRSurfaceDrms[1] = DrmCreateSurface(width, height);
+  if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) {
+    // GRSurfaceDrms and drm_fd should be freed in d'tor.
+    return nullptr;
+  }
+
+  current_buffer = 0;
+
+  DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1]);
+
+  return GRSurfaceDrms[0];
 }
 
-static minui_backend drm_backend = {
-    .init = drm_init,
-    .flip = drm_flip,
-    .blank = drm_blank,
-    .exit = drm_exit,
-};
+GRSurface* MinuiBackendDrm::Flip() {
+  int ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
+                            GRSurfaceDrms[current_buffer]->fb_id, 0, nullptr);
+  if (ret < 0) {
+    printf("drmModePageFlip failed ret=%d\n", ret);
+    return nullptr;
+  }
+  current_buffer = 1 - current_buffer;
+  return GRSurfaceDrms[current_buffer];
+}
 
-minui_backend* open_drm() {
-    return &drm_backend;
+MinuiBackendDrm::~MinuiBackendDrm() {
+  DrmDisableCrtc(drm_fd, main_monitor_crtc);
+  DrmDestroySurface(GRSurfaceDrms[0]);
+  DrmDestroySurface(GRSurfaceDrms[1]);
+  drmModeFreeCrtc(main_monitor_crtc);
+  drmModeFreeConnector(main_monitor_connector);
+  close(drm_fd);
+  drm_fd = -1;
 }
diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h
new file mode 100644
index 0000000..de96212
--- /dev/null
+++ b/minui/graphics_drm.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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 agreed 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 _GRAPHICS_DRM_H_
+#define _GRAPHICS_DRM_H_
+
+#include <stdint.h>
+
+#include <xf86drmMode.h>
+
+#include "graphics.h"
+#include "minui/minui.h"
+
+class GRSurfaceDrm : public GRSurface {
+ private:
+  uint32_t fb_id;
+  uint32_t handle;
+
+  friend class MinuiBackendDrm;
+};
+
+class MinuiBackendDrm : public MinuiBackend {
+ public:
+  GRSurface* Init() override;
+  GRSurface* Flip() override;
+  void Blank(bool) override;
+  ~MinuiBackendDrm() override;
+  MinuiBackendDrm();
+
+ private:
+  void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc);
+  void DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface);
+  GRSurfaceDrm* DrmCreateSurface(int width, int height);
+  void DrmDestroySurface(GRSurfaceDrm* surface);
+  void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc);
+  drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index);
+
+  GRSurfaceDrm* GRSurfaceDrms[2];
+  int current_buffer;
+  drmModeCrtc* main_monitor_crtc;
+  drmModeConnector* main_monitor_connector;
+  int drm_fd;
+};
+
+#endif  // _GRAPHICS_DRM_H_
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index 0788f75..746f42a 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -14,188 +14,154 @@
  * limitations under the License.
  */
 
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
+#include "graphics_fbdev.h"
 
 #include <fcntl.h>
+#include <linux/fb.h>
 #include <stdio.h>
-
-#include <sys/cdefs.h>
+#include <stdlib.h>
+#include <string.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/types.h>
+#include <unistd.h>
 
-#include <linux/fb.h>
-#include <linux/kd.h>
+#include "minui/minui.h"
 
-#include "minui.h"
-#include "graphics.h"
+MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {}
 
-static GRSurface* fbdev_init(minui_backend*);
-static GRSurface* fbdev_flip(minui_backend*);
-static void fbdev_blank(minui_backend*, bool);
-static void fbdev_exit(minui_backend*);
-
-static GRSurface gr_framebuffer[2];
-static bool double_buffered;
-static GRSurface* gr_draw = NULL;
-static int displayed_buffer;
-
-static fb_var_screeninfo vi;
-static int fb_fd = -1;
-
-static minui_backend my_backend = {
-    .init = fbdev_init,
-    .flip = fbdev_flip,
-    .blank = fbdev_blank,
-    .exit = fbdev_exit,
-};
-
-minui_backend* open_fbdev() {
-    return &my_backend;
+void MinuiBackendFbdev::Blank(bool blank) {
+  int ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
+  if (ret < 0) perror("ioctl(): blank");
 }
 
-static void fbdev_blank(minui_backend* backend __unused, bool blank)
-{
-    int ret;
+void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) {
+  if (n > 1 || !double_buffered) return;
 
-    ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
-    if (ret < 0)
-        perror("ioctl(): blank");
+  vi.yres_virtual = gr_framebuffer[0].height * 2;
+  vi.yoffset = n * gr_framebuffer[0].height;
+  vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+  if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
+    perror("active fb swap failed");
+  }
+  displayed_buffer = n;
 }
 
-static void set_displayed_framebuffer(unsigned n)
-{
-    if (n > 1 || !double_buffered) return;
+GRSurface* MinuiBackendFbdev::Init() {
+  int fd = open("/dev/graphics/fb0", O_RDWR);
+  if (fd == -1) {
+    perror("cannot open fb0");
+    return nullptr;
+  }
 
-    vi.yres_virtual = gr_framebuffer[0].height * 2;
-    vi.yoffset = n * gr_framebuffer[0].height;
-    vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
-    if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
-        perror("active fb swap failed");
+  fb_fix_screeninfo fi;
+  if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+    perror("failed to get fb0 info");
+    close(fd);
+    return nullptr;
+  }
+
+  if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
+    perror("failed to get fb0 info");
+    close(fd);
+    return nullptr;
+  }
+
+  // We print this out for informational purposes only, but
+  // throughout we assume that the framebuffer device uses an RGBX
+  // pixel format.  This is the case for every development device I
+  // have access to.  For some of those devices (eg, hammerhead aka
+  // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
+  // different format (XBGR) but actually produces the correct
+  // results on the display when you write RGBX.
+  //
+  // If you have a device that actually *needs* another pixel format
+  // (ie, BGRX, or 565), patches welcome...
+
+  printf(
+      "fb0 reports (possibly inaccurate):\n"
+      "  vi.bits_per_pixel = %d\n"
+      "  vi.red.offset   = %3d   .length = %3d\n"
+      "  vi.green.offset = %3d   .length = %3d\n"
+      "  vi.blue.offset  = %3d   .length = %3d\n",
+      vi.bits_per_pixel, vi.red.offset, vi.red.length, vi.green.offset, vi.green.length,
+      vi.blue.offset, vi.blue.length);
+
+  void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+  if (bits == MAP_FAILED) {
+    perror("failed to mmap framebuffer");
+    close(fd);
+    return nullptr;
+  }
+
+  memset(bits, 0, fi.smem_len);
+
+  gr_framebuffer[0].width = vi.xres;
+  gr_framebuffer[0].height = vi.yres;
+  gr_framebuffer[0].row_bytes = fi.line_length;
+  gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
+  gr_framebuffer[0].data = static_cast<uint8_t*>(bits);
+  memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
+
+  /* check if we can use double buffering */
+  if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
+    double_buffered = true;
+
+    memcpy(gr_framebuffer + 1, gr_framebuffer, sizeof(GRSurface));
+    gr_framebuffer[1].data =
+        gr_framebuffer[0].data + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
+
+    gr_draw = gr_framebuffer + 1;
+
+  } else {
+    double_buffered = false;
+
+    // Without double-buffering, we allocate RAM for a buffer to
+    // draw in, and then "flipping" the buffer consists of a
+    // memcpy from the buffer we allocated to the framebuffer.
+
+    gr_draw = static_cast<GRSurface*>(malloc(sizeof(GRSurface)));
+    memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
+    gr_draw->data = static_cast<unsigned char*>(malloc(gr_draw->height * gr_draw->row_bytes));
+    if (!gr_draw->data) {
+      perror("failed to allocate in-memory surface");
+      return nullptr;
     }
-    displayed_buffer = n;
+  }
+
+  memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
+  fb_fd = fd;
+  SetDisplayedFramebuffer(0);
+
+  printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+
+  Blank(true);
+  Blank(false);
+
+  return gr_draw;
 }
 
-static GRSurface* fbdev_init(minui_backend* backend) {
-    int fd = open("/dev/graphics/fb0", O_RDWR);
-    if (fd == -1) {
-        perror("cannot open fb0");
-        return NULL;
-    }
-
-    fb_fix_screeninfo fi;
-    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
-        perror("failed to get fb0 info");
-        close(fd);
-        return NULL;
-    }
-
-    if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
-        perror("failed to get fb0 info");
-        close(fd);
-        return NULL;
-    }
-
-    // We print this out for informational purposes only, but
-    // throughout we assume that the framebuffer device uses an RGBX
-    // pixel format.  This is the case for every development device I
-    // have access to.  For some of those devices (eg, hammerhead aka
-    // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
-    // different format (XBGR) but actually produces the correct
-    // results on the display when you write RGBX.
-    //
-    // If you have a device that actually *needs* another pixel format
-    // (ie, BGRX, or 565), patches welcome...
-
-    printf("fb0 reports (possibly inaccurate):\n"
-           "  vi.bits_per_pixel = %d\n"
-           "  vi.red.offset   = %3d   .length = %3d\n"
-           "  vi.green.offset = %3d   .length = %3d\n"
-           "  vi.blue.offset  = %3d   .length = %3d\n",
-           vi.bits_per_pixel,
-           vi.red.offset, vi.red.length,
-           vi.green.offset, vi.green.length,
-           vi.blue.offset, vi.blue.length);
-
-    void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-    if (bits == MAP_FAILED) {
-        perror("failed to mmap framebuffer");
-        close(fd);
-        return NULL;
-    }
-
-    memset(bits, 0, fi.smem_len);
-
-    gr_framebuffer[0].width = vi.xres;
-    gr_framebuffer[0].height = vi.yres;
-    gr_framebuffer[0].row_bytes = fi.line_length;
-    gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
-    gr_framebuffer[0].data = reinterpret_cast<uint8_t*>(bits);
-    memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
-
-    /* check if we can use double buffering */
-    if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
-        double_buffered = true;
-
-        memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface));
-        gr_framebuffer[1].data = gr_framebuffer[0].data +
-            gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
-
-        gr_draw = gr_framebuffer+1;
-
-    } else {
-        double_buffered = false;
-
-        // Without double-buffering, we allocate RAM for a buffer to
-        // draw in, and then "flipping" the buffer consists of a
-        // memcpy from the buffer we allocated to the framebuffer.
-
-        gr_draw = (GRSurface*) malloc(sizeof(GRSurface));
-        memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
-        gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes);
-        if (!gr_draw->data) {
-            perror("failed to allocate in-memory surface");
-            return NULL;
-        }
-    }
-
-    memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
-    fb_fd = fd;
-    set_displayed_framebuffer(0);
-
-    printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
-
-    fbdev_blank(backend, true);
-    fbdev_blank(backend, false);
-
-    return gr_draw;
+GRSurface* MinuiBackendFbdev::Flip() {
+  if (double_buffered) {
+    // Change gr_draw to point to the buffer currently displayed,
+    // then flip the driver so we're displaying the other buffer
+    // instead.
+    gr_draw = gr_framebuffer + displayed_buffer;
+    SetDisplayedFramebuffer(1 - displayed_buffer);
+  } else {
+    // Copy from the in-memory surface to the framebuffer.
+    memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes);
+  }
+  return gr_draw;
 }
 
-static GRSurface* fbdev_flip(minui_backend* backend __unused) {
-    if (double_buffered) {
-        // Change gr_draw to point to the buffer currently displayed,
-        // then flip the driver so we're displaying the other buffer
-        // instead.
-        gr_draw = gr_framebuffer + displayed_buffer;
-        set_displayed_framebuffer(1-displayed_buffer);
-    } else {
-        // Copy from the in-memory surface to the framebuffer.
-        memcpy(gr_framebuffer[0].data, gr_draw->data,
-               gr_draw->height * gr_draw->row_bytes);
-    }
-    return gr_draw;
-}
+MinuiBackendFbdev::~MinuiBackendFbdev() {
+  close(fb_fd);
+  fb_fd = -1;
 
-static void fbdev_exit(minui_backend* backend __unused) {
-    close(fb_fd);
-    fb_fd = -1;
-
-    if (!double_buffered && gr_draw) {
-        free(gr_draw->data);
-        free(gr_draw);
-    }
-    gr_draw = NULL;
+  if (!double_buffered && gr_draw) {
+    free(gr_draw->data);
+    free(gr_draw);
+  }
+  gr_draw = nullptr;
 }
diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h
new file mode 100644
index 0000000..107e195
--- /dev/null
+++ b/minui/graphics_fbdev.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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 agreed 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 _GRAPHICS_FBDEV_H_
+#define _GRAPHICS_FBDEV_H_
+
+#include <linux/fb.h>
+
+#include "graphics.h"
+#include "minui/minui.h"
+
+class MinuiBackendFbdev : public MinuiBackend {
+ public:
+  GRSurface* Init() override;
+  GRSurface* Flip() override;
+  void Blank(bool) override;
+  ~MinuiBackendFbdev() override;
+  MinuiBackendFbdev();
+
+ private:
+  void SetDisplayedFramebuffer(unsigned n);
+
+  GRSurface gr_framebuffer[2];
+  bool double_buffered;
+  GRSurface* gr_draw;
+  int displayed_buffer;
+  fb_var_screeninfo vi;
+  int fb_fd;
+};
+
+#endif  // _GRAPHICS_FBDEV_H_
diff --git a/minui/minui.h b/minui/include/minui/minui.h
similarity index 86%
rename from minui/minui.h
rename to minui/include/minui/minui.h
index d30426d..78dd4cb 100644
--- a/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 
 #include <functional>
+#include <string>
 
 //
 // Graphics.
@@ -70,15 +71,14 @@
 
 struct input_event;
 
-// TODO: move these over to std::function.
-typedef int (*ev_callback)(int fd, uint32_t epevents, void* data);
-typedef int (*ev_set_key_callback)(int code, int value, void* data);
+using ev_callback = std::function<int(int fd, uint32_t epevents)>;
+using ev_set_key_callback = std::function<int(int code, int value)>;
 
-int ev_init(ev_callback input_cb, void* data);
+int ev_init(ev_callback input_cb);
 void ev_exit();
-int ev_add_fd(int fd, ev_callback cb, void* data);
-void ev_iterate_available_keys(std::function<void(int)> f);
-int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data);
+int ev_add_fd(int fd, ev_callback cb);
+void ev_iterate_available_keys(const std::function<void(int)>& f);
+int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
 
 // 'timeout' has the same semantics as poll(2).
 //    0 : don't block
@@ -94,7 +94,7 @@
 // Resources
 //
 
-bool matches_locale(const char* prefix, const char* locale);
+bool matches_locale(const std::string& prefix, const std::string& locale);
 
 // res_create_*_surface() functions return 0 if no error, else
 // negative.
@@ -123,8 +123,8 @@
 // given locale.  The image is expected to be a composite of multiple
 // translations of the same text, with special added rows that encode
 // the subimages' size and intended locale in the pixel data.  See
-// development/tools/recovery_l10n for an app that will generate these
-// specialized images from Android resources.
+// bootable/recovery/tools/recovery_l10n for an app that will generate
+// these specialized images from Android resources.
 int res_create_localized_alpha_surface(const char* name, const char* locale,
                                        GRSurface** pSurface);
 
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 40d3c2c..86c731b 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -14,29 +14,31 @@
  * limitations under the License.
  */
 
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <linux/kd.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
-
-#include <fcntl.h>
-#include <stdio.h>
-
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/types.h>
+#include <unistd.h>
 
-#include <linux/fb.h>
-#include <linux/kd.h>
+#include <regex>
+#include <string>
+#include <vector>
 
+#include <android-base/strings.h>
 #include <png.h>
 
-#include "minui.h"
+#include "minui/minui.h"
 
 #define SURFACE_DATA_ALIGNMENT 8
 
 static GRSurface* malloc_surface(size_t data_size) {
     size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT;
-    unsigned char* temp = reinterpret_cast<unsigned char*>(malloc(size));
+    unsigned char* temp = static_cast<unsigned char*>(malloc(size));
     if (temp == NULL) return NULL;
     GRSurface* surface = reinterpret_cast<GRSurface*>(temp);
     surface->data = temp + sizeof(GRSurface) +
@@ -220,7 +222,7 @@
     png_set_bgr(png_ptr);
 #endif
 
-    p_row = reinterpret_cast<unsigned char*>(malloc(width * 4));
+    p_row = static_cast<unsigned char*>(malloc(width * 4));
     for (y = 0; y < height; ++y) {
         png_read_row(png_ptr, p_row, NULL);
         transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
@@ -268,7 +270,7 @@
         printf("  found fps = %d\n", *fps);
     }
 
-    if (frames <= 0 || fps <= 0) {
+    if (*frames <= 0 || *fps <= 0) {
         printf("bad number of frames (%d) and/or FPS (%d)\n", *frames, *fps);
         result = -10;
         goto exit;
@@ -280,7 +282,7 @@
         goto exit;
     }
 
-    surface = reinterpret_cast<GRSurface**>(malloc(*frames * sizeof(GRSurface*)));
+    surface = static_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
     if (surface == NULL) {
         result = -8;
         goto exit;
@@ -297,7 +299,7 @@
     png_set_bgr(png_ptr);
 #endif
 
-    p_row = reinterpret_cast<unsigned char*>(malloc(width * 4));
+    p_row = static_cast<unsigned char*>(malloc(width * 4));
     for (y = 0; y < height; ++y) {
         png_read_row(png_ptr, p_row, NULL);
         int frame = y % *frames;
@@ -307,7 +309,7 @@
     }
     free(p_row);
 
-    *pSurface = reinterpret_cast<GRSurface**>(surface);
+    *pSurface = surface;
 
 exit:
     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
@@ -315,7 +317,7 @@
     if (result < 0) {
         if (surface) {
             for (int i = 0; i < *frames; ++i) {
-                if (surface[i]) free(surface[i]);
+                free(surface[i]);
             }
             free(surface);
         }
@@ -372,14 +374,26 @@
 
 // This function tests if a locale string stored in PNG (prefix) matches
 // the locale string provided by the system (locale).
-bool matches_locale(const char* prefix, const char* locale) {
-    if (locale == NULL) return false;
+bool matches_locale(const std::string& prefix, const std::string& locale) {
+  // According to the BCP 47 format, A locale string may consists of:
+  // language-{extlang}-{script}-{region}-{variant}
+  // The locale headers in PNG mostly consist of language-{region} except for sr-Latn, and some
+  // android's system locale can have the format language-{script}-{region}.
 
-    // Return true if the whole string of prefix matches the top part of
-    // locale. For instance, prefix == "en" matches locale == "en_US";
-    // and prefix == "zh_CN" matches locale == "zh_CN_#Hans".
+  // Return true if the whole string of prefix matches the top part of locale. Otherwise try to
+  // match the locale string without the {script} section.
+  // For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale
+  // == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN".
+  if (android::base::StartsWith(locale, prefix.c_str())) {
+    return true;
+  }
 
-    return (strncmp(prefix, locale, strlen(prefix)) == 0);
+  size_t separator = prefix.find('-');
+  if (separator == std::string::npos) {
+    return false;
+  }
+  std::regex loc_regex(prefix.substr(0, separator) + "-[A-Za-z]*" + prefix.substr(separator));
+  return std::regex_match(locale, loc_regex);
 }
 
 int res_create_localized_alpha_surface(const char* name,
@@ -391,18 +405,13 @@
     png_infop info_ptr = NULL;
     png_uint_32 width, height;
     png_byte channels;
-    unsigned char* row;
     png_uint_32 y;
+    std::vector<unsigned char> row;
 
     *pSurface = NULL;
 
     if (locale == NULL) {
-        surface = malloc_surface(0);
-        surface->width = 0;
-        surface->height = 0;
-        surface->row_bytes = 0;
-        surface->pixel_bytes = 1;
-        goto exit;
+        return result;
     }
 
     result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
@@ -413,13 +422,13 @@
         goto exit;
     }
 
-    row = reinterpret_cast<unsigned char*>(malloc(width));
+    row.resize(width);
     for (y = 0; y < height; ++y) {
-        png_read_row(png_ptr, row, NULL);
+        png_read_row(png_ptr, row.data(), NULL);
         int w = (row[1] << 8) | row[0];
         int h = (row[3] << 8) | row[2];
-        int len = row[4];
-        char* loc = (char*)row+5;
+        __unused int len = row[4];
+        char* loc = reinterpret_cast<char*>(&row[5]);
 
         if (y+1+h >= height || matches_locale(loc, locale)) {
             printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
@@ -436,16 +445,16 @@
 
             int i;
             for (i = 0; i < h; ++i, ++y) {
-                png_read_row(png_ptr, row, NULL);
-                memcpy(surface->data + i*w, row, w);
+                png_read_row(png_ptr, row.data(), NULL);
+                memcpy(surface->data + i*w, row.data(), w);
             }
 
-            *pSurface = reinterpret_cast<GRSurface*>(surface);
+            *pSurface = surface;
             break;
         } else {
             int i;
             for (i = 0; i < h; ++i, ++y) {
-                png_read_row(png_ptr, row, NULL);
+                png_read_row(png_ptr, row.data(), NULL);
             }
         }
     }
diff --git a/minzip/Android.mk b/minzip/Android.mk
deleted file mode 100644
index 22eabfb..0000000
--- a/minzip/Android.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-	Hash.c \
-	SysUtil.c \
-	DirUtil.c \
-	Inlines.c \
-	Zip.c
-
-LOCAL_C_INCLUDES := \
-	external/zlib \
-	external/safe-iop/include
-
-LOCAL_STATIC_LIBRARIES := libselinux
-
-LOCAL_MODULE := libminzip
-
-LOCAL_CLANG := true
-
-LOCAL_CFLAGS += -Werror -Wall
-
-include $(BUILD_STATIC_LIBRARY)
diff --git a/minzip/Bits.h b/minzip/Bits.h
deleted file mode 100644
index f96e6c4..0000000
--- a/minzip/Bits.h
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * Some handy functions for manipulating bits and bytes.
- */
-#ifndef _MINZIP_BITS
-#define _MINZIP_BITS
-
-#include "inline_magic.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-/*
- * Get 1 byte.  (Included to make the code more legible.)
- */
-INLINE unsigned char get1(unsigned const char* pSrc)
-{
-    return *pSrc;
-}
-
-/*
- * Get 2 big-endian bytes.
- */
-INLINE unsigned short get2BE(unsigned char const* pSrc)
-{
-    unsigned short result;
-
-    result = *pSrc++ << 8;
-    result |= *pSrc++;
-
-    return result;
-}
-
-/*
- * Get 4 big-endian bytes.
- */
-INLINE unsigned int get4BE(unsigned char const* pSrc)
-{
-    unsigned int result;
-
-    result = *pSrc++ << 24;
-    result |= *pSrc++ << 16;
-    result |= *pSrc++ << 8;
-    result |= *pSrc++;
-
-    return result;
-}
-
-/*
- * Get 8 big-endian bytes.
- */
-INLINE unsigned long long get8BE(unsigned char const* pSrc)
-{
-    unsigned long long result;
-
-    result = (unsigned long long) *pSrc++ << 56;
-    result |= (unsigned long long) *pSrc++ << 48;
-    result |= (unsigned long long) *pSrc++ << 40;
-    result |= (unsigned long long) *pSrc++ << 32;
-    result |= (unsigned long long) *pSrc++ << 24;
-    result |= (unsigned long long) *pSrc++ << 16;
-    result |= (unsigned long long) *pSrc++ << 8;
-    result |= (unsigned long long) *pSrc++;
-
-    return result;
-}
-
-/*
- * Get 2 little-endian bytes.
- */
-INLINE unsigned short get2LE(unsigned char const* pSrc)
-{
-    unsigned short result;
-
-    result = *pSrc++;
-    result |= *pSrc++ << 8;
-
-    return result;
-}
-
-/*
- * Get 4 little-endian bytes.
- */
-INLINE unsigned int get4LE(unsigned char const* pSrc)
-{
-    unsigned int result;
-
-    result = *pSrc++;
-    result |= *pSrc++ << 8;
-    result |= *pSrc++ << 16;
-    result |= *pSrc++ << 24;
-
-    return result;
-}
-
-/*
- * Get 8 little-endian bytes.
- */
-INLINE unsigned long long get8LE(unsigned char const* pSrc)
-{
-    unsigned long long result;
-
-    result = (unsigned long long) *pSrc++;
-    result |= (unsigned long long) *pSrc++ << 8;
-    result |= (unsigned long long) *pSrc++ << 16;
-    result |= (unsigned long long) *pSrc++ << 24;
-    result |= (unsigned long long) *pSrc++ << 32;
-    result |= (unsigned long long) *pSrc++ << 40;
-    result |= (unsigned long long) *pSrc++ << 48;
-    result |= (unsigned long long) *pSrc++ << 56;
-
-    return result;
-}
-
-/*
- * Grab 1 byte and advance the data pointer.
- */
-INLINE unsigned char read1(unsigned const char** ppSrc)
-{
-    return *(*ppSrc)++;
-}
-
-/*
- * Grab 2 big-endian bytes and advance the data pointer.
- */
-INLINE unsigned short read2BE(unsigned char const** ppSrc)
-{
-    unsigned short result;
-
-    result = *(*ppSrc)++ << 8;
-    result |= *(*ppSrc)++;
-
-    return result;
-}
-
-/*
- * Grab 4 big-endian bytes and advance the data pointer.
- */
-INLINE unsigned int read4BE(unsigned char const** ppSrc)
-{
-    unsigned int result;
-
-    result = *(*ppSrc)++ << 24;
-    result |= *(*ppSrc)++ << 16;
-    result |= *(*ppSrc)++ << 8;
-    result |= *(*ppSrc)++;
-
-    return result;
-}
-
-/*
- * Get 8 big-endian bytes.
- */
-INLINE unsigned long long read8BE(unsigned char const** ppSrc)
-{
-    unsigned long long result;
-
-    result = (unsigned long long) *(*ppSrc)++ << 56;
-    result |= (unsigned long long) *(*ppSrc)++ << 48;
-    result |= (unsigned long long) *(*ppSrc)++ << 40;
-    result |= (unsigned long long) *(*ppSrc)++ << 32;
-    result |= (unsigned long long) *(*ppSrc)++ << 24;
-    result |= (unsigned long long) *(*ppSrc)++ << 16;
-    result |= (unsigned long long) *(*ppSrc)++ << 8;
-    result |= (unsigned long long) *(*ppSrc)++;
-
-    return result;
-}
-
-/*
- * Grab 2 little-endian bytes and advance the data pointer.
- */
-INLINE unsigned short read2LE(unsigned char const** ppSrc)
-{
-    unsigned short result;
-
-    result = *(*ppSrc)++;
-    result |= *(*ppSrc)++ << 8;
-
-    return result;
-}
-
-/*
- * Grab 4 little-endian bytes and advance the data pointer.
- */
-INLINE unsigned int read4LE(unsigned char const** ppSrc)
-{
-    unsigned int result;
-
-    result = *(*ppSrc)++;
-    result |= *(*ppSrc)++ << 8;
-    result |= *(*ppSrc)++ << 16;
-    result |= *(*ppSrc)++ << 24;
-
-    return result;
-}
-
-/*
- * Get 8 little-endian bytes.
- */
-INLINE unsigned long long read8LE(unsigned char const** ppSrc)
-{
-    unsigned long long result;
-
-    result = (unsigned long long) *(*ppSrc)++;
-    result |= (unsigned long long) *(*ppSrc)++ << 8;
-    result |= (unsigned long long) *(*ppSrc)++ << 16;
-    result |= (unsigned long long) *(*ppSrc)++ << 24;
-    result |= (unsigned long long) *(*ppSrc)++ << 32;
-    result |= (unsigned long long) *(*ppSrc)++ << 40;
-    result |= (unsigned long long) *(*ppSrc)++ << 48;
-    result |= (unsigned long long) *(*ppSrc)++ << 56;
-
-    return result;
-}
-
-/*
- * Skip over a UTF-8 string.
- */
-INLINE void skipUtf8String(unsigned char const** ppSrc)
-{
-    unsigned int length = read4BE(ppSrc);
-
-    (*ppSrc) += length;
-}
-
-/*
- * Read a UTF-8 string into a fixed-size buffer, and null-terminate it.
- *
- * Returns the length of the original string.
- */
-INLINE int readUtf8String(unsigned char const** ppSrc, char* buf, size_t bufLen)
-{
-    unsigned int length = read4BE(ppSrc);
-    size_t copyLen = (length < bufLen) ? length : bufLen-1;
-
-    memcpy(buf, *ppSrc, copyLen);
-    buf[copyLen] = '\0';
-
-    (*ppSrc) += length;
-    return length;
-}
-
-/*
- * Read a UTF-8 string into newly-allocated storage, and null-terminate it.
- *
- * Returns the string and its length.  (The latter is probably unnecessary
- * for the way we're using UTF8.)
- */
-INLINE char* readNewUtf8String(unsigned char const** ppSrc, size_t* pLength)
-{
-    unsigned int length = read4BE(ppSrc);
-    char* buf;
-
-    buf = (char*) malloc(length+1);
-
-    memcpy(buf, *ppSrc, length);
-    buf[length] = '\0';
-
-    (*ppSrc) += length;
-
-    *pLength = length;
-    return buf;
-}
-
-
-/*
- * Set 1 byte.  (Included to make the code more legible.)
- */
-INLINE void set1(unsigned char* buf, unsigned char val)
-{
-    *buf = (unsigned char)(val);
-}
-
-/*
- * Set 2 big-endian bytes.
- */
-INLINE void set2BE(unsigned char* buf, unsigned short val)
-{
-    *buf++ = (unsigned char)(val >> 8);
-    *buf = (unsigned char)(val);
-}
-
-/*
- * Set 4 big-endian bytes.
- */
-INLINE void set4BE(unsigned char* buf, unsigned int val)
-{
-    *buf++ = (unsigned char)(val >> 24);
-    *buf++ = (unsigned char)(val >> 16);
-    *buf++ = (unsigned char)(val >> 8);
-    *buf = (unsigned char)(val);
-}
-
-/*
- * Set 8 big-endian bytes.
- */
-INLINE void set8BE(unsigned char* buf, unsigned long long val)
-{
-    *buf++ = (unsigned char)(val >> 56);
-    *buf++ = (unsigned char)(val >> 48);
-    *buf++ = (unsigned char)(val >> 40);
-    *buf++ = (unsigned char)(val >> 32);
-    *buf++ = (unsigned char)(val >> 24);
-    *buf++ = (unsigned char)(val >> 16);
-    *buf++ = (unsigned char)(val >> 8);
-    *buf = (unsigned char)(val);
-}
-
-/*
- * Set 2 little-endian bytes.
- */
-INLINE void set2LE(unsigned char* buf, unsigned short val)
-{
-    *buf++ = (unsigned char)(val);
-    *buf = (unsigned char)(val >> 8);
-}
-
-/*
- * Set 4 little-endian bytes.
- */
-INLINE void set4LE(unsigned char* buf, unsigned int val)
-{
-    *buf++ = (unsigned char)(val);
-    *buf++ = (unsigned char)(val >> 8);
-    *buf++ = (unsigned char)(val >> 16);
-    *buf = (unsigned char)(val >> 24);
-}
-
-/*
- * Set 8 little-endian bytes.
- */
-INLINE void set8LE(unsigned char* buf, unsigned long long val)
-{
-    *buf++ = (unsigned char)(val);
-    *buf++ = (unsigned char)(val >> 8);
-    *buf++ = (unsigned char)(val >> 16);
-    *buf++ = (unsigned char)(val >> 24);
-    *buf++ = (unsigned char)(val >> 32);
-    *buf++ = (unsigned char)(val >> 40);
-    *buf++ = (unsigned char)(val >> 48);
-    *buf = (unsigned char)(val >> 56);
-}
-
-/*
- * Stuff a UTF-8 string into the buffer.
- */
-INLINE void setUtf8String(unsigned char* buf, const unsigned char* str)
-{
-    unsigned int strLen = strlen((const char*)str);
-
-    set4BE(buf, strLen);
-    memcpy(buf + sizeof(unsigned int), str, strLen);
-}
-
-#endif /*_MINZIP_BITS*/
diff --git a/minzip/Hash.c b/minzip/Hash.c
deleted file mode 100644
index 49bcb31..0000000
--- a/minzip/Hash.c
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * Hash table.  The dominant calls are add and lookup, with removals
- * happening very infrequently.  We use probing, and don't worry much
- * about tombstone removal.
- */
-#include <stdlib.h>
-#include <assert.h>
-
-#define LOG_TAG "minzip"
-#include "Log.h"
-#include "Hash.h"
-
-/* table load factor, i.e. how full can it get before we resize */
-//#define LOAD_NUMER  3       // 75%
-//#define LOAD_DENOM  4
-#define LOAD_NUMER  5       // 62.5%
-#define LOAD_DENOM  8
-//#define LOAD_NUMER  1       // 50%
-//#define LOAD_DENOM  2
-
-/*
- * Compute the capacity needed for a table to hold "size" elements.
- */
-size_t mzHashSize(size_t size) {
-    return (size * LOAD_DENOM) / LOAD_NUMER +1;
-}
-
-/*
- * Round up to the next highest power of 2.
- *
- * Found on http://graphics.stanford.edu/~seander/bithacks.html.
- */
-unsigned int roundUpPower2(unsigned int val)
-{
-    val--;
-    val |= val >> 1;
-    val |= val >> 2;
-    val |= val >> 4;
-    val |= val >> 8;
-    val |= val >> 16;
-    val++;
-
-    return val;
-}
-
-/*
- * Create and initialize a hash table.
- */
-HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc)
-{
-    HashTable* pHashTable;
-
-    assert(initialSize > 0);
-
-    pHashTable = (HashTable*) malloc(sizeof(*pHashTable));
-    if (pHashTable == NULL)
-        return NULL;
-
-    pHashTable->tableSize = roundUpPower2(initialSize);
-    pHashTable->numEntries = pHashTable->numDeadEntries = 0;
-    pHashTable->freeFunc = freeFunc;
-    pHashTable->pEntries =
-        (HashEntry*) calloc((size_t)pHashTable->tableSize, sizeof(HashTable));
-    if (pHashTable->pEntries == NULL) {
-        free(pHashTable);
-        return NULL;
-    }
-
-    return pHashTable;
-}
-
-/*
- * Clear out all entries.
- */
-void mzHashTableClear(HashTable* pHashTable)
-{
-    HashEntry* pEnt;
-    int i;
-
-    pEnt = pHashTable->pEntries;
-    for (i = 0; i < pHashTable->tableSize; i++, pEnt++) {
-        if (pEnt->data == HASH_TOMBSTONE) {
-            // nuke entry
-            pEnt->data = NULL;
-        } else if (pEnt->data != NULL) {
-            // call free func then nuke entry
-            if (pHashTable->freeFunc != NULL)
-                (*pHashTable->freeFunc)(pEnt->data);
-            pEnt->data = NULL;
-        }
-    }
-
-    pHashTable->numEntries = 0;
-    pHashTable->numDeadEntries = 0;
-}
-
-/*
- * Free the table.
- */
-void mzHashTableFree(HashTable* pHashTable)
-{
-    if (pHashTable == NULL)
-        return;
-    mzHashTableClear(pHashTable);
-    free(pHashTable->pEntries);
-    free(pHashTable);
-}
-
-#ifndef NDEBUG
-/*
- * Count up the number of tombstone entries in the hash table.
- */
-static int countTombStones(HashTable* pHashTable)
-{
-    int i, count;
-
-    for (count = i = 0; i < pHashTable->tableSize; i++) {
-        if (pHashTable->pEntries[i].data == HASH_TOMBSTONE)
-            count++;
-    }
-    return count;
-}
-#endif
-
-/*
- * Resize a hash table.  We do this when adding an entry increased the
- * size of the table beyond its comfy limit.
- *
- * This essentially requires re-inserting all elements into the new storage.
- *
- * If multiple threads can access the hash table, the table's lock should
- * have been grabbed before issuing the "lookup+add" call that led to the
- * resize, so we don't have a synchronization problem here.
- */
-static bool resizeHash(HashTable* pHashTable, int newSize)
-{
-    HashEntry* pNewEntries;
-    int i;
-
-    assert(countTombStones(pHashTable) == pHashTable->numDeadEntries);
-
-    pNewEntries = (HashEntry*) calloc(newSize, sizeof(HashTable));
-    if (pNewEntries == NULL)
-        return false;
-
-    for (i = 0; i < pHashTable->tableSize; i++) {
-        void* data = pHashTable->pEntries[i].data;
-        if (data != NULL && data != HASH_TOMBSTONE) {
-            int hashValue = pHashTable->pEntries[i].hashValue;
-            int newIdx;
-
-            /* probe for new spot, wrapping around */
-            newIdx = hashValue & (newSize-1);
-            while (pNewEntries[newIdx].data != NULL)
-                newIdx = (newIdx + 1) & (newSize-1);
-
-            pNewEntries[newIdx].hashValue = hashValue;
-            pNewEntries[newIdx].data = data;
-        }
-    }
-
-    free(pHashTable->pEntries);
-    pHashTable->pEntries = pNewEntries;
-    pHashTable->tableSize = newSize;
-    pHashTable->numDeadEntries = 0;
-
-    assert(countTombStones(pHashTable) == 0);
-    return true;
-}
-
-/*
- * Look up an entry.
- *
- * We probe on collisions, wrapping around the table.
- */
-void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item,
-    HashCompareFunc cmpFunc, bool doAdd)
-{
-    HashEntry* pEntry;
-    HashEntry* pEnd;
-    void* result = NULL;
-
-    assert(pHashTable->tableSize > 0);
-    assert(item != HASH_TOMBSTONE);
-    assert(item != NULL);
-
-    /* jump to the first entry and probe for a match */
-    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
-    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
-    while (pEntry->data != NULL) {
-        if (pEntry->data != HASH_TOMBSTONE &&
-            pEntry->hashValue == itemHash &&
-            (*cmpFunc)(pEntry->data, item) == 0)
-        {
-            /* match */
-            break;
-        }
-
-        pEntry++;
-        if (pEntry == pEnd) {     /* wrap around to start */
-            if (pHashTable->tableSize == 1)
-                break;      /* edge case - single-entry table */
-            pEntry = pHashTable->pEntries;
-        }
-    }
-
-    if (pEntry->data == NULL) {
-        if (doAdd) {
-            pEntry->hashValue = itemHash;
-            pEntry->data = item;
-            pHashTable->numEntries++;
-
-            /*
-             * We've added an entry.  See if this brings us too close to full.
-             */
-            if ((pHashTable->numEntries+pHashTable->numDeadEntries) * LOAD_DENOM
-                > pHashTable->tableSize * LOAD_NUMER)
-            {
-                if (!resizeHash(pHashTable, pHashTable->tableSize * 2)) {
-                    /* don't really have a way to indicate failure */
-                    LOGE("Dalvik hash resize failure\n");
-                    abort();
-                }
-                /* note "pEntry" is now invalid */
-            }
-
-            /* full table is bad -- search for nonexistent never halts */
-            assert(pHashTable->numEntries < pHashTable->tableSize);
-            result = item;
-        } else {
-            assert(result == NULL);
-        }
-    } else {
-        result = pEntry->data;
-    }
-
-    return result;
-}
-
-/*
- * Remove an entry from the table.
- *
- * Does NOT invoke the "free" function on the item.
- */
-bool mzHashTableRemove(HashTable* pHashTable, unsigned int itemHash, void* item)
-{
-    HashEntry* pEntry;
-    HashEntry* pEnd;
-
-    assert(pHashTable->tableSize > 0);
-
-    /* jump to the first entry and probe for a match */
-    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
-    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
-    while (pEntry->data != NULL) {
-        if (pEntry->data == item) {
-            pEntry->data = HASH_TOMBSTONE;
-            pHashTable->numEntries--;
-            pHashTable->numDeadEntries++;
-            return true;
-        }
-
-        pEntry++;
-        if (pEntry == pEnd) {     /* wrap around to start */
-            if (pHashTable->tableSize == 1)
-                break;      /* edge case - single-entry table */
-            pEntry = pHashTable->pEntries;
-        }
-    }
-
-    return false;
-}
-
-/*
- * Execute a function on every entry in the hash table.
- *
- * If "func" returns a nonzero value, terminate early and return the value.
- */
-int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg)
-{
-    int i, val;
-
-    for (i = 0; i < pHashTable->tableSize; i++) {
-        HashEntry* pEnt = &pHashTable->pEntries[i];
-
-        if (pEnt->data != NULL && pEnt->data != HASH_TOMBSTONE) {
-            val = (*func)(pEnt->data, arg);
-            if (val != 0)
-                return val;
-        }
-    }
-
-    return 0;
-}
-
-
-/*
- * Look up an entry, counting the number of times we have to probe.
- *
- * Returns -1 if the entry wasn't found.
- */
-int countProbes(HashTable* pHashTable, unsigned int itemHash, const void* item,
-    HashCompareFunc cmpFunc)
-{
-    HashEntry* pEntry;
-    HashEntry* pEnd;
-    int count = 0;
-
-    assert(pHashTable->tableSize > 0);
-    assert(item != HASH_TOMBSTONE);
-    assert(item != NULL);
-
-    /* jump to the first entry and probe for a match */
-    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
-    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
-    while (pEntry->data != NULL) {
-        if (pEntry->data != HASH_TOMBSTONE &&
-            pEntry->hashValue == itemHash &&
-            (*cmpFunc)(pEntry->data, item) == 0)
-        {
-            /* match */
-            break;
-        }
-
-        pEntry++;
-        if (pEntry == pEnd) {     /* wrap around to start */
-            if (pHashTable->tableSize == 1)
-                break;      /* edge case - single-entry table */
-            pEntry = pHashTable->pEntries;
-        }
-
-        count++;
-    }
-    if (pEntry->data == NULL)
-        return -1;
-
-    return count;
-}
-
-/*
- * Evaluate the amount of probing required for the specified hash table.
- *
- * We do this by running through all entries in the hash table, computing
- * the hash value and then doing a lookup.
- *
- * The caller should lock the table before calling here.
- */
-void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc,
-    HashCompareFunc cmpFunc)
-{
-    int numEntries, minProbe, maxProbe, totalProbe;
-    HashIter iter;
-
-    numEntries = maxProbe = totalProbe = 0;
-    minProbe = 65536*32767;
-
-    for (mzHashIterBegin(pHashTable, &iter); !mzHashIterDone(&iter);
-        mzHashIterNext(&iter))
-    {
-        const void* data = (const void*)mzHashIterData(&iter);
-        int count;
-
-        count = countProbes(pHashTable, (*calcFunc)(data), data, cmpFunc);
-
-        numEntries++;
-
-        if (count < minProbe)
-            minProbe = count;
-        if (count > maxProbe)
-            maxProbe = count;
-        totalProbe += count;
-    }
-
-    LOGV("Probe: min=%d max=%d, total=%d in %d (%d), avg=%.3f\n",
-        minProbe, maxProbe, totalProbe, numEntries, pHashTable->tableSize,
-        (float) totalProbe / (float) numEntries);
-}
diff --git a/minzip/Hash.h b/minzip/Hash.h
deleted file mode 100644
index e83eac4..0000000
--- a/minzip/Hash.h
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2007 The Android Open Source Project
- *
- * General purpose hash table, used for finding classes, methods, etc.
- *
- * When the number of elements reaches 3/4 of the table's capacity, the
- * table will be resized.
- */
-#ifndef _MINZIP_HASH
-#define _MINZIP_HASH
-
-#include "inline_magic.h"
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <assert.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* compute the hash of an item with a specific type */
-typedef unsigned int (*HashCompute)(const void* item);
-
-/*
- * Compare a hash entry with a "loose" item after their hash values match.
- * Returns { <0, 0, >0 } depending on ordering of items (same semantics
- * as strcmp()).
- */
-typedef int (*HashCompareFunc)(const void* tableItem, const void* looseItem);
-
-/*
- * This function will be used to free entries in the table.  This can be
- * NULL if no free is required, free(), or a custom function.
- */
-typedef void (*HashFreeFunc)(void* ptr);
-
-/*
- * Used by mzHashForeach().
- */
-typedef int (*HashForeachFunc)(void* data, void* arg);
-
-/*
- * One entry in the hash table.  "data" values are expected to be (or have
- * the same characteristics as) valid pointers.  In particular, a NULL
- * value for "data" indicates an empty slot, and HASH_TOMBSTONE indicates
- * a no-longer-used slot that must be stepped over during probing.
- *
- * Attempting to add a NULL or tombstone value is an error.
- *
- * When an entry is released, we will call (HashFreeFunc)(entry->data).
- */
-typedef struct HashEntry {
-    unsigned int hashValue;
-    void* data;
-} HashEntry;
-
-#define HASH_TOMBSTONE ((void*) 0xcbcacccd)     // invalid ptr value
-
-/*
- * Expandable hash table.
- *
- * This structure should be considered opaque.
- */
-typedef struct HashTable {
-    int         tableSize;          /* must be power of 2 */
-    int         numEntries;         /* current #of "live" entries */
-    int         numDeadEntries;     /* current #of tombstone entries */
-    HashEntry*  pEntries;           /* array on heap */
-    HashFreeFunc freeFunc;
-} HashTable;
-
-/*
- * Create and initialize a HashTable structure, using "initialSize" as
- * a basis for the initial capacity of the table.  (The actual initial
- * table size may be adjusted upward.)  If you know exactly how many
- * elements the table will hold, pass the result from mzHashSize() in.)
- *
- * Returns "false" if unable to allocate the table.
- */
-HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc);
-
-/*
- * Compute the capacity needed for a table to hold "size" elements.  Use
- * this when you know ahead of time how many elements the table will hold.
- * Pass this value into mzHashTableCreate() to ensure that you can add
- * all elements without needing to reallocate the table.
- */
-size_t mzHashSize(size_t size);
-
-/*
- * Clear out a hash table, freeing the contents of any used entries.
- */
-void mzHashTableClear(HashTable* pHashTable);
-
-/*
- * Free a hash table.
- */
-void mzHashTableFree(HashTable* pHashTable);
-
-/*
- * Get #of entries in hash table.
- */
-INLINE int mzHashTableNumEntries(HashTable* pHashTable) {
-    return pHashTable->numEntries;
-}
-
-/*
- * Get total size of hash table (for memory usage calculations).
- */
-INLINE int mzHashTableMemUsage(HashTable* pHashTable) {
-    return sizeof(HashTable) + pHashTable->tableSize * sizeof(HashEntry);
-}
-
-/*
- * Look up an entry in the table, possibly adding it if it's not there.
- *
- * If "item" is not found, and "doAdd" is false, NULL is returned.
- * Otherwise, a pointer to the found or added item is returned.  (You can
- * tell the difference by seeing if return value == item.)
- *
- * An "add" operation may cause the entire table to be reallocated.
- */
-void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item,
-    HashCompareFunc cmpFunc, bool doAdd);
-
-/*
- * Remove an item from the hash table, given its "data" pointer.  Does not
- * invoke the "free" function; just detaches it from the table.
- */
-bool mzHashTableRemove(HashTable* pHashTable, unsigned int hash, void* item);
-
-/*
- * Execute "func" on every entry in the hash table.
- *
- * If "func" returns a nonzero value, terminate early and return the value.
- */
-int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg);
-
-/*
- * An alternative to mzHashForeach(), using an iterator.
- *
- * Use like this:
- *   HashIter iter;
- *   for (mzHashIterBegin(hashTable, &iter); !mzHashIterDone(&iter);
- *       mzHashIterNext(&iter))
- *   {
- *       MyData* data = (MyData*)mzHashIterData(&iter);
- *   }
- */
-typedef struct HashIter {
-    void*       data;
-    HashTable*  pHashTable;
-    int         idx;
-} HashIter;
-INLINE void mzHashIterNext(HashIter* pIter) {
-    int i = pIter->idx +1;
-    int lim = pIter->pHashTable->tableSize;
-    for ( ; i < lim; i++) {
-        void* data = pIter->pHashTable->pEntries[i].data;
-        if (data != NULL && data != HASH_TOMBSTONE)
-            break;
-    }
-    pIter->idx = i;
-}
-INLINE void mzHashIterBegin(HashTable* pHashTable, HashIter* pIter) {
-    pIter->pHashTable = pHashTable;
-    pIter->idx = -1;
-    mzHashIterNext(pIter);
-}
-INLINE bool mzHashIterDone(HashIter* pIter) {
-    return (pIter->idx >= pIter->pHashTable->tableSize);
-}
-INLINE void* mzHashIterData(HashIter* pIter) {
-    assert(pIter->idx >= 0 && pIter->idx < pIter->pHashTable->tableSize);
-    return pIter->pHashTable->pEntries[pIter->idx].data;
-}
-
-
-/*
- * Evaluate hash table performance by examining the number of times we
- * have to probe for an entry.
- *
- * The caller should lock the table beforehand.
- */
-typedef unsigned int (*HashCalcFunc)(const void* item);
-void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc,
-    HashCompareFunc cmpFunc);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /*_MINZIP_HASH*/
diff --git a/minzip/Inlines.c b/minzip/Inlines.c
deleted file mode 100644
index 91f8775..0000000
--- a/minzip/Inlines.c
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2007 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 agreed 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.
- */
-
-/* Make sure that non-inlined versions of INLINED-marked functions
- * exist so that debug builds (which don't generally do inlining)
- * don't break.
- */
-#define MINZIP_GENERATE_INLINES 1
-#include "Bits.h"
-#include "Hash.h"
-#include "SysUtil.h"
-#include "Zip.h"
diff --git a/minzip/Log.h b/minzip/Log.h
deleted file mode 100644
index 36e62f5..0000000
--- a/minzip/Log.h
+++ /dev/null
@@ -1,207 +0,0 @@
-//
-// Copyright 2005 The Android Open Source Project
-//
-// C/C++ logging functions.  See the logging documentation for API details.
-//
-// We'd like these to be available from C code (in case we import some from
-// somewhere), so this has a C interface.
-//
-// The output will be correct when the log file is shared between multiple
-// threads and/or multiple processes so long as the operating system
-// supports O_APPEND.  These calls have mutex-protected data structures
-// and so are NOT reentrant.  Do not use LOG in a signal handler.
-//
-#ifndef _MINZIP_LOG_H
-#define _MINZIP_LOG_H
-
-#include <stdio.h>
-
-// ---------------------------------------------------------------------
-
-/*
- * Normally we strip LOGV (VERBOSE messages) from release builds.
- * You can modify this (for example with "#define LOG_NDEBUG 0"
- * at the top of your source file) to change that behavior.
- */
-#ifndef LOG_NDEBUG
-#ifdef NDEBUG
-#define LOG_NDEBUG 1
-#else
-#define LOG_NDEBUG 0
-#endif
-#endif
-
-/*
- * This is the local tag used for the following simplified
- * logging macros.  You can change this preprocessor definition
- * before using the other macros to change the tag.
- */
-#ifndef LOG_TAG
-#define LOG_TAG NULL
-#endif
-
-// ---------------------------------------------------------------------
-
-/*
- * Simplified macro to send a verbose log message using the current LOG_TAG.
- */
-#ifndef LOGV
-#if LOG_NDEBUG
-#define LOGV(...)   ((void)0)
-#else
-#define LOGV(...) ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
-#endif
-#endif
-
-#define CONDITION(cond)     (__builtin_expect((cond)!=0, 0))
-
-#ifndef LOGV_IF
-#if LOG_NDEBUG
-#define LOGV_IF(cond, ...)   ((void)0)
-#else
-#define LOGV_IF(cond, ...) \
-    ( (CONDITION(cond)) \
-    ? ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
-    : (void)0 )
-#endif
-#endif
-
-#define LOGVV LOGV
-#define LOGVV_IF LOGV_IF
-
-/*
- * Simplified macro to send a debug log message using the current LOG_TAG.
- */
-#ifndef LOGD
-#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
-#endif
-
-#ifndef LOGD_IF
-#define LOGD_IF(cond, ...) \
-    ( (CONDITION(cond)) \
-    ? ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
-    : (void)0 )
-#endif
-
-/*
- * Simplified macro to send an info log message using the current LOG_TAG.
- */
-#ifndef LOGI
-#define LOGI(...) ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
-#endif
-
-#ifndef LOGI_IF
-#define LOGI_IF(cond, ...) \
-    ( (CONDITION(cond)) \
-    ? ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
-    : (void)0 )
-#endif
-
-/*
- * Simplified macro to send a warning log message using the current LOG_TAG.
- */
-#ifndef LOGW
-#define LOGW(...) ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
-#endif
-
-#ifndef LOGW_IF
-#define LOGW_IF(cond, ...) \
-    ( (CONDITION(cond)) \
-    ? ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
-    : (void)0 )
-#endif
-
-/*
- * Simplified macro to send an error log message using the current LOG_TAG.
- */
-#ifndef LOGE
-#define LOGE(...) ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
-#endif
-
-#ifndef LOGE_IF
-#define LOGE_IF(cond, ...) \
-    ( (CONDITION(cond)) \
-    ? ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
-    : (void)0 )
-#endif
-
-
-/*
- * Conditional based on whether the current LOG_TAG is enabled at
- * verbose priority.
- */
-#ifndef IF_LOGV
-#if LOG_NDEBUG
-#define IF_LOGV() if (false)
-#else
-#define IF_LOGV() IF_LOG(LOG_VERBOSE, LOG_TAG)
-#endif
-#endif
-
-/*
- * Conditional based on whether the current LOG_TAG is enabled at
- * debug priority.
- */
-#ifndef IF_LOGD
-#define IF_LOGD() IF_LOG(LOG_DEBUG, LOG_TAG)
-#endif
-
-/*
- * Conditional based on whether the current LOG_TAG is enabled at
- * info priority.
- */
-#ifndef IF_LOGI
-#define IF_LOGI() IF_LOG(LOG_INFO, LOG_TAG)
-#endif
-
-/*
- * Conditional based on whether the current LOG_TAG is enabled at
- * warn priority.
- */
-#ifndef IF_LOGW
-#define IF_LOGW() IF_LOG(LOG_WARN, LOG_TAG)
-#endif
-
-/*
- * Conditional based on whether the current LOG_TAG is enabled at
- * error priority.
- */
-#ifndef IF_LOGE
-#define IF_LOGE() IF_LOG(LOG_ERROR, LOG_TAG)
-#endif
-
-// ---------------------------------------------------------------------
-
-/*
- * Basic log message macro.
- *
- * Example:
- *  LOG(LOG_WARN, NULL, "Failed with error %d", errno);
- *
- * The second argument may be NULL or "" to indicate the "global" tag.
- *
- * Non-gcc probably won't have __FUNCTION__.  It's not vital.  gcc also
- * offers __PRETTY_FUNCTION__, which is rather more than we need.
- */
-#ifndef LOG
-#define LOG(priority, tag, ...) \
-    LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
-#endif
-
-/*
- * Log macro that allows you to specify a number for the priority.
- */
-#ifndef LOG_PRI
-#define LOG_PRI(priority, tag, ...) \
-    printf(tag ": " __VA_ARGS__)
-#endif
-
-/*
- * Conditional given a desired logging priority and tag.
- */
-#ifndef IF_LOG
-#define IF_LOG(priority, tag) \
-    if (1)
-#endif
-
-#endif // _MINZIP_LOG_H
diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c
deleted file mode 100644
index e7dd17b..0000000
--- a/minzip/SysUtil.c
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * System utilities.
- */
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#define LOG_TAG "sysutil"
-#include "Log.h"
-#include "SysUtil.h"
-
-static bool sysMapFD(int fd, MemMapping* pMap) {
-    assert(pMap != NULL);
-
-    struct stat sb;
-    if (fstat(fd, &sb) == -1) {
-        LOGE("fstat(%d) failed: %s\n", fd, strerror(errno));
-        return false;
-    }
-
-    void* memPtr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
-    if (memPtr == MAP_FAILED) {
-        LOGE("mmap(%d, R, PRIVATE, %d, 0) failed: %s\n", (int) sb.st_size, fd, strerror(errno));
-        return false;
-    }
-
-    pMap->addr = memPtr;
-    pMap->length = sb.st_size;
-    pMap->range_count = 1;
-    pMap->ranges = malloc(sizeof(MappedRange));
-    if (pMap->ranges == NULL) {
-        LOGE("malloc failed: %s\n", strerror(errno));
-        munmap(memPtr, sb.st_size);
-        return false;
-    }
-    pMap->ranges[0].addr = memPtr;
-    pMap->ranges[0].length = sb.st_size;
-
-    return true;
-}
-
-static int sysMapBlockFile(FILE* mapf, MemMapping* pMap)
-{
-    char block_dev[PATH_MAX+1];
-    size_t size;
-    unsigned int blksize;
-    size_t blocks;
-    unsigned int range_count;
-    unsigned int i;
-
-    if (fgets(block_dev, sizeof(block_dev), mapf) == NULL) {
-        LOGE("failed to read block device from header\n");
-        return -1;
-    }
-    for (i = 0; i < sizeof(block_dev); ++i) {
-        if (block_dev[i] == '\n') {
-            block_dev[i] = 0;
-            break;
-        }
-    }
-
-    if (fscanf(mapf, "%zu %u\n%u\n", &size, &blksize, &range_count) != 3) {
-        LOGE("failed to parse block map header\n");
-        return -1;
-    }
-    if (blksize != 0) {
-        blocks = ((size-1) / blksize) + 1;
-    }
-    if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0) {
-        LOGE("invalid data in block map file: size %zu, blksize %u, range_count %u\n",
-             size, blksize, range_count);
-        return -1;
-    }
-
-    pMap->range_count = range_count;
-    pMap->ranges = calloc(range_count, sizeof(MappedRange));
-    if (pMap->ranges == NULL) {
-        LOGE("calloc(%u, %zu) failed: %s\n", range_count, sizeof(MappedRange), strerror(errno));
-        return -1;
-    }
-
-    // Reserve enough contiguous address space for the whole file.
-    unsigned char* reserve;
-    reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
-    if (reserve == MAP_FAILED) {
-        LOGE("failed to reserve address space: %s\n", strerror(errno));
-        free(pMap->ranges);
-        return -1;
-    }
-
-    int fd = open(block_dev, O_RDONLY);
-    if (fd < 0) {
-        LOGE("failed to open block device %s: %s\n", block_dev, strerror(errno));
-        munmap(reserve, blocks * blksize);
-        free(pMap->ranges);
-        return -1;
-    }
-
-    unsigned char* next = reserve;
-    size_t remaining_size = blocks * blksize;
-    bool success = true;
-    for (i = 0; i < range_count; ++i) {
-        size_t start, end;
-        if (fscanf(mapf, "%zu %zu\n", &start, &end) != 2) {
-            LOGE("failed to parse range %d in block map\n", i);
-            success = false;
-            break;
-        }
-        size_t length = (end - start) * blksize;
-        if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) {
-          LOGE("unexpected range in block map: %zu %zu\n", start, end);
-          success = false;
-          break;
-        }
-
-        void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize);
-        if (addr == MAP_FAILED) {
-            LOGE("failed to map block %d: %s\n", i, strerror(errno));
-            success = false;
-            break;
-        }
-        pMap->ranges[i].addr = addr;
-        pMap->ranges[i].length = length;
-
-        next += length;
-        remaining_size -= length;
-    }
-    if (success && remaining_size != 0) {
-      LOGE("ranges in block map are invalid: remaining_size = %zu\n", remaining_size);
-      success = false;
-    }
-    if (!success) {
-      close(fd);
-      munmap(reserve, blocks * blksize);
-      free(pMap->ranges);
-      return -1;
-    }
-
-    close(fd);
-    pMap->addr = reserve;
-    pMap->length = size;
-
-    LOGI("mmapped %d ranges\n", range_count);
-
-    return 0;
-}
-
-int sysMapFile(const char* fn, MemMapping* pMap)
-{
-    memset(pMap, 0, sizeof(*pMap));
-
-    if (fn && fn[0] == '@') {
-        // A map of blocks
-        FILE* mapf = fopen(fn+1, "r");
-        if (mapf == NULL) {
-            LOGE("Unable to open '%s': %s\n", fn+1, strerror(errno));
-            return -1;
-        }
-
-        if (sysMapBlockFile(mapf, pMap) != 0) {
-            LOGE("Map of '%s' failed\n", fn);
-            fclose(mapf);
-            return -1;
-        }
-
-        fclose(mapf);
-    } else {
-        // This is a regular file.
-        int fd = open(fn, O_RDONLY);
-        if (fd == -1) {
-            LOGE("Unable to open '%s': %s\n", fn, strerror(errno));
-            return -1;
-        }
-
-        if (!sysMapFD(fd, pMap)) {
-            LOGE("Map of '%s' failed\n", fn);
-            close(fd);
-            return -1;
-        }
-
-        close(fd);
-    }
-    return 0;
-}
-
-/*
- * Release a memory mapping.
- */
-void sysReleaseMap(MemMapping* pMap)
-{
-    int i;
-    for (i = 0; i < pMap->range_count; ++i) {
-        if (munmap(pMap->ranges[i].addr, pMap->ranges[i].length) < 0) {
-            LOGE("munmap(%p, %d) failed: %s\n",
-                 pMap->ranges[i].addr, (int)pMap->ranges[i].length, strerror(errno));
-        }
-    }
-    free(pMap->ranges);
-    pMap->ranges = NULL;
-    pMap->range_count = 0;
-}
diff --git a/minzip/SysUtil.h b/minzip/SysUtil.h
deleted file mode 100644
index 7adff1e..0000000
--- a/minzip/SysUtil.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * System utilities.
- */
-#ifndef _MINZIP_SYSUTIL
-#define _MINZIP_SYSUTIL
-
-#include <stdio.h>
-#include <sys/types.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct MappedRange {
-    void* addr;
-    size_t length;
-} MappedRange;
-
-/*
- * Use this to keep track of mapped segments.
- */
-typedef struct MemMapping {
-    unsigned char* addr;           /* start of data */
-    size_t         length;         /* length of data */
-
-    int            range_count;
-    MappedRange*   ranges;
-} MemMapping;
-
-/*
- * Map a file into a private, read-only memory segment.  If 'fn'
- * begins with an '@' character, it is a map of blocks to be mapped,
- * otherwise it is treated as an ordinary file.
- *
- * On success, "pMap" is filled in, and zero is returned.
- */
-int sysMapFile(const char* fn, MemMapping* pMap);
-
-/*
- * Release the pages associated with a shared memory segment.
- *
- * This does not free "pMap"; it just releases the memory.
- */
-void sysReleaseMap(MemMapping* pMap);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /*_MINZIP_SYSUTIL*/
diff --git a/minzip/Zip.c b/minzip/Zip.c
deleted file mode 100644
index bdb565c..0000000
--- a/minzip/Zip.c
+++ /dev/null
@@ -1,1016 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * Simple Zip file support.
- */
-#include "safe_iop.h"
-#include "zlib.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdint.h>     // for uintptr_t
-#include <stdlib.h>
-#include <sys/stat.h>   // for S_ISLNK()
-#include <unistd.h>
-
-#define LOG_TAG "minzip"
-#include "Zip.h"
-#include "Bits.h"
-#include "Log.h"
-#include "DirUtil.h"
-
-#undef NDEBUG   // do this after including Log.h
-#include <assert.h>
-
-#define SORT_ENTRIES 1
-
-/*
- * Offset and length constants (java.util.zip naming convention).
- */
-enum {
-    CENSIG = 0x02014b50,      // PK12
-    CENHDR = 46,
-
-    CENVEM =  4,
-    CENVER =  6,
-    CENFLG =  8,
-    CENHOW = 10,
-    CENTIM = 12,
-    CENCRC = 16,
-    CENSIZ = 20,
-    CENLEN = 24,
-    CENNAM = 28,
-    CENEXT = 30,
-    CENCOM = 32,
-    CENDSK = 34,
-    CENATT = 36,
-    CENATX = 38,
-    CENOFF = 42,
-
-    ENDSIG = 0x06054b50,     // PK56
-    ENDHDR = 22,
-
-    ENDSUB =  8,
-    ENDTOT = 10,
-    ENDSIZ = 12,
-    ENDOFF = 16,
-    ENDCOM = 20,
-
-    EXTSIG = 0x08074b50,     // PK78
-    EXTHDR = 16,
-
-    EXTCRC =  4,
-    EXTSIZ =  8,
-    EXTLEN = 12,
-
-    LOCSIG = 0x04034b50,      // PK34
-    LOCHDR = 30,
-
-    LOCVER =  4,
-    LOCFLG =  6,
-    LOCHOW =  8,
-    LOCTIM = 10,
-    LOCCRC = 14,
-    LOCSIZ = 18,
-    LOCLEN = 22,
-    LOCNAM = 26,
-    LOCEXT = 28,
-
-    STORED = 0,
-    DEFLATED = 8,
-
-    CENVEM_UNIX = 3 << 8,   // the high byte of CENVEM
-};
-
-
-/*
- * For debugging, dump the contents of a ZipEntry.
- */
-#if 0
-static void dumpEntry(const ZipEntry* pEntry)
-{
-    LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName);
-    LOGI("   off=%ld comp=%ld uncomp=%ld how=%d\n", pEntry->offset,
-        pEntry->compLen, pEntry->uncompLen, pEntry->compression);
-}
-#endif
-
-/*
- * (This is a mzHashTableLookup callback.)
- *
- * Compare two ZipEntry structs, by name.
- */
-static int hashcmpZipEntry(const void* ventry1, const void* ventry2)
-{
-    const ZipEntry* entry1 = (const ZipEntry*) ventry1;
-    const ZipEntry* entry2 = (const ZipEntry*) ventry2;
-
-    if (entry1->fileNameLen != entry2->fileNameLen)
-        return entry1->fileNameLen - entry2->fileNameLen;
-    return memcmp(entry1->fileName, entry2->fileName, entry1->fileNameLen);
-}
-
-/*
- * (This is a mzHashTableLookup callback.)
- *
- * find a ZipEntry struct by name.
- */
-static int hashcmpZipName(const void* ventry, const void* vname)
-{
-    const ZipEntry* entry = (const ZipEntry*) ventry;
-    const char* name = (const char*) vname;
-    unsigned int nameLen = strlen(name);
-
-    if (entry->fileNameLen != nameLen)
-        return entry->fileNameLen - nameLen;
-    return memcmp(entry->fileName, name, nameLen);
-}
-
-/*
- * Compute the hash code for a ZipEntry filename.
- *
- * Not expected to be compatible with any other hash function, so we init
- * to 2 to ensure it doesn't happen to match.
- */
-static unsigned int computeHash(const char* name, int nameLen)
-{
-    unsigned int hash = 2;
-
-    while (nameLen--)
-        hash = hash * 31 + *name++;
-
-    return hash;
-}
-
-static void addEntryToHashTable(HashTable* pHash, ZipEntry* pEntry)
-{
-    unsigned int itemHash = computeHash(pEntry->fileName, pEntry->fileNameLen);
-    const ZipEntry* found;
-
-    found = (const ZipEntry*)mzHashTableLookup(pHash,
-                itemHash, pEntry, hashcmpZipEntry, true);
-    if (found != pEntry) {
-        LOGW("WARNING: duplicate entry '%.*s' in Zip\n",
-            found->fileNameLen, found->fileName);
-        /* keep going */
-    }
-}
-
-static int validFilename(const char *fileName, unsigned int fileNameLen)
-{
-    // Forbid super long filenames.
-    if (fileNameLen >= PATH_MAX) {
-        LOGW("Filename too long (%d chatacters)\n", fileNameLen);
-        return 0;
-    }
-
-    // Require all characters to be printable ASCII (no NUL, no UTF-8, etc).
-    unsigned int i;
-    for (i = 0; i < fileNameLen; ++i) {
-        if (fileName[i] < 32 || fileName[i] >= 127) {
-            LOGW("Filename contains invalid character '\%03o'\n", fileName[i]);
-            return 0;
-        }
-    }
-
-    return 1;
-}
-
-/*
- * Parse the contents of a Zip archive.  After confirming that the file
- * is in fact a Zip, we scan out the contents of the central directory and
- * store it in a hash table.
- *
- * Returns "true" on success.
- */
-static bool parseZipArchive(ZipArchive* pArchive)
-{
-    bool result = false;
-    const unsigned char* ptr;
-    unsigned int i, numEntries, cdOffset;
-    unsigned int val;
-
-    /*
-     * The first 4 bytes of the file will either be the local header
-     * signature for the first file (LOCSIG) or, if the archive doesn't
-     * have any files in it, the end-of-central-directory signature (ENDSIG).
-     */
-    val = get4LE(pArchive->addr);
-    if (val == ENDSIG) {
-        LOGW("Found Zip archive, but it looks empty\n");
-        goto bail;
-    } else if (val != LOCSIG) {
-        LOGW("Not a Zip archive (found 0x%08x)\n", val);
-        goto bail;
-    }
-
-    /*
-     * Find the EOCD.  We'll find it immediately unless they have a file
-     * comment.
-     */
-    ptr = pArchive->addr + pArchive->length - ENDHDR;
-
-    while (ptr >= (const unsigned char*) pArchive->addr) {
-        if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG)
-            break;
-        ptr--;
-    }
-    if (ptr < (const unsigned char*) pArchive->addr) {
-        LOGW("Could not find end-of-central-directory in Zip\n");
-        goto bail;
-    }
-
-    /*
-     * There are two interesting items in the EOCD block: the number of
-     * entries in the file, and the file offset of the start of the
-     * central directory.
-     */
-    numEntries = get2LE(ptr + ENDSUB);
-    cdOffset = get4LE(ptr + ENDOFF);
-
-    LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset);
-    if (numEntries == 0 || cdOffset >= pArchive->length) {
-        LOGW("Invalid entries=%d offset=%d (len=%zd)\n",
-            numEntries, cdOffset, pArchive->length);
-        goto bail;
-    }
-
-    /*
-     * Create data structures to hold entries.
-     */
-    pArchive->numEntries = numEntries;
-    pArchive->pEntries = (ZipEntry*) calloc(numEntries, sizeof(ZipEntry));
-    pArchive->pHash = mzHashTableCreate(mzHashSize(numEntries), NULL);
-    if (pArchive->pEntries == NULL || pArchive->pHash == NULL)
-        goto bail;
-
-    ptr = pArchive->addr + cdOffset;
-    for (i = 0; i < numEntries; i++) {
-        ZipEntry* pEntry;
-        unsigned int fileNameLen, extraLen, commentLen, localHdrOffset;
-        const unsigned char* localHdr;
-        const char *fileName;
-
-        if (ptr + CENHDR > (const unsigned char*)pArchive->addr + pArchive->length) {
-            LOGW("Ran off the end (at %d)\n", i);
-            goto bail;
-        }
-        if (get4LE(ptr) != CENSIG) {
-            LOGW("Missed a central dir sig (at %d)\n", i);
-            goto bail;
-        }
-
-        localHdrOffset = get4LE(ptr + CENOFF);
-        fileNameLen = get2LE(ptr + CENNAM);
-        extraLen = get2LE(ptr + CENEXT);
-        commentLen = get2LE(ptr + CENCOM);
-        fileName = (const char*)ptr + CENHDR;
-        if (fileName + fileNameLen > (const char*)pArchive->addr + pArchive->length) {
-            LOGW("Filename ran off the end (at %d)\n", i);
-            goto bail;
-        }
-        if (!validFilename(fileName, fileNameLen)) {
-            LOGW("Invalid filename (at %d)\n", i);
-            goto bail;
-        }
-
-#if SORT_ENTRIES
-        /* Figure out where this entry should go (binary search).
-         */
-        if (i > 0) {
-            int low, high;
-
-            low = 0;
-            high = i - 1;
-            while (low <= high) {
-                int mid;
-                int diff;
-                int diffLen;
-
-                mid = low + ((high - low) / 2); // avoid overflow
-
-                if (pArchive->pEntries[mid].fileNameLen < fileNameLen) {
-                    diffLen = pArchive->pEntries[mid].fileNameLen;
-                } else {
-                    diffLen = fileNameLen;
-                }
-                diff = strncmp(pArchive->pEntries[mid].fileName, fileName,
-                        diffLen);
-                if (diff == 0) {
-                    diff = pArchive->pEntries[mid].fileNameLen - fileNameLen;
-                }
-                if (diff < 0) {
-                    low = mid + 1;
-                } else if (diff > 0) {
-                    high = mid - 1;
-                } else {
-                    high = mid;
-                    break;
-                }
-            }
-
-            unsigned int target = high + 1;
-            assert(target <= i);
-            if (target != i) {
-                /* It belongs somewhere other than at the end of
-                 * the list.  Make some room at [target].
-                 */
-                memmove(pArchive->pEntries + target + 1,
-                        pArchive->pEntries + target,
-                        (i - target) * sizeof(ZipEntry));
-            }
-            pEntry = &pArchive->pEntries[target];
-        } else {
-            pEntry = &pArchive->pEntries[0];
-        }
-#else
-        pEntry = &pArchive->pEntries[i];
-#endif
-        pEntry->fileNameLen = fileNameLen;
-        pEntry->fileName = fileName;
-
-        pEntry->compLen = get4LE(ptr + CENSIZ);
-        pEntry->uncompLen = get4LE(ptr + CENLEN);
-        pEntry->compression = get2LE(ptr + CENHOW);
-        pEntry->modTime = get4LE(ptr + CENTIM);
-        pEntry->crc32 = get4LE(ptr + CENCRC);
-
-        /* These two are necessary for finding the mode of the file.
-         */
-        pEntry->versionMadeBy = get2LE(ptr + CENVEM);
-        if ((pEntry->versionMadeBy & 0xff00) != 0 &&
-                (pEntry->versionMadeBy & 0xff00) != CENVEM_UNIX)
-        {
-            LOGW("Incompatible \"version made by\": 0x%02x (at %d)\n",
-                    pEntry->versionMadeBy >> 8, i);
-            goto bail;
-        }
-        pEntry->externalFileAttributes = get4LE(ptr + CENATX);
-
-        // Perform pArchive->addr + localHdrOffset, ensuring that it won't
-        // overflow. This is needed because localHdrOffset is untrusted.
-        if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pArchive->addr,
-            (uintptr_t)localHdrOffset)) {
-            LOGW("Integer overflow adding in parseZipArchive\n");
-            goto bail;
-        }
-        if ((uintptr_t)localHdr + LOCHDR >
-            (uintptr_t)pArchive->addr + pArchive->length) {
-            LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i);
-            goto bail;
-        }
-        if (get4LE(localHdr) != LOCSIG) {
-            LOGW("Missed a local header sig (at %d)\n", i);
-            goto bail;
-        }
-        pEntry->offset = localHdrOffset + LOCHDR
-            + get2LE(localHdr + LOCNAM) + get2LE(localHdr + LOCEXT);
-        if (!safe_add(NULL, pEntry->offset, pEntry->compLen)) {
-            LOGW("Integer overflow adding in parseZipArchive\n");
-            goto bail;
-        }
-        if ((size_t)pEntry->offset + pEntry->compLen > pArchive->length) {
-            LOGW("Data ran off the end (at %d)\n", i);
-            goto bail;
-        }
-
-#if !SORT_ENTRIES
-        /* Add to hash table; no need to lock here.
-         * Can't do this now if we're sorting, because entries
-         * will move around.
-         */
-        addEntryToHashTable(pArchive->pHash, pEntry);
-#endif
-
-        //dumpEntry(pEntry);
-        ptr += CENHDR + fileNameLen + extraLen + commentLen;
-    }
-
-#if SORT_ENTRIES
-    /* If we're sorting, we have to wait until all entries
-     * are in their final places, otherwise the pointers will
-     * probably point to the wrong things.
-     */
-    for (i = 0; i < numEntries; i++) {
-        /* Add to hash table; no need to lock here.
-         */
-        addEntryToHashTable(pArchive->pHash, &pArchive->pEntries[i]);
-    }
-#endif
-
-    result = true;
-
-bail:
-    if (!result) {
-        mzHashTableFree(pArchive->pHash);
-        pArchive->pHash = NULL;
-    }
-    return result;
-}
-
-/*
- * Open a Zip archive and scan out the contents.
- *
- * The easiest way to do this is to mmap() the whole thing and do the
- * traditional backward scan for central directory.  Since the EOCD is
- * a relatively small bit at the end, we should end up only touching a
- * small set of pages.
- *
- * This will be called on non-Zip files, especially during startup, so
- * we don't want to be too noisy about failures.  (Do we want a "quiet"
- * flag?)
- *
- * On success, we fill out the contents of "pArchive".
- */
-int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive)
-{
-    int err;
-
-    if (length < ENDHDR) {
-        err = -1;
-        LOGW("Archive %p is too small to be zip (%zd)\n", pArchive, length);
-        goto bail;
-    }
-
-    pArchive->addr = addr;
-    pArchive->length = length;
-
-    if (!parseZipArchive(pArchive)) {
-        err = -1;
-        LOGW("Parsing archive %p failed\n", pArchive);
-        goto bail;
-    }
-
-    err = 0;
-
-bail:
-    if (err != 0)
-        mzCloseZipArchive(pArchive);
-    return err;
-}
-
-/*
- * Close a ZipArchive, closing the file and freeing the contents.
- *
- * NOTE: the ZipArchive may not have been fully created.
- */
-void mzCloseZipArchive(ZipArchive* pArchive)
-{
-    LOGV("Closing archive %p\n", pArchive);
-
-    free(pArchive->pEntries);
-
-    mzHashTableFree(pArchive->pHash);
-
-    pArchive->pHash = NULL;
-    pArchive->pEntries = NULL;
-}
-
-/*
- * Find a matching entry.
- *
- * Returns NULL if no matching entry found.
- */
-const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
-        const char* entryName)
-{
-    unsigned int itemHash = computeHash(entryName, strlen(entryName));
-
-    return (const ZipEntry*)mzHashTableLookup(pArchive->pHash,
-                itemHash, (char*) entryName, hashcmpZipName, false);
-}
-
-/*
- * Return true if the entry is a symbolic link.
- */
-static bool mzIsZipEntrySymlink(const ZipEntry* pEntry)
-{
-    if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) {
-        return S_ISLNK(pEntry->externalFileAttributes >> 16);
-    }
-    return false;
-}
-
-/* Call processFunction on the uncompressed data of a STORED entry.
- */
-static bool processStoredEntry(const ZipArchive *pArchive,
-    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
-    void *cookie)
-{
-    return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie);
-}
-
-static bool processDeflatedEntry(const ZipArchive *pArchive,
-    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
-    void *cookie)
-{
-    long result = -1;
-    unsigned char procBuf[32 * 1024];
-    z_stream zstream;
-    int zerr;
-    long compRemaining;
-
-    compRemaining = pEntry->compLen;
-
-    /*
-     * Initialize the zlib stream.
-     */
-    memset(&zstream, 0, sizeof(zstream));
-    zstream.zalloc = Z_NULL;
-    zstream.zfree = Z_NULL;
-    zstream.opaque = Z_NULL;
-    zstream.next_in = pArchive->addr + pEntry->offset;
-    zstream.avail_in = pEntry->compLen;
-    zstream.next_out = (Bytef*) procBuf;
-    zstream.avail_out = sizeof(procBuf);
-    zstream.data_type = Z_UNKNOWN;
-
-    /*
-     * Use the undocumented "negative window bits" feature to tell zlib
-     * that there's no zlib header waiting for it.
-     */
-    zerr = inflateInit2(&zstream, -MAX_WBITS);
-    if (zerr != Z_OK) {
-        if (zerr == Z_VERSION_ERROR) {
-            LOGE("Installed zlib is not compatible with linked version (%s)\n",
-                ZLIB_VERSION);
-        } else {
-            LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
-        }
-        goto bail;
-    }
-
-    /*
-     * Loop while we have data.
-     */
-    do {
-        /* uncompress the data */
-        zerr = inflate(&zstream, Z_NO_FLUSH);
-        if (zerr != Z_OK && zerr != Z_STREAM_END) {
-            LOGW("zlib inflate call failed (zerr=%d)\n", zerr);
-            goto z_bail;
-        }
-
-        /* write when we're full or when we're done */
-        if (zstream.avail_out == 0 ||
-            (zerr == Z_STREAM_END && zstream.avail_out != sizeof(procBuf)))
-        {
-            long procSize = zstream.next_out - procBuf;
-            LOGVV("+++ processing %d bytes\n", (int) procSize);
-            bool ret = processFunction(procBuf, procSize, cookie);
-            if (!ret) {
-                LOGW("Process function elected to fail (in inflate)\n");
-                goto z_bail;
-            }
-
-            zstream.next_out = procBuf;
-            zstream.avail_out = sizeof(procBuf);
-        }
-    } while (zerr == Z_OK);
-
-    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
-
-    // success!
-    result = zstream.total_out;
-
-z_bail:
-    inflateEnd(&zstream);        /* free up any allocated structures */
-
-bail:
-    if (result != pEntry->uncompLen) {
-        if (result != -1)        // error already shown?
-            LOGW("Size mismatch on inflated file (%ld vs %ld)\n",
-                result, pEntry->uncompLen);
-        return false;
-    }
-    return true;
-}
-
-/*
- * Stream the uncompressed data through the supplied function,
- * passing cookie to it each time it gets called.  processFunction
- * may be called more than once.
- *
- * If processFunction returns false, the operation is abandoned and
- * mzProcessZipEntryContents() immediately returns false.
- *
- * This is useful for calculating the hash of an entry's uncompressed contents.
- */
-bool mzProcessZipEntryContents(const ZipArchive *pArchive,
-    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
-    void *cookie)
-{
-    bool ret = false;
-
-    switch (pEntry->compression) {
-    case STORED:
-        ret = processStoredEntry(pArchive, pEntry, processFunction, cookie);
-        break;
-    case DEFLATED:
-        ret = processDeflatedEntry(pArchive, pEntry, processFunction, cookie);
-        break;
-    default:
-        LOGE("Unsupported compression type %d for entry '%s'\n",
-                pEntry->compression, pEntry->fileName);
-        break;
-    }
-
-    return ret;
-}
-
-typedef struct {
-    char *buf;
-    int bufLen;
-} CopyProcessArgs;
-
-static bool copyProcessFunction(const unsigned char *data, int dataLen,
-        void *cookie)
-{
-    CopyProcessArgs *args = (CopyProcessArgs *)cookie;
-    if (dataLen <= args->bufLen) {
-        memcpy(args->buf, data, dataLen);
-        args->buf += dataLen;
-        args->bufLen -= dataLen;
-        return true;
-    }
-    return false;
-}
-
-/*
- * Read an entry into a buffer allocated by the caller.
- */
-bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
-        char *buf, int bufLen)
-{
-    CopyProcessArgs args;
-    bool ret;
-
-    args.buf = buf;
-    args.bufLen = bufLen;
-    ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction,
-            (void *)&args);
-    if (!ret) {
-        LOGE("Can't extract entry to buffer.\n");
-        return false;
-    }
-    return true;
-}
-
-static bool writeProcessFunction(const unsigned char *data, int dataLen,
-                                 void *cookie)
-{
-    int fd = (int)(intptr_t)cookie;
-    if (dataLen == 0) {
-        return true;
-    }
-    ssize_t soFar = 0;
-    while (true) {
-        ssize_t n = TEMP_FAILURE_RETRY(write(fd, data+soFar, dataLen-soFar));
-        if (n <= 0) {
-            LOGE("Error writing %zd bytes from zip file from %p: %s\n",
-                 dataLen-soFar, data+soFar, strerror(errno));
-            return false;
-        } else if (n > 0) {
-            soFar += n;
-            if (soFar == dataLen) return true;
-            if (soFar > dataLen) {
-                LOGE("write overrun?  (%zd bytes instead of %d)\n",
-                     soFar, dataLen);
-                return false;
-            }
-        }
-    }
-}
-
-/*
- * Uncompress "pEntry" in "pArchive" to "fd" at the current offset.
- */
-bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
-    const ZipEntry *pEntry, int fd)
-{
-    bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
-                                         (void*)(intptr_t)fd);
-    if (!ret) {
-        LOGE("Can't extract entry to file.\n");
-        return false;
-    }
-    return true;
-}
-
-typedef struct {
-    unsigned char* buffer;
-    long len;
-} BufferExtractCookie;
-
-static bool bufferProcessFunction(const unsigned char *data, int dataLen,
-    void *cookie) {
-    BufferExtractCookie *bec = (BufferExtractCookie*)cookie;
-
-    memmove(bec->buffer, data, dataLen);
-    bec->buffer += dataLen;
-    bec->len -= dataLen;
-
-    return true;
-}
-
-/*
- * Uncompress "pEntry" in "pArchive" to buffer, which must be large
- * enough to hold mzGetZipEntryUncomplen(pEntry) bytes.
- */
-bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive,
-    const ZipEntry *pEntry, unsigned char *buffer)
-{
-    BufferExtractCookie bec;
-    bec.buffer = buffer;
-    bec.len = mzGetZipEntryUncompLen(pEntry);
-
-    bool ret = mzProcessZipEntryContents(pArchive, pEntry,
-        bufferProcessFunction, (void*)&bec);
-    if (!ret || bec.len != 0) {
-        LOGE("Can't extract entry to memory buffer.\n");
-        return false;
-    }
-    return true;
-}
-
-
-/* Helper state to make path translation easier and less malloc-happy.
- */
-typedef struct {
-    const char *targetDir;
-    const char *zipDir;
-    char *buf;
-    int targetDirLen;
-    int zipDirLen;
-    int bufLen;
-} MzPathHelper;
-
-/* Given the values of targetDir and zipDir in the helper,
- * return the target filename of the provided entry.
- * The helper must be initialized first.
- */
-static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry)
-{
-    int needLen;
-    bool firstTime = (helper->buf == NULL);
-
-    /* target file <-- targetDir + / + entry[zipDirLen:]
-     */
-    needLen = helper->targetDirLen + 1 +
-            pEntry->fileNameLen - helper->zipDirLen + 1;
-    if (needLen > helper->bufLen) {
-        char *newBuf;
-
-        needLen *= 2;
-        newBuf = (char *)realloc(helper->buf, needLen);
-        if (newBuf == NULL) {
-            return NULL;
-        }
-        helper->buf = newBuf;
-        helper->bufLen = needLen;
-    }
-
-    /* Every path will start with the target path and a slash.
-     */
-    if (firstTime) {
-        char *p = helper->buf;
-        memcpy(p, helper->targetDir, helper->targetDirLen);
-        p += helper->targetDirLen;
-        if (p == helper->buf || p[-1] != '/') {
-            helper->targetDirLen += 1;
-            *p++ = '/';
-        }
-    }
-
-    /* Replace the custom part of the path with the appropriate
-     * part of the entry's path.
-     */
-    char *epath = helper->buf + helper->targetDirLen;
-    memcpy(epath, pEntry->fileName + helper->zipDirLen,
-            pEntry->fileNameLen - helper->zipDirLen);
-    epath += pEntry->fileNameLen - helper->zipDirLen;
-    *epath = '\0';
-
-    return helper->buf;
-}
-
-/*
- * Inflate all entries under zipDir to the directory specified by
- * targetDir, which must exist and be a writable directory.
- *
- * The immediate children of zipDir will become the immediate
- * children of targetDir; e.g., if the archive contains the entries
- *
- *     a/b/c/one
- *     a/b/c/two
- *     a/b/c/d/three
- *
- * and mzExtractRecursive(a, "a/b/c", "/tmp") is called, the resulting
- * files will be
- *
- *     /tmp/one
- *     /tmp/two
- *     /tmp/d/three
- *
- * Returns true on success, false on failure.
- */
-bool mzExtractRecursive(const ZipArchive *pArchive,
-                        const char *zipDir, const char *targetDir,
-                        const struct utimbuf *timestamp,
-                        void (*callback)(const char *fn, void *), void *cookie,
-                        struct selabel_handle *sehnd)
-{
-    if (zipDir[0] == '/') {
-        LOGE("mzExtractRecursive(): zipDir must be a relative path.\n");
-        return false;
-    }
-    if (targetDir[0] != '/') {
-        LOGE("mzExtractRecursive(): targetDir must be an absolute path.\n");
-        return false;
-    }
-
-    unsigned int zipDirLen;
-    char *zpath;
-
-    zipDirLen = strlen(zipDir);
-    zpath = (char *)malloc(zipDirLen + 2);
-    if (zpath == NULL) {
-        LOGE("Can't allocate %d bytes for zip path\n", zipDirLen + 2);
-        return false;
-    }
-    /* If zipDir is empty, we'll extract the entire zip file.
-     * Otherwise, canonicalize the path.
-     */
-    if (zipDirLen > 0) {
-        /* Make sure there's (hopefully, exactly one) slash at the
-         * end of the path.  This way we don't need to worry about
-         * accidentally extracting "one/twothree" when a path like
-         * "one/two" is specified.
-         */
-        memcpy(zpath, zipDir, zipDirLen);
-        if (zpath[zipDirLen-1] != '/') {
-            zpath[zipDirLen++] = '/';
-        }
-    }
-    zpath[zipDirLen] = '\0';
-
-    /* Set up the helper structure that we'll use to assemble paths.
-     */
-    MzPathHelper helper;
-    helper.targetDir = targetDir;
-    helper.targetDirLen = strlen(helper.targetDir);
-    helper.zipDir = zpath;
-    helper.zipDirLen = strlen(helper.zipDir);
-    helper.buf = NULL;
-    helper.bufLen = 0;
-
-    /* Walk through the entries and extract anything whose path begins
-     * with zpath.
-    //TODO: since the entries are sorted, binary search for the first match
-    //      and stop after the first non-match.
-     */
-    unsigned int i;
-    bool seenMatch = false;
-    int ok = true;
-    int extractCount = 0;
-    for (i = 0; i < pArchive->numEntries; i++) {
-        ZipEntry *pEntry = pArchive->pEntries + i;
-        if (pEntry->fileNameLen < zipDirLen) {
-       //TODO: look out for a single empty directory entry that matches zpath, but
-       //      missing the trailing slash.  Most zip files seem to include
-       //      the trailing slash, but I think it's legal to leave it off.
-       //      e.g., zpath "a/b/", entry "a/b", with no children of the entry.
-            /* No chance of matching.
-             */
-#if SORT_ENTRIES
-            if (seenMatch) {
-                /* Since the entries are sorted, we can give up
-                 * on the first mismatch after the first match.
-                 */
-                break;
-            }
-#endif
-            continue;
-        }
-        /* If zpath is empty, this strncmp() will match everything,
-         * which is what we want.
-         */
-        if (strncmp(pEntry->fileName, zpath, zipDirLen) != 0) {
-#if SORT_ENTRIES
-            if (seenMatch) {
-                /* Since the entries are sorted, we can give up
-                 * on the first mismatch after the first match.
-                 */
-                break;
-            }
-#endif
-            continue;
-        }
-        /* This entry begins with zipDir, so we'll extract it.
-         */
-        seenMatch = true;
-
-        /* Find the target location of the entry.
-         */
-        const char *targetFile = targetEntryPath(&helper, pEntry);
-        if (targetFile == NULL) {
-            LOGE("Can't assemble target path for \"%.*s\"\n",
-                    pEntry->fileNameLen, pEntry->fileName);
-            ok = false;
-            break;
-        }
-
-#define UNZIP_DIRMODE 0755
-#define UNZIP_FILEMODE 0644
-        /*
-         * Create the file or directory. We ignore directory entries
-         * because we recursively create paths to each file entry we encounter
-         * in the zip archive anyway.
-         *
-         * NOTE: A "directory entry" in a zip archive is just a zero length
-         * entry that ends in a "/". They're not mandatory and many tools get
-         * rid of them. We need to process them only if we want to preserve
-         * empty directories from the archive.
-         */
-        if (pEntry->fileName[pEntry->fileNameLen-1] != '/') {
-            /* This is not a directory.  First, make sure that
-             * the containing directory exists.
-             */
-            int ret = dirCreateHierarchy(
-                    targetFile, UNZIP_DIRMODE, timestamp, true, sehnd);
-            if (ret != 0) {
-                LOGE("Can't create containing directory for \"%s\": %s\n",
-                        targetFile, strerror(errno));
-                ok = false;
-                break;
-            }
-
-            /*
-             * The entry is a regular file or a symlink. Open the target for writing.
-             *
-             * TODO: This behavior for symlinks seems rather bizarre. For a
-             * symlink foo/bar/baz -> foo/tar/taz, we will create a file called
-             * "foo/bar/baz" whose contents are the literal "foo/tar/taz". We
-             * warn about this for now and preserve older behavior.
-             */
-            if (mzIsZipEntrySymlink(pEntry)) {
-                LOGE("Symlink entry \"%.*s\" will be output as a regular file.",
-                     pEntry->fileNameLen, pEntry->fileName);
-            }
-
-            char *secontext = NULL;
-
-            if (sehnd) {
-                selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE);
-                setfscreatecon(secontext);
-            }
-
-            int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC,
-                UNZIP_FILEMODE);
-
-            if (secontext) {
-                freecon(secontext);
-                setfscreatecon(NULL);
-            }
-
-            if (fd < 0) {
-                LOGE("Can't create target file \"%s\": %s\n",
-                        targetFile, strerror(errno));
-                ok = false;
-                break;
-            }
-
-            bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd);
-            if (ok) {
-                ok = (fsync(fd) == 0);
-            }
-            if (close(fd) != 0) {
-                ok = false;
-            }
-            if (!ok) {
-                LOGE("Error extracting \"%s\"\n", targetFile);
-                ok = false;
-                break;
-            }
-
-            if (timestamp != NULL && utime(targetFile, timestamp)) {
-                LOGE("Error touching \"%s\"\n", targetFile);
-                ok = false;
-                break;
-            }
-
-            LOGV("Extracted file \"%s\"\n", targetFile);
-            ++extractCount;
-        }
-
-        if (callback != NULL) callback(targetFile, cookie);
-    }
-
-    LOGV("Extracted %d file(s)\n", extractCount);
-
-    free(helper.buf);
-    free(zpath);
-
-    return ok;
-}
diff --git a/minzip/Zip.h b/minzip/Zip.h
deleted file mode 100644
index 86d8db5..0000000
--- a/minzip/Zip.h
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * Simple Zip archive support.
- */
-#ifndef _MINZIP_ZIP
-#define _MINZIP_ZIP
-
-#include "inline_magic.h"
-
-#include <stdlib.h>
-#include <utime.h>
-
-#include "Hash.h"
-#include "SysUtil.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <selinux/selinux.h>
-#include <selinux/label.h>
-
-/*
- * One entry in the Zip archive.  Treat this as opaque -- use accessors below.
- *
- * TODO: we're now keeping the pages mapped so we don't have to copy the
- * filename.  We can change the accessors to retrieve the various pieces
- * directly from the source file instead of copying them out, for a very
- * slight speed hit and a modest reduction in memory usage.
- */
-typedef struct ZipEntry {
-    unsigned int fileNameLen;
-    const char*  fileName;       // not null-terminated
-    long         offset;
-    long         compLen;
-    long         uncompLen;
-    int          compression;
-    long         modTime;
-    long         crc32;
-    int          versionMadeBy;
-    long         externalFileAttributes;
-} ZipEntry;
-
-/*
- * One Zip archive.  Treat as opaque.
- */
-typedef struct ZipArchive {
-    unsigned int   numEntries;
-    ZipEntry*      pEntries;
-    HashTable*     pHash;          // maps file name to ZipEntry
-    unsigned char* addr;
-    size_t         length;
-} ZipArchive;
-
-/*
- * Represents a non-NUL-terminated string,
- * which is how entry names are stored.
- */
-typedef struct {
-    const char *str;
-    size_t len;
-} UnterminatedString;
-
-/*
- * Open a Zip archive.
- *
- * On success, returns 0 and populates "pArchive".  Returns nonzero errno
- * value on failure.
- */
-int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive);
-
-/*
- * Close archive, releasing resources associated with it.
- *
- * Depending on the implementation this could unmap pages used by classes
- * stored in a Jar.  This should only be done after unloading classes.
- */
-void mzCloseZipArchive(ZipArchive* pArchive);
-
-
-/*
- * Find an entry in the Zip archive, by name.
- */
-const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
-        const char* entryName);
-
-INLINE long mzGetZipEntryOffset(const ZipEntry* pEntry) {
-    return pEntry->offset;
-}
-INLINE long mzGetZipEntryUncompLen(const ZipEntry* pEntry) {
-    return pEntry->uncompLen;
-}
-
-/*
- * Type definition for the callback function used by
- * mzProcessZipEntryContents().
- */
-typedef bool (*ProcessZipEntryContentsFunction)(const unsigned char *data,
-    int dataLen, void *cookie);
-
-/*
- * Stream the uncompressed data through the supplied function,
- * passing cookie to it each time it gets called.  processFunction
- * may be called more than once.
- *
- * If processFunction returns false, the operation is abandoned and
- * mzProcessZipEntryContents() immediately returns false.
- *
- * This is useful for calculating the hash of an entry's uncompressed contents.
- */
-bool mzProcessZipEntryContents(const ZipArchive *pArchive,
-    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
-    void *cookie);
-
-/*
- * Read an entry into a buffer allocated by the caller.
- */
-bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
-        char* buf, int bufLen);
-
-/*
- * Inflate and write an entry to a file.
- */
-bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
-    const ZipEntry *pEntry, int fd);
-
-/*
- * Inflate and write an entry to a memory buffer, which must be long
- * enough to hold mzGetZipEntryUncomplen(pEntry) bytes.
- */
-bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive,
-    const ZipEntry *pEntry, unsigned char* buffer);
-
-/*
- * Inflate all files under zipDir to the directory specified by
- * targetDir, which must exist and be a writable directory.
- *
- * Directory entries and symlinks are not extracted.
- *
- *
- * The immediate children of zipDir will become the immediate
- * children of targetDir; e.g., if the archive contains the entries
- *
- *     a/b/c/one
- *     a/b/c/two
- *     a/b/c/d/three
- *
- * and mzExtractRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
- * files will be
- *
- *     /tmp/one
- *     /tmp/two
- *     /tmp/d/three
- *
- * If timestamp is non-NULL, file timestamps will be set accordingly.
- *
- * If callback is non-NULL, it will be invoked with each unpacked file.
- *
- * Returns true on success, false on failure.
- */
-bool mzExtractRecursive(const ZipArchive *pArchive,
-        const char *zipDir, const char *targetDir,
-        const struct utimbuf *timestamp,
-        void (*callback)(const char *fn, void*), void *cookie,
-        struct selabel_handle *sehnd);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /*_MINZIP_ZIP*/
diff --git a/mounts.cpp b/mounts.cpp
new file mode 100644
index 0000000..f23376b
--- /dev/null
+++ b/mounts.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 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 agreed 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 "mounts.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+
+#include <string>
+#include <vector>
+
+struct MountedVolume {
+    std::string device;
+    std::string mount_point;
+    std::string filesystem;
+    std::string flags;
+};
+
+std::vector<MountedVolume*> g_mounts_state;
+
+bool scan_mounted_volumes() {
+    for (size_t i = 0; i < g_mounts_state.size(); ++i) {
+        delete g_mounts_state[i];
+    }
+    g_mounts_state.clear();
+
+    // Open and read mount table entries.
+    FILE* fp = setmntent("/proc/mounts", "re");
+    if (fp == NULL) {
+        return false;
+    }
+    mntent* e;
+    while ((e = getmntent(fp)) != NULL) {
+        MountedVolume* v = new MountedVolume;
+        v->device = e->mnt_fsname;
+        v->mount_point = e->mnt_dir;
+        v->filesystem = e->mnt_type;
+        v->flags = e->mnt_opts;
+        g_mounts_state.push_back(v);
+    }
+    endmntent(fp);
+    return true;
+}
+
+MountedVolume* find_mounted_volume_by_device(const char* device) {
+    for (size_t i = 0; i < g_mounts_state.size(); ++i) {
+        if (g_mounts_state[i]->device == device) return g_mounts_state[i];
+    }
+    return nullptr;
+}
+
+MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) {
+    for (size_t i = 0; i < g_mounts_state.size(); ++i) {
+        if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i];
+    }
+    return nullptr;
+}
+
+int unmount_mounted_volume(MountedVolume* volume) {
+    // Intentionally pass the empty string to umount if the caller tries
+    // to unmount a volume they already unmounted using this
+    // function.
+    std::string mount_point = volume->mount_point;
+    volume->mount_point.clear();
+    return umount(mount_point.c_str());
+}
+
+int remount_read_only(MountedVolume* volume) {
+    return mount(volume->device.c_str(), volume->mount_point.c_str(), volume->filesystem.c_str(),
+                 MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0);
+}
diff --git a/minzip/inline_magic.h b/mounts.h
similarity index 64%
copy from minzip/inline_magic.h
copy to mounts.h
index 59c659f..1b76703 100644
--- a/minzip/inline_magic.h
+++ b/mounts.h
@@ -14,13 +14,19 @@
  * limitations under the License.
  */
 
-#ifndef MINZIP_INLINE_MAGIC_H_
-#define MINZIP_INLINE_MAGIC_H_
+#ifndef MOUNTS_H_
+#define MOUNTS_H_
 
-#ifndef MINZIP_GENERATE_INLINES
-#define INLINE extern inline __attribute((__gnu_inline__))
-#else
-#define INLINE
+struct MountedVolume;
+
+bool scan_mounted_volumes();
+
+MountedVolume* find_mounted_volume_by_device(const char* device);
+
+MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point);
+
+int unmount_mounted_volume(MountedVolume* volume);
+
+int remount_read_only(MountedVolume* volume);
+
 #endif
-
-#endif  // MINZIP_INLINE_MAGIC_H_
diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk
deleted file mode 100644
index b7d35c2..0000000
--- a/mtdutils/Android.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-	mtdutils.c \
-	mounts.c
-
-LOCAL_MODULE := libmtdutils
-LOCAL_CLANG := true
-
-include $(BUILD_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := flash_image.c
-LOCAL_MODULE := flash_image
-LOCAL_MODULE_TAGS := eng
-LOCAL_STATIC_LIBRARIES := libmtdutils
-LOCAL_SHARED_LIBRARIES := libcutils liblog libc
-include $(BUILD_EXECUTABLE)
diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c
deleted file mode 100644
index 36ffa13..0000000
--- a/mtdutils/flash_image.c
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2008 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 agreed 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 <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "cutils/log.h"
-#include "mtdutils.h"
-
-#ifdef LOG_TAG
-#undef LOG_TAG
-#endif
-#define LOG_TAG "flash_image"
-
-#define HEADER_SIZE 2048  // size of header to compare for equality
-
-void die(const char *msg, ...) {
-    int err = errno;
-    va_list args;
-    va_start(args, msg);
-    char buf[1024];
-    vsnprintf(buf, sizeof(buf), msg, args);
-    va_end(args);
-
-    if (err != 0) {
-        strlcat(buf, ": ", sizeof(buf));
-        strlcat(buf, strerror(err), sizeof(buf));
-    }
-
-    fprintf(stderr, "%s\n", buf);
-    ALOGE("%s\n", buf);
-    exit(1);
-}
-
-/* Read an image file and write it to a flash partition. */
-
-int main(int argc, char **argv) {
-    const MtdPartition *ptn;
-    MtdWriteContext *write;
-    void *data;
-    unsigned sz;
-
-    if (argc != 3) {
-        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
-        return 2;
-    }
-
-    if (mtd_scan_partitions() <= 0) die("error scanning partitions");
-    const MtdPartition *partition = mtd_find_partition_by_name(argv[1]);
-    if (partition == NULL) die("can't find %s partition", argv[1]);
-
-    // If the first part of the file matches the partition, skip writing
-
-    int fd = open(argv[2], O_RDONLY);
-    if (fd < 0) die("error opening %s", argv[2]);
-
-    char header[HEADER_SIZE];
-    int headerlen = TEMP_FAILURE_RETRY(read(fd, header, sizeof(header)));
-    if (headerlen <= 0) die("error reading %s header", argv[2]);
-
-    MtdReadContext *in = mtd_read_partition(partition);
-    if (in == NULL) {
-        ALOGW("error opening %s: %s\n", argv[1], strerror(errno));
-        // just assume it needs re-writing
-    } else {
-        char check[HEADER_SIZE];
-        int checklen = mtd_read_data(in, check, sizeof(check));
-        if (checklen <= 0) {
-            ALOGW("error reading %s: %s\n", argv[1], strerror(errno));
-            // just assume it needs re-writing
-        } else if (checklen == headerlen && !memcmp(header, check, headerlen)) {
-            ALOGI("header is the same, not flashing %s\n", argv[1]);
-            return 0;
-        }
-        mtd_read_close(in);
-    }
-
-    // Skip the header (we'll come back to it), write everything else
-    ALOGI("flashing %s from %s\n", argv[1], argv[2]);
-
-    MtdWriteContext *out = mtd_write_partition(partition);
-    if (out == NULL) die("error writing %s", argv[1]);
-
-    char buf[HEADER_SIZE];
-    memset(buf, 0, headerlen);
-    int wrote = mtd_write_data(out, buf, headerlen);
-    if (wrote != headerlen) die("error writing %s", argv[1]);
-
-    int len;
-    while ((len = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf)))) > 0) {
-        wrote = mtd_write_data(out, buf, len);
-        if (wrote != len) die("error writing %s", argv[1]);
-    }
-    if (len < 0) die("error reading %s", argv[2]);
-
-    if (mtd_write_close(out)) die("error closing %s", argv[1]);
-
-    // Now come back and write the header last
-
-    out = mtd_write_partition(partition);
-    if (out == NULL) die("error re-opening %s", argv[1]);
-
-    wrote = mtd_write_data(out, header, headerlen);
-    if (wrote != headerlen) die("error re-writing %s", argv[1]);
-
-    // Need to write a complete block, so write the rest of the first block
-    size_t block_size;
-    if (mtd_partition_info(partition, NULL, &block_size, NULL))
-        die("error getting %s block size", argv[1]);
-
-    if (TEMP_FAILURE_RETRY(lseek(fd, headerlen, SEEK_SET)) != headerlen)
-        die("error rewinding %s", argv[2]);
-
-    int left = block_size - headerlen;
-    while (left < 0) left += block_size;
-    while (left > 0) {
-        len = TEMP_FAILURE_RETRY(read(fd, buf, left > (int)sizeof(buf) ? (int)sizeof(buf) : left));
-        if (len <= 0) die("error reading %s", argv[2]);
-        if (mtd_write_data(out, buf, len) != len)
-            die("error writing %s", argv[1]);
-        left -= len;
-    }
-
-    if (mtd_write_close(out)) die("error closing %s", argv[1]);
-    return 0;
-}
diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c
deleted file mode 100644
index 6a9b03d..0000000
--- a/mtdutils/mounts.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2007 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 agreed 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 <mntent.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <sys/mount.h>
-
-#include "mounts.h"
-
-struct MountedVolume {
-    const char *device;
-    const char *mount_point;
-    const char *filesystem;
-    const char *flags;
-};
-
-typedef struct {
-    MountedVolume *volumes;
-    int volumes_allocd;
-    int volume_count;
-} MountsState;
-
-static MountsState g_mounts_state = {
-    NULL,   // volumes
-    0,      // volumes_allocd
-    0       // volume_count
-};
-
-static inline void
-free_volume_internals(const MountedVolume *volume, int zero)
-{
-    free((char *)volume->device);
-    free((char *)volume->mount_point);
-    free((char *)volume->filesystem);
-    free((char *)volume->flags);
-    if (zero) {
-        memset((void *)volume, 0, sizeof(*volume));
-    }
-}
-
-#define PROC_MOUNTS_FILENAME   "/proc/mounts"
-
-int
-scan_mounted_volumes()
-{
-    FILE* fp;
-    struct mntent* mentry;
-
-    if (g_mounts_state.volumes == NULL) {
-        const int numv = 32;
-        MountedVolume *volumes = malloc(numv * sizeof(*volumes));
-        if (volumes == NULL) {
-            errno = ENOMEM;
-            return -1;
-        }
-        g_mounts_state.volumes = volumes;
-        g_mounts_state.volumes_allocd = numv;
-        memset(volumes, 0, numv * sizeof(*volumes));
-    } else {
-        /* Free the old volume strings.
-         */
-        int i;
-        for (i = 0; i < g_mounts_state.volume_count; i++) {
-            free_volume_internals(&g_mounts_state.volumes[i], 1);
-        }
-    }
-    g_mounts_state.volume_count = 0;
-
-    /* Open and read mount table entries. */
-    fp = setmntent(PROC_MOUNTS_FILENAME, "r");
-    if (fp == NULL) {
-        return -1;
-    }
-    while ((mentry = getmntent(fp)) != NULL) {
-        MountedVolume* v = &g_mounts_state.volumes[g_mounts_state.volume_count++];
-        v->device = strdup(mentry->mnt_fsname);
-        v->mount_point = strdup(mentry->mnt_dir);
-        v->filesystem = strdup(mentry->mnt_type);
-        v->flags = strdup(mentry->mnt_opts);
-    }
-    endmntent(fp);
-    return 0;
-}
-
-const MountedVolume *
-find_mounted_volume_by_device(const char *device)
-{
-    if (g_mounts_state.volumes != NULL) {
-        int i;
-        for (i = 0; i < g_mounts_state.volume_count; i++) {
-            MountedVolume *v = &g_mounts_state.volumes[i];
-            /* May be null if it was unmounted and we haven't rescanned.
-             */
-            if (v->device != NULL) {
-                if (strcmp(v->device, device) == 0) {
-                    return v;
-                }
-            }
-        }
-    }
-    return NULL;
-}
-
-const MountedVolume *
-find_mounted_volume_by_mount_point(const char *mount_point)
-{
-    if (g_mounts_state.volumes != NULL) {
-        int i;
-        for (i = 0; i < g_mounts_state.volume_count; i++) {
-            MountedVolume *v = &g_mounts_state.volumes[i];
-            /* May be null if it was unmounted and we haven't rescanned.
-             */
-            if (v->mount_point != NULL) {
-                if (strcmp(v->mount_point, mount_point) == 0) {
-                    return v;
-                }
-            }
-        }
-    }
-    return NULL;
-}
-
-int
-unmount_mounted_volume(const MountedVolume *volume)
-{
-    /* Intentionally pass NULL to umount if the caller tries
-     * to unmount a volume they already unmounted using this
-     * function.
-     */
-    int ret = umount(volume->mount_point);
-    if (ret == 0) {
-        free_volume_internals(volume, 1);
-        return 0;
-    }
-    return ret;
-}
-
-int
-remount_read_only(const MountedVolume* volume)
-{
-    return mount(volume->device, volume->mount_point, volume->filesystem,
-                 MS_NOATIME | MS_NODEV | MS_NODIRATIME |
-                 MS_RDONLY | MS_REMOUNT, 0);
-}
diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h
deleted file mode 100644
index d721355..0000000
--- a/mtdutils/mounts.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2007 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 agreed 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 MTDUTILS_MOUNTS_H_
-#define MTDUTILS_MOUNTS_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct MountedVolume MountedVolume;
-
-int scan_mounted_volumes(void);
-
-const MountedVolume *find_mounted_volume_by_device(const char *device);
-
-const MountedVolume *
-find_mounted_volume_by_mount_point(const char *mount_point);
-
-int unmount_mounted_volume(const MountedVolume *volume);
-
-int remount_read_only(const MountedVolume* volume);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif  // MTDUTILS_MOUNTS_H_
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
deleted file mode 100644
index cd4f52c..0000000
--- a/mtdutils/mtdutils.c
+++ /dev/null
@@ -1,561 +0,0 @@
-/*
- * Copyright (C) 2007 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 agreed 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <sys/mount.h>  // for _IOW, _IOR, mount()
-#include <sys/stat.h>
-#include <mtd/mtd-user.h>
-#undef NDEBUG
-#include <assert.h>
-
-#include "mtdutils.h"
-
-struct MtdPartition {
-    int device_index;
-    unsigned int size;
-    unsigned int erase_size;
-    char *name;
-};
-
-struct MtdReadContext {
-    const MtdPartition *partition;
-    char *buffer;
-    size_t consumed;
-    int fd;
-};
-
-struct MtdWriteContext {
-    const MtdPartition *partition;
-    char *buffer;
-    size_t stored;
-    int fd;
-
-    off_t* bad_block_offsets;
-    int bad_block_alloc;
-    int bad_block_count;
-};
-
-typedef struct {
-    MtdPartition *partitions;
-    int partitions_allocd;
-    int partition_count;
-} MtdState;
-
-static MtdState g_mtd_state = {
-    NULL,   // partitions
-    0,      // partitions_allocd
-    -1      // partition_count
-};
-
-#define MTD_PROC_FILENAME   "/proc/mtd"
-
-int
-mtd_scan_partitions()
-{
-    char buf[2048];
-    const char *bufp;
-    int fd;
-    int i;
-    ssize_t nbytes;
-
-    if (g_mtd_state.partitions == NULL) {
-        const int nump = 32;
-        MtdPartition *partitions = malloc(nump * sizeof(*partitions));
-        if (partitions == NULL) {
-            errno = ENOMEM;
-            return -1;
-        }
-        g_mtd_state.partitions = partitions;
-        g_mtd_state.partitions_allocd = nump;
-        memset(partitions, 0, nump * sizeof(*partitions));
-    }
-    g_mtd_state.partition_count = 0;
-
-    /* Initialize all of the entries to make things easier later.
-     * (Lets us handle sparsely-numbered partitions, which
-     * may not even be possible.)
-     */
-    for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
-        MtdPartition *p = &g_mtd_state.partitions[i];
-        if (p->name != NULL) {
-            free(p->name);
-            p->name = NULL;
-        }
-        p->device_index = -1;
-    }
-
-    /* Open and read the file contents.
-     */
-    fd = open(MTD_PROC_FILENAME, O_RDONLY);
-    if (fd < 0) {
-        goto bail;
-    }
-    nbytes = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf) - 1));
-    close(fd);
-    if (nbytes < 0) {
-        goto bail;
-    }
-    buf[nbytes] = '\0';
-
-    /* Parse the contents of the file, which looks like:
-     *
-     *     # cat /proc/mtd
-     *     dev:    size   erasesize  name
-     *     mtd0: 00080000 00020000 "bootloader"
-     *     mtd1: 00400000 00020000 "mfg_and_gsm"
-     *     mtd2: 00400000 00020000 "0000000c"
-     *     mtd3: 00200000 00020000 "0000000d"
-     *     mtd4: 04000000 00020000 "system"
-     *     mtd5: 03280000 00020000 "userdata"
-     */
-    bufp = buf;
-    while (nbytes > 0) {
-        int mtdnum, mtdsize, mtderasesize;
-        int matches;
-        char mtdname[64];
-        mtdname[0] = '\0';
-        mtdnum = -1;
-
-        matches = sscanf(bufp, "mtd%d: %x %x \"%63[^\"]",
-                &mtdnum, &mtdsize, &mtderasesize, mtdname);
-        /* This will fail on the first line, which just contains
-         * column headers.
-         */
-        if (matches == 4) {
-            MtdPartition *p = &g_mtd_state.partitions[mtdnum];
-            p->device_index = mtdnum;
-            p->size = mtdsize;
-            p->erase_size = mtderasesize;
-            p->name = strdup(mtdname);
-            if (p->name == NULL) {
-                errno = ENOMEM;
-                goto bail;
-            }
-            g_mtd_state.partition_count++;
-        }
-
-        /* Eat the line.
-         */
-        while (nbytes > 0 && *bufp != '\n') {
-            bufp++;
-            nbytes--;
-        }
-        if (nbytes > 0) {
-            bufp++;
-            nbytes--;
-        }
-    }
-
-    return g_mtd_state.partition_count;
-
-bail:
-    // keep "partitions" around so we can free the names on a rescan.
-    g_mtd_state.partition_count = -1;
-    return -1;
-}
-
-const MtdPartition *
-mtd_find_partition_by_name(const char *name)
-{
-    if (g_mtd_state.partitions != NULL) {
-        int i;
-        for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
-            MtdPartition *p = &g_mtd_state.partitions[i];
-            if (p->device_index >= 0 && p->name != NULL) {
-                if (strcmp(p->name, name) == 0) {
-                    return p;
-                }
-            }
-        }
-    }
-    return NULL;
-}
-
-int
-mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
-        const char *filesystem, int read_only)
-{
-    const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
-    char devname[64];
-    int rv = -1;
-
-    sprintf(devname, "/dev/block/mtdblock%d", partition->device_index);
-    if (!read_only) {
-        rv = mount(devname, mount_point, filesystem, flags, NULL);
-    }
-    if (read_only || rv < 0) {
-        rv = mount(devname, mount_point, filesystem, flags | MS_RDONLY, 0);
-        if (rv < 0) {
-            printf("Failed to mount %s on %s: %s\n",
-                    devname, mount_point, strerror(errno));
-        } else {
-            printf("Mount %s on %s read-only\n", devname, mount_point);
-        }
-    }
-#if 1   //TODO: figure out why this is happening; remove include of stat.h
-    if (rv >= 0) {
-        /* For some reason, the x bits sometimes aren't set on the root
-         * of mounted volumes.
-         */
-        struct stat st;
-        rv = stat(mount_point, &st);
-        if (rv < 0) {
-            return rv;
-        }
-        mode_t new_mode = st.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
-        if (new_mode != st.st_mode) {
-printf("Fixing execute permissions for %s\n", mount_point);
-            rv = chmod(mount_point, new_mode);
-            if (rv < 0) {
-                printf("Couldn't fix permissions for %s: %s\n",
-                        mount_point, strerror(errno));
-            }
-        }
-    }
-#endif
-    return rv;
-}
-
-int
-mtd_partition_info(const MtdPartition *partition,
-        size_t *total_size, size_t *erase_size, size_t *write_size)
-{
-    char mtddevname[32];
-    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
-    int fd = open(mtddevname, O_RDONLY);
-    if (fd < 0) return -1;
-
-    struct mtd_info_user mtd_info;
-    int ret = ioctl(fd, MEMGETINFO, &mtd_info);
-    close(fd);
-    if (ret < 0) return -1;
-
-    if (total_size != NULL) *total_size = mtd_info.size;
-    if (erase_size != NULL) *erase_size = mtd_info.erasesize;
-    if (write_size != NULL) *write_size = mtd_info.writesize;
-    return 0;
-}
-
-MtdReadContext *mtd_read_partition(const MtdPartition *partition)
-{
-    MtdReadContext *ctx = (MtdReadContext*) malloc(sizeof(MtdReadContext));
-    if (ctx == NULL) return NULL;
-
-    ctx->buffer = malloc(partition->erase_size);
-    if (ctx->buffer == NULL) {
-        free(ctx);
-        return NULL;
-    }
-
-    char mtddevname[32];
-    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
-    ctx->fd = open(mtddevname, O_RDONLY);
-    if (ctx->fd < 0) {
-        free(ctx->buffer);
-        free(ctx);
-        return NULL;
-    }
-
-    ctx->partition = partition;
-    ctx->consumed = partition->erase_size;
-    return ctx;
-}
-
-static int read_block(const MtdPartition *partition, int fd, char *data)
-{
-    struct mtd_ecc_stats before, after;
-    if (ioctl(fd, ECCGETSTATS, &before)) {
-        printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
-        return -1;
-    }
-
-    loff_t pos = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR));
-    if (pos == -1) {
-        printf("mtd: read_block: couldn't SEEK_CUR: %s\n", strerror(errno));
-        return -1;
-    }
-
-    ssize_t size = partition->erase_size;
-    int mgbb;
-
-    while (pos + size <= (int) partition->size) {
-        if (TEMP_FAILURE_RETRY(lseek64(fd, pos, SEEK_SET)) != pos ||
-                    TEMP_FAILURE_RETRY(read(fd, data, size)) != size) {
-            printf("mtd: read error at 0x%08llx (%s)\n",
-                   (long long)pos, strerror(errno));
-        } else if (ioctl(fd, ECCGETSTATS, &after)) {
-            printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
-            return -1;
-        } else if (after.failed != before.failed) {
-            printf("mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n",
-                   after.corrected - before.corrected,
-                   after.failed - before.failed, (long long)pos);
-            // copy the comparison baseline for the next read.
-            memcpy(&before, &after, sizeof(struct mtd_ecc_stats));
-        } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) {
-            fprintf(stderr,
-                    "mtd: MEMGETBADBLOCK returned %d at 0x%08llx: %s\n",
-                    mgbb, (long long)pos, strerror(errno));
-        } else {
-            return 0;  // Success!
-        }
-
-        pos += partition->erase_size;
-    }
-
-    errno = ENOSPC;
-    return -1;
-}
-
-ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len)
-{
-    size_t read = 0;
-    while (read < len) {
-        if (ctx->consumed < ctx->partition->erase_size) {
-            size_t avail = ctx->partition->erase_size - ctx->consumed;
-            size_t copy = len - read < avail ? len - read : avail;
-            memcpy(data + read, ctx->buffer + ctx->consumed, copy);
-            ctx->consumed += copy;
-            read += copy;
-        }
-
-        // Read complete blocks directly into the user's buffer
-        while (ctx->consumed == ctx->partition->erase_size &&
-               len - read >= ctx->partition->erase_size) {
-            if (read_block(ctx->partition, ctx->fd, data + read)) return -1;
-            read += ctx->partition->erase_size;
-        }
-
-        if (read >= len) {
-            return read;
-        }
-
-        // Read the next block into the buffer
-        if (ctx->consumed == ctx->partition->erase_size && read < len) {
-            if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
-            ctx->consumed = 0;
-        }
-    }
-
-    return read;
-}
-
-void mtd_read_close(MtdReadContext *ctx)
-{
-    close(ctx->fd);
-    free(ctx->buffer);
-    free(ctx);
-}
-
-MtdWriteContext *mtd_write_partition(const MtdPartition *partition)
-{
-    MtdWriteContext *ctx = (MtdWriteContext*) malloc(sizeof(MtdWriteContext));
-    if (ctx == NULL) return NULL;
-
-    ctx->bad_block_offsets = NULL;
-    ctx->bad_block_alloc = 0;
-    ctx->bad_block_count = 0;
-
-    ctx->buffer = malloc(partition->erase_size);
-    if (ctx->buffer == NULL) {
-        free(ctx);
-        return NULL;
-    }
-
-    char mtddevname[32];
-    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
-    ctx->fd = open(mtddevname, O_RDWR);
-    if (ctx->fd < 0) {
-        free(ctx->buffer);
-        free(ctx);
-        return NULL;
-    }
-
-    ctx->partition = partition;
-    ctx->stored = 0;
-    return ctx;
-}
-
-static void add_bad_block_offset(MtdWriteContext *ctx, off_t pos) {
-    if (ctx->bad_block_count + 1 > ctx->bad_block_alloc) {
-        ctx->bad_block_alloc = (ctx->bad_block_alloc*2) + 1;
-        ctx->bad_block_offsets = realloc(ctx->bad_block_offsets,
-                                         ctx->bad_block_alloc * sizeof(off_t));
-    }
-    ctx->bad_block_offsets[ctx->bad_block_count++] = pos;
-}
-
-static int write_block(MtdWriteContext *ctx, const char *data)
-{
-    const MtdPartition *partition = ctx->partition;
-    int fd = ctx->fd;
-
-    off_t pos = TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_CUR));
-    if (pos == (off_t) -1) {
-        printf("mtd: write_block: couldn't SEEK_CUR: %s\n", strerror(errno));
-        return -1;
-    }
-
-    ssize_t size = partition->erase_size;
-    while (pos + size <= (int) partition->size) {
-        loff_t bpos = pos;
-        int ret = ioctl(fd, MEMGETBADBLOCK, &bpos);
-        if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) {
-            add_bad_block_offset(ctx, pos);
-            fprintf(stderr,
-                    "mtd: not writing bad block at 0x%08lx (ret %d): %s\n",
-                    pos, ret, strerror(errno));
-            pos += partition->erase_size;
-            continue;  // Don't try to erase known factory-bad blocks.
-        }
-
-        struct erase_info_user erase_info;
-        erase_info.start = pos;
-        erase_info.length = size;
-        int retry;
-        for (retry = 0; retry < 2; ++retry) {
-            if (ioctl(fd, MEMERASE, &erase_info) < 0) {
-                printf("mtd: erase failure at 0x%08lx (%s)\n",
-                        pos, strerror(errno));
-                continue;
-            }
-            if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos ||
-                TEMP_FAILURE_RETRY(write(fd, data, size)) != size) {
-                printf("mtd: write error at 0x%08lx (%s)\n",
-                        pos, strerror(errno));
-            }
-
-            char verify[size];
-            if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos ||
-                TEMP_FAILURE_RETRY(read(fd, verify, size)) != size) {
-                printf("mtd: re-read error at 0x%08lx (%s)\n",
-                        pos, strerror(errno));
-                continue;
-            }
-            if (memcmp(data, verify, size) != 0) {
-                printf("mtd: verification error at 0x%08lx (%s)\n",
-                        pos, strerror(errno));
-                continue;
-            }
-
-            if (retry > 0) {
-                printf("mtd: wrote block after %d retries\n", retry);
-            }
-            printf("mtd: successfully wrote block at %lx\n", pos);
-            return 0;  // Success!
-        }
-
-        // Try to erase it once more as we give up on this block
-        add_bad_block_offset(ctx, pos);
-        printf("mtd: skipping write block at 0x%08lx\n", pos);
-        ioctl(fd, MEMERASE, &erase_info);
-        pos += partition->erase_size;
-    }
-
-    // Ran out of space on the device
-    errno = ENOSPC;
-    return -1;
-}
-
-ssize_t mtd_write_data(MtdWriteContext *ctx, const char *data, size_t len)
-{
-    size_t wrote = 0;
-    while (wrote < len) {
-        // Coalesce partial writes into complete blocks
-        if (ctx->stored > 0 || len - wrote < ctx->partition->erase_size) {
-            size_t avail = ctx->partition->erase_size - ctx->stored;
-            size_t copy = len - wrote < avail ? len - wrote : avail;
-            memcpy(ctx->buffer + ctx->stored, data + wrote, copy);
-            ctx->stored += copy;
-            wrote += copy;
-        }
-
-        // If a complete block was accumulated, write it
-        if (ctx->stored == ctx->partition->erase_size) {
-            if (write_block(ctx, ctx->buffer)) return -1;
-            ctx->stored = 0;
-        }
-
-        // Write complete blocks directly from the user's buffer
-        while (ctx->stored == 0 && len - wrote >= ctx->partition->erase_size) {
-            if (write_block(ctx, data + wrote)) return -1;
-            wrote += ctx->partition->erase_size;
-        }
-    }
-
-    return wrote;
-}
-
-off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks)
-{
-    // Zero-pad and write any pending data to get us to a block boundary
-    if (ctx->stored > 0) {
-        size_t zero = ctx->partition->erase_size - ctx->stored;
-        memset(ctx->buffer + ctx->stored, 0, zero);
-        if (write_block(ctx, ctx->buffer)) return -1;
-        ctx->stored = 0;
-    }
-
-    off_t pos = TEMP_FAILURE_RETRY(lseek(ctx->fd, 0, SEEK_CUR));
-    if ((off_t) pos == (off_t) -1) {
-        printf("mtd_erase_blocks: couldn't SEEK_CUR: %s\n", strerror(errno));
-        return -1;
-    }
-
-    const int total = (ctx->partition->size - pos) / ctx->partition->erase_size;
-    if (blocks < 0) blocks = total;
-    if (blocks > total) {
-        errno = ENOSPC;
-        return -1;
-    }
-
-    // Erase the specified number of blocks
-    while (blocks-- > 0) {
-        loff_t bpos = pos;
-        if (ioctl(ctx->fd, MEMGETBADBLOCK, &bpos) > 0) {
-            printf("mtd: not erasing bad block at 0x%08lx\n", pos);
-            pos += ctx->partition->erase_size;
-            continue;  // Don't try to erase known factory-bad blocks.
-        }
-
-        struct erase_info_user erase_info;
-        erase_info.start = pos;
-        erase_info.length = ctx->partition->erase_size;
-        if (ioctl(ctx->fd, MEMERASE, &erase_info) < 0) {
-            printf("mtd: erase failure at 0x%08lx\n", pos);
-        }
-        pos += ctx->partition->erase_size;
-    }
-
-    return pos;
-}
-
-int mtd_write_close(MtdWriteContext *ctx)
-{
-    int r = 0;
-    // Make sure any pending data gets written
-    if (mtd_erase_blocks(ctx, 0) == (off_t) -1) r = -1;
-    if (close(ctx->fd)) r = -1;
-    free(ctx->bad_block_offsets);
-    free(ctx->buffer);
-    free(ctx);
-    return r;
-}
diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h
deleted file mode 100644
index 8059d6a..0000000
--- a/mtdutils/mtdutils.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2007 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 agreed 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 MTDUTILS_H_
-#define MTDUTILS_H_
-
-#include <sys/types.h>  // for size_t, etc.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct MtdPartition MtdPartition;
-
-int mtd_scan_partitions(void);
-
-const MtdPartition *mtd_find_partition_by_name(const char *name);
-
-/* mount_point is like "/system"
- * filesystem is like "yaffs2"
- */
-int mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
-        const char *filesystem, int read_only);
-
-/* get the partition and the minimum erase/write block size.  NULL is ok.
- */
-int mtd_partition_info(const MtdPartition *partition,
-        size_t *total_size, size_t *erase_size, size_t *write_size);
-
-/* read or write raw data from a partition, starting at the beginning.
- * skips bad blocks as best we can.
- */
-typedef struct MtdReadContext MtdReadContext;
-typedef struct MtdWriteContext MtdWriteContext;
-
-MtdReadContext *mtd_read_partition(const MtdPartition *);
-ssize_t mtd_read_data(MtdReadContext *, char *data, size_t data_len);
-void mtd_read_close(MtdReadContext *);
-
-MtdWriteContext *mtd_write_partition(const MtdPartition *);
-ssize_t mtd_write_data(MtdWriteContext *, const char *data, size_t data_len);
-off_t mtd_erase_blocks(MtdWriteContext *, int blocks);  /* 0 ok, -1 for all */
-int mtd_write_close(MtdWriteContext *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif  // MTDUTILS_H_
diff --git a/otafault/Android.mk b/otafault/Android.mk
index ba7add8..71c2c62 100644
--- a/otafault/Android.mk
+++ b/otafault/Android.mk
@@ -17,11 +17,13 @@
 include $(CLEAR_VARS)
 
 otafault_static_libs := \
-    libbase \
-    libminzip \
+    libziparchive \
     libz \
-    libselinux
+    libselinux \
+    libbase \
+    liblog
 
+LOCAL_CFLAGS := -Werror
 LOCAL_SRC_FILES := config.cpp ota_io.cpp
 LOCAL_MODULE_TAGS := eng
 LOCAL_MODULE := libotafault
@@ -32,12 +34,15 @@
 
 include $(BUILD_STATIC_LIBRARY)
 
+# otafault_test (static executable)
+# ===============================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := config.cpp ota_io.cpp test.cpp
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := otafault_test
 LOCAL_STATIC_LIBRARIES := $(otafault_static_libs)
+LOCAL_CFLAGS := -Werror
 LOCAL_C_INCLUDES := bootable/recovery
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
diff --git a/otafault/config.cpp b/otafault/config.cpp
index b456739..8590833 100644
--- a/otafault/config.cpp
+++ b/otafault/config.cpp
@@ -21,38 +21,42 @@
 #include <unistd.h>
 
 #include <android-base/stringprintf.h>
+#include <ziparchive/zip_archive.h>
 
-#include "minzip/Zip.h"
 #include "config.h"
 #include "ota_io.h"
 
 #define OTAIO_MAX_FNAME_SIZE 128
 
-static ZipArchive* archive;
+static ZipArchiveHandle archive;
+static bool is_retry = false;
 static std::map<std::string, bool> should_inject_cache;
 
 static std::string get_type_path(const char* io_type) {
     return android::base::StringPrintf("%s/%s", OTAIO_BASE_DIR, io_type);
 }
 
-void ota_io_init(ZipArchive* za) {
+void ota_io_init(ZipArchiveHandle za, bool retry) {
     archive = za;
+    is_retry = retry;
     ota_set_fault_files();
 }
 
 bool should_fault_inject(const char* io_type) {
     // archive will be NULL if we used an entry point other
     // than updater/updater.cpp:main
-    if (archive == NULL) {
+    if (archive == nullptr || is_retry) {
         return false;
     }
     const std::string type_path = get_type_path(io_type);
     if (should_inject_cache.find(type_path) != should_inject_cache.end()) {
         return should_inject_cache[type_path];
     }
-    const ZipEntry* entry = mzFindZipEntry(archive, type_path.c_str());
-    should_inject_cache[type_path] = entry != nullptr;
-    return entry != NULL;
+    ZipString zip_type_path(type_path.c_str());
+    ZipEntry entry;
+    int status = FindEntry(archive, zip_type_path, &entry);
+    should_inject_cache[type_path] = (status == 0);
+    return (status == 0);
 }
 
 bool should_hit_cache() {
@@ -63,7 +67,9 @@
     std::string type_path = get_type_path(io_type);
     std::string fname;
     fname.resize(OTAIO_MAX_FNAME_SIZE);
-    const ZipEntry* entry = mzFindZipEntry(archive, type_path.c_str());
-    mzReadZipEntry(archive, entry, &fname[0], OTAIO_MAX_FNAME_SIZE);
+    ZipString zip_type_path(type_path.c_str());
+    ZipEntry entry;
+    int status = FindEntry(archive, zip_type_path, &entry);
+    ExtractToMemory(archive, &entry, reinterpret_cast<uint8_t*>(&fname[0]), OTAIO_MAX_FNAME_SIZE);
     return fname;
 }
diff --git a/otafault/config.h b/otafault/config.h
index 4430be3..4adbdd1 100644
--- a/otafault/config.h
+++ b/otafault/config.h
@@ -41,7 +41,7 @@
 
 #include <stdbool.h>
 
-#include "minzip/Zip.h"
+#include <ziparchive/zip_archive.h>
 
 #define OTAIO_BASE_DIR ".libotafault"
 #define OTAIO_READ "READ"
@@ -52,7 +52,7 @@
 /*
  * Initialize libotafault by providing a reference to the OTA package.
  */
-void ota_io_init(ZipArchive* za);
+void ota_io_init(ZipArchiveHandle zip, bool retry);
 
 /*
  * Return true if a config file is present for the given IO type.
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
index 0445853..3a89bb5 100644
--- a/otafault/ota_io.cpp
+++ b/otafault/ota_io.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <map>
+#include "ota_io.h"
 
 #include <errno.h>
 #include <fcntl.h>
@@ -22,15 +22,17 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include <map>
+#include <memory>
+
 #include "config.h"
-#include "ota_io.h"
 
 static std::map<intptr_t, const char*> filename_cache;
 static std::string read_fault_file_name = "";
 static std::string write_fault_file_name = "";
 static std::string fsync_fault_file_name = "";
 
-static bool get_hit_file(const char* cached_path, std::string ffn) {
+static bool get_hit_file(const char* cached_path, const std::string& ffn) {
     return should_hit_cache()
         ? !strncmp(cached_path, OTAIO_CACHE_FNAME, strlen(cached_path))
         : !strncmp(cached_path, ffn.c_str(), strlen(cached_path));
@@ -68,17 +70,33 @@
     return fh;
 }
 
-int ota_close(int fd) {
+static int __ota_close(int fd) {
     // descriptors can be reused, so make sure not to leave them in the cache
     filename_cache.erase(fd);
     return close(fd);
 }
 
-int ota_fclose(FILE* fh) {
-    filename_cache.erase((intptr_t)fh);
+void OtaCloser::Close(int fd) {
+    __ota_close(fd);
+}
+
+int ota_close(unique_fd& fd) {
+    return __ota_close(fd.release());
+}
+
+static int __ota_fclose(FILE* fh) {
+    filename_cache.erase(reinterpret_cast<intptr_t>(fh));
     return fclose(fh);
 }
 
+void OtaFcloser::operator()(FILE* f) const {
+    __ota_fclose(f);
+};
+
+int ota_fclose(unique_file& fh) {
+  return __ota_fclose(fh.release());
+}
+
 size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) {
     if (should_fault_inject(OTAIO_READ)) {
         auto cached = filename_cache.find((intptr_t)stream);
@@ -92,6 +110,7 @@
         }
     }
     size_t status = fread(ptr, size, nitems, stream);
+    // If I/O error occurs, set the retry-update flag.
     if (status != nitems && errno == EIO) {
         have_eio_error = true;
     }
diff --git a/otafault/ota_io.h b/otafault/ota_io.h
index 84187a7..9428f1b 100644
--- a/otafault/ota_io.h
+++ b/otafault/ota_io.h
@@ -26,6 +26,10 @@
 #include <stdio.h>
 #include <sys/stat.h>
 
+#include <memory>
+
+#include <android-base/unique_fd.h>
+
 #define OTAIO_CACHE_FNAME "/cache/saved.file"
 
 void ota_set_fault_files();
@@ -36,10 +40,6 @@
 
 FILE* ota_fopen(const char* filename, const char* mode);
 
-int ota_close(int fd);
-
-int ota_fclose(FILE* fh);
-
 size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream);
 
 ssize_t ota_read(int fd, void* buf, size_t nbyte);
@@ -50,4 +50,20 @@
 
 int ota_fsync(int fd);
 
+struct OtaCloser {
+  static void Close(int);
+};
+
+using unique_fd = android::base::unique_fd_impl<OtaCloser>;
+
+int ota_close(unique_fd& fd);
+
+struct OtaFcloser {
+  void operator()(FILE*) const;
+};
+
+using unique_file = std::unique_ptr<FILE, OtaFcloser>;
+
+int ota_fclose(unique_file& fh);
+
 #endif
diff --git a/otautil/Android.mk b/otautil/Android.mk
new file mode 100644
index 0000000..f7ca9a9
--- /dev/null
+++ b/otautil/Android.mk
@@ -0,0 +1,33 @@
+# 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 agreed 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    SysUtil.cpp \
+    DirUtil.cpp \
+    ZipUtil.cpp \
+    ThermalUtil.cpp
+
+LOCAL_STATIC_LIBRARIES := \
+    libselinux \
+    libbase
+
+LOCAL_MODULE := libotautil
+LOCAL_CFLAGS := \
+    -Werror \
+    -Wall
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/minzip/DirUtil.c b/otautil/DirUtil.cpp
similarity index 77%
rename from minzip/DirUtil.c
rename to otautil/DirUtil.cpp
index 97cb2e0..e08e360 100644
--- a/minzip/DirUtil.c
+++ b/otautil/DirUtil.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "DirUtil.h"
+
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
@@ -24,7 +26,10 @@
 #include <dirent.h>
 #include <limits.h>
 
-#include "DirUtil.h"
+#include <string>
+
+#include <selinux/label.h>
+#include <selinux/selinux.h>
 
 typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus;
 
@@ -66,43 +71,25 @@
         errno = ENOENT;
         return -1;
     }
-
-    /* Allocate a path that we can modify; stick a slash on
-     * the end to make things easier.
-     */
-    size_t pathLen = strlen(path);
-    char *cpath = (char *)malloc(pathLen + 2);
-    if (cpath == NULL) {
-        errno = ENOMEM;
-        return -1;
-    }
-    memcpy(cpath, path, pathLen);
+    // Allocate a path that we can modify; stick a slash on
+    // the end to make things easier.
+    std::string cpath = path;
     if (stripFileName) {
-        /* Strip everything after the last slash.
-         */
-        char *c = cpath + pathLen - 1;
-        while (c != cpath && *c != '/') {
-            c--;
-        }
-        if (c == cpath) {
-            //xxx test this path
-            /* No directory component.  Act like the path was empty.
-             */
+        // Strip everything after the last slash.
+        size_t pos = cpath.rfind('/');
+        if (pos == std::string::npos) {
             errno = ENOENT;
-            free(cpath);
             return -1;
         }
-        c[1] = '\0';    // Terminate after the slash we found.
+        cpath.resize(pos + 1);
     } else {
-        /* Make sure that the path ends in a slash.
-         */
-        cpath[pathLen] = '/';
-        cpath[pathLen + 1] = '\0';
+        // Make sure that the path ends in a slash.
+        cpath.push_back('/');
     }
 
     /* See if it already exists.
      */
-    ds = getPathDirStatus(cpath);
+    ds = getPathDirStatus(cpath.c_str());
     if (ds == DDIR) {
         return 0;
     } else if (ds == DILLEGAL) {
@@ -112,7 +99,8 @@
     /* Walk up the path from the root and make each level.
      * If a directory already exists, no big deal.
      */
-    char *p = cpath;
+    const char *path_start = &cpath[0];
+    char *p = &cpath[0];
     while (*p != '\0') {
         /* Skip any slashes, watching out for the end of the string.
          */
@@ -135,12 +123,11 @@
         /* Check this part of the path and make a new directory
          * if necessary.
          */
-        ds = getPathDirStatus(cpath);
+        ds = getPathDirStatus(path_start);
         if (ds == DILLEGAL) {
             /* Could happen if some other process/thread is
              * messing with the filesystem.
              */
-            free(cpath);
             return -1;
         } else if (ds == DMISSING) {
             int err;
@@ -148,11 +135,11 @@
             char *secontext = NULL;
 
             if (sehnd) {
-                selabel_lookup(sehnd, &secontext, cpath, mode);
+                selabel_lookup(sehnd, &secontext, path_start, mode);
                 setfscreatecon(secontext);
             }
 
-            err = mkdir(cpath, mode);
+            err = mkdir(path_start, mode);
 
             if (secontext) {
                 freecon(secontext);
@@ -160,22 +147,17 @@
             }
 
             if (err != 0) {
-                free(cpath);
                 return -1;
             }
-            if (timestamp != NULL && utime(cpath, timestamp)) {
-                free(cpath);
+            if (timestamp != NULL && utime(path_start, timestamp)) {
                 return -1;
             }
         }
         // else, this directory already exists.
-        
-        /* Repair the path and continue.
-         */
+
+        // Repair the path and continue.
         *p = '/';
     }
-    free(cpath);
-
     return 0;
 }
 
diff --git a/minzip/DirUtil.h b/otautil/DirUtil.h
similarity index 96%
rename from minzip/DirUtil.h
rename to otautil/DirUtil.h
index 85a0012..85b83c3 100644
--- a/minzip/DirUtil.h
+++ b/otautil/DirUtil.h
@@ -24,8 +24,7 @@
 extern "C" {
 #endif
 
-#include <selinux/selinux.h>
-#include <selinux/label.h>
+struct selabel_handle;
 
 /* Like "mkdir -p", try to guarantee that all directories
  * specified in path are present, creating as many directories
diff --git a/otautil/SysUtil.cpp b/otautil/SysUtil.cpp
new file mode 100644
index 0000000..a2133b9
--- /dev/null
+++ b/otautil/SysUtil.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2006 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 agreed 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 "SysUtil.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+static bool sysMapFD(int fd, MemMapping* pMap) {
+  CHECK(pMap != nullptr);
+
+  struct stat sb;
+  if (fstat(fd, &sb) == -1) {
+    PLOG(ERROR) << "fstat(" << fd << ") failed";
+    return false;
+  }
+
+  void* memPtr = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (memPtr == MAP_FAILED) {
+    PLOG(ERROR) << "mmap(" << sb.st_size << ", R, PRIVATE, " << fd << ", 0) failed";
+    return false;
+  }
+
+  pMap->addr = static_cast<unsigned char*>(memPtr);
+  pMap->length = sb.st_size;
+  pMap->ranges.push_back({ memPtr, static_cast<size_t>(sb.st_size) });
+
+  return true;
+}
+
+// A "block map" which looks like this (from uncrypt/uncrypt.cpp):
+//
+//     /dev/block/platform/msm_sdcc.1/by-name/userdata     # block device
+//     49652 4096                        # file size in bytes, block size
+//     3                                 # count of block ranges
+//     1000 1008                         # block range 0
+//     2100 2102                         # ... block range 1
+//     30 33                             # ... block range 2
+//
+// Each block range represents a half-open interval; the line "30 33"
+// reprents the blocks [30, 31, 32].
+static int sysMapBlockFile(const char* filename, MemMapping* pMap) {
+  CHECK(pMap != nullptr);
+
+  std::string content;
+  if (!android::base::ReadFileToString(filename, &content)) {
+    PLOG(ERROR) << "Failed to read " << filename;
+    return -1;
+  }
+
+  std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n");
+  if (lines.size() < 4) {
+    LOG(ERROR) << "Block map file is too short: " << lines.size();
+    return -1;
+  }
+
+  size_t size;
+  unsigned int blksize;
+  if (sscanf(lines[1].c_str(), "%zu %u", &size, &blksize) != 2) {
+    LOG(ERROR) << "Failed to parse file size and block size: " << lines[1];
+    return -1;
+  }
+
+  size_t range_count;
+  if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) {
+    LOG(ERROR) << "Failed to parse block map header: " << lines[2];
+    return -1;
+  }
+
+  size_t blocks;
+  if (blksize != 0) {
+    blocks = ((size - 1) / blksize) + 1;
+  }
+  if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0 ||
+      lines.size() != 3 + range_count) {
+    LOG(ERROR) << "Invalid data in block map file: size " << size << ", blksize " << blksize
+               << ", range_count " << range_count << ", lines " << lines.size();
+    return -1;
+  }
+
+  // Reserve enough contiguous address space for the whole file.
+  void* reserve = mmap64(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
+  if (reserve == MAP_FAILED) {
+    PLOG(ERROR) << "failed to reserve address space";
+    return -1;
+  }
+
+  const std::string& block_dev = lines[0];
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_dev.c_str(), O_RDONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "failed to open block device " << block_dev;
+    munmap(reserve, blocks * blksize);
+    return -1;
+  }
+
+  pMap->ranges.resize(range_count);
+
+  unsigned char* next = static_cast<unsigned char*>(reserve);
+  size_t remaining_size = blocks * blksize;
+  bool success = true;
+  for (size_t i = 0; i < range_count; ++i) {
+    const std::string& line = lines[i + 3];
+
+    size_t start, end;
+    if (sscanf(line.c_str(), "%zu %zu\n", &start, &end) != 2) {
+      LOG(ERROR) << "failed to parse range " << i << " in block map: " << line;
+      success = false;
+      break;
+    }
+    size_t length = (end - start) * blksize;
+    if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) {
+      LOG(ERROR) << "unexpected range in block map: " << start << " " << end;
+      success = false;
+      break;
+    }
+
+    void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
+                        static_cast<off64_t>(start) * blksize);
+    if (addr == MAP_FAILED) {
+      PLOG(ERROR) << "failed to map block " << i;
+      success = false;
+      break;
+    }
+    pMap->ranges[i].addr = addr;
+    pMap->ranges[i].length = length;
+
+    next += length;
+    remaining_size -= length;
+  }
+  if (success && remaining_size != 0) {
+    LOG(ERROR) << "ranges in block map are invalid: remaining_size = " << remaining_size;
+    success = false;
+  }
+  if (!success) {
+    munmap(reserve, blocks * blksize);
+    return -1;
+  }
+
+  pMap->addr = static_cast<unsigned char*>(reserve);
+  pMap->length = size;
+
+  LOG(INFO) << "mmapped " << range_count << " ranges";
+
+  return 0;
+}
+
+int sysMapFile(const char* fn, MemMapping* pMap) {
+  if (fn == nullptr || pMap == nullptr) {
+    LOG(ERROR) << "Invalid argument(s)";
+    return -1;
+  }
+
+  *pMap = {};
+
+  if (fn[0] == '@') {
+    if (sysMapBlockFile(fn + 1, pMap) != 0) {
+      LOG(ERROR) << "Map of '" << fn << "' failed";
+      return -1;
+    }
+  } else {
+    // This is a regular file.
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn, O_RDONLY)));
+    if (fd == -1) {
+      PLOG(ERROR) << "Unable to open '" << fn << "'";
+      return -1;
+    }
+
+    if (!sysMapFD(fd, pMap)) {
+      LOG(ERROR) << "Map of '" << fn << "' failed";
+      return -1;
+    }
+  }
+  return 0;
+}
+
+/*
+ * Release a memory mapping.
+ */
+void sysReleaseMap(MemMapping* pMap) {
+  std::for_each(pMap->ranges.cbegin(), pMap->ranges.cend(), [](const MappedRange& range) {
+    if (munmap(range.addr, range.length) == -1) {
+      PLOG(ERROR) << "munmap(" << range.addr << ", " << range.length << ") failed";
+    }
+  });
+  pMap->ranges.clear();
+}
diff --git a/otautil/SysUtil.h b/otautil/SysUtil.h
new file mode 100644
index 0000000..6a79bf3
--- /dev/null
+++ b/otautil/SysUtil.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2006 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 agreed 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 _OTAUTIL_SYSUTIL
+#define _OTAUTIL_SYSUTIL
+
+#include <sys/types.h>
+
+#include <vector>
+
+struct MappedRange {
+  void* addr;
+  size_t length;
+};
+
+/*
+ * Use this to keep track of mapped segments.
+ */
+struct MemMapping {
+  unsigned char* addr; /* start of data */
+  size_t length;       /* length of data */
+
+  std::vector<MappedRange> ranges;
+};
+
+/*
+ * Map a file into a private, read-only memory segment.  If 'fn'
+ * begins with an '@' character, it is a map of blocks to be mapped,
+ * otherwise it is treated as an ordinary file.
+ *
+ * On success, "pMap" is filled in, and zero is returned.
+ */
+int sysMapFile(const char* fn, MemMapping* pMap);
+
+/*
+ * Release the pages associated with a shared memory segment.
+ *
+ * This does not free "pMap"; it just releases the memory.
+ */
+void sysReleaseMap(MemMapping* pMap);
+
+#endif  // _OTAUTIL_SYSUTIL
diff --git a/otautil/ThermalUtil.cpp b/otautil/ThermalUtil.cpp
new file mode 100644
index 0000000..13d3643
--- /dev/null
+++ b/otautil/ThermalUtil.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 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 agreed 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 "ThermalUtil.h"
+
+#include <dirent.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
+static constexpr auto THERMAL_PREFIX = "/sys/class/thermal/";
+
+static int thermal_filter(const dirent* de) {
+  if (android::base::StartsWith(de->d_name, "thermal_zone")) {
+    return 1;
+  }
+  return 0;
+}
+
+static std::vector<std::string> InitThermalPaths() {
+  dirent** namelist;
+  int n = scandir(THERMAL_PREFIX, &namelist, thermal_filter, alphasort);
+  if (n == -1) {
+    PLOG(ERROR) << "Failed to scandir " << THERMAL_PREFIX;
+    return {};
+  }
+  if (n == 0) {
+    LOG(ERROR) << "Failed to find CPU thermal info in " << THERMAL_PREFIX;
+    return {};
+  }
+
+  std::vector<std::string> thermal_paths;
+  while (n--) {
+    thermal_paths.push_back(THERMAL_PREFIX + std::string(namelist[n]->d_name) + "/temp");
+    free(namelist[n]);
+  }
+  free(namelist);
+  return thermal_paths;
+}
+
+int GetMaxValueFromThermalZone() {
+  static std::vector<std::string> thermal_paths = InitThermalPaths();
+  int max_temperature = -1;
+  for (const auto& path : thermal_paths) {
+    std::string content;
+    if (!android::base::ReadFileToString(path, &content)) {
+      PLOG(WARNING) << "Failed to read " << path;
+      continue;
+    }
+
+    int temperature;
+    if (!android::base::ParseInt(android::base::Trim(content), &temperature)) {
+      LOG(WARNING) << "Failed to parse integer in " << content;
+      continue;
+    }
+    max_temperature = std::max(temperature, max_temperature);
+  }
+  LOG(INFO) << "current maximum temperature: " << max_temperature;
+  return max_temperature;
+}
\ No newline at end of file
diff --git a/updater/install.h b/otautil/ThermalUtil.h
similarity index 61%
copy from updater/install.h
copy to otautil/ThermalUtil.h
index 70e3434..43ab559 100644
--- a/updater/install.h
+++ b/otautil/ThermalUtil.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,14 +14,11 @@
  * limitations under the License.
  */
 
-#ifndef _UPDATER_INSTALL_H_
-#define _UPDATER_INSTALL_H_
+#ifndef OTAUTIL_THERMALUTIL_H
+#define OTAUTIL_THERMALUTIL_H
 
-void RegisterInstallFunctions();
+// We can find the temperature reported by all sensors in /sys/class/thermal/thermal_zone*/temp.
+// Their values are in millidegree Celsius; and we will log the maximum one.
+int GetMaxValueFromThermalZone();
 
-// uiPrintf function prints msg to screen as well as logs
-void uiPrintf(State* state, const char* format, ...);
-
-static int make_parents(char* name);
-
-#endif
+#endif  // OTAUTIL_THERMALUTIL_H
diff --git a/otautil/ZipUtil.cpp b/otautil/ZipUtil.cpp
new file mode 100644
index 0000000..714c956
--- /dev/null
+++ b/otautil/ZipUtil.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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 agreed 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 "ZipUtil.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <utime.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <ziparchive/zip_archive.h>
+
+#include "DirUtil.h"
+
+static constexpr mode_t UNZIP_DIRMODE = 0755;
+static constexpr mode_t UNZIP_FILEMODE = 0644;
+
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+                             const std::string& dest_path, const struct utimbuf* timestamp,
+                             struct selabel_handle* sehnd) {
+    if (!zip_path.empty() && zip_path[0] == '/') {
+        LOG(ERROR) << "ExtractPackageRecursive(): zip_path must be a relative path " << zip_path;
+        return false;
+    }
+    if (dest_path.empty() || dest_path[0] != '/') {
+        LOG(ERROR) << "ExtractPackageRecursive(): dest_path must be an absolute path " << dest_path;
+        return false;
+    }
+
+    void* cookie;
+    std::string target_dir(dest_path);
+    if (dest_path.back() != '/') {
+        target_dir += '/';
+    }
+    std::string prefix_path(zip_path);
+    if (!zip_path.empty() && zip_path.back() != '/') {
+        prefix_path += '/';
+    }
+    const ZipString zip_prefix(prefix_path.c_str());
+
+    int ret = StartIteration(zip, &cookie, &zip_prefix, nullptr);
+    if (ret != 0) {
+        LOG(ERROR) << "failed to start iterating zip entries.";
+        return false;
+    }
+
+    std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+    ZipEntry entry;
+    ZipString name;
+    int extractCount = 0;
+    while (Next(cookie, &entry, &name) == 0) {
+        std::string entry_name(name.name, name.name + name.name_length);
+        CHECK_LE(prefix_path.size(), entry_name.size());
+        std::string path = target_dir + entry_name.substr(prefix_path.size());
+        // Skip dir.
+        if (path.back() == '/') {
+            continue;
+        }
+        //TODO(b/31917448) handle the symlink.
+
+        if (dirCreateHierarchy(path.c_str(), UNZIP_DIRMODE, timestamp, true, sehnd) != 0) {
+            LOG(ERROR) << "failed to create dir for " << path;
+            return false;
+        }
+
+        char *secontext = NULL;
+        if (sehnd) {
+            selabel_lookup(sehnd, &secontext, path.c_str(), UNZIP_FILEMODE);
+            setfscreatecon(secontext);
+        }
+        android::base::unique_fd fd(open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, UNZIP_FILEMODE));
+        if (fd == -1) {
+            PLOG(ERROR) << "Can't create target file \"" << path << "\"";
+            return false;
+        }
+        if (secontext) {
+            freecon(secontext);
+            setfscreatecon(NULL);
+        }
+
+        int err = ExtractEntryToFile(zip, &entry, fd);
+        if (err != 0) {
+            LOG(ERROR) << "Error extracting \"" << path << "\" : " << ErrorCodeString(err);
+            return false;
+        }
+
+        if (fsync(fd) != 0) {
+            PLOG(ERROR) << "Error syncing file descriptor when extracting \"" << path << "\"";
+            return false;
+        }
+
+        if (timestamp != nullptr && utime(path.c_str(), timestamp)) {
+            PLOG(ERROR) << "Error touching \"" << path << "\"";
+            return false;
+        }
+
+        LOG(INFO) << "Extracted file \"" << path << "\"";
+        ++extractCount;
+    }
+
+    LOG(INFO) << "Extracted " << extractCount << " file(s)";
+    return true;
+}
diff --git a/otautil/ZipUtil.h b/otautil/ZipUtil.h
new file mode 100644
index 0000000..cda405c
--- /dev/null
+++ b/otautil/ZipUtil.h
@@ -0,0 +1,57 @@
+/*
+ * 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 agreed 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 _OTAUTIL_ZIPUTIL_H
+#define _OTAUTIL_ZIPUTIL_H
+
+#include <utime.h>
+
+#include <string>
+
+#include <selinux/label.h>
+#include <ziparchive/zip_archive.h>
+
+/*
+ * Inflate all files under zip_path to the directory specified by
+ * dest_path, which must exist and be a writable directory. The zip_path
+ * is allowed to be an empty string, in which case the whole package
+ * will be extracted.
+ *
+ * Directory entries are not extracted.
+ *
+ * The immediate children of zip_path will become the immediate
+ * children of dest_path; e.g., if the archive contains the entries
+ *
+ *     a/b/c/one
+ *     a/b/c/two
+ *     a/b/c/d/three
+ *
+ * and ExtractPackageRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
+ * files will be
+ *
+ *     /tmp/one
+ *     /tmp/two
+ *     /tmp/d/three
+ *
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
+ *
+ * Returns true on success, false on failure.
+ */
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+                             const std::string& dest_path, const struct utimbuf* timestamp,
+                             struct selabel_handle* sehnd);
+
+#endif // _OTAUTIL_ZIPUTIL_H
diff --git a/print_sha1.h b/print_sha1.h
index c7c1f36..1f85895 100644
--- a/print_sha1.h
+++ b/print_sha1.h
@@ -20,7 +20,7 @@
 #include <stdint.h>
 #include <string>
 
-#include "openssl/sha.h"
+#include <openssl/sha.h>
 
 static std::string print_sha1(const uint8_t* sha1, size_t len) {
     const char* hex = "0123456789abcdef";
@@ -41,7 +41,7 @@
 }
 
 static std::string print_hex(const uint8_t* bytes, size_t len) {
-  return print_sha1(bytes, len);
+    return print_sha1(bytes, len);
 }
 
 #endif  // RECOVERY_PRINT_SHA1_H
diff --git a/updater/install.h b/private/install.h
similarity index 61%
copy from updater/install.h
copy to private/install.h
index 70e3434..12d303b 100644
--- a/updater/install.h
+++ b/private/install.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef _UPDATER_INSTALL_H_
-#define _UPDATER_INSTALL_H_
+// Private headers exposed for testing purpose only.
 
-void RegisterInstallFunctions();
+#pragma once
 
-// uiPrintf function prints msg to screen as well as logs
-void uiPrintf(State* state, const char* format, ...);
+#include <string>
+#include <vector>
 
-static int make_parents(char* name);
+#include <ziparchive/zip_archive.h>
 
-#endif
+int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
+                          int status_fd, std::vector<std::string>* cmd);
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index 25df03f..d706cca 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "recovery-persist"
-
 //
 // Strictly to deal with reboot into system after OTA after /data
 // mounts to pull the last pmsg file data and place it
@@ -32,7 +30,6 @@
 //    --force-persist  ignore /cache mount, always rotate in the contents.
 //
 
-#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -40,31 +37,31 @@
 
 #include <string>
 
-#include <android/log.h> /* Android Log Priority Tags */
 #include <android-base/file.h>
-#include <log/log.h>
-#include <log/logger.h> /* Android Log packet format */
+#include <android-base/logging.h>
 #include <private/android_logger.h> /* private pmsg functions */
 
+#include "rotate_logs.h"
+
 static const char *LAST_LOG_FILE = "/data/misc/recovery/last_log";
 static const char *LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0";
 static const char *LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg";
 static const char *LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0";
 static const char *ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops";
 
-static const int KEEP_LOG_COUNT = 10;
-
 // close a file, log an error if the error indicator is set
 static void check_and_fclose(FILE *fp, const char *name) {
     fflush(fp);
-    if (ferror(fp)) SLOGE("%s %s", name, strerror(errno));
+    if (ferror(fp)) {
+        PLOG(ERROR) << "Error in " << name;
+    }
     fclose(fp);
 }
 
 static void copy_file(const char* source, const char* destination) {
     FILE* dest_fp = fopen(destination, "w");
     if (dest_fp == nullptr) {
-        SLOGE("%s %s", destination, strerror(errno));
+        PLOG(ERROR) << "Can't open " << destination;
     } else {
         FILE* source_fp = fopen(source, "r");
         if (source_fp != nullptr) {
@@ -81,39 +78,6 @@
 
 static bool rotated = false;
 
-// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
-// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
-// Overwrite any existing last_log.$max and last_kmsg.$max.
-static void rotate_logs(int max) {
-    // Logs should only be rotated once.
-
-    if (rotated) {
-        return;
-    }
-    rotated = true;
-
-    for (int i = max-1; i >= 0; --i) {
-        std::string old_log(LAST_LOG_FILE);
-        if (i > 0) {
-          old_log += "." + std::to_string(i);
-        }
-        std::string new_log(LAST_LOG_FILE);
-        new_log += "." + std::to_string(i+1);
-
-        // Ignore errors if old_log doesn't exist.
-        rename(old_log.c_str(), new_log.c_str());
-
-        std::string old_kmsg(LAST_KMSG_FILE);
-        if (i > 0) {
-          old_kmsg += "." + std::to_string(i);
-        }
-        std::string new_kmsg(LAST_KMSG_FILE);
-        new_kmsg += "." + std::to_string(i+1);
-
-        rename(old_kmsg.c_str(), new_kmsg.c_str());
-    }
-}
-
 ssize_t logsave(
         log_id_t /* logId */,
         char /* prio */,
@@ -139,7 +103,8 @@
     // already-rotated files? Algorithm thus far is KISS: one file,
     // one rotation allowed.
 
-    rotate_logs(KEEP_LOG_COUNT);
+    rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
+    rotated = true;
 
     return android::base::WriteStringToFile(buffer, destination.c_str());
 }
@@ -157,7 +122,7 @@
     static const char mounts_file[] = "/proc/mounts";
     FILE *fp = fopen(mounts_file, "r");
     if (!fp) {
-        SLOGV("%s %s", mounts_file, strerror(errno));
+        PLOG(ERROR) << "failed to open " << mounts_file;
     } else {
         char *line = NULL;
         size_t len = 0;
diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp
index 70adc70..14565d3 100644
--- a/recovery-refresh.cpp
+++ b/recovery-refresh.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "recovery-refresh"
-
 //
 // Strictly to deal with reboot into system after OTA, then
 // reboot while in system before boot complete landing us back
@@ -40,65 +38,11 @@
 //
 
 #include <string.h>
-
 #include <string>
 
-#include <android/log.h> /* Android Log Priority Tags */
-#include <log/logger.h> /* Android Log packet format */
 #include <private/android_logger.h> /* private pmsg functions */
 
-static const char LAST_KMSG_FILE[] = "recovery/last_kmsg";
-static const char LAST_LOG_FILE[] = "recovery/last_log";
-
-static ssize_t logbasename(
-        log_id_t /* logId */,
-        char /* prio */,
-        const char *filename,
-        const char * /* buf */, size_t len,
-        void *arg) {
-    if (strstr(LAST_KMSG_FILE, filename) ||
-            strstr(LAST_LOG_FILE, filename)) {
-        bool *doRotate = reinterpret_cast<bool *>(arg);
-        *doRotate = true;
-    }
-    return len;
-}
-
-static ssize_t logrotate(
-        log_id_t logId,
-        char prio,
-        const char *filename,
-        const char *buf, size_t len,
-        void *arg) {
-    bool *doRotate = reinterpret_cast<bool *>(arg);
-    if (!*doRotate) {
-        return __android_log_pmsg_file_write(logId, prio, filename, buf, len);
-    }
-
-    std::string name(filename);
-    size_t dot = name.find_last_of(".");
-    std::string sub = name.substr(0, dot);
-
-    if (!strstr(LAST_KMSG_FILE, sub.c_str()) &&
-                !strstr(LAST_LOG_FILE, sub.c_str())) {
-        return __android_log_pmsg_file_write(logId, prio, filename, buf, len);
-    }
-
-    // filename rotation
-    if (dot == std::string::npos) {
-        name += ".1";
-    } else {
-        std::string number = name.substr(dot + 1);
-        if (!isdigit(number.data()[0])) {
-            name += ".1";
-        } else {
-            unsigned long long i = std::stoull(number);
-            name = sub + "." + std::to_string(i + 1);
-        }
-    }
-
-    return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len);
-}
+#include "rotate_logs.h"
 
 int main(int argc, char **argv) {
     static const char filter[] = "recovery/";
@@ -106,7 +50,6 @@
     static const char rotate_flag[] = "--rotate";
     ssize_t ret;
     bool doRotate = false;
-
     // Take last pmsg contents and rewrite it to the current pmsg session.
     if ((argc <= 1) || !argv[1] ||
             (((doRotate = strcmp(argv[1], rotate_flag))) &&
diff --git a/recovery.cpp b/recovery.cpp
index 0f0b978..c1a31b6 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -34,23 +34,30 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <chrono>
+#include <memory>
 #include <string>
 #include <vector>
 
 #include <adb.h>
-#include <android/log.h> /* Android Log Priority Tags */
 #include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <bootloader_message/bootloader_message.h>
 #include <cutils/android_reboot.h>
-#include <cutils/properties.h>
-#include <log/logger.h> /* Android Log packet format */
-#include <private/android_logger.h> /* private pmsg functions */
-
+#include <cutils/properties.h> /* for property_list */
 #include <healthd/BatteryMonitor.h>
+#include <private/android_logger.h> /* private pmsg functions */
+#include <private/android_filesystem_config.h>  /* for AID_SYSTEM */
+#include <selinux/android.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <ziparchive/zip_archive.h>
 
 #include "adb_install.h"
 #include "common.h"
@@ -59,18 +66,16 @@
 #include "fuse_sdcard_provider.h"
 #include "fuse_sideload.h"
 #include "install.h"
+#include "minadbd/minadbd.h"
 #include "minui/minui.h"
-#include "minzip/DirUtil.h"
-#include "minzip/Zip.h"
+#include "otautil/DirUtil.h"
 #include "roots.h"
-#include "ui.h"
-#include "unique_fd.h"
+#include "rotate_logs.h"
 #include "screen_ui.h"
-
-struct selabel_handle *sehandle;
+#include "stub_ui.h"
+#include "ui.h"
 
 static const struct option OPTIONS[] = {
-  { "send_intent", required_argument, NULL, 'i' },
   { "update_package", required_argument, NULL, 'u' },
   { "retry_count", required_argument, NULL, 'n' },
   { "wipe_data", no_argument, NULL, 'w' },
@@ -80,12 +85,12 @@
   { "sideload_auto_reboot", no_argument, NULL, 'a' },
   { "just_exit", no_argument, NULL, 'x' },
   { "locale", required_argument, NULL, 'l' },
-  { "stages", required_argument, NULL, 'g' },
   { "shutdown_after", no_argument, NULL, 'p' },
   { "reason", required_argument, NULL, 'r' },
   { "security", no_argument, NULL, 'e'},
   { "wipe_ab", no_argument, NULL, 0 },
   { "wipe_package_size", required_argument, NULL, 0 },
+  { "prompt_and_wipe_data", no_argument, NULL, 0 },
   { NULL, 0, NULL, 0 },
 };
 
@@ -97,7 +102,6 @@
 
 static const char *CACHE_LOG_DIR = "/cache/recovery";
 static const char *COMMAND_FILE = "/cache/recovery/command";
-static const char *INTENT_FILE = "/cache/recovery/intent";
 static const char *LOG_FILE = "/cache/recovery/log";
 static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
 static const char *LOCALE_FILE = "/cache/recovery/last_locale";
@@ -110,7 +114,6 @@
 static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
 static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
 static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
-static const int KEEP_LOG_COUNT = 10;
 // We will try to apply the update package 5 times at most in case of an I/O error.
 static const int EIO_RETRY_COUNT = 4;
 static const int BATTERY_READ_TIMEOUT_IN_SEC = 10;
@@ -119,25 +122,28 @@
 // So we should check battery with a slightly lower limitation.
 static const int BATTERY_OK_PERCENTAGE = 20;
 static const int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15;
-constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe";
+static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe";
+static constexpr const char* DEFAULT_LOCALE = "en-US";
 
-RecoveryUI* ui = NULL;
-static const char* locale = "en_US";
-char* stage = NULL;
-char* reason = NULL;
-bool modified_flash = false;
+static std::string locale;
 static bool has_cache = false;
 
+RecoveryUI* ui = nullptr;
+bool modified_flash = false;
+std::string stage;
+const char* reason = nullptr;
+struct selabel_handle* sehandle;
+
 /*
  * The recovery tool communicates with the main system through /cache files.
  *   /cache/recovery/command - INPUT - command line for tool, one arg per line
  *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
- *   /cache/recovery/intent - OUTPUT - intent that was passed in
  *
  * The arguments which may be supplied in the recovery.command file:
- *   --send_intent=anystring - write the text out to recovery.intent
  *   --update_package=path - verify install an OTA package file
  *   --wipe_data - erase user data (and cache), then reboot
+ *   --prompt_and_wipe_data - prompt the user that data is corrupt,
+ *       with their consent erase user data (and cache), then reboot
  *   --wipe_cache - wipe cache (but not user data), then reboot
  *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
  *   --just_exit - do nothing; exit and reboot
@@ -170,30 +176,13 @@
  *    -- after this, rebooting will (try to) restart the main system --
  * 7. ** if install failed **
  *    7a. prompt_and_wait() shows an error icon and waits for the user
- *    7b; the user reboots (pulling the battery, etc) into the main system
- * 8. main() calls maybe_install_firmware_update()
- *    ** if the update contained radio/hboot firmware **:
- *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
- *        -- after this, rebooting will reformat cache & restart main system --
- *    8b. m_i_f_u() writes firmware image into raw cache partition
- *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
- *        -- after this, rebooting will attempt to reinstall firmware --
- *    8d. bootloader tries to flash firmware
- *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
- *        -- after this, rebooting will reformat cache & restart main system --
- *    8f. erase_volume() reformats /cache
- *    8g. finish_recovery() erases BCB
- *        -- after this, rebooting will (try to) restart the main system --
- * 9. main() calls reboot() to boot main system
+ *    7b. the user reboots (pulling the battery, etc) into the main system
  */
 
-static const int MAX_ARG_LENGTH = 4096;
-static const int MAX_ARGS = 100;
-
 // open a given path, mounting partitions as necessary
 FILE* fopen_path(const char *path, const char *mode) {
     if (ensure_path_mounted(path) != 0) {
-        LOGE("Can't mount %s\n", path);
+        LOG(ERROR) << "Can't mount " << path;
         return NULL;
     }
 
@@ -208,19 +197,31 @@
 // close a file, log an error if the error indicator is set
 static void check_and_fclose(FILE *fp, const char *name) {
     fflush(fp);
-    if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno));
+    if (fsync(fileno(fp)) == -1) {
+        PLOG(ERROR) << "Failed to fsync " << name;
+    }
+    if (ferror(fp)) {
+        PLOG(ERROR) << "Error in " << name;
+    }
     fclose(fp);
 }
 
 bool is_ro_debuggable() {
-    char value[PROPERTY_VALUE_MAX+1];
-    return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1');
+    return android::base::GetBoolProperty("ro.debuggable", false);
+}
+
+bool reboot(const std::string& command) {
+    std::string cmd = command;
+    if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+        cmd += ",quiescent";
+    }
+    return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
 }
 
 static void redirect_stdio(const char* filename) {
     int pipefd[2];
     if (pipe(pipefd) == -1) {
-        LOGE("pipe failed: %s\n", strerror(errno));
+        PLOG(ERROR) << "pipe failed";
 
         // Fall back to traditional logging mode without timestamps.
         // If these fail, there's not really anywhere to complain...
@@ -232,7 +233,7 @@
 
     pid_t pid = fork();
     if (pid == -1) {
-        LOGE("fork failed: %s\n", strerror(errno));
+        PLOG(ERROR) << "fork failed";
 
         // Fall back to traditional logging mode without timestamps.
         // If these fail, there's not really anywhere to complain...
@@ -251,17 +252,17 @@
         // Child logger to actually write to the log file.
         FILE* log_fp = fopen(filename, "a");
         if (log_fp == nullptr) {
-            LOGE("fopen \"%s\" failed: %s\n", filename, strerror(errno));
+            PLOG(ERROR) << "fopen \"" << filename << "\" failed";
             close(pipefd[0]);
-            _exit(1);
+            _exit(EXIT_FAILURE);
         }
 
         FILE* pipe_fp = fdopen(pipefd[0], "r");
         if (pipe_fp == nullptr) {
-            LOGE("fdopen failed: %s\n", strerror(errno));
+            PLOG(ERROR) << "fdopen failed";
             check_and_fclose(log_fp, filename);
             close(pipefd[0]);
-            _exit(1);
+            _exit(EXIT_FAILURE);
         }
 
         char* line = nullptr;
@@ -278,12 +279,12 @@
             fflush(log_fp);
         }
 
-        LOGE("getline failed: %s\n", strerror(errno));
+        PLOG(ERROR) << "getline failed";
 
         free(line);
         check_and_fclose(log_fp, filename);
         close(pipefd[0]);
-        _exit(1);
+        _exit(EXIT_FAILURE);
     } else {
         // Redirect stdout/stderr to the logger process.
         // Close the unused read end.
@@ -293,10 +294,10 @@
         setbuf(stderr, nullptr);
 
         if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
-            LOGE("dup2 stdout failed: %s\n", strerror(errno));
+            PLOG(ERROR) << "dup2 stdout failed";
         }
         if (dup2(pipefd[1], STDERR_FILENO) == -1) {
-            LOGE("dup2 stderr failed: %s\n", strerror(errno));
+            PLOG(ERROR) << "dup2 stderr failed";
         }
 
         close(pipefd[1]);
@@ -307,104 +308,95 @@
 //   - the actual command line
 //   - the bootloader control block (one per line, after "recovery")
 //   - the contents of COMMAND_FILE (one per line)
-static void
-get_args(int *argc, char ***argv) {
-    bootloader_message boot = {};
-    std::string err;
-    if (!read_bootloader_message(&boot, &err)) {
-        LOGE("%s\n", err.c_str());
-        // If fails, leave a zeroed bootloader_message.
-        memset(&boot, 0, sizeof(boot));
-    }
-    stage = strndup(boot.stage, sizeof(boot.stage));
+static std::vector<std::string> get_args(const int argc, char** const argv) {
+  CHECK_GT(argc, 0);
 
-    if (boot.command[0] != 0 && boot.command[0] != 255) {
-        LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command);
-    }
+  bootloader_message boot = {};
+  std::string err;
+  if (!read_bootloader_message(&boot, &err)) {
+    LOG(ERROR) << err;
+    // If fails, leave a zeroed bootloader_message.
+    boot = {};
+  }
+  stage = std::string(boot.stage);
 
-    if (boot.status[0] != 0 && boot.status[0] != 255) {
-        LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status);
-    }
+  if (boot.command[0] != 0) {
+    std::string boot_command = std::string(boot.command, sizeof(boot.command));
+    LOG(INFO) << "Boot command: " << boot_command;
+  }
 
-    // --- if arguments weren't supplied, look in the bootloader control block
-    if (*argc <= 1) {
-        boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
-        const char *arg = strtok(boot.recovery, "\n");
-        if (arg != NULL && !strcmp(arg, "recovery")) {
-            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
-            (*argv)[0] = strdup(arg);
-            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
-                if ((arg = strtok(NULL, "\n")) == NULL) break;
-                (*argv)[*argc] = strdup(arg);
-            }
-            LOGI("Got arguments from boot message\n");
-        } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {
-            LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
-        }
-    }
+  if (boot.status[0] != 0) {
+    std::string boot_status = std::string(boot.status, sizeof(boot.status));
+    LOG(INFO) << "Boot status: " << boot_status;
+  }
 
-    // --- if that doesn't work, try the command file (if we have /cache).
-    if (*argc <= 1 && has_cache) {
-        FILE *fp = fopen_path(COMMAND_FILE, "r");
-        if (fp != NULL) {
-            char *token;
-            char *argv0 = (*argv)[0];
-            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
-            (*argv)[0] = argv0;  // use the same program name
+  std::vector<std::string> args(argv, argv + argc);
 
-            char buf[MAX_ARG_LENGTH];
-            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
-                if (!fgets(buf, sizeof(buf), fp)) break;
-                token = strtok(buf, "\r\n");
-                if (token != NULL) {
-                    (*argv)[*argc] = strdup(token);  // Strip newline.
-                } else {
-                    --*argc;
-                }
-            }
+  // --- if arguments weren't supplied, look in the bootloader control block
+  if (args.size() == 1) {
+    boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
+    std::string boot_recovery(boot.recovery);
+    std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
+    if (!tokens.empty() && tokens[0] == "recovery") {
+      for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
+        // Skip empty and '\0'-filled tokens.
+        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
+      }
+      LOG(INFO) << "Got " << args.size() << " arguments from boot message";
+    } else if (boot.recovery[0] != 0) {
+      LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
+    }
+  }
 
-            check_and_fclose(fp, COMMAND_FILE);
-            LOGI("Got arguments from %s\n", COMMAND_FILE);
-        }
+  // --- if that doesn't work, try the command file (if we have /cache).
+  if (args.size() == 1 && has_cache) {
+    std::string content;
+    if (ensure_path_mounted(COMMAND_FILE) == 0 &&
+        android::base::ReadFileToString(COMMAND_FILE, &content)) {
+      std::vector<std::string> tokens = android::base::Split(content, "\n");
+      // All the arguments in COMMAND_FILE are needed (unlike the BCB message,
+      // COMMAND_FILE doesn't use filename as the first argument).
+      for (auto it = tokens.begin(); it != tokens.end(); it++) {
+        // Skip empty and '\0'-filled tokens.
+        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
+      }
+      LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;
     }
+  }
 
-    // --> write the arguments we have back into the bootloader control block
-    // always boot into recovery after this (until finish_recovery() is called)
-    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
-    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
-    int i;
-    for (i = 1; i < *argc; ++i) {
-        strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
-        strlcat(boot.recovery, "\n", sizeof(boot.recovery));
-    }
-    if (!write_bootloader_message(boot, &err)) {
-        LOGE("%s\n", err.c_str());
-    }
+  // Write the arguments (excluding the filename in args[0]) back into the
+  // bootloader control block. So the device will always boot into recovery to
+  // finish the pending work, until finish_recovery() is called.
+  std::vector<std::string> options(args.cbegin() + 1, args.cend());
+  if (!update_bootloader_message(options, &err)) {
+    LOG(ERROR) << "Failed to set BCB message: " << err;
+  }
+
+  return args;
 }
 
-static void
-set_sdcard_update_bootloader_message() {
-    bootloader_message boot = {};
-    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
-    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
-    std::string err;
-    if (!write_bootloader_message(boot, &err)) {
-        LOGE("%s\n", err.c_str());
-    }
+// Set the BCB to reboot back into recovery (it won't resume the install from
+// sdcard though).
+static void set_sdcard_update_bootloader_message() {
+  std::vector<std::string> options;
+  std::string err;
+  if (!update_bootloader_message(options, &err)) {
+    LOG(ERROR) << "Failed to set BCB message: " << err;
+  }
 }
 
 // Read from kernel log into buffer and write out to file.
 static void save_kernel_log(const char* destination) {
     int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0);
     if (klog_buf_len <= 0) {
-        LOGE("Error getting klog size: %s\n", strerror(errno));
+        PLOG(ERROR) << "Error getting klog size";
         return;
     }
 
     std::string buffer(klog_buf_len, 0);
     int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len);
     if (n == -1) {
-        LOGE("Error in reading klog: %s\n", strerror(errno));
+        PLOG(ERROR) << "Error in reading klog";
         return;
     }
     buffer.resize(n);
@@ -424,17 +416,17 @@
 }
 
 // How much of the temp log we have copied to the copy in cache.
-static long tmplog_offset = 0;
+static off_t tmplog_offset = 0;
 
 static void copy_log_file(const char* source, const char* destination, bool append) {
     FILE* dest_fp = fopen_path(destination, append ? "a" : "w");
     if (dest_fp == nullptr) {
-        LOGE("Can't open %s\n", destination);
+        PLOG(ERROR) << "Can't open " << destination;
     } else {
         FILE* source_fp = fopen(source, "r");
         if (source_fp != nullptr) {
             if (append) {
-                fseek(source_fp, tmplog_offset, SEEK_SET);  // Since last write
+                fseeko(source_fp, tmplog_offset, SEEK_SET);  // Since last write
             }
             char buf[4096];
             size_t bytes;
@@ -442,7 +434,7 @@
                 fwrite(buf, 1, bytes, dest_fp);
             }
             if (append) {
-                tmplog_offset = ftell(source_fp);
+                tmplog_offset = ftello(source_fp);
             }
             check_and_fclose(source_fp, source);
         }
@@ -450,37 +442,6 @@
     }
 }
 
-// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
-// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
-// Overwrite any existing last_log.$max and last_kmsg.$max.
-static void rotate_logs(int max) {
-    // Logs should only be rotated once.
-    static bool rotated = false;
-    if (rotated) {
-        return;
-    }
-    rotated = true;
-    ensure_path_mounted(LAST_LOG_FILE);
-    ensure_path_mounted(LAST_KMSG_FILE);
-
-    for (int i = max-1; i >= 0; --i) {
-        std::string old_log = android::base::StringPrintf("%s", LAST_LOG_FILE);
-        if (i > 0) {
-          old_log += "." + std::to_string(i);
-        }
-        std::string new_log = android::base::StringPrintf("%s.%d", LAST_LOG_FILE, i+1);
-        // Ignore errors if old_log doesn't exist.
-        rename(old_log.c_str(), new_log.c_str());
-
-        std::string old_kmsg = android::base::StringPrintf("%s", LAST_KMSG_FILE);
-        if (i > 0) {
-          old_kmsg += "." + std::to_string(i);
-        }
-        std::string new_kmsg = android::base::StringPrintf("%s.%d", LAST_KMSG_FILE, i+1);
-        rename(old_kmsg.c_str(), new_kmsg.c_str());
-    }
-}
-
 static void copy_logs() {
     // We only rotate and record the log of the current session if there are
     // actual attempts to modify the flash, such as wipes, installs from BCB
@@ -499,7 +460,9 @@
         return;
     }
 
-    rotate_logs(KEEP_LOG_COUNT);
+    ensure_path_mounted(LAST_LOG_FILE);
+    ensure_path_mounted(LAST_KMSG_FILE);
+    rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
 
     // Copy logs to cache so the system can find out what happened.
     copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
@@ -507,60 +470,43 @@
     copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
     save_kernel_log(LAST_KMSG_FILE);
     chmod(LOG_FILE, 0600);
-    chown(LOG_FILE, 1000, 1000);   // system user
+    chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
     chmod(LAST_KMSG_FILE, 0600);
-    chown(LAST_KMSG_FILE, 1000, 1000);   // system user
+    chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM);
     chmod(LAST_LOG_FILE, 0640);
     chmod(LAST_INSTALL_FILE, 0644);
     sync();
 }
 
 // clear the recovery command and prepare to boot a (hopefully working) system,
-// copy our log file to cache as well (for the system to read), and
-// record any intent we were asked to communicate back to the system.
-// this function is idempotent: call it as many times as you like.
-static void
-finish_recovery(const char *send_intent) {
-    // By this point, we're ready to return to the main system...
-    if (send_intent != NULL && has_cache) {
-        FILE *fp = fopen_path(INTENT_FILE, "w");
-        if (fp == NULL) {
-            LOGE("Can't open %s\n", INTENT_FILE);
-        } else {
-            fputs(send_intent, fp);
-            check_and_fclose(fp, INTENT_FILE);
-        }
-    }
-
+// copy our log file to cache as well (for the system to read). This function is
+// idempotent: call it as many times as you like.
+static void finish_recovery() {
     // Save the locale to cache, so if recovery is next started up
     // without a --locale argument (eg, directly from the bootloader)
     // it will use the last-known locale.
-    if (locale != NULL) {
-        size_t len = strlen(locale);
-        __pmsg_write(LOCALE_FILE, locale, len);
-        if (has_cache) {
-            LOGI("Saving locale \"%s\"\n", locale);
-            FILE* fp = fopen_path(LOCALE_FILE, "w");
-            fwrite(locale, 1, len, fp);
-            fflush(fp);
-            fsync(fileno(fp));
-            check_and_fclose(fp, LOCALE_FILE);
+    if (!locale.empty() && has_cache) {
+        LOG(INFO) << "Saving locale \"" << locale << "\"";
+
+        FILE* fp = fopen_path(LOCALE_FILE, "w");
+        if (!android::base::WriteStringToFd(locale, fileno(fp))) {
+            PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
         }
+        check_and_fclose(fp, LOCALE_FILE);
     }
 
     copy_logs();
 
     // Reset to normal system boot so recovery won't cycle indefinitely.
-    bootloader_message boot = {};
     std::string err;
-    if (!write_bootloader_message(boot, &err)) {
-        LOGE("%s\n", err.c_str());
+    if (!clear_bootloader_message(&err)) {
+        LOG(ERROR) << "Failed to clear BCB message: " << err;
     }
 
     // Remove the command file, so recovery won't repeat indefinitely.
     if (has_cache) {
         if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
-            LOGW("Can't unlink %s\n", COMMAND_FILE);
+            LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
         }
         ensure_path_unmounted(CACHE_ROOT);
     }
@@ -568,287 +514,239 @@
     sync();  // For good measure.
 }
 
-typedef struct _saved_log_file {
-    char* name;
-    struct stat st;
-    unsigned char* data;
-    struct _saved_log_file* next;
-} saved_log_file;
+struct saved_log_file {
+  std::string name;
+  struct stat sb;
+  std::string data;
+};
 
 static bool erase_volume(const char* volume) {
-    bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
-    bool is_data = (strcmp(volume, DATA_ROOT) == 0);
+  bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+  bool is_data = (strcmp(volume, DATA_ROOT) == 0);
 
-    ui->SetBackground(RecoveryUI::ERASING);
-    ui->SetProgressType(RecoveryUI::INDETERMINATE);
+  ui->SetBackground(RecoveryUI::ERASING);
+  ui->SetProgressType(RecoveryUI::INDETERMINATE);
 
-    saved_log_file* head = NULL;
+  std::vector<saved_log_file> log_files;
 
-    if (is_cache) {
-        // If we're reformatting /cache, we load any past logs
-        // (i.e. "/cache/recovery/last_*") and the current log
-        // ("/cache/recovery/log") into memory, so we can restore them after
-        // the reformat.
+  if (is_cache) {
+    // If we're reformatting /cache, we load any past logs
+    // (i.e. "/cache/recovery/last_*") and the current log
+    // ("/cache/recovery/log") into memory, so we can restore them after
+    // the reformat.
 
-        ensure_path_mounted(volume);
-
-        DIR* d;
-        struct dirent* de;
-        d = opendir(CACHE_LOG_DIR);
-        if (d) {
-            char path[PATH_MAX];
-            strcpy(path, CACHE_LOG_DIR);
-            strcat(path, "/");
-            int path_len = strlen(path);
-            while ((de = readdir(d)) != NULL) {
-                if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
-                    saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
-                    strcpy(path+path_len, de->d_name);
-                    p->name = strdup(path);
-                    if (stat(path, &(p->st)) == 0) {
-                        // truncate files to 512kb
-                        if (p->st.st_size > (1 << 19)) {
-                            p->st.st_size = 1 << 19;
-                        }
-                        p->data = (unsigned char*) malloc(p->st.st_size);
-                        FILE* f = fopen(path, "rb");
-                        fread(p->data, 1, p->st.st_size, f);
-                        fclose(f);
-                        p->next = head;
-                        head = p;
-                    } else {
-                        free(p);
-                    }
-                }
-            }
-            closedir(d);
-        } else {
-            if (errno != ENOENT) {
-                printf("opendir failed: %s\n", strerror(errno));
-            }
-        }
-    }
-
-    ui->Print("Formatting %s...\n", volume);
-
-    ensure_path_unmounted(volume);
-
-    int result;
-
-    if (is_data && reason && strcmp(reason, "convert_fbe") == 0) {
-        // Create convert_fbe breadcrumb file to signal to init
-        // to convert to file based encryption, not full disk encryption
-        if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
-            ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
-            return true;
-        }
-        FILE* f = fopen(CONVERT_FBE_FILE, "wb");
-        if (!f) {
-            ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
-            return true;
-        }
-        fclose(f);
-        result = format_volume(volume, CONVERT_FBE_DIR);
-        remove(CONVERT_FBE_FILE);
-        rmdir(CONVERT_FBE_DIR);
-    } else {
-        result = format_volume(volume);
-    }
-
-    if (is_cache) {
-        while (head) {
-            FILE* f = fopen_path(head->name, "wb");
-            if (f) {
-                fwrite(head->data, 1, head->st.st_size, f);
-                fclose(f);
-                chmod(head->name, head->st.st_mode);
-                chown(head->name, head->st.st_uid, head->st.st_gid);
-            }
-            free(head->name);
-            free(head->data);
-            saved_log_file* temp = head->next;
-            free(head);
-            head = temp;
-        }
-
-        // Any part of the log we'd copied to cache is now gone.
-        // Reset the pointer so we copy from the beginning of the temp
-        // log.
-        tmplog_offset = 0;
-        copy_logs();
-    }
-
-    return (result == 0);
-}
-
-static int
-get_menu_selection(const char* const * headers, const char* const * items,
-                   int menu_only, int initial_selection, Device* device) {
-    // throw away keys pressed previously, so user doesn't
-    // accidentally trigger menu items.
-    ui->FlushKeys();
-
-    ui->StartMenu(headers, items, initial_selection);
-    int selected = initial_selection;
-    int chosen_item = -1;
-
-    while (chosen_item < 0) {
-        int key = ui->WaitKey();
-        int visible = ui->IsTextVisible();
-
-        if (key == -1) {   // ui_wait_key() timed out
-            if (ui->WasTextEverVisible()) {
-                continue;
-            } else {
-                LOGI("timed out waiting for key input; rebooting.\n");
-                ui->EndMenu();
-                return 0; // XXX fixme
-            }
-        }
-
-        int action = device->HandleMenuKey(key, visible);
-
-        if (action < 0) {
-            switch (action) {
-                case Device::kHighlightUp:
-                    selected = ui->SelectMenu(--selected);
-                    break;
-                case Device::kHighlightDown:
-                    selected = ui->SelectMenu(++selected);
-                    break;
-                case Device::kInvokeItem:
-                    chosen_item = selected;
-                    break;
-                case Device::kNoAction:
-                    break;
-            }
-        } else if (!menu_only) {
-            chosen_item = action;
-        }
-    }
-
-    ui->EndMenu();
-    return chosen_item;
-}
-
-static int compare_string(const void* a, const void* b) {
-    return strcmp(*(const char**)a, *(const char**)b);
-}
-
-// Returns a malloc'd path, or NULL.
-static char* browse_directory(const char* path, Device* device) {
-    ensure_path_mounted(path);
-
-    DIR* d = opendir(path);
-    if (d == NULL) {
-        LOGE("error opening %s: %s\n", path, strerror(errno));
-        return NULL;
-    }
-
-    int d_size = 0;
-    int d_alloc = 10;
-    char** dirs = (char**)malloc(d_alloc * sizeof(char*));
-    int z_size = 1;
-    int z_alloc = 10;
-    char** zips = (char**)malloc(z_alloc * sizeof(char*));
-    zips[0] = strdup("../");
+    ensure_path_mounted(volume);
 
     struct dirent* de;
-    while ((de = readdir(d)) != NULL) {
-        int name_len = strlen(de->d_name);
+    std::unique_ptr<DIR, decltype(&closedir)> d(opendir(CACHE_LOG_DIR), closedir);
+    if (d) {
+      while ((de = readdir(d.get())) != nullptr) {
+        if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
+          std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name);
 
-        if (de->d_type == DT_DIR) {
-            // skip "." and ".." entries
-            if (name_len == 1 && de->d_name[0] == '.') continue;
-            if (name_len == 2 && de->d_name[0] == '.' &&
-                de->d_name[1] == '.') continue;
-
-            if (d_size >= d_alloc) {
-                d_alloc *= 2;
-                dirs = (char**)realloc(dirs, d_alloc * sizeof(char*));
+          struct stat sb;
+          if (stat(path.c_str(), &sb) == 0) {
+            // truncate files to 512kb
+            if (sb.st_size > (1 << 19)) {
+              sb.st_size = 1 << 19;
             }
-            dirs[d_size] = (char*)malloc(name_len + 2);
-            strcpy(dirs[d_size], de->d_name);
-            dirs[d_size][name_len] = '/';
-            dirs[d_size][name_len+1] = '\0';
-            ++d_size;
-        } else if (de->d_type == DT_REG &&
-                   name_len >= 4 &&
-                   strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) {
-            if (z_size >= z_alloc) {
-                z_alloc *= 2;
-                zips = (char**)realloc(zips, z_alloc * sizeof(char*));
-            }
-            zips[z_size++] = strdup(de->d_name);
+
+            std::string data(sb.st_size, '\0');
+            FILE* f = fopen(path.c_str(), "rb");
+            fread(&data[0], 1, data.size(), f);
+            fclose(f);
+
+            log_files.emplace_back(saved_log_file{ path, sb, data });
+          }
         }
+      }
+    } else {
+      if (errno != ENOENT) {
+        PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR;
+      }
     }
-    closedir(d);
+  }
 
-    qsort(dirs, d_size, sizeof(char*), compare_string);
-    qsort(zips, z_size, sizeof(char*), compare_string);
+  ui->Print("Formatting %s...\n", volume);
 
-    // append dirs to the zips list
-    if (d_size + z_size + 1 > z_alloc) {
-        z_alloc = d_size + z_size + 1;
-        zips = (char**)realloc(zips, z_alloc * sizeof(char*));
+  ensure_path_unmounted(volume);
+
+  int result;
+
+  if (is_data && reason && strcmp(reason, "convert_fbe") == 0) {
+    // Create convert_fbe breadcrumb file to signal to init
+    // to convert to file based encryption, not full disk encryption
+    if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
+      ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
+      return true;
     }
-    memcpy(zips + z_size, dirs, d_size * sizeof(char*));
-    free(dirs);
-    z_size += d_size;
-    zips[z_size] = NULL;
+    FILE* f = fopen(CONVERT_FBE_FILE, "wb");
+    if (!f) {
+      ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
+      return true;
+    }
+    fclose(f);
+    result = format_volume(volume, CONVERT_FBE_DIR);
+    remove(CONVERT_FBE_FILE);
+    rmdir(CONVERT_FBE_DIR);
+  } else {
+    result = format_volume(volume);
+  }
 
-    const char* headers[] = { "Choose a package to install:", path, NULL };
-
-    char* result;
-    int chosen_item = 0;
-    while (true) {
-        chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device);
-
-        char* item = zips[chosen_item];
-        int item_len = strlen(item);
-        if (chosen_item == 0) {          // item 0 is always "../"
-            // go up but continue browsing (if the caller is update_directory)
-            result = NULL;
-            break;
+  if (is_cache) {
+    // Re-create the log dir and write back the log entries.
+    if (ensure_path_mounted(CACHE_LOG_DIR) == 0 &&
+        dirCreateHierarchy(CACHE_LOG_DIR, 0777, nullptr, false, sehandle) == 0) {
+      for (const auto& log : log_files) {
+        if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid,
+                                              log.sb.st_gid)) {
+          PLOG(ERROR) << "Failed to write to " << log.name;
         }
-
-        char new_path[PATH_MAX];
-        strlcpy(new_path, path, PATH_MAX);
-        strlcat(new_path, "/", PATH_MAX);
-        strlcat(new_path, item, PATH_MAX);
-
-        if (item[item_len-1] == '/') {
-            // recurse down into a subdirectory
-            new_path[strlen(new_path)-1] = '\0';  // truncate the trailing '/'
-            result = browse_directory(new_path, device);
-            if (result) break;
-        } else {
-            // selected a zip file: return the malloc'd path to the caller.
-            result = strdup(new_path);
-            break;
-        }
+      }
+    } else {
+      PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR;
     }
 
-    for (int i = 0; i < z_size; ++i) free(zips[i]);
-    free(zips);
+    // Any part of the log we'd copied to cache is now gone.
+    // Reset the pointer so we copy from the beginning of the temp
+    // log.
+    tmplog_offset = 0;
+    copy_logs();
+  }
 
-    return result;
+  return (result == 0);
+}
+
+// Display a menu with the specified 'headers' and 'items'. Device specific HandleMenuKey() may
+// return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only
+// a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the
+// (non-negative) chosen item number, or -1 if timed out waiting for input.
+static int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only,
+                              int initial_selection, Device* device) {
+  // Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
+  ui->FlushKeys();
+
+  ui->StartMenu(headers, items, initial_selection);
+
+  int selected = initial_selection;
+  int chosen_item = -1;
+  while (chosen_item < 0) {
+    int key = ui->WaitKey();
+    if (key == -1) {  // WaitKey() timed out.
+      if (ui->WasTextEverVisible()) {
+        continue;
+      } else {
+        LOG(INFO) << "Timed out waiting for key input; rebooting.";
+        ui->EndMenu();
+        return -1;
+      }
+    }
+
+    bool visible = ui->IsTextVisible();
+    int action = device->HandleMenuKey(key, visible);
+
+    if (action < 0) {
+      switch (action) {
+        case Device::kHighlightUp:
+          selected = ui->SelectMenu(--selected);
+          break;
+        case Device::kHighlightDown:
+          selected = ui->SelectMenu(++selected);
+          break;
+        case Device::kInvokeItem:
+          chosen_item = selected;
+          break;
+        case Device::kNoAction:
+          break;
+      }
+    } else if (!menu_only) {
+      chosen_item = action;
+    }
+  }
+
+  ui->EndMenu();
+  return chosen_item;
+}
+
+// Returns the selected filename, or an empty string.
+static std::string browse_directory(const std::string& path, Device* device) {
+  ensure_path_mounted(path.c_str());
+
+  std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir);
+  if (!d) {
+    PLOG(ERROR) << "error opening " << path;
+    return "";
+  }
+
+  std::vector<std::string> dirs;
+  std::vector<std::string> zips = { "../" };  // "../" is always the first entry.
+
+  dirent* de;
+  while ((de = readdir(d.get())) != nullptr) {
+    std::string name(de->d_name);
+
+    if (de->d_type == DT_DIR) {
+      // Skip "." and ".." entries.
+      if (name == "." || name == "..") continue;
+      dirs.push_back(name + "/");
+    } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) {
+      zips.push_back(name);
+    }
+  }
+
+  std::sort(dirs.begin(), dirs.end());
+  std::sort(zips.begin(), zips.end());
+
+  // Append dirs to the zips list.
+  zips.insert(zips.end(), dirs.begin(), dirs.end());
+
+  const char* entries[zips.size() + 1];
+  entries[zips.size()] = nullptr;
+  for (size_t i = 0; i < zips.size(); i++) {
+    entries[i] = zips[i].c_str();
+  }
+
+  const char* headers[] = { "Choose a package to install:", path.c_str(), nullptr };
+
+  int chosen_item = 0;
+  while (true) {
+    chosen_item = get_menu_selection(headers, entries, true, chosen_item, device);
+
+    const std::string& item = zips[chosen_item];
+    if (chosen_item == 0) {
+      // Go up but continue browsing (if the caller is browse_directory).
+      return "";
+    }
+
+    std::string new_path = path + "/" + item;
+    if (new_path.back() == '/') {
+      // Recurse down into a subdirectory.
+      new_path.pop_back();
+      std::string result = browse_directory(new_path, device);
+      if (!result.empty()) return result;
+    } else {
+      // Selected a zip file: return the path to the caller.
+      return new_path;
+    }
+  }
+
+  // Unreachable.
 }
 
 static bool yes_no(Device* device, const char* question1, const char* question2) {
     const char* headers[] = { question1, question2, NULL };
     const char* items[] = { " No", " Yes", NULL };
 
-    int chosen_item = get_menu_selection(headers, items, 1, 0, device);
+    int chosen_item = get_menu_selection(headers, items, true, 0, device);
     return (chosen_item == 1);
 }
 
-// Return true on success.
-static bool wipe_data(int should_confirm, Device* device) {
-    if (should_confirm && !yes_no(device, "Wipe all user data?", "  THIS CAN NOT BE UNDONE!")) {
-        return false;
-    }
+static bool ask_to_wipe_data(Device* device) {
+    return yes_no(device, "Wipe all user data?", "  THIS CAN NOT BE UNDONE!");
+}
 
+// Return true on success.
+static bool wipe_data(Device* device) {
     modified_flash = true;
 
     ui->Print("\n-- Wiping data...\n");
@@ -861,6 +759,30 @@
     return success;
 }
 
+static bool prompt_and_wipe_data(Device* device) {
+  const char* const headers[] = {
+    "Can't load Android system. Your data may be corrupt.",
+    "If you continue to get this message, you may need to",
+    "perform a factory data reset and erase all user data",
+    "stored on this device.",
+    NULL
+  };
+  const char* const items[] = {
+    "Try again",
+    "Factory data reset",
+    NULL
+  };
+  for (;;) {
+    int chosen_item = get_menu_selection(headers, items, true, 0, device);
+    if (chosen_item != 1) {
+      return true;  // Just reboot, no wipe; not a failure, user asked for it
+    }
+    if (ask_to_wipe_data(device)) {
+      return wipe_data(device);
+    }
+  }
+}
+
 // Return true on success.
 static bool wipe_cache(bool should_confirm, Device* device) {
     if (!has_cache) {
@@ -880,47 +802,45 @@
     return success;
 }
 
-// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported.
-// Otherwise, it goes with BLKDISCARD (if device supports BLKDISCARDZEROES) or
-// BLKZEROOUT.
+// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
+// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
 static bool secure_wipe_partition(const std::string& partition) {
-    unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
-    if (fd.get() == -1) {
-        LOGE("failed to open \"%s\": %s\n", partition.c_str(), strerror(errno));
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open \"" << partition << "\"";
+    return false;
+  }
+
+  uint64_t range[2] = { 0, 0 };
+  if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
+    PLOG(ERROR) << "Failed to get partition size";
+    return false;
+  }
+  LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
+
+  LOG(INFO) << "  Trying BLKSECDISCARD...";
+  if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
+    PLOG(WARNING) << "  Failed";
+
+    // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
+    unsigned int zeroes;
+    if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
+      LOG(INFO) << "  Trying BLKDISCARD...";
+      if (ioctl(fd, BLKDISCARD, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
         return false;
-    }
-
-    uint64_t range[2] = {0, 0};
-    if (ioctl(fd.get(), BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
-        LOGE("failed to get partition size: %s\n", strerror(errno));
+      }
+    } else {
+      LOG(INFO) << "  Trying BLKZEROOUT...";
+      if (ioctl(fd, BLKZEROOUT, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
         return false;
+      }
     }
-    printf("Secure-wiping \"%s\" from %" PRIu64 " to %" PRIu64 ".\n",
-           partition.c_str(), range[0], range[1]);
+  }
 
-    printf("Trying BLKSECDISCARD...\t");
-    if (ioctl(fd.get(), BLKSECDISCARD, &range) == -1) {
-        printf("failed: %s\n", strerror(errno));
-
-        // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
-        unsigned int zeroes;
-        if (ioctl(fd.get(), BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
-            printf("Trying BLKDISCARD...\t");
-            if (ioctl(fd.get(), BLKDISCARD, &range) == -1) {
-                printf("failed: %s\n", strerror(errno));
-                return false;
-            }
-        } else {
-            printf("Trying BLKZEROOUT...\t");
-            if (ioctl(fd.get(), BLKZEROOUT, &range) == -1) {
-                printf("failed: %s\n", strerror(errno));
-                return false;
-            }
-        }
-    }
-
-    printf("done\n");
-    return true;
+  LOG(INFO) << "  Done";
+  return true;
 }
 
 // Check if the wipe package matches expectation:
@@ -928,35 +848,35 @@
 // 2. check metadata (ota-type, pre-device and serial number if having one).
 static bool check_wipe_package(size_t wipe_package_size) {
     if (wipe_package_size == 0) {
-        LOGE("wipe_package_size is zero.\n");
+        LOG(ERROR) << "wipe_package_size is zero";
         return false;
     }
     std::string wipe_package;
     std::string err_str;
     if (!read_wipe_package(&wipe_package, wipe_package_size, &err_str)) {
-        LOGE("Failed to read wipe package: %s\n", err_str.c_str());
+        PLOG(ERROR) << "Failed to read wipe package";
         return false;
     }
     if (!verify_package(reinterpret_cast<const unsigned char*>(wipe_package.data()),
                         wipe_package.size())) {
-        LOGE("Failed to verify package.\n");
+        LOG(ERROR) << "Failed to verify package";
         return false;
     }
 
     // Extract metadata
-    ZipArchive zip;
-    int err = mzOpenZipArchive(reinterpret_cast<unsigned char*>(&wipe_package[0]),
-                               wipe_package.size(), &zip);
+    ZipArchiveHandle zip;
+    int err = OpenArchiveFromMemory(static_cast<void*>(&wipe_package[0]), wipe_package.size(),
+                                    "wipe_package", &zip);
     if (err != 0) {
-        LOGE("Can't open wipe package: %s\n", err != -1 ? strerror(err) : "bad");
+        LOG(ERROR) << "Can't open wipe package : " << ErrorCodeString(err);
         return false;
     }
     std::string metadata;
-    if (!read_metadata_from_package(&zip, &metadata)) {
-        mzCloseZipArchive(&zip);
+    if (!read_metadata_from_package(zip, &metadata)) {
+        CloseArchive(zip);
         return false;
     }
-    mzCloseZipArchive(&zip);
+    CloseArchive(zip);
 
     // Check metadata
     std::vector<std::string> lines = android::base::Split(metadata, "\n");
@@ -969,13 +889,11 @@
             ota_type_matched = true;
         } else if (android::base::StartsWith(line, "pre-device=")) {
             std::string device_type = line.substr(strlen("pre-device="));
-            char real_device_type[PROPERTY_VALUE_MAX];
-            property_get("ro.build.product", real_device_type, "");
+            std::string real_device_type = android::base::GetProperty("ro.build.product", "");
             device_type_matched = (device_type == real_device_type);
         } else if (android::base::StartsWith(line, "serialno=")) {
             std::string serial_no = line.substr(strlen("serialno="));
-            char real_serial_no[PROPERTY_VALUE_MAX];
-            property_get("ro.serialno", real_serial_no, "");
+            std::string real_serial_no = android::base::GetProperty("ro.serialno", "");
             has_serial_number = true;
             serial_number_matched = (serial_no == real_serial_no);
         }
@@ -990,12 +908,12 @@
     ui->SetProgressType(RecoveryUI::INDETERMINATE);
 
     if (!check_wipe_package(wipe_package_size)) {
-        LOGE("Failed to verify wipe package\n");
+        LOG(ERROR) << "Failed to verify wipe package";
         return false;
     }
     std::string partition_list;
     if (!android::base::ReadFileToString(RECOVERY_WIPE, &partition_list)) {
-        LOGE("failed to read \"%s\".\n", RECOVERY_WIPE);
+        LOG(ERROR) << "failed to read \"" << RECOVERY_WIPE << "\"";
         return false;
     }
 
@@ -1014,98 +932,94 @@
 }
 
 static void choose_recovery_file(Device* device) {
-    // "Back" + KEEP_LOG_COUNT * 2 + terminating nullptr entry
-    char* entries[1 + KEEP_LOG_COUNT * 2 + 1];
-    memset(entries, 0, sizeof(entries));
-
-    unsigned int n = 0;
-
-    if (has_cache) {
-        // Add LAST_LOG_FILE + LAST_LOG_FILE.x
-        // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x
-        for (int i = 0; i < KEEP_LOG_COUNT; i++) {
-            char* log_file;
-            int ret;
-            ret = (i == 0) ? asprintf(&log_file, "%s", LAST_LOG_FILE) :
-                    asprintf(&log_file, "%s.%d", LAST_LOG_FILE, i);
-            if (ret == -1) {
-                // memory allocation failure - return early. Should never happen.
-                return;
-            }
-            if ((ensure_path_mounted(log_file) != 0) || (access(log_file, R_OK) == -1)) {
-                free(log_file);
-            } else {
-                entries[n++] = log_file;
-            }
-
-            char* kmsg_file;
-            ret = (i == 0) ? asprintf(&kmsg_file, "%s", LAST_KMSG_FILE) :
-                    asprintf(&kmsg_file, "%s.%d", LAST_KMSG_FILE, i);
-            if (ret == -1) {
-                // memory allocation failure - return early. Should never happen.
-                return;
-            }
-            if ((ensure_path_mounted(kmsg_file) != 0) || (access(kmsg_file, R_OK) == -1)) {
-                free(kmsg_file);
-            } else {
-                entries[n++] = kmsg_file;
-            }
+  std::vector<std::string> entries;
+  if (has_cache) {
+    for (int i = 0; i < KEEP_LOG_COUNT; i++) {
+      auto add_to_entries = [&](const char* filename) {
+        std::string log_file(filename);
+        if (i > 0) {
+          log_file += "." + std::to_string(i);
         }
+
+        if (ensure_path_mounted(log_file.c_str()) == 0 && access(log_file.c_str(), R_OK) == 0) {
+          entries.push_back(std::move(log_file));
+        }
+      };
+
+      // Add LAST_LOG_FILE + LAST_LOG_FILE.x
+      add_to_entries(LAST_LOG_FILE);
+
+      // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x
+      add_to_entries(LAST_KMSG_FILE);
+    }
+  } else {
+    // If cache partition is not found, view /tmp/recovery.log instead.
+    if (access(TEMPORARY_LOG_FILE, R_OK) == -1) {
+      return;
     } else {
-        // If cache partition is not found, view /tmp/recovery.log instead.
-        ui->Print("No /cache partition found.\n");
-        if (access(TEMPORARY_LOG_FILE, R_OK) == -1) {
-            return;
-        } else{
-            entries[n++] = strdup(TEMPORARY_LOG_FILE);
-        }
+      entries.push_back(TEMPORARY_LOG_FILE);
     }
+  }
 
-    entries[n++] = strdup("Back");
+  entries.push_back("Back");
 
-    const char* headers[] = { "Select file to view", nullptr };
+  std::vector<const char*> menu_entries(entries.size());
+  std::transform(entries.cbegin(), entries.cend(), menu_entries.begin(),
+                 [](const std::string& entry) { return entry.c_str(); });
+  menu_entries.push_back(nullptr);
 
-    while (true) {
-        int chosen_item = get_menu_selection(headers, entries, 1, 0, device);
-        if (strcmp(entries[chosen_item], "Back") == 0) break;
+  const char* headers[] = { "Select file to view", nullptr };
 
-        ui->ShowFile(entries[chosen_item]);
-    }
+  int chosen_item = 0;
+  while (true) {
+    chosen_item = get_menu_selection(headers, menu_entries.data(), true, chosen_item, device);
+    if (entries[chosen_item] == "Back") break;
 
-    for (size_t i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) {
-        free(entries[i]);
-    }
+    ui->ShowFile(entries[chosen_item].c_str());
+  }
 }
 
-static void run_graphics_test(Device* device) {
-    // Switch to graphics screen.
-    ui->ShowText(false);
+static void run_graphics_test() {
+  // Switch to graphics screen.
+  ui->ShowText(false);
 
-    ui->SetProgressType(RecoveryUI::INDETERMINATE);
-    ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
-    sleep(1);
+  ui->SetProgressType(RecoveryUI::INDETERMINATE);
+  ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
+  sleep(1);
 
-    ui->SetBackground(RecoveryUI::ERROR);
-    sleep(1);
+  ui->SetBackground(RecoveryUI::ERROR);
+  sleep(1);
 
-    ui->SetBackground(RecoveryUI::NO_COMMAND);
-    sleep(1);
+  ui->SetBackground(RecoveryUI::NO_COMMAND);
+  sleep(1);
 
-    ui->SetBackground(RecoveryUI::ERASING);
-    sleep(1);
+  ui->SetBackground(RecoveryUI::ERASING);
+  sleep(1);
 
-    ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
+  // Calling SetBackground() after SetStage() to trigger a redraw.
+  ui->SetStage(1, 3);
+  ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
+  sleep(1);
+  ui->SetStage(2, 3);
+  ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
+  sleep(1);
+  ui->SetStage(3, 3);
+  ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
+  sleep(1);
 
-    ui->SetProgressType(RecoveryUI::DETERMINATE);
-    ui->ShowProgress(1.0, 10.0);
-    float fraction = 0.0;
-    for (size_t i = 0; i < 100; ++i) {
-      fraction += .01;
-      ui->SetProgress(fraction);
-      usleep(100000);
-    }
+  ui->SetStage(-1, -1);
+  ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
 
-    ui->ShowText(true);
+  ui->SetProgressType(RecoveryUI::DETERMINATE);
+  ui->ShowProgress(1.0, 10.0);
+  float fraction = 0.0;
+  for (size_t i = 0; i < 100; ++i) {
+    fraction += .01;
+    ui->SetProgress(fraction);
+    usleep(100000);
+  }
+
+  ui->ShowText(true);
 }
 
 // How long (in seconds) we wait for the fuse-provided package file to
@@ -1120,14 +1034,14 @@
         return INSTALL_ERROR;
     }
 
-    char* path = browse_directory(SDCARD_ROOT, device);
-    if (path == NULL) {
+    std::string path = browse_directory(SDCARD_ROOT, device);
+    if (path.empty()) {
         ui->Print("\n-- No package file selected.\n");
         ensure_path_unmounted(SDCARD_ROOT);
         return INSTALL_ERROR;
     }
 
-    ui->Print("\n-- Install %s ...\n", path);
+    ui->Print("\n-- Install %s ...\n", path.c_str());
     set_sdcard_update_bootloader_message();
 
     // We used to use fuse in a thread as opposed to a process. Since accessing
@@ -1135,7 +1049,7 @@
     // to deadlock when a page fault occurs. (Bug: 26313124)
     pid_t child;
     if ((child = fork()) == 0) {
-        bool status = start_sdcard_fuse(path);
+        bool status = start_sdcard_fuse(path.c_str());
 
         _exit(status ? EXIT_SUCCESS : EXIT_FAILURE);
     }
@@ -1158,7 +1072,7 @@
                 sleep(1);
                 continue;
             } else {
-                LOGE("Timed out waiting for the fuse-provided package.\n");
+                LOG(ERROR) << "Timed out waiting for the fuse-provided package.";
                 result = INSTALL_ERROR;
                 kill(child, SIGKILL);
                 break;
@@ -1180,118 +1094,116 @@
     }
 
     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-        LOGE("Error exit from the fuse process: %d\n", WEXITSTATUS(status));
+        LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status);
     }
 
     ensure_path_unmounted(SDCARD_ROOT);
     return result;
 }
 
-// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER.  Returning NO_ACTION
-// means to take the default, which is to reboot or shutdown depending
-// on if the --shutdown_after flag was passed to recovery.
-static Device::BuiltinAction
-prompt_and_wait(Device* device, int status) {
-    for (;;) {
-        finish_recovery(NULL);
-        switch (status) {
-            case INSTALL_SUCCESS:
-            case INSTALL_NONE:
-                ui->SetBackground(RecoveryUI::NO_COMMAND);
-                break;
+// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default,
+// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
+static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
+  for (;;) {
+    finish_recovery();
+    switch (status) {
+      case INSTALL_SUCCESS:
+      case INSTALL_NONE:
+        ui->SetBackground(RecoveryUI::NO_COMMAND);
+        break;
 
-            case INSTALL_ERROR:
-            case INSTALL_CORRUPT:
-                ui->SetBackground(RecoveryUI::ERROR);
-                break;
-        }
-        ui->SetProgressType(RecoveryUI::EMPTY);
-
-        int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), 0, 0, device);
-
-        // device-specific code may take some action here.  It may
-        // return one of the core actions handled in the switch
-        // statement below.
-        Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item);
-
-        bool should_wipe_cache = false;
-        switch (chosen_action) {
-            case Device::NO_ACTION:
-                break;
-
-            case Device::REBOOT:
-            case Device::SHUTDOWN:
-            case Device::REBOOT_BOOTLOADER:
-                return chosen_action;
-
-            case Device::WIPE_DATA:
-                wipe_data(ui->IsTextVisible(), device);
-                if (!ui->IsTextVisible()) return Device::NO_ACTION;
-                break;
-
-            case Device::WIPE_CACHE:
-                wipe_cache(ui->IsTextVisible(), device);
-                if (!ui->IsTextVisible()) return Device::NO_ACTION;
-                break;
-
-            case Device::APPLY_ADB_SIDELOAD:
-            case Device::APPLY_SDCARD:
-                {
-                    bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
-                    if (adb) {
-                        status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
-                    } else {
-                        status = apply_from_sdcard(device, &should_wipe_cache);
-                    }
-
-                    if (status == INSTALL_SUCCESS && should_wipe_cache) {
-                        if (!wipe_cache(false, device)) {
-                            status = INSTALL_ERROR;
-                        }
-                    }
-
-                    if (status != INSTALL_SUCCESS) {
-                        ui->SetBackground(RecoveryUI::ERROR);
-                        ui->Print("Installation aborted.\n");
-                        copy_logs();
-                    } else if (!ui->IsTextVisible()) {
-                        return Device::NO_ACTION;  // reboot if logs aren't visible
-                    } else {
-                        ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card");
-                    }
-                }
-                break;
-
-            case Device::VIEW_RECOVERY_LOGS:
-                choose_recovery_file(device);
-                break;
-
-            case Device::RUN_GRAPHICS_TEST:
-                run_graphics_test(device);
-                break;
-
-            case Device::MOUNT_SYSTEM:
-                char system_root_image[PROPERTY_VALUE_MAX];
-                property_get("ro.build.system_root_image", system_root_image, "");
-
-                // For a system image built with the root directory (i.e.
-                // system_root_image == "true"), we mount it to /system_root, and symlink /system
-                // to /system_root/system to make adb shell work (the symlink is created through
-                // the build system).
-                // Bug: 22855115
-                if (strcmp(system_root_image, "true") == 0) {
-                    if (ensure_path_mounted_at("/", "/system_root") != -1) {
-                        ui->Print("Mounted /system.\n");
-                    }
-                } else {
-                    if (ensure_path_mounted("/system") != -1) {
-                        ui->Print("Mounted /system.\n");
-                    }
-                }
-
-                break;
-        }
+      case INSTALL_ERROR:
+      case INSTALL_CORRUPT:
+        ui->SetBackground(RecoveryUI::ERROR);
+        break;
     }
+    ui->SetProgressType(RecoveryUI::EMPTY);
+
+    int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device);
+
+    // Device-specific code may take some action here. It may return one of the core actions
+    // handled in the switch statement below.
+    Device::BuiltinAction chosen_action =
+        (chosen_item == -1) ? Device::REBOOT : device->InvokeMenuItem(chosen_item);
+
+    bool should_wipe_cache = false;
+    switch (chosen_action) {
+      case Device::NO_ACTION:
+        break;
+
+      case Device::REBOOT:
+      case Device::SHUTDOWN:
+      case Device::REBOOT_BOOTLOADER:
+        return chosen_action;
+
+      case Device::WIPE_DATA:
+        if (ui->IsTextVisible()) {
+          if (ask_to_wipe_data(device)) {
+            wipe_data(device);
+          }
+        } else {
+          wipe_data(device);
+          return Device::NO_ACTION;
+        }
+        break;
+
+      case Device::WIPE_CACHE:
+        wipe_cache(ui->IsTextVisible(), device);
+        if (!ui->IsTextVisible()) return Device::NO_ACTION;
+        break;
+
+      case Device::APPLY_ADB_SIDELOAD:
+      case Device::APPLY_SDCARD:
+        {
+          bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
+          if (adb) {
+            status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
+          } else {
+            status = apply_from_sdcard(device, &should_wipe_cache);
+          }
+
+          if (status == INSTALL_SUCCESS && should_wipe_cache) {
+            if (!wipe_cache(false, device)) {
+              status = INSTALL_ERROR;
+            }
+          }
+
+          if (status != INSTALL_SUCCESS) {
+            ui->SetBackground(RecoveryUI::ERROR);
+            ui->Print("Installation aborted.\n");
+            copy_logs();
+          } else if (!ui->IsTextVisible()) {
+            return Device::NO_ACTION;  // reboot if logs aren't visible
+          } else {
+            ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card");
+          }
+        }
+        break;
+
+      case Device::VIEW_RECOVERY_LOGS:
+        choose_recovery_file(device);
+        break;
+
+      case Device::RUN_GRAPHICS_TEST:
+        run_graphics_test();
+        break;
+
+      case Device::MOUNT_SYSTEM:
+        // For a system image built with the root directory (i.e. system_root_image == "true"), we
+        // mount it to /system_root, and symlink /system to /system_root/system to make adb shell
+        // work (the symlink is created through the build system). (Bug: 22855115)
+        if (android::base::GetBoolProperty("ro.build.system_root_image", false)) {
+          if (ensure_path_mounted_at("/", "/system_root") != -1) {
+            ui->Print("Mounted /system.\n");
+          }
+        } else {
+          if (ensure_path_mounted("/system") != -1) {
+            ui->Print("Mounted /system.\n");
+          }
+        }
+        break;
+    }
+  }
 }
 
 static void
@@ -1299,40 +1211,44 @@
     printf("%s=%s\n", key, name);
 }
 
-static void
-load_locale_from_cache() {
-    FILE* fp = fopen_path(LOCALE_FILE, "r");
-    char buffer[80];
-    if (fp != NULL) {
-        fgets(buffer, sizeof(buffer), fp);
-        int j = 0;
-        unsigned int i;
-        for (i = 0; i < sizeof(buffer) && buffer[i]; ++i) {
-            if (!isspace(buffer[i])) {
-                buffer[j++] = buffer[i];
-            }
-        }
-        buffer[j] = 0;
-        locale = strdup(buffer);
-        check_and_fclose(fp, LOCALE_FILE);
+static std::string load_locale_from_cache() {
+    if (ensure_path_mounted(LOCALE_FILE) != 0) {
+        LOG(ERROR) << "Can't mount " << LOCALE_FILE;
+        return "";
+    }
+
+    std::string content;
+    if (!android::base::ReadFileToString(LOCALE_FILE, &content)) {
+        PLOG(ERROR) << "Can't read " << LOCALE_FILE;
+        return "";
+    }
+
+    return android::base::Trim(content);
+}
+
+void ui_print(const char* format, ...) {
+    std::string buffer;
+    va_list ap;
+    va_start(ap, format);
+    android::base::StringAppendV(&buffer, format, ap);
+    va_end(ap);
+
+    if (ui != nullptr) {
+        ui->Print("%s", buffer.c_str());
+    } else {
+        fputs(buffer.c_str(), stdout);
     }
 }
 
-static RecoveryUI* gCurrentUI = NULL;
+static constexpr char log_characters[] = "VDIWEF";
 
-void
-ui_print(const char* format, ...) {
-    char buffer[256];
-
-    va_list ap;
-    va_start(ap, format);
-    vsnprintf(buffer, sizeof(buffer), format, ap);
-    va_end(ap);
-
-    if (gCurrentUI != NULL) {
-        gCurrentUI->Print("%s", buffer);
+void UiLogger(android::base::LogId id, android::base::LogSeverity severity,
+               const char* tag, const char* file, unsigned int line,
+               const char* message) {
+    if (severity >= android::base::ERROR && ui != nullptr) {
+        ui->Print("E:%s\n", message);
     } else {
-        fputs(buffer, stdout);
+        fprintf(stdout, "%c:%s\n", log_characters[severity], message);
     }
 }
 
@@ -1390,42 +1306,32 @@
     }
 }
 
-static void set_retry_bootloader_message(int retry_count, int argc, char** argv) {
-    bootloader_message boot = {};
-    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
-    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
+static void set_retry_bootloader_message(int retry_count, const std::vector<std::string>& args) {
+  std::vector<std::string> options;
+  for (const auto& arg : args) {
+    if (!android::base::StartsWith(arg, "--retry_count")) {
+      options.push_back(arg);
+    }
+  }
 
-    for (int i = 1; i < argc; ++i) {
-        if (strstr(argv[i], "retry_count") == nullptr) {
-            strlcat(boot.recovery, argv[i], sizeof(boot.recovery));
-            strlcat(boot.recovery, "\n", sizeof(boot.recovery));
-        }
-    }
-
-    // Initialize counter to 1 if it's not in BCB, otherwise increment it by 1.
-    if (retry_count == 0) {
-        strlcat(boot.recovery, "--retry_count=1\n", sizeof(boot.recovery));
-    } else {
-        char buffer[20];
-        snprintf(buffer, sizeof(buffer), "--retry_count=%d\n", retry_count+1);
-        strlcat(boot.recovery, buffer, sizeof(boot.recovery));
-    }
-    std::string err;
-    if (!write_bootloader_message(boot, &err)) {
-        LOGE("%s\n", err.c_str());
-    }
+  // Increment the retry counter by 1.
+  options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count + 1));
+  std::string err;
+  if (!update_bootloader_message(options, &err)) {
+    LOG(ERROR) << err;
+  }
 }
 
 static bool bootreason_in_blacklist() {
-    char bootreason[PROPERTY_VALUE_MAX];
-    if (property_get("ro.boot.bootreason", bootreason, nullptr) > 0) {
-        for (const auto& str : bootreason_blacklist) {
-            if (strcasecmp(str.c_str(), bootreason) == 0) {
-                return true;
-            }
-        }
+  std::string bootreason = android::base::GetProperty("ro.boot.bootreason", "");
+  if (!bootreason.empty()) {
+    for (const auto& str : bootreason_blacklist) {
+      if (strcasecmp(str.c_str(), bootreason.c_str()) == 0) {
+        return true;
+      }
     }
-    return false;
+  }
+  return false;
 }
 
 static void log_failure_code(ErrorCode code, const char *update_package) {
@@ -1436,68 +1342,23 @@
     };
     std::string log_content = android::base::Join(log_buffer, "\n");
     if (!android::base::WriteStringToFile(log_content, TEMPORARY_INSTALL_FILE)) {
-        LOGE("failed to write %s: %s\n", TEMPORARY_INSTALL_FILE, strerror(errno));
+        PLOG(ERROR) << "failed to write " << TEMPORARY_INSTALL_FILE;
     }
 
     // Also write the info into last_log.
-    LOGI("%s\n", log_content.c_str());
-}
-
-static ssize_t logbasename(
-        log_id_t /* logId */,
-        char /* prio */,
-        const char *filename,
-        const char * /* buf */, size_t len,
-        void *arg) {
-    if (strstr(LAST_KMSG_FILE, filename) ||
-            strstr(LAST_LOG_FILE, filename)) {
-        bool *doRotate = reinterpret_cast<bool *>(arg);
-        *doRotate = true;
-    }
-    return len;
-}
-
-static ssize_t logrotate(
-        log_id_t logId,
-        char prio,
-        const char *filename,
-        const char *buf, size_t len,
-        void *arg) {
-    bool *doRotate = reinterpret_cast<bool *>(arg);
-    if (!*doRotate) {
-        return __android_log_pmsg_file_write(logId, prio, filename, buf, len);
-    }
-
-    std::string name(filename);
-    size_t dot = name.find_last_of(".");
-    std::string sub = name.substr(0, dot);
-
-    if (!strstr(LAST_KMSG_FILE, sub.c_str()) &&
-                !strstr(LAST_LOG_FILE, sub.c_str())) {
-        return __android_log_pmsg_file_write(logId, prio, filename, buf, len);
-    }
-
-    // filename rotation
-    if (dot == std::string::npos) {
-        name += ".1";
-    } else {
-        std::string number = name.substr(dot + 1);
-        if (!isdigit(number.data()[0])) {
-            name += ".1";
-        } else {
-            unsigned long long i = std::stoull(number);
-            name = sub + "." + std::to_string(i + 1);
-        }
-    }
-
-    return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len);
+    LOG(INFO) << log_content;
 }
 
 int main(int argc, char **argv) {
+    // We don't have logcat yet under recovery; so we'll print error on screen and
+    // log to stdout (which is redirected to recovery.log) as we used to do.
+    android::base::InitLogging(argv, &UiLogger);
+
     // Take last pmsg contents and rewrite it to the current pmsg session.
     static const char filter[] = "recovery/";
     // Do we need to rotate?
     bool doRotate = false;
+
     __android_log_pmsg_file_read(
         LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
         logbasename, &doRotate);
@@ -1514,7 +1375,7 @@
     // only way recovery should be run with this argument is when it
     // starts a copy of itself from the apply_from_adb() function.
     if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
-        adb_server_main(0, DEFAULT_ADB_PORT, -1);
+        minadbd_main();
         return 0;
     }
 
@@ -1529,11 +1390,14 @@
     load_volume_table();
     has_cache = volume_for_path(CACHE_ROOT) != nullptr;
 
-    get_args(&argc, &argv);
+    std::vector<std::string> args = get_args(argc, argv);
+    std::vector<char*> args_to_parse(args.size());
+    std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
+                   [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
 
-    const char *send_intent = NULL;
     const char *update_package = NULL;
     bool should_wipe_data = false;
+    bool should_prompt_and_wipe_data = false;
     bool should_wipe_cache = false;
     bool should_wipe_ab = false;
     size_t wipe_package_size = 0;
@@ -1547,9 +1411,9 @@
 
     int arg;
     int option_index;
-    while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
+    while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
+                              &option_index)) != -1) {
         switch (arg) {
-        case 'i': send_intent = optarg; break;
         case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
         case 'u': update_package = optarg; break;
         case 'w': should_wipe_data = true; break;
@@ -1559,64 +1423,67 @@
         case 'a': sideload = true; sideload_auto_reboot = true; break;
         case 'x': just_exit = true; break;
         case 'l': locale = optarg; break;
-        case 'g': {
-            if (stage == NULL || *stage == '\0') {
-                char buffer[20] = "1/";
-                strncat(buffer, optarg, sizeof(buffer)-3);
-                stage = strdup(buffer);
-            }
-            break;
-        }
         case 'p': shutdown_after = true; break;
         case 'r': reason = optarg; break;
         case 'e': security_update = true; break;
         case 0: {
-            if (strcmp(OPTIONS[option_index].name, "wipe_ab") == 0) {
+            std::string option = OPTIONS[option_index].name;
+            if (option == "wipe_ab") {
                 should_wipe_ab = true;
-                break;
-            } else if (strcmp(OPTIONS[option_index].name, "wipe_package_size") == 0) {
+            } else if (option == "wipe_package_size") {
                 android::base::ParseUint(optarg, &wipe_package_size);
-                break;
+            } else if (option == "prompt_and_wipe_data") {
+                should_prompt_and_wipe_data = true;
             }
             break;
         }
         case '?':
-            LOGE("Invalid command argument\n");
+            LOG(ERROR) << "Invalid command argument";
             continue;
         }
     }
 
-    if (locale == nullptr && has_cache) {
-        load_locale_from_cache();
+    if (locale.empty()) {
+        if (has_cache) {
+            locale = load_locale_from_cache();
+        }
+
+        if (locale.empty()) {
+            locale = DEFAULT_LOCALE;
+        }
     }
-    printf("locale is [%s]\n", locale);
-    printf("stage is [%s]\n", stage);
+
+    printf("locale is [%s]\n", locale.c_str());
+    printf("stage is [%s]\n", stage.c_str());
     printf("reason is [%s]\n", reason);
 
     Device* device = make_device();
-    ui = device->GetUI();
-    gCurrentUI = ui;
+    if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+        printf("Quiescent recovery mode.\n");
+        ui = new StubRecoveryUI();
+    } else {
+        ui = device->GetUI();
 
-    ui->SetLocale(locale);
-    ui->Init();
+        if (!ui->Init(locale)) {
+            printf("Failed to initialize UI, use stub UI instead.\n");
+            ui = new StubRecoveryUI();
+        }
+    }
+
     // Set background string to "installing security update" for security update,
     // otherwise set it to "installing system update".
     ui->SetSystemUpdateText(security_update);
 
     int st_cur, st_max;
-    if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) {
+    if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
         ui->SetStage(st_cur, st_max);
     }
 
     ui->SetBackground(RecoveryUI::NONE);
     if (show_text) ui->ShowText(true);
 
-    struct selinux_opt seopts[] = {
-      { SELABEL_OPT_PATH, "/file_contexts" }
-    };
-
-    sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
-
+    sehandle = selinux_android_file_context_handle();
+    selinux_android_set_sehandle(sehandle);
     if (!sehandle) {
         ui->Print("Warning: No file_contexts\n");
     }
@@ -1624,30 +1491,10 @@
     device->StartRecovery();
 
     printf("Command:");
-    for (arg = 0; arg < argc; arg++) {
-        printf(" \"%s\"", argv[arg]);
+    for (const auto& arg : args) {
+        printf(" \"%s\"", arg.c_str());
     }
-    printf("\n");
-
-    if (update_package) {
-        // For backwards compatibility on the cache partition only, if
-        // we're given an old 'root' path "CACHE:foo", change it to
-        // "/cache/foo".
-        if (strncmp(update_package, "CACHE:", 6) == 0) {
-            int len = strlen(update_package) + 10;
-            char* modified_path = (char*)malloc(len);
-            if (modified_path) {
-                strlcpy(modified_path, "/cache/", len);
-                strlcat(modified_path, update_package+6, len);
-                printf("(replacing path \"%s\" with \"%s\")\n",
-                       update_package, modified_path);
-                update_package = modified_path;
-            }
-            else
-                printf("modified_path allocation failed\n");
-        }
-    }
-    printf("\n");
+    printf("\n\n");
 
     property_list(print_property, NULL);
     printf("\n");
@@ -1685,13 +1532,12 @@
                 // times before we abandon this OTA update.
                 if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
                     copy_logs();
-                    set_retry_bootloader_message(retry_count, argc, argv);
+                    set_retry_bootloader_message(retry_count, args);
                     // Print retry count on screen.
                     ui->Print("Retry attempt %d\n", retry_count);
 
                     // Reboot and retry the update
-                    int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery");
-                    if (ret < 0) {
+                    if (!reboot("reboot,recovery")) {
                         ui->Print("Reboot failed\n");
                     } else {
                         while (true) {
@@ -1708,9 +1554,16 @@
             }
         }
     } else if (should_wipe_data) {
-        if (!wipe_data(false, device)) {
+        if (!wipe_data(device)) {
             status = INSTALL_ERROR;
         }
+    } else if (should_prompt_and_wipe_data) {
+        ui->ShowText(true);
+        ui->SetBackground(RecoveryUI::ERROR);
+        if (!prompt_and_wipe_data(device)) {
+            status = INSTALL_ERROR;
+        }
+        ui->ShowText(false);
     } else if (should_wipe_cache) {
         if (!wipe_cache(false, device)) {
             status = INSTALL_ERROR;
@@ -1766,26 +1619,26 @@
     }
 
     // Save logs and clean up before rebooting or shutting down.
-    finish_recovery(send_intent);
+    finish_recovery();
 
     switch (after) {
         case Device::SHUTDOWN:
             ui->Print("Shutting down...\n");
-            property_set(ANDROID_RB_PROPERTY, "shutdown,");
+            android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
             break;
 
         case Device::REBOOT_BOOTLOADER:
             ui->Print("Rebooting to bootloader...\n");
-            property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
+            android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
             break;
 
         default:
             ui->Print("Rebooting...\n");
-            property_set(ANDROID_RB_PROPERTY, "reboot,");
+            reboot("reboot,");
             break;
     }
     while (true) {
-      pause();
+        pause();
     }
     // Should be unreachable.
     return EXIT_SUCCESS;
diff --git a/res-hdpi/images/erasing_text.png b/res-hdpi/images/erasing_text.png
index e500d02..0982544 100644
--- a/res-hdpi/images/erasing_text.png
+++ b/res-hdpi/images/erasing_text.png
Binary files differ
diff --git a/res-hdpi/images/error_text.png b/res-hdpi/images/error_text.png
index 9a3597b..3a06f6e 100644
--- a/res-hdpi/images/error_text.png
+++ b/res-hdpi/images/error_text.png
Binary files differ
diff --git a/res-hdpi/images/installing_security_text.png b/res-hdpi/images/installing_security_text.png
index 76e7474..b1acd23 100644
--- a/res-hdpi/images/installing_security_text.png
+++ b/res-hdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-hdpi/images/installing_text.png b/res-hdpi/images/installing_text.png
index 5d2a5fa..f0f5d8b 100644
--- a/res-hdpi/images/installing_text.png
+++ b/res-hdpi/images/installing_text.png
Binary files differ
diff --git a/res-hdpi/images/no_command_text.png b/res-hdpi/images/no_command_text.png
index a567ad1..def5036 100644
--- a/res-hdpi/images/no_command_text.png
+++ b/res-hdpi/images/no_command_text.png
Binary files differ
diff --git a/res-mdpi/images/erasing_text.png b/res-mdpi/images/erasing_text.png
index ad68a19..82b4461 100644
--- a/res-mdpi/images/erasing_text.png
+++ b/res-mdpi/images/erasing_text.png
Binary files differ
diff --git a/res-mdpi/images/error_text.png b/res-mdpi/images/error_text.png
index 8ea5acb..adb4513 100644
--- a/res-mdpi/images/error_text.png
+++ b/res-mdpi/images/error_text.png
Binary files differ
diff --git a/res-mdpi/images/installing_security_text.png b/res-mdpi/images/installing_security_text.png
index 615b9b7..54e5564 100644
--- a/res-mdpi/images/installing_security_text.png
+++ b/res-mdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-mdpi/images/installing_text.png b/res-mdpi/images/installing_text.png
index 6cf8716..d423318 100644
--- a/res-mdpi/images/installing_text.png
+++ b/res-mdpi/images/installing_text.png
Binary files differ
diff --git a/res-mdpi/images/no_command_text.png b/res-mdpi/images/no_command_text.png
index 5cc6b37..cd77ff4 100644
--- a/res-mdpi/images/no_command_text.png
+++ b/res-mdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xhdpi/images/erasing_text.png b/res-xhdpi/images/erasing_text.png
index 01176e8..333edbe 100644
--- a/res-xhdpi/images/erasing_text.png
+++ b/res-xhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xhdpi/images/error_text.png b/res-xhdpi/images/error_text.png
index 4f890fa..e262584 100644
--- a/res-xhdpi/images/error_text.png
+++ b/res-xhdpi/images/error_text.png
Binary files differ
diff --git a/res-xhdpi/images/installing_security_text.png b/res-xhdpi/images/installing_security_text.png
index 6192832..e0f0f3e 100644
--- a/res-xhdpi/images/installing_security_text.png
+++ b/res-xhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xhdpi/images/installing_text.png b/res-xhdpi/images/installing_text.png
index 4413529..a7e67f5 100644
--- a/res-xhdpi/images/installing_text.png
+++ b/res-xhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xhdpi/images/no_command_text.png b/res-xhdpi/images/no_command_text.png
index 9f74037..13aef7b 100644
--- a/res-xhdpi/images/no_command_text.png
+++ b/res-xhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xxhdpi/images/erasing_text.png b/res-xxhdpi/images/erasing_text.png
index b8653eb..80e7c47 100644
--- a/res-xxhdpi/images/erasing_text.png
+++ b/res-xxhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xxhdpi/images/error_text.png b/res-xxhdpi/images/error_text.png
index d77ee50..32a1965 100644
--- a/res-xxhdpi/images/error_text.png
+++ b/res-xxhdpi/images/error_text.png
Binary files differ
diff --git a/res-xxhdpi/images/installing_security_text.png b/res-xxhdpi/images/installing_security_text.png
index ca0c191..c53c9ac 100644
--- a/res-xxhdpi/images/installing_security_text.png
+++ b/res-xxhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xxhdpi/images/installing_text.png b/res-xxhdpi/images/installing_text.png
index a76a174..38b18d2 100644
--- a/res-xxhdpi/images/installing_text.png
+++ b/res-xxhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xxhdpi/images/no_command_text.png b/res-xxhdpi/images/no_command_text.png
index 5e363e3..a0666d8 100644
--- a/res-xxhdpi/images/no_command_text.png
+++ b/res-xxhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/erasing_text.png b/res-xxxhdpi/images/erasing_text.png
index f4a4661..4f7b37b 100644
--- a/res-xxxhdpi/images/erasing_text.png
+++ b/res-xxxhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/error_text.png b/res-xxxhdpi/images/error_text.png
index 317a771..052bf21 100644
--- a/res-xxxhdpi/images/error_text.png
+++ b/res-xxxhdpi/images/error_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/installing_security_text.png b/res-xxxhdpi/images/installing_security_text.png
index c99c907..a9e739b 100644
--- a/res-xxxhdpi/images/installing_security_text.png
+++ b/res-xxxhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/installing_text.png b/res-xxxhdpi/images/installing_text.png
index 91d8330..2d19486 100644
--- a/res-xxxhdpi/images/installing_text.png
+++ b/res-xxxhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/no_command_text.png b/res-xxxhdpi/images/no_command_text.png
index b6eb964..ee0c238 100644
--- a/res-xxxhdpi/images/no_command_text.png
+++ b/res-xxxhdpi/images/no_command_text.png
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index f361cb8..6e5ef98 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "roots.h"
+
 #include <errno.h>
 #include <stdlib.h>
 #include <sys/mount.h>
@@ -24,13 +26,13 @@
 #include <ctype.h>
 #include <fcntl.h>
 
+#include <android-base/logging.h>
+#include <ext4_utils/make_ext4fs.h>
+#include <ext4_utils/wipe.h>
 #include <fs_mgr.h>
-#include "mtdutils/mtdutils.h"
-#include "mtdutils/mounts.h"
-#include "roots.h"
+
 #include "common.h"
-#include "make_ext4fs.h"
-#include "wipe.h"
+#include "mounts.h"
 #include "cryptfs.h"
 
 static struct fstab *fstab = NULL;
@@ -42,15 +44,15 @@
     int i;
     int ret;
 
-    fstab = fs_mgr_read_fstab("/etc/recovery.fstab");
+    fstab = fs_mgr_read_fstab_default();
     if (!fstab) {
-        LOGE("failed to read /etc/recovery.fstab\n");
+        LOG(ERROR) << "failed to read default fstab";
         return;
     }
 
     ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
     if (ret < 0 ) {
-        LOGE("failed to add /tmp entry to fstab\n");
+        LOG(ERROR) << "failed to add /tmp entry to fstab";
         fs_mgr_free_fstab(fstab);
         fstab = NULL;
         return;
@@ -74,7 +76,7 @@
 int ensure_path_mounted_at(const char* path, const char* mount_point) {
     Volume* v = volume_for_path(path);
     if (v == NULL) {
-        LOGE("unknown volume for path [%s]\n", path);
+        LOG(ERROR) << "unknown volume for path [" << path << "]";
         return -1;
     }
     if (strcmp(v->fs_type, "ramdisk") == 0) {
@@ -82,10 +84,8 @@
         return 0;
     }
 
-    int result;
-    result = scan_mounted_volumes();
-    if (result < 0) {
-        LOGE("failed to scan mounted volumes\n");
+    if (!scan_mounted_volumes()) {
+        LOG(ERROR) << "failed to scan mounted volumes";
         return -1;
     }
 
@@ -93,8 +93,7 @@
         mount_point = v->mount_point;
     }
 
-    const MountedVolume* mv =
-        find_mounted_volume_by_mount_point(mount_point);
+    MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
     if (mv) {
         // volume is already mounted
         return 0;
@@ -102,29 +101,30 @@
 
     mkdir(mount_point, 0755);  // in case it doesn't already exist
 
-    if (strcmp(v->fs_type, "yaffs2") == 0) {
-        // mount an MTD partition as a YAFFS2 filesystem.
-        mtd_scan_partitions();
-        const MtdPartition* partition;
-        partition = mtd_find_partition_by_name(v->blk_device);
-        if (partition == NULL) {
-            LOGE("failed to find \"%s\" partition to mount at \"%s\"\n",
-                 v->blk_device, mount_point);
-            return -1;
-        }
-        return mtd_mount_partition(partition, mount_point, v->fs_type, 0);
-    } else if (strcmp(v->fs_type, "ext4") == 0 ||
+    if (strcmp(v->fs_type, "ext4") == 0 ||
                strcmp(v->fs_type, "squashfs") == 0 ||
                strcmp(v->fs_type, "vfat") == 0) {
-        result = mount(v->blk_device, mount_point, v->fs_type,
-                       v->flags, v->fs_options);
-        if (result == 0) return 0;
+        int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
+        if (result == -1 && fs_mgr_is_formattable(v)) {
+            LOG(ERROR) << "failed to mount " << mount_point << " (" << strerror(errno)
+                       << ") , formatting.....";
+            bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer");
+            if (fs_mgr_do_format(v, crypt_footer) == 0) {
+                result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
+            } else {
+                PLOG(ERROR) << "failed to format " << mount_point;
+                return -1;
+            }
+        }
 
-        LOGE("failed to mount %s (%s)\n", mount_point, strerror(errno));
-        return -1;
+        if (result == -1) {
+            PLOG(ERROR) << "failed to mount " << mount_point;
+            return -1;
+        }
+        return 0;
     }
 
-    LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, mount_point);
+    LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point;
     return -1;
 }
 
@@ -136,7 +136,7 @@
 int ensure_path_unmounted(const char* path) {
     Volume* v = volume_for_path(path);
     if (v == NULL) {
-        LOGE("unknown volume for path [%s]\n", path);
+        LOG(ERROR) << "unknown volume for path [" << path << "]";
         return -1;
     }
     if (strcmp(v->fs_type, "ramdisk") == 0) {
@@ -144,15 +144,12 @@
         return -1;
     }
 
-    int result;
-    result = scan_mounted_volumes();
-    if (result < 0) {
-        LOGE("failed to scan mounted volumes\n");
+    if (!scan_mounted_volumes()) {
+        LOG(ERROR) << "failed to scan mounted volumes";
         return -1;
     }
 
-    const MountedVolume* mv =
-        find_mounted_volume_by_mount_point(v->mount_point);
+    MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point);
     if (mv == NULL) {
         // volume is already unmounted
         return 0;
@@ -166,11 +163,11 @@
     pid_t child;
     if ((child = vfork()) == 0) {
         execv(path, argv);
-        _exit(-1);
+        _exit(EXIT_FAILURE);
     }
     waitpid(child, &status, 0);
     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-        LOGE("%s failed with status %d\n", path, WEXITSTATUS(status));
+        LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status);
     }
     return WEXITSTATUS(status);
 }
@@ -178,55 +175,32 @@
 int format_volume(const char* volume, const char* directory) {
     Volume* v = volume_for_path(volume);
     if (v == NULL) {
-        LOGE("unknown volume \"%s\"\n", volume);
+        LOG(ERROR) << "unknown volume \"" << volume << "\"";
         return -1;
     }
     if (strcmp(v->fs_type, "ramdisk") == 0) {
         // you can't format the ramdisk.
-        LOGE("can't format_volume \"%s\"", volume);
+        LOG(ERROR) << "can't format_volume \"" << volume << "\"";
         return -1;
     }
     if (strcmp(v->mount_point, volume) != 0) {
-        LOGE("can't give path \"%s\" to format_volume\n", volume);
+        LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume";
         return -1;
     }
 
     if (ensure_path_unmounted(volume) != 0) {
-        LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point);
+        LOG(ERROR) << "format_volume failed to unmount \"" << v->mount_point << "\"";
         return -1;
     }
 
-    if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) {
-        mtd_scan_partitions();
-        const MtdPartition* partition = mtd_find_partition_by_name(v->blk_device);
-        if (partition == NULL) {
-            LOGE("format_volume: no MTD partition \"%s\"\n", v->blk_device);
-            return -1;
-        }
-
-        MtdWriteContext *write = mtd_write_partition(partition);
-        if (write == NULL) {
-            LOGW("format_volume: can't open MTD \"%s\"\n", v->blk_device);
-            return -1;
-        } else if (mtd_erase_blocks(write, -1) == (off_t) -1) {
-            LOGW("format_volume: can't erase MTD \"%s\"\n", v->blk_device);
-            mtd_write_close(write);
-            return -1;
-        } else if (mtd_write_close(write)) {
-            LOGW("format_volume: can't close MTD \"%s\"\n", v->blk_device);
-            return -1;
-        }
-        return 0;
-    }
-
     if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) {
         // if there's a key_loc that looks like a path, it should be a
         // block device for storing encryption metadata.  wipe it too.
         if (v->key_loc != NULL && v->key_loc[0] == '/') {
-            LOGI("wiping %s\n", v->key_loc);
+            LOG(INFO) << "wiping " << v->key_loc;
             int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644);
             if (fd < 0) {
-                LOGE("format_volume: failed to open %s\n", v->key_loc);
+                LOG(ERROR) << "format_volume: failed to open " << v->key_loc;
                 return -1;
             }
             wipe_block_device(fd, get_file_size(fd));
@@ -241,19 +215,27 @@
         }
         int result;
         if (strcmp(v->fs_type, "ext4") == 0) {
-            result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory);
+            if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
+                result = make_ext4fs_directory_align(v->blk_device, length, volume, sehandle,
+                        directory, v->erase_blk_size, v->logical_blk_size);
+            } else {
+                result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory);
+            }
         } else {   /* Has to be f2fs because we checked earlier. */
             if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) {
-                LOGE("format_volume: crypt footer + negative length (%zd) not supported on %s\n", length, v->fs_type);
+                LOG(ERROR) << "format_volume: crypt footer + negative length (" << length
+                           << ") not supported on " << v->fs_type;
                 return -1;
             }
             if (length < 0) {
-                LOGE("format_volume: negative length (%zd) not supported on %s\n", length, v->fs_type);
+                LOG(ERROR) << "format_volume: negative length (" << length
+                           << ") not supported on " << v->fs_type;
                 return -1;
             }
             char *num_sectors;
             if (asprintf(&num_sectors, "%zd", length / 512) <= 0) {
-                LOGE("format_volume: failed to create %s command for %s\n", v->fs_type, v->blk_device);
+                LOG(ERROR) << "format_volume: failed to create " << v->fs_type
+                           << " command for " << v->blk_device;
                 return -1;
             }
             const char *f2fs_path = "/sbin/mkfs.f2fs";
@@ -263,13 +245,13 @@
             free(num_sectors);
         }
         if (result != 0) {
-            LOGE("format_volume: make %s failed on %s with %d(%s)\n", v->fs_type, v->blk_device, result, strerror(errno));
+            PLOG(ERROR) << "format_volume: make " << v->fs_type << " failed on " << v->blk_device;
             return -1;
         }
         return 0;
     }
 
-    LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type);
+    LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported";
     return -1;
 }
 
@@ -279,7 +261,7 @@
 
 int setup_install_mounts() {
     if (fstab == NULL) {
-        LOGE("can't set up install mounts: no fstab loaded\n");
+        LOG(ERROR) << "can't set up install mounts: no fstab loaded";
         return -1;
     }
     for (int i = 0; i < fstab->num_entries; ++i) {
@@ -288,13 +270,13 @@
         if (strcmp(v->mount_point, "/tmp") == 0 ||
             strcmp(v->mount_point, "/cache") == 0) {
             if (ensure_path_mounted(v->mount_point) != 0) {
-                LOGE("failed to mount %s\n", v->mount_point);
+                LOG(ERROR) << "failed to mount " << v->mount_point;
                 return -1;
             }
 
         } else {
             if (ensure_path_unmounted(v->mount_point) != 0) {
-                LOGE("failed to unmount %s\n", v->mount_point);
+                LOG(ERROR) << "failed to unmount " << v->mount_point;
                 return -1;
             }
         }
diff --git a/roots.h b/roots.h
index a14b7d9..542f03b 100644
--- a/roots.h
+++ b/roots.h
@@ -17,7 +17,7 @@
 #ifndef RECOVERY_ROOTS_H_
 #define RECOVERY_ROOTS_H_
 
-#include "common.h"
+typedef struct fstab_rec Volume;
 
 // Load and parse volume data from /etc/recovery.fstab.
 void load_volume_table();
diff --git a/rotate_logs.cpp b/rotate_logs.cpp
new file mode 100644
index 0000000..fc22021
--- /dev/null
+++ b/rotate_logs.cpp
@@ -0,0 +1,115 @@
+/*
+ * 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 agreed 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 "rotate_logs.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <private/android_logger.h> /* private pmsg functions */
+
+static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg";
+static const std::string LAST_LOG_FILTER = "recovery/last_log";
+
+ssize_t logbasename(
+        log_id_t /* logId */,
+        char /* prio */,
+        const char *filename,
+        const char * /* buf */, size_t len,
+        void *arg) {
+    bool* doRotate  = static_cast<bool*>(arg);
+    if (LAST_KMSG_FILTER.find(filename) != std::string::npos ||
+            LAST_LOG_FILTER.find(filename) != std::string::npos) {
+        *doRotate = true;
+    }
+    return len;
+}
+
+ssize_t logrotate(
+        log_id_t logId,
+        char prio,
+        const char *filename,
+        const char *buf, size_t len,
+        void *arg) {
+    bool* doRotate  = static_cast<bool*>(arg);
+    if (!*doRotate) {
+        return __android_log_pmsg_file_write(logId, prio, filename, buf, len);
+    }
+
+    std::string name(filename);
+    size_t dot = name.find_last_of('.');
+    std::string sub = name.substr(0, dot);
+
+    if (LAST_KMSG_FILTER.find(sub) == std::string::npos &&
+            LAST_LOG_FILTER.find(sub) == std::string::npos) {
+        return __android_log_pmsg_file_write(logId, prio, filename, buf, len);
+    }
+
+    // filename rotation
+    if (dot == std::string::npos) {
+        name += ".1";
+    } else {
+        std::string number = name.substr(dot + 1);
+        if (!isdigit(number[0])) {
+            name += ".1";
+        } else {
+            size_t i;
+            if (!android::base::ParseUint(number, &i)) {
+                LOG(ERROR) << "failed to parse uint in " << number;
+                return -1;
+            }
+            name = sub + "." + std::to_string(i + 1);
+        }
+    }
+
+    return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len);
+}
+
+// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
+// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
+// Overwrite any existing last_log.$max and last_kmsg.$max.
+void rotate_logs(const char* last_log_file, const char* last_kmsg_file) {
+    // Logs should only be rotated once.
+    static bool rotated = false;
+    if (rotated) {
+        return;
+    }
+    rotated = true;
+
+    for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) {
+        std::string old_log = android::base::StringPrintf("%s", last_log_file);
+        if (i > 0) {
+          old_log += "." + std::to_string(i);
+        }
+        std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i+1);
+        // Ignore errors if old_log doesn't exist.
+        rename(old_log.c_str(), new_log.c_str());
+
+        std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file);
+        if (i > 0) {
+          old_kmsg += "." + std::to_string(i);
+        }
+        std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i+1);
+        rename(old_kmsg.c_str(), new_kmsg.c_str());
+    }
+}
diff --git a/rotate_logs.h b/rotate_logs.h
new file mode 100644
index 0000000..809c213
--- /dev/null
+++ b/rotate_logs.h
@@ -0,0 +1,44 @@
+/*
+ * 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 agreed 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 _ROTATE_LOGS_H
+#define _ROTATE_LOGS_H
+
+#include <string>
+
+#include <private/android_logger.h> /* private pmsg functions */
+
+constexpr int KEEP_LOG_COUNT = 10;
+
+ssize_t logbasename(log_id_t /* logId */,
+        char /* prio */,
+        const char *filename,
+        const char * /* buf */, size_t len,
+        void *arg);
+
+ssize_t logrotate(
+        log_id_t logId,
+        char prio,
+        const char *filename,
+        const char *buf, size_t len,
+        void *arg);
+
+// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
+// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
+// Overwrite any existing last_log.$max and last_kmsg.$max.
+void rotate_logs(const char* last_log_file, const char* last_kmsg_file);
+
+#endif //_ROTATE_LOG_H
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 95b97d1..bb2772d 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -29,11 +29,13 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <string>
 #include <vector>
 
+#include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/stringprintf.h>
-#include <cutils/properties.h>
 
 #include "common.h"
 #include "device.h"
@@ -50,37 +52,34 @@
     return tv.tv_sec + tv.tv_usec / 1000000.0;
 }
 
-ScreenRecoveryUI::ScreenRecoveryUI() :
-    currentIcon(NONE),
-    locale(nullptr),
-    progressBarType(EMPTY),
-    progressScopeStart(0),
-    progressScopeSize(0),
-    progress(0),
-    pagesIdentical(false),
-    text_cols_(0),
-    text_rows_(0),
-    text_(nullptr),
-    text_col_(0),
-    text_row_(0),
-    text_top_(0),
-    show_text(false),
-    show_text_ever(false),
-    menu_(nullptr),
-    show_menu(false),
-    menu_items(0),
-    menu_sel(0),
-    file_viewer_text_(nullptr),
-    intro_frames(0),
-    loop_frames(0),
-    current_frame(0),
-    intro_done(false),
-    animation_fps(30), // TODO: there's currently no way to infer this.
-    stage(-1),
-    max_stage(-1),
-    updateMutex(PTHREAD_MUTEX_INITIALIZER),
-    rtl_locale(false) {
-}
+ScreenRecoveryUI::ScreenRecoveryUI()
+    : currentIcon(NONE),
+      progressBarType(EMPTY),
+      progressScopeStart(0),
+      progressScopeSize(0),
+      progress(0),
+      pagesIdentical(false),
+      text_cols_(0),
+      text_rows_(0),
+      text_(nullptr),
+      text_col_(0),
+      text_row_(0),
+      text_top_(0),
+      show_text(false),
+      show_text_ever(false),
+      menu_(nullptr),
+      show_menu(false),
+      menu_items(0),
+      menu_sel(0),
+      file_viewer_text_(nullptr),
+      intro_frames(0),
+      loop_frames(0),
+      current_frame(0),
+      intro_done(false),
+      animation_fps(30),  // TODO: there's currently no way to infer this.
+      stage(-1),
+      max_stage(-1),
+      updateMutex(PTHREAD_MUTEX_INITIALIZER) {}
 
 GRSurface* ScreenRecoveryUI::GetCurrentFrame() {
     if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
@@ -99,7 +98,7 @@
     }
 }
 
-int ScreenRecoveryUI::PixelsFromDp(int dp) {
+int ScreenRecoveryUI::PixelsFromDp(int dp) const {
     return dp * density_;
 }
 
@@ -174,51 +173,50 @@
 // Does not flip pages.
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::draw_foreground_locked() {
-    if (currentIcon != NONE) {
-        GRSurface* frame = GetCurrentFrame();
-        int frame_width = gr_get_width(frame);
-        int frame_height = gr_get_height(frame);
-        int frame_x = (gr_fb_width() - frame_width) / 2;
-        int frame_y = GetAnimationBaseline();
-        gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
-    }
+  if (currentIcon != NONE) {
+    GRSurface* frame = GetCurrentFrame();
+    int frame_width = gr_get_width(frame);
+    int frame_height = gr_get_height(frame);
+    int frame_x = (gr_fb_width() - frame_width) / 2;
+    int frame_y = GetAnimationBaseline();
+    gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
+  }
 
-    if (progressBarType != EMPTY) {
-        int width = gr_get_width(progressBarEmpty);
-        int height = gr_get_height(progressBarEmpty);
+  if (progressBarType != EMPTY) {
+    int width = gr_get_width(progressBarEmpty);
+    int height = gr_get_height(progressBarEmpty);
 
-        int progress_x = (gr_fb_width() - width)/2;
-        int progress_y = GetProgressBaseline();
+    int progress_x = (gr_fb_width() - width) / 2;
+    int progress_y = GetProgressBaseline();
 
-        // Erase behind the progress bar (in case this was a progress-only update)
-        gr_color(0, 0, 0, 255);
-        gr_fill(progress_x, progress_y, width, height);
+    // Erase behind the progress bar (in case this was a progress-only update)
+    gr_color(0, 0, 0, 255);
+    gr_fill(progress_x, progress_y, width, height);
 
-        if (progressBarType == DETERMINATE) {
-            float p = progressScopeStart + progress * progressScopeSize;
-            int pos = (int) (p * width);
+    if (progressBarType == DETERMINATE) {
+      float p = progressScopeStart + progress * progressScopeSize;
+      int pos = static_cast<int>(p * width);
 
-            if (rtl_locale) {
-                // Fill the progress bar from right to left.
-                if (pos > 0) {
-                    gr_blit(progressBarFill, width-pos, 0, pos, height,
-                            progress_x+width-pos, progress_y);
-                }
-                if (pos < width-1) {
-                    gr_blit(progressBarEmpty, 0, 0, width-pos, height, progress_x, progress_y);
-                }
-            } else {
-                // Fill the progress bar from left to right.
-                if (pos > 0) {
-                    gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
-                }
-                if (pos < width-1) {
-                    gr_blit(progressBarEmpty, pos, 0, width-pos, height,
-                            progress_x+pos, progress_y);
-                }
-            }
+      if (rtl_locale_) {
+        // Fill the progress bar from right to left.
+        if (pos > 0) {
+          gr_blit(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
+                  progress_y);
         }
+        if (pos < width - 1) {
+          gr_blit(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
+        }
+      } else {
+        // Fill the progress bar from left to right.
+        if (pos > 0) {
+          gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
+        }
+        if (pos < width - 1) {
+          gr_blit(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
+        }
+      }
     }
+  }
 }
 
 void ScreenRecoveryUI::SetColor(UIElement e) {
@@ -258,12 +256,12 @@
     *y += 4;
 }
 
-void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) {
+void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const {
     gr_text(gr_sys_font(), x, *y, line, bold);
     *y += char_height_ + 4;
 }
 
-void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) {
+void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) const {
     for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
         DrawTextLine(x, y, lines[i], false);
     }
@@ -292,8 +290,8 @@
 
         int y = 0;
         if (show_menu) {
-            char recovery_fingerprint[PROPERTY_VALUE_MAX];
-            property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, "");
+            std::string recovery_fingerprint =
+                    android::base::GetProperty("ro.bootimage.build.fingerprint", "");
 
             SetColor(INFO);
             DrawTextLine(TEXT_INDENT, &y, "Android Recovery", true);
@@ -410,22 +408,22 @@
         // minimum of 20ms delay between frames
         double delay = interval - (end-start);
         if (delay < 0.02) delay = 0.02;
-        usleep((long)(delay * 1000000));
+        usleep(static_cast<useconds_t>(delay * 1000000));
     }
 }
 
 void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
     int result = res_create_display_surface(filename, surface);
     if (result < 0) {
-        LOGE("couldn't load bitmap %s (error %d)\n", filename, result);
+        LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
     }
 }
 
 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
-    int result = res_create_localized_alpha_surface(filename, locale, surface);
-    if (result < 0) {
-        LOGE("couldn't load bitmap %s (error %d)\n", filename, result);
-    }
+  int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface);
+  if (result < 0) {
+    LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
+  }
 }
 
 static char** Alloc2d(size_t rows, size_t cols) {
@@ -447,51 +445,58 @@
     Redraw();
 }
 
-void ScreenRecoveryUI::InitTextParams() {
-    gr_init();
+bool ScreenRecoveryUI::InitTextParams() {
+    if (gr_init() < 0) {
+      return false;
+    }
 
     gr_font_size(gr_sys_font(), &char_width_, &char_height_);
     text_rows_ = gr_fb_height() / char_height_;
     text_cols_ = gr_fb_width() / char_width_;
+    return true;
 }
 
-void ScreenRecoveryUI::Init() {
-    RecoveryUI::Init();
-    InitTextParams();
+bool ScreenRecoveryUI::Init(const std::string& locale) {
+  RecoveryUI::Init(locale);
+  if (!InitTextParams()) {
+    return false;
+  }
 
-    density_ = static_cast<float>(property_get_int32("ro.sf.lcd_density", 160)) / 160.f;
+  density_ = static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f;
 
-    // Are we portrait or landscape?
-    layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
-    // Are we the large variant of our base layout?
-    if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
+  // Are we portrait or landscape?
+  layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
+  // Are we the large variant of our base layout?
+  if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
 
-    text_ = Alloc2d(text_rows_, text_cols_ + 1);
-    file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
-    menu_ = Alloc2d(text_rows_, text_cols_ + 1);
+  text_ = Alloc2d(text_rows_, text_cols_ + 1);
+  file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
+  menu_ = Alloc2d(text_rows_, text_cols_ + 1);
 
-    text_col_ = text_row_ = 0;
-    text_top_ = 1;
+  text_col_ = text_row_ = 0;
+  text_top_ = 1;
 
-    LoadBitmap("icon_error", &error_icon);
+  LoadBitmap("icon_error", &error_icon);
 
-    LoadBitmap("progress_empty", &progressBarEmpty);
-    LoadBitmap("progress_fill", &progressBarFill);
+  LoadBitmap("progress_empty", &progressBarEmpty);
+  LoadBitmap("progress_fill", &progressBarFill);
 
-    LoadBitmap("stage_empty", &stageMarkerEmpty);
-    LoadBitmap("stage_fill", &stageMarkerFill);
+  LoadBitmap("stage_empty", &stageMarkerEmpty);
+  LoadBitmap("stage_fill", &stageMarkerFill);
 
-    // Background text for "installing_update" could be "installing update"
-    // or "installing security update". It will be set after UI init according
-    // to commands in BCB.
-    installing_text = nullptr;
-    LoadLocalizedBitmap("erasing_text", &erasing_text);
-    LoadLocalizedBitmap("no_command_text", &no_command_text);
-    LoadLocalizedBitmap("error_text", &error_text);
+  // Background text for "installing_update" could be "installing update"
+  // or "installing security update". It will be set after UI init according
+  // to commands in BCB.
+  installing_text = nullptr;
+  LoadLocalizedBitmap("erasing_text", &erasing_text);
+  LoadLocalizedBitmap("no_command_text", &no_command_text);
+  LoadLocalizedBitmap("error_text", &error_text);
 
-    LoadAnimation();
+  LoadAnimation();
 
-    pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
+  pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
+
+  return true;
 }
 
 void ScreenRecoveryUI::LoadAnimation() {
@@ -531,31 +536,6 @@
     }
 }
 
-void ScreenRecoveryUI::SetLocale(const char* new_locale) {
-    this->locale = new_locale;
-    this->rtl_locale = false;
-
-    if (locale) {
-        char* lang = strdup(locale);
-        for (char* p = lang; *p; ++p) {
-            if (*p == '_') {
-                *p = '\0';
-                break;
-            }
-        }
-
-        // A bit cheesy: keep an explicit list of supported RTL languages.
-        if (strcmp(lang, "ar") == 0 ||   // Arabic
-            strcmp(lang, "fa") == 0 ||   // Persian (Farsi)
-            strcmp(lang, "he") == 0 ||   // Hebrew (new language code)
-            strcmp(lang, "iw") == 0 ||   // Hebrew (old language code)
-            strcmp(lang, "ur") == 0) {   // Urdu
-            rtl_locale = true;
-        }
-        free(lang);
-    }
-}
-
 void ScreenRecoveryUI::SetBackground(Icon icon) {
     pthread_mutex_lock(&updateMutex);
 
@@ -675,8 +655,8 @@
 }
 
 void ScreenRecoveryUI::ShowFile(FILE* fp) {
-    std::vector<long> offsets;
-    offsets.push_back(ftell(fp));
+    std::vector<off_t> offsets;
+    offsets.push_back(ftello(fp));
     ClearText();
 
     struct stat sb;
@@ -686,7 +666,7 @@
     while (true) {
         if (show_prompt) {
             PrintOnScreenOnly("--(%d%% of %d bytes)--",
-                  static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))),
+                  static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
                   static_cast<int>(sb.st_size));
             Redraw();
             while (show_prompt) {
@@ -705,7 +685,7 @@
                     if (feof(fp)) {
                         return;
                     }
-                    offsets.push_back(ftell(fp));
+                    offsets.push_back(ftello(fp));
                 }
             }
             ClearText();
diff --git a/screen_ui.h b/screen_ui.h
index de7b644..a2322c3 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -20,8 +20,12 @@
 #include <pthread.h>
 #include <stdio.h>
 
+#include <string>
+
 #include "ui.h"
-#include "minui/minui.h"
+
+// From minui/minui.h.
+struct GRSurface;
 
 // Implementation of RecoveryUI appropriate for devices with a screen
 // (shows an icon + a progress bar, text logging, menu, etc.)
@@ -29,8 +33,7 @@
   public:
     ScreenRecoveryUI();
 
-    void Init();
-    void SetLocale(const char* locale);
+    bool Init(const std::string& locale) override;
 
     // overall recovery state ("background image")
     void SetBackground(Icon icon);
@@ -71,8 +74,6 @@
   protected:
     Icon currentIcon;
 
-    const char* locale;
-
     // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
     float density_;
     // The layout to use.
@@ -135,9 +136,8 @@
     int char_width_;
     int char_height_;
     pthread_mutex_t updateMutex;
-    bool rtl_locale;
 
-    virtual void InitTextParams();
+    virtual bool InitTextParams();
 
     virtual void draw_background_locked();
     virtual void draw_foreground_locked();
@@ -160,14 +160,14 @@
     void LoadBitmap(const char* filename, GRSurface** surface);
     void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
 
-    int PixelsFromDp(int dp);
+    int PixelsFromDp(int dp) const;
     virtual int GetAnimationBaseline();
     virtual int GetProgressBaseline();
     virtual int GetTextBaseline();
 
     void DrawHorizontalRule(int* y);
-    void DrawTextLine(int x, int* y, const char* line, bool bold);
-    void DrawTextLines(int x, int* y, const char* const* lines);
+    void DrawTextLine(int x, int* y, const char* line, bool bold) const;
+    void DrawTextLines(int x, int* y, const char* const* lines) const;
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/stub_ui.h b/stub_ui.h
new file mode 100644
index 0000000..85dbcfd
--- /dev/null
+++ b/stub_ui.h
@@ -0,0 +1,65 @@
+/*
+ * 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 agreed 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 RECOVERY_STUB_UI_H
+#define RECOVERY_STUB_UI_H
+
+#include "ui.h"
+
+// Stub implementation of RecoveryUI for devices without screen.
+class StubRecoveryUI : public RecoveryUI {
+ public:
+  StubRecoveryUI() = default;
+
+  void SetBackground(Icon icon) override {}
+  void SetSystemUpdateText(bool security_update) override {}
+
+  // progress indicator
+  void SetProgressType(ProgressType type) override {}
+  void ShowProgress(float portion, float seconds) override {}
+  void SetProgress(float fraction) override {}
+
+  void SetStage(int current, int max) override {}
+
+  // text log
+  void ShowText(bool visible) override {}
+  bool IsTextVisible() override {
+    return false;
+  }
+  bool WasTextEverVisible() override {
+    return false;
+  }
+
+  // printing messages
+  void Print(const char* fmt, ...) override {
+    va_list ap;
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+  }
+  void PrintOnScreenOnly(const char* fmt, ...) override {}
+  void ShowFile(const char* filename) override {}
+
+  // menu display
+  void StartMenu(const char* const* headers, const char* const* items,
+                 int initial_selection) override {}
+  int SelectMenu(int sel) override {
+    return sel;
+  }
+  void EndMenu() override {}
+};
+
+#endif  // RECOVERY_STUB_UI_H
diff --git a/tests/Android.mk b/tests/Android.mk
index a66991b..f59f486 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -18,52 +18,187 @@
 
 # Unit tests
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
+LOCAL_CFLAGS := -Werror
 LOCAL_MODULE := recovery_unit_test
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 LOCAL_STATIC_LIBRARIES := \
     libverifier \
-    libminui
+    libminui \
+    libotautil \
+    libziparchive \
+    libutils \
+    libz \
+    libselinux \
+    libbase
 
-LOCAL_SRC_FILES := unit/asn1_decoder_test.cpp
-LOCAL_SRC_FILES += unit/recovery_test.cpp
-LOCAL_SRC_FILES += unit/locale_test.cpp
+LOCAL_SRC_FILES := \
+    unit/asn1_decoder_test.cpp \
+    unit/dirutil_test.cpp \
+    unit/locale_test.cpp \
+    unit/sysutil_test.cpp \
+    unit/zip_test.cpp \
+    unit/ziputil_test.cpp
+
 LOCAL_C_INCLUDES := bootable/recovery
 LOCAL_SHARED_LIBRARIES := liblog
 include $(BUILD_NATIVE_TEST)
 
-# Component tests
+# Manual tests
 include $(CLEAR_VARS)
 LOCAL_CLANG := true
-LOCAL_CFLAGS += -Wno-unused-parameter
+LOCAL_CFLAGS := -Werror
+LOCAL_MODULE := recovery_manual_test
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_STATIC_LIBRARIES := \
+    libminui \
+    libbase
+
+LOCAL_SRC_FILES := manual/recovery_test.cpp
+LOCAL_SHARED_LIBRARIES := \
+    liblog \
+    libpng
+
+resource_files := $(call find-files-in-subdirs, bootable/recovery, \
+    "*_text.png", \
+    res-mdpi/images \
+    res-hdpi/images \
+    res-xhdpi/images \
+    res-xxhdpi/images \
+    res-xxxhdpi/images \
+    )
+
+# The resource image files that will go to $OUT/data/nativetest/recovery.
+testimage_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
+GEN := $(addprefix $(testimage_out_path)/, $(resource_files))
+
+$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN): $(testimage_out_path)/% : bootable/recovery/%
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN)
+
+include $(BUILD_NATIVE_TEST)
+
+# Component tests
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := \
+    -Werror \
+    -D_FILE_OFFSET_BITS=64
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+ifeq ($(AB_OTA_UPDATER),true)
+LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+endif
+
 LOCAL_MODULE := recovery_component_test
 LOCAL_C_INCLUDES := bootable/recovery
 LOCAL_SRC_FILES := \
-    component/verifier_test.cpp \
-    component/applypatch_test.cpp
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_STATIC_LIBRARIES := \
-    libapplypatch \
-    libotafault \
-    libmtdutils \
-    libbase \
-    libverifier \
-    libcrypto_static \
-    libminui \
-    libminzip \
-    libcutils \
-    libbz \
-    libz \
-    libc
+    component/applypatch_test.cpp \
+    component/bootloader_message_test.cpp \
+    component/edify_test.cpp \
+    component/imgdiff_test.cpp \
+    component/install_test.cpp \
+    component/sideload_test.cpp \
+    component/uncrypt_test.cpp \
+    component/updater_test.cpp \
+    component/verifier_test.cpp
 
-testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/recovery
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+
+tune2fs_static_libraries := \
+    libext2_com_err \
+    libext2_blkid \
+    libext2_quota \
+    libext2_uuid \
+    libext2_e2p \
+    libext2fs
+
+LOCAL_STATIC_LIBRARIES := \
+    libapplypatch_modes \
+    libapplypatch \
+    libedify \
+    libimgdiff \
+    libimgpatch \
+    libbsdiff \
+    libbspatch \
+    libotafault \
+    librecovery \
+    libupdater \
+    libbootloader_message \
+    libverifier \
+    libotautil \
+    libmounts \
+    libdivsufsort \
+    libdivsufsort64 \
+    libfs_mgr \
+    liblog \
+    libvintf_recovery \
+    libvintf \
+    libtinyxml2 \
+    libselinux \
+    libext4_utils \
+    libsparse \
+    libcrypto_utils \
+    libcrypto \
+    libbz \
+    libziparchive \
+    libutils \
+    libz \
+    libbase \
+    libtune2fs \
+    libfec \
+    libfec_rs \
+    libsquashfs_utils \
+    libcutils \
+    $(tune2fs_static_libraries)
+
 testdata_files := $(call find-subdir-files, testdata/*)
 
+# The testdata files that will go to $OUT/data/nativetest/recovery.
+testdata_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
 GEN := $(addprefix $(testdata_out_path)/, $(testdata_files))
 $(GEN): PRIVATE_PATH := $(LOCAL_PATH)
 $(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
 $(GEN): $(testdata_out_path)/% : $(LOCAL_PATH)/%
 	$(transform-generated-source)
 LOCAL_GENERATED_SOURCES += $(GEN)
+
+# A copy of the testdata to be packed into continuous_native_tests.zip.
+testdata_continuous_zip_prefix := \
+    $(call intermediates-dir-for,PACKAGING,recovery_component_test)/DATA
+testdata_continuous_zip_path := $(testdata_continuous_zip_prefix)/nativetest/recovery
+GEN := $(addprefix $(testdata_continuous_zip_path)/, $(testdata_files))
+$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN): $(testdata_continuous_zip_path)/% : $(LOCAL_PATH)/%
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN)
+LOCAL_PICKUP_FILES := $(testdata_continuous_zip_prefix)
+
 include $(BUILD_NATIVE_TEST)
+
+# Host tests
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := -Werror
+LOCAL_MODULE := recovery_host_test
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_C_INCLUDES := bootable/recovery
+LOCAL_SRC_FILES := \
+    component/imgdiff_test.cpp
+LOCAL_STATIC_LIBRARIES := \
+    libimgdiff \
+    libimgpatch \
+    libbsdiff \
+    libbspatch \
+    libziparchive \
+    libutils \
+    libbase \
+    libcrypto \
+    libbz \
+    libdivsufsort64 \
+    libdivsufsort \
+    libz
+LOCAL_SHARED_LIBRARIES := \
+    liblog
+include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/common/component_test_util.h b/tests/common/component_test_util.h
new file mode 100644
index 0000000..3fee32d
--- /dev/null
+++ b/tests/common/component_test_util.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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 _COMPONENT_TEST_UTIL_H
+#define _COMPONENT_TEST_UTIL_H
+
+#include <string>
+
+#include <android-base/properties.h>
+#include <fs_mgr.h>
+
+// Check if the /misc entry exists in the fstab.
+static bool parse_misc() {
+  std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
+                                                             fs_mgr_free_fstab);
+  if (!fstab) {
+    GTEST_LOG_(INFO) << "Failed to read default fstab";
+    return false;
+  }
+
+  fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc");
+  if (record == nullptr) {
+    GTEST_LOG_(INFO) << "Failed to find /misc in fstab.";
+    return false;
+  }
+  return true;
+}
+
+#endif //_COMPONENT_TEST_UTIL_H
+
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
index 3490f68..f6b6922 100644
--- a/tests/common/test_constants.h
+++ b/tests/common/test_constants.h
@@ -13,13 +13,27 @@
  * 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
+#include <stdlib.h>
 
-#endif
+// Zip entries in ziptest_valid.zip.
+static const std::string kATxtContents("abcdefghabcdefgh\n");
+static const std::string kBTxtContents("abcdefgh\n");
+static const std::string kCTxtContents("abcdefghabcdefgh\n");
+static const std::string kDTxtContents("abcdefgh\n");
+
+// echo -n -e "abcdefghabcdefgh\n" | sha1sum
+static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643");
+// echo -n -e "abcdefgh\n" | sha1sum
+static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666");
+
+static const char* data_root = getenv("ANDROID_DATA");
+
+static std::string from_testdata_base(const std::string& fname) {
+  return std::string(data_root) + "/nativetest/recovery/testdata/" + fname;
+}
+
+#endif  // _OTA_TEST_CONSTANTS_H
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
index b44ddd1..5cba68f 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -23,176 +23,155 @@
 #include <sys/types.h>
 #include <time.h>
 
+#include <memory>
 #include <string>
+#include <vector>
 
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/test_utils.h>
+#include <openssl/sha.h>
 
 #include "applypatch/applypatch.h"
+#include "applypatch/applypatch_modes.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 void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) {
+  ASSERT_NE(nullptr, sha1);
 
-static std::string sha1sum(const std::string& fname) {
-    uint8_t digest[SHA_DIGEST_LENGTH];
-    std::string data;
-    android::base::ReadFileToString(fname, &data);
+  std::string data;
+  ASSERT_TRUE(android::base::ReadFileToString(fname, &data));
 
-    SHA1((const uint8_t*)data.c_str(), data.size(), digest);
-    return print_sha1(digest);
+  if (fsize != nullptr) {
+    *fsize = data.size();
+  }
+
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
+  *sha1 = 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);
+  std::string content;
+  content.reserve(1024);
+  for (size_t i = 0; i < 1024; i++) {
+    content[i] = rand() % 256;
+  }
+  ASSERT_TRUE(android::base::WriteStringToFile(content, fname));
 }
 
-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]);
+static bool file_cmp(const std::string& f1, const std::string& f2) {
+  std::string c1;
+  android::base::ReadFileToString(f1, &c1);
+  std::string c2;
+  android::base::ReadFileToString(f2, &c2);
+  return c1 == c2;
 }
 
 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";
+ 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);
+    // write stuff to rand_file
+    ASSERT_TRUE(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());
+    // set up SHA constants
+    sha1sum(old_file, &old_sha1, &old_size);
+    sha1sum(new_file, &new_sha1, &new_size);
+    srand(time(nullptr));
+    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_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 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;
+  static size_t old_size;
+  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 cp(const std::string& src, const std::string& tgt) {
+  std::string cmd = "cp " + src + " " + tgt;
+  system(cmd.c_str());
 }
 
 static void backup_old() {
-    cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
+  cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
 }
 
 static void restore_old() {
-    cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
+  cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
 }
 
 class ApplyPatchCacheTest : public ApplyPatchTest {
-    public:
-        virtual void SetUp() {
-            backup_old();
-        }
+ public:
+  virtual void SetUp() {
+    backup_old();
+  }
 
-        virtual void TearDown() {
-            restore_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);
+ public:
+  static void SetUpTestCase() {
+    ApplyPatchTest::SetUpTestCase();
 
-            struct FileContents fc;
+    output_f = new TemporaryFile();
+    output_loc = std::string(output_f->path);
 
-            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);
+    struct FileContents fc;
 
-            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();
-        }
+    ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc));
+    patches.push_back(
+        std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
 
-        static std::vector<Value*> patches;
-        static TemporaryFile* output_f;
-        static std::string output_loc;
+    ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc));
+    patches.push_back(
+        std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
+  }
+
+  static void TearDownTestCase() {
+    delete output_f;
+    patches.clear();
+  }
+
+  static std::vector<std::unique_ptr<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");
-        }
+ public:
+  virtual void SetUp() {
+    ApplyPatchCacheTest::SetUp();
+    cp(cache_file, "/cache/reallysaved.file");
+  }
 
-        virtual void TearDown() {
-            cp("/cache/reallysaved.file", cache_file);
-            ApplyPatchCacheTest::TearDown();
-        }
+  virtual void TearDown() {
+    cp("/cache/reallysaved.file", cache_file);
+    ApplyPatchCacheTest::TearDown();
+  }
 };
 
 std::string ApplyPatchTest::rand_file;
@@ -202,191 +181,263 @@
 std::string ApplyPatchTest::new_sha1;
 std::string ApplyPatchTest::bad_sha1_a;
 std::string ApplyPatchTest::bad_sha1_b;
-
+size_t ApplyPatchTest::old_size;
 size_t ApplyPatchTest::new_size;
 
-std::vector<Value*> ApplyPatchFullTest::patches;
+std::vector<std::unique_ptr<Value>> ApplyPatchFullTest::patches;
 TemporaryFile* ApplyPatchFullTest::output_f;
 std::string ApplyPatchFullTest::output_loc;
 
+TEST_F(ApplyPatchTest, CheckModeSkip) {
+  std::vector<std::string> sha1s;
+  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
+}
+
 TEST_F(ApplyPatchTest, CheckModeSingle) {
-    char* s = &old_sha1[0];
-    ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+  std::vector<std::string> sha1s = { old_sha1 };
+  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
 }
 
 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));
+  std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
 }
 
 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));
+  std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+  ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
+}
+
+TEST_F(ApplyPatchTest, CheckModeEmmcTarget) {
+  // EMMC:old_file:size:sha1 should pass the check.
+  std::string src_file =
+      "EMMC:" + old_file + ":" + std::to_string(old_size) + ":" + old_sha1;
+  std::vector<std::string> sha1s;
+  ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+  // EMMC:old_file:(size-1):sha1:(size+1):sha1 should fail the check.
+  src_file = "EMMC:" + old_file + ":" + std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size + 1) + ":" + old_sha1;
+  ASSERT_EQ(1, applypatch_check(src_file.c_str(), sha1s));
+
+  // EMMC:old_file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
+  src_file = "EMMC:" + old_file + ":" +
+             std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size) + ":" + old_sha1 + ":" +
+             std::to_string(old_size + 1) + ":" + old_sha1;
+  ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+  // EMMC:old_file:(size+1):sha1:(size-1):sha1:size:sha1 should pass the check.
+  src_file = "EMMC:" + old_file + ":" +
+             std::to_string(old_size + 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size) + ":" + old_sha1;
+  ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+  // EMMC:new_file:(size+1):old_sha1:(size-1):old_sha1:size:old_sha1:size:new_sha1
+  // should pass the check.
+  src_file = "EMMC:" + new_file + ":" +
+             std::to_string(old_size + 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size) + ":" + old_sha1 + ":" +
+             std::to_string(new_size) + ":" + new_sha1;
+  ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
 }
 
 TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) {
-    mangle_file(old_file);
-    char* s = &old_sha1[0];
-    ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+  mangle_file(old_file);
+  std::vector<std::string> sha1s = { old_sha1 };
+  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
 }
 
 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));
+  mangle_file(old_file);
+  std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
 }
 
 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));
+  mangle_file(old_file);
+  std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+  ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
 }
 
 TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) {
-    unlink(&old_file[0]);
-    char* s = &old_sha1[0];
-    ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+  unlink(&old_file[0]);
+  std::vector<std::string> sha1s = { old_sha1 };
+  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
 }
 
 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));
+  unlink(&old_file[0]);
+  std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
 }
 
 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));
+  unlink(&old_file[0]);
+  std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+  ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
 }
 
-TEST_F(ApplyPatchFullTest, ApplyInPlace) {
-    std::vector<char*> sha1s;
-    sha1s.push_back(&bad_sha1_a[0]);
-    sha1s.push_back(&old_sha1[0]);
+TEST(ApplyPatchModesTest, InvalidArgs) {
+  // At least two args (including the filename).
+  ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" }));
 
-    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));
+  // Unrecognized args.
+  ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" }));
 }
 
-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(ApplyPatchModesTest, PatchModeEmmcTarget) {
+  std::string boot_img = from_testdata_base("boot.img");
+  size_t boot_img_size;
+  std::string boot_img_sha1;
+  sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
+
+  std::string recovery_img = from_testdata_base("recovery.img");
+  size_t size;
+  std::string recovery_img_sha1;
+  sha1sum(recovery_img, &recovery_img_sha1, &size);
+  std::string recovery_img_size = std::to_string(size);
+
+  std::string bonus_file = from_testdata_base("bonus.file");
+
+  // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+  TemporaryFile tmp1;
+  std::string src_file =
+      "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
+  std::string tgt_file = "EMMC:" + std::string(tmp1.path);
+  std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+  std::vector<const char*> args = {
+    "applypatch",
+    "-b",
+    bonus_file.c_str(),
+    src_file.c_str(),
+    tgt_file.c_str(),
+    recovery_img_sha1.c_str(),
+    recovery_img_size.c_str(),
+    patch.c_str()
+  };
+  ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
+
+  // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+  TemporaryFile tmp2;
+  patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
+  tgt_file = "EMMC:" + std::string(tmp2.path);
+  std::vector<const char*> args2 = {
+    "applypatch",
+    src_file.c_str(),
+    tgt_file.c_str(),
+    recovery_img_sha1.c_str(),
+    recovery_img_size.c_str(),
+    patch.c_str()
+  };
+  ASSERT_EQ(0, applypatch_modes(args2.size(), args2.data()));
+
+  // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> \
+  //               <src-sha1-fake>:<patch1> <src-sha1>:<patch2>
+  TemporaryFile tmp3;
+  tgt_file = "EMMC:" + std::string(tmp3.path);
+  std::string bad_sha1_a = android::base::StringPrintf("%040x", rand());
+  std::string bad_sha1_b = android::base::StringPrintf("%040x", rand());
+  std::string patch1 = bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p");
+  std::string patch2 = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+  std::string patch3 = bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p");
+  std::vector<const char*> args3 = {
+    "applypatch",
+    "-b",
+    bonus_file.c_str(),
+    src_file.c_str(),
+    tgt_file.c_str(),
+    recovery_img_sha1.c_str(),
+    recovery_img_size.c_str(),
+    patch1.c_str(),
+    patch2.c_str(),
+    patch3.c_str()
+  };
+  ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data()));
 }
 
-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(ApplyPatchModesTest, PatchModeInvalidArgs) {
+  // Invalid bonus file.
+  ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" }));
+
+  std::string bonus_file = from_testdata_base("bonus.file");
+  // With bonus file, but missing args.
+  ASSERT_EQ(2, applypatch_modes(3, (const char* []){ "applypatch", "-b", bonus_file.c_str() }));
+
+  std::string boot_img = from_testdata_base("boot.img");
+  size_t boot_img_size;
+  std::string boot_img_sha1;
+  sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
+
+  std::string recovery_img = from_testdata_base("recovery.img");
+  size_t size;
+  std::string recovery_img_sha1;
+  sha1sum(recovery_img, &recovery_img_sha1, &size);
+  std::string recovery_img_size = std::to_string(size);
+
+  // Bonus file is not supported in flash mode.
+  // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size>
+  TemporaryFile tmp4;
+  std::vector<const char*> args4 = {
+    "applypatch",
+    "-b",
+    bonus_file.c_str(),
+    boot_img.c_str(),
+    tmp4.path,
+    recovery_img_sha1.c_str(),
+    recovery_img_size.c_str()
+  };
+  ASSERT_NE(0, applypatch_modes(args4.size(), args4.data()));
+
+  // Failed to parse patch args.
+  TemporaryFile tmp5;
+  std::string bad_arg1 =
+      "invalid-sha1:filename" + from_testdata_base("recovery-from-boot-with-bonus.p");
+  std::vector<const char*> args5 = {
+    "applypatch",
+    boot_img.c_str(),
+    tmp5.path,
+    recovery_img_sha1.c_str(),
+    recovery_img_size.c_str(),
+    bad_arg1.c_str()
+  };
+  ASSERT_NE(0, applypatch_modes(args5.size(), args5.data()));
+
+  // Target size cannot be zero.
+  TemporaryFile tmp6;
+  std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
+  std::vector<const char*> args6 = {
+    "applypatch",
+    boot_img.c_str(),
+    tmp6.path,
+    recovery_img_sha1.c_str(),
+    "0",  // target size
+    patch.c_str()
+  };
+  ASSERT_NE(0, applypatch_modes(args6.size(), args6.data()));
 }
 
-TEST_F(ApplyPatchDoubleCacheTest, ApplyDoubleCorruptedInNewLocation) {
-    mangle_file(old_file);
-    mangle_file(cache_file);
+TEST(ApplyPatchModesTest, CheckModeInvalidArgs) {
+  // Insufficient args.
+  ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
+}
 
-    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));
+TEST(ApplyPatchModesTest, SpaceModeInvalidArgs) {
+  // Insufficient args.
+  ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-s" }));
+
+  // Invalid bytes arg.
+  ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "x" }));
+
+  // 0 is invalid.
+  ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0" }));
+
+  // 0x10 is fine.
+  ASSERT_EQ(0, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0x10" }));
+}
+
+TEST(ApplyPatchModesTest, ShowLicenses) {
+  ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
 }
diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp
new file mode 100644
index 0000000..0357acc
--- /dev/null
+++ b/tests/component/bootloader_message_test.cpp
@@ -0,0 +1,207 @@
+/*
+ * 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 agreed 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 <string>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+#include "common/component_test_util.h"
+
+class BootloaderMessageTest : public ::testing::Test {
+ protected:
+  BootloaderMessageTest() : has_misc(true) {}
+
+  virtual void SetUp() override {
+    has_misc = parse_misc();
+  }
+
+  virtual void TearDown() override {
+    // Clear the BCB.
+    if (has_misc) {
+      std::string err;
+      ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+    }
+  }
+
+  bool has_misc;
+};
+
+TEST_F(BootloaderMessageTest, clear_bootloader_message) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  // Clear the BCB.
+  std::string err;
+  ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+
+  // Verify the content.
+  bootloader_message boot;
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  // All the bytes should be cleared.
+  ASSERT_EQ(std::string(sizeof(boot), '\0'),
+            std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+}
+
+TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  // Write the BCB.
+  bootloader_message boot = {};
+  strlcpy(boot.command, "command", sizeof(boot.command));
+  strlcpy(boot.recovery, "message1\nmessage2\n", sizeof(boot.recovery));
+  strlcpy(boot.status, "status1", sizeof(boot.status));
+
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+
+  // Read and verify.
+  bootloader_message boot_verify;
+  ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
+
+  ASSERT_EQ(std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)),
+            std::string(reinterpret_cast<const char*>(&boot_verify), sizeof(boot_verify)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  // Write the options to BCB.
+  std::vector<std::string> options = { "option1", "option2" };
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+  // Inject some bytes into boot, which should be overwritten while reading.
+  bootloader_message boot;
+  strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+  strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
+
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  // Verify that command and recovery fields should be set.
+  ASSERT_EQ("boot-recovery", std::string(boot.command));
+  std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+  ASSERT_EQ(expected, std::string(boot.recovery));
+
+  // The rest should be cleared.
+  ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+  ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+  ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+            std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options_empty) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  // Write empty vector.
+  std::vector<std::string> options;
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+  // Read and verify.
+  bootloader_message boot;
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  // command and recovery fields should be set.
+  ASSERT_EQ("boot-recovery", std::string(boot.command));
+  ASSERT_EQ("recovery\n", std::string(boot.recovery));
+
+  // The rest should be cleared.
+  ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+  ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+  ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+            std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  // Write super long message.
+  std::vector<std::string> options;
+  for (int i = 0; i < 100; i++) {
+    options.push_back("option: " + std::to_string(i));
+  }
+
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+  // Read and verify.
+  bootloader_message boot;
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  // Make sure it's long enough.
+  std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+  ASSERT_GE(expected.size(), sizeof(boot.recovery));
+
+  // command and recovery fields should be set.
+  ASSERT_EQ("boot-recovery", std::string(boot.command));
+  ASSERT_EQ(expected.substr(0, sizeof(boot.recovery) - 1), std::string(boot.recovery));
+  ASSERT_EQ('\0', boot.recovery[sizeof(boot.recovery) - 1]);
+
+  // The rest should be cleared.
+  ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+  ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+  ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+            std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, update_bootloader_message) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  // Inject some bytes into boot, which should be not overwritten later.
+  bootloader_message boot;
+  strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+  strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+
+  // Update the BCB message.
+  std::vector<std::string> options = { "option1", "option2" };
+  ASSERT_TRUE(update_bootloader_message(options, &err)) << "Failed to update BCB: " << err;
+
+  bootloader_message boot_verify;
+  ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
+
+  // Verify that command and recovery fields should be set.
+  ASSERT_EQ("boot-recovery", std::string(boot_verify.command));
+  std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+  ASSERT_EQ(expected, std::string(boot_verify.recovery));
+
+  // The rest should be intact.
+  ASSERT_EQ(std::string(boot.status), std::string(boot_verify.status));
+  ASSERT_EQ(std::string(boot.stage), std::string(boot_verify.stage));
+  ASSERT_EQ(std::string(boot.reserved), std::string(boot_verify.reserved));
+}
diff --git a/tests/component/edify_test.cpp b/tests/component/edify_test.cpp
new file mode 100644
index 0000000..61a1e6b
--- /dev/null
+++ b/tests/component/edify_test.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2009 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 agreed 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 <memory>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "edify/expr.h"
+
+static void expect(const char* expr_str, const char* expected) {
+    std::unique_ptr<Expr> e;
+    int error_count = 0;
+    EXPECT_EQ(0, parse_string(expr_str, &e, &error_count));
+    EXPECT_EQ(0, error_count);
+
+    State state(expr_str, nullptr);
+
+    std::string result;
+    bool status = Evaluate(&state, e, &result);
+
+    if (expected == nullptr) {
+        EXPECT_FALSE(status);
+    } else {
+        EXPECT_STREQ(expected, result.c_str());
+    }
+
+}
+
+class EdifyTest : public ::testing::Test {
+  protected:
+    virtual void SetUp() {
+        RegisterBuiltins();
+    }
+};
+
+TEST_F(EdifyTest, parsing) {
+    expect("a", "a");
+    expect("\"a\"", "a");
+    expect("\"\\x61\"", "a");
+    expect("# this is a comment\n"
+           "  a\n"
+           "   \n",
+           "a");
+}
+
+TEST_F(EdifyTest, sequence) {
+    // sequence operator
+    expect("a; b; c", "c");
+}
+
+TEST_F(EdifyTest, concat) {
+    // string concat operator
+    expect("a + b", "ab");
+    expect("a + \n \"b\"", "ab");
+    expect("a + b +\nc\n", "abc");
+
+    // string concat function
+    expect("concat(a, b)", "ab");
+    expect("concat(a,\n \"b\")", "ab");
+    expect("concat(a + b,\nc,\"d\")", "abcd");
+    expect("\"concat\"(a + b,\nc,\"d\")", "abcd");
+}
+
+TEST_F(EdifyTest, logical) {
+    // logical and
+    expect("a && b", "b");
+    expect("a && \"\"", "");
+    expect("\"\" && b", "");
+    expect("\"\" && \"\"", "");
+    expect("\"\" && abort()", "");   // test short-circuiting
+    expect("t && abort()", nullptr);
+
+    // logical or
+    expect("a || b", "a");
+    expect("a || \"\"", "a");
+    expect("\"\" || b", "b");
+    expect("\"\" || \"\"", "");
+    expect("a || abort()", "a");     // test short-circuiting
+    expect("\"\" || abort()", NULL);
+
+    // logical not
+    expect("!a", "");
+    expect("! \"\"", "t");
+    expect("!!a", "t");
+}
+
+TEST_F(EdifyTest, precedence) {
+    // precedence
+    expect("\"\" == \"\" && b", "b");
+    expect("a + b == ab", "t");
+    expect("ab == a + b", "t");
+    expect("a + (b == ab)", "a");
+    expect("(ab == a) + b", "b");
+}
+
+TEST_F(EdifyTest, substring) {
+    // substring function
+    expect("is_substring(cad, abracadabra)", "t");
+    expect("is_substring(abrac, abracadabra)", "t");
+    expect("is_substring(dabra, abracadabra)", "t");
+    expect("is_substring(cad, abracxadabra)", "");
+    expect("is_substring(abrac, axbracadabra)", "");
+    expect("is_substring(dabra, abracadabrxa)", "");
+}
+
+TEST_F(EdifyTest, ifelse) {
+    // ifelse function
+    expect("ifelse(t, yes, no)", "yes");
+    expect("ifelse(!t, yes, no)", "no");
+    expect("ifelse(t, yes, abort())", "yes");
+    expect("ifelse(!t, abort(), no)", "no");
+}
+
+TEST_F(EdifyTest, if_statement) {
+    // if "statements"
+    expect("if t then yes else no endif", "yes");
+    expect("if \"\" then yes else no endif", "no");
+    expect("if \"\" then yes endif", "");
+    expect("if \"\"; t then yes endif", "yes");
+}
+
+TEST_F(EdifyTest, comparison) {
+    // numeric comparisons
+    expect("less_than_int(3, 14)", "t");
+    expect("less_than_int(14, 3)", "");
+    expect("less_than_int(x, 3)", "");
+    expect("less_than_int(3, x)", "");
+    expect("greater_than_int(3, 14)", "");
+    expect("greater_than_int(14, 3)", "t");
+    expect("greater_than_int(x, 3)", "");
+    expect("greater_than_int(3, x)", "");
+}
+
+TEST_F(EdifyTest, big_string) {
+    // big string
+    expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str());
+}
+
+TEST_F(EdifyTest, unknown_function) {
+    // unknown function
+    const char* script1 = "unknown_function()";
+    std::unique_ptr<Expr> expr;
+    int error_count = 0;
+    EXPECT_EQ(1, parse_string(script1, &expr, &error_count));
+    EXPECT_EQ(1, error_count);
+
+    const char* script2 = "abc; unknown_function()";
+    error_count = 0;
+    EXPECT_EQ(1, parse_string(script2, &expr, &error_count));
+    EXPECT_EQ(1, error_count);
+
+    const char* script3 = "unknown_function1() || yes";
+    error_count = 0;
+    EXPECT_EQ(1, parse_string(script3, &expr, &error_count));
+    EXPECT_EQ(1, error_count);
+}
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
new file mode 100644
index 0000000..2f64850
--- /dev/null
+++ b/tests/component/imgdiff_test.cpp
@@ -0,0 +1,585 @@
+/*
+ * 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 agreed 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 <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/memory.h>
+#include <android-base/test_utils.h>
+#include <applypatch/imgdiff.h>
+#include <applypatch/imgpatch.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_writer.h>
+
+using android::base::get_unaligned;
+
+static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
+  std::string* s = static_cast<std::string*>(token);
+  s->append(reinterpret_cast<const char*>(data), len);
+  return len;
+}
+
+// Sanity check for the given imgdiff patch header.
+static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw,
+                                size_t* num_deflate) {
+  const size_t size = patch.size();
+  const char* data = patch.data();
+
+  ASSERT_GE(size, 12U);
+  ASSERT_EQ("IMGDIFF2", std::string(data, 8));
+
+  const int num_chunks = get_unaligned<int32_t>(data + 8);
+  ASSERT_GE(num_chunks, 0);
+
+  size_t normal = 0;
+  size_t raw = 0;
+  size_t deflate = 0;
+
+  size_t pos = 12;
+  for (int i = 0; i < num_chunks; ++i) {
+    ASSERT_LE(pos + 4, size);
+    int type = get_unaligned<int32_t>(data + pos);
+    pos += 4;
+    if (type == CHUNK_NORMAL) {
+      pos += 24;
+      ASSERT_LE(pos, size);
+      normal++;
+    } else if (type == CHUNK_RAW) {
+      ASSERT_LE(pos + 4, size);
+      ssize_t data_len = get_unaligned<int32_t>(data + pos);
+      ASSERT_GT(data_len, 0);
+      pos += 4 + data_len;
+      ASSERT_LE(pos, size);
+      raw++;
+    } else if (type == CHUNK_DEFLATE) {
+      pos += 60;
+      ASSERT_LE(pos, size);
+      deflate++;
+    } else {
+      FAIL() << "Invalid patch type: " << type;
+    }
+  }
+
+  if (num_normal != nullptr) *num_normal = normal;
+  if (num_raw != nullptr) *num_raw = raw;
+  if (num_deflate != nullptr) *num_deflate = deflate;
+}
+
+TEST(ImgdiffTest, invalid_args) {
+  // Insufficient inputs.
+  ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" }));
+  ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-z" }));
+  ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-b" }));
+  ASSERT_EQ(2, imgdiff(3, (const char* []){ "imgdiff", "-z", "-b" }));
+
+  // Failed to read bonus file.
+  ASSERT_EQ(1, imgdiff(3, (const char* []){ "imgdiff", "-b", "doesntexist" }));
+
+  // Failed to read input files.
+  ASSERT_EQ(1, imgdiff(4, (const char* []){ "imgdiff", "doesntexist", "doesntexist", "output" }));
+  ASSERT_EQ(
+      1, imgdiff(5, (const char* []){ "imgdiff", "-z", "doesntexist", "doesntexist", "output" }));
+}
+
+TEST(ImgdiffTest, image_mode_smoke) {
+  // Random bytes.
+  const std::string src("abcdefg");
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  const std::string tgt("abcdefgxyz");
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_store) {
+  // Construct src and tgt zip files.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  ZipWriter src_writer(src_file_ptr);
+  ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0));  // Store mode.
+  const std::string src_content("abcdefg");
+  ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+  ASSERT_EQ(0, src_writer.FinishEntry());
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+  ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0));  // Store mode.
+  const std::string tgt_content("abcdefgxyz");
+  ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+  std::string src;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_compressed) {
+  // Construct src and tgt zip files.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  ZipWriter src_writer(src_file_ptr);
+  ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+  const std::string src_content("abcdefg");
+  ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+  ASSERT_EQ(0, src_writer.FinishEntry());
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+  ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+  const std::string tgt_content("abcdefgxyz");
+  ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+  std::string src;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
+  // Construct src and tgt zip files.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  ZipWriter src_writer(src_file_ptr);
+  ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+  const std::string src_content("abcdefg");
+  ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+  ASSERT_EQ(0, src_writer.FinishEntry());
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+  ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+  const std::string tgt_content("abcdefgxyz");
+  ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+  ASSERT_EQ(0, tgt_writer.Finish());
+  // Add trailing zeros to the target zip file.
+  std::vector<uint8_t> zeros(10);
+  ASSERT_EQ(zeros.size(), fwrite(zeros.data(), sizeof(uint8_t), zeros.size(), tgt_file_ptr));
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+  std::string src;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_simple) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+                                       '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+                                       '\x00', '\x00', '\x00' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+  const std::vector<char> tgt_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z',    '\x1f', '\x8b',
+    '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+    '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_different_num_chunks) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd) + gzipped "test".
+  const std::vector<char> src_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'h',    '\x1f', '\x8b', '\x08',
+    '\x00', '\xc4', '\x1e', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', '\x02',
+    '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', '\x00', '\x00', '\x00', '\x1f', '\x8b',
+    '\x08', '\x00', '\xb2', '\x3a', '\x53', '\x58', '\x00', '\x03', '\x2b', '\x49', '\x2d',
+    '\x2e', '\x01', '\x00', '\x0c', '\x7e', '\x7f', '\xd8', '\x04', '\x00', '\x00', '\x00'
+  };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+  const std::vector<char> tgt_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z',    '\x1f', '\x8b',
+    '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+    '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(1, imgdiff(args.size(), args.data()));
+}
+
+TEST(ImgdiffTest, image_mode_merge_chunks) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+                                       '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+                                       '\x00', '\x00', '\x00' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: gzipped "xyz" + "abcdefgh".
+  const std::vector<char> tgt_data = {
+    '\x1f', '\x8b', '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8',
+    '\xa8', '\xac', '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00',
+    '\x00', '\x00', 'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  // Since a gzipped entry will become CHUNK_RAW (header) + CHUNK_DEFLATE (data) +
+  // CHUNK_RAW (footer), they both should contain the same chunk types after merging.
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_spurious_magic) {
+  // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', 't',    'e',    's',    't' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz".
+  const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW (header) entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_short_input1) {
+  // src: "abcdefgh" + '0x1f8b0b'.
+  const std::vector<char> src_data = { 'a', 'b', 'c',    'd',    'e',   'f',
+                                       'g', 'h', '\x1f', '\x8b', '\x08' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz".
+  const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW (header) entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_short_input2) {
+  // src: "abcdefgh" + '0x1f8b0b00'.
+  const std::vector<char> src_data = { 'a', 'b', 'c',    'd',    'e',    'f',
+                                       'g', 'h', '\x1f', '\x8b', '\x08', '\x00' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz".
+  const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW (header) entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_single_entry_long) {
+  // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', 't',    'e',    's',    't' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + 200 bytes.
+  std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  tgt_data.resize(tgt_data.size() + 200);
+
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_NORMAL entry, since it's exceeding the 160-byte limit for RAW.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(1U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(0U, num_raw);
+
+  std::string patched;
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
new file mode 100644
index 0000000..a5c0c10
--- /dev/null
+++ b/tests/component/install_test.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2017 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 <stdio.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <vintf/VintfObjectRecovery.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include "install.h"
+#include "private/install.h"
+
+TEST(InstallTest, verify_package_compatibility_no_entry) {
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  // The archive must have something to be opened correctly.
+  ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  // Doesn't contain compatibility zip entry.
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  ASSERT_TRUE(verify_package_compatibility(zip));
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_invalid_entry) {
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  // Empty compatibility zip entry.
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  ASSERT_FALSE(verify_package_compatibility(zip));
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
+  TemporaryFile compatibility_zip_file;
+  FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+  ZipWriter compatibility_zip_writer(compatibility_zip);
+  ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
+  std::string malformed_xml = "malformed";
+  ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(malformed_xml.data(), malformed_xml.size()));
+  ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
+  ASSERT_EQ(0, compatibility_zip_writer.Finish());
+  ASSERT_EQ(0, fclose(compatibility_zip));
+
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
+  std::string compatibility_zip_content;
+  ASSERT_TRUE(
+      android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+  ASSERT_EQ(0,
+            writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  std::vector<std::string> compatibility_info;
+  compatibility_info.push_back(malformed_xml);
+  // Malformed compatibility zip is expected to be rejected by libvintf. But we defer that to
+  // libvintf.
+  std::string err;
+  bool result =
+      android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
+  ASSERT_EQ(result, verify_package_compatibility(zip));
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml) {
+  static constexpr const char* system_manifest_xml_path = "/system/manifest.xml";
+  if (access(system_manifest_xml_path, R_OK) == -1) {
+    GTEST_LOG_(INFO) << "Test skipped on devices w/o /system/manifest.xml.";
+    return;
+  }
+  std::string system_manifest_xml_content;
+  ASSERT_TRUE(
+      android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
+  TemporaryFile compatibility_zip_file;
+  FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+  ZipWriter compatibility_zip_writer(compatibility_zip);
+  ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
+  ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
+                                                   system_manifest_xml_content.size()));
+  ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
+  ASSERT_EQ(0, compatibility_zip_writer.Finish());
+  ASSERT_EQ(0, fclose(compatibility_zip));
+
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
+  std::string compatibility_zip_content;
+  ASSERT_TRUE(
+      android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+  ASSERT_EQ(0,
+            writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  std::vector<std::string> compatibility_info;
+  compatibility_info.push_back(system_manifest_xml_content);
+  std::string err;
+  bool result =
+      android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
+  // Make sure the result is consistent with libvintf library.
+  ASSERT_EQ(result, verify_package_compatibility(zip));
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, update_binary_command_smoke) {
+#ifdef AB_OTA_UPDATER
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.StartEntry("payload_properties.txt", kCompressStored));
+  const std::string properties = "some_properties";
+  ASSERT_EQ(0, writer.WriteBytes(properties.data(), properties.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  // A metadata entry is mandatory.
+  ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+  std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
+  ASSERT_NE("", timestamp);
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
+      },
+      "\n");
+  ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  int status_fd = 10;
+  std::string path = "/path/to/update.zip";
+  std::vector<std::string> cmd;
+  ASSERT_EQ(0, update_binary_command(path, zip, 0, status_fd, &cmd));
+  ASSERT_EQ("/sbin/update_engine_sideload", cmd[0]);
+  ASSERT_EQ("--payload=file://" + path, cmd[1]);
+  ASSERT_EQ("--headers=" + properties, cmd[3]);
+  ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+  CloseArchive(zip);
+#else
+  // Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
+  GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
+#endif  // AB_OTA_UPDATER
+}
+
+TEST(InstallTest, update_binary_command_invalid) {
+#ifdef AB_OTA_UPDATER
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  // Missing payload_properties.txt.
+  ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
+  ASSERT_EQ(0, writer.FinishEntry());
+  // A metadata entry is mandatory.
+  ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+  std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
+  ASSERT_NE("", timestamp);
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
+      },
+      "\n");
+  ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  int status_fd = 10;
+  std::string path = "/path/to/update.zip";
+  std::vector<std::string> cmd;
+  ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(path, zip, 0, status_fd, &cmd));
+  CloseArchive(zip);
+#else
+  // Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
+  GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
+#endif  // AB_OTA_UPDATER
+}
diff --git a/minzip/inline_magic.h b/tests/component/sideload_test.cpp
similarity index 66%
copy from minzip/inline_magic.h
copy to tests/component/sideload_test.cpp
index 59c659f..ea93e9b 100644
--- a/minzip/inline_magic.h
+++ b/tests/component/sideload_test.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -13,14 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <unistd.h>
+#include <gtest/gtest.h>
 
-#ifndef MINZIP_INLINE_MAGIC_H_
-#define MINZIP_INLINE_MAGIC_H_
-
-#ifndef MINZIP_GENERATE_INLINES
-#define INLINE extern inline __attribute((__gnu_inline__))
-#else
-#define INLINE
-#endif
-
-#endif  // MINZIP_INLINE_MAGIC_H_
+TEST(SideloadTest, fusedevice) {
+  ASSERT_NE(-1, access("/dev/fuse", R_OK | W_OK));
+}
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
new file mode 100644
index 0000000..4f2b816
--- /dev/null
+++ b/tests/component/uncrypt_test.cpp
@@ -0,0 +1,192 @@
+/*
+ * 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 agreed 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 <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+#include "common/component_test_util.h"
+
+static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt";
+static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb";
+static const std::string INIT_SVC_CLEAR_BCB = "init.svc.clear-bcb";
+static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt";
+static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30;
+
+class UncryptTest : public ::testing::Test {
+ protected:
+  UncryptTest() : has_misc(true) {}
+
+  virtual void SetUp() override {
+    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
+
+    bool success = false;
+    for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+      std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
+      std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
+      std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
+      LOG(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb << "] uncrypt: ["
+                << uncrypt << "]";
+      if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
+        success = true;
+        break;
+      }
+      sleep(1);
+    }
+
+    ASSERT_TRUE(success) << "uncrypt service is not available.";
+
+    has_misc = parse_misc();
+  }
+
+  bool has_misc;
+};
+
+TEST_F(UncryptTest, setup_bcb) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  // Trigger the setup-bcb service.
+  ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb"));
+
+  // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+  sleep(1);
+
+  struct sockaddr_un un = {};
+  un.sun_family = AF_UNIX;
+  strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+  int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  ASSERT_NE(-1, sockfd);
+
+  // Connect to the uncrypt socket.
+  bool success = false;
+  for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+    if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+      success = true;
+      break;
+    }
+    sleep(1);
+  }
+  ASSERT_TRUE(success);
+
+  // Send out the BCB message.
+  std::string message = "--update_message=abc value";
+  std::string message_in_bcb = "recovery\n--update_message=abc value\n";
+  int length = static_cast<int>(message.size());
+  int length_out = htonl(length);
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int)))
+      << "Failed to write length: " << strerror(errno);
+  ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length))
+      << "Failed to write message: " << strerror(errno);
+
+  // Check the status code from uncrypt.
+  int status;
+  ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+  ASSERT_EQ(100U, ntohl(status));
+
+  // Ack having received the status code.
+  int code = 0;
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+  ASSERT_EQ(0, close(sockfd));
+
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+
+  // Verify the message by reading from BCB directly.
+  bootloader_message boot;
+  std::string err;
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  ASSERT_EQ("boot-recovery", std::string(boot.command));
+  ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
+
+  // The rest of the boot.recovery message should be zero'd out.
+  ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery));
+  size_t left = sizeof(boot.recovery) - message_in_bcb.size();
+  ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left));
+
+  // Clear the BCB.
+  ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+}
+
+TEST_F(UncryptTest, clear_bcb) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  // Trigger the clear-bcb service.
+  ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb"));
+
+  // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+  sleep(1);
+
+  struct sockaddr_un un = {};
+  un.sun_family = AF_UNIX;
+  strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+  int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  ASSERT_NE(-1, sockfd);
+
+  // Connect to the uncrypt socket.
+  bool success = false;
+  for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+    if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+      success = true;
+      break;
+    }
+    sleep(1);
+  }
+  ASSERT_TRUE(success);
+
+  // Check the status code from uncrypt.
+  int status;
+  ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+  ASSERT_EQ(100U, ntohl(status));
+
+  // Ack having received the status code.
+  int code = 0;
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+  ASSERT_EQ(0, close(sockfd));
+
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+
+  // Verify the content by reading from BCB directly.
+  bootloader_message boot;
+  std::string err;
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  // All the bytes should be cleared.
+  ASSERT_EQ(std::string(sizeof(boot), '\0'),
+            std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
new file mode 100644
index 0000000..5652ddf
--- /dev/null
+++ b/tests/component/updater_test.cpp
@@ -0,0 +1,609 @@
+/*
+ * 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 agreed 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 <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <bootloader_message/bootloader_message.h>
+#include <bsdiff.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include "common/test_constants.h"
+#include "edify/expr.h"
+#include "error_code.h"
+#include "otautil/SysUtil.h"
+#include "print_sha1.h"
+#include "updater/blockimg.h"
+#include "updater/install.h"
+#include "updater/updater.h"
+
+struct selabel_handle *sehandle = nullptr;
+
+static void expect(const char* expected, const char* expr_str, CauseCode cause_code,
+                   UpdaterInfo* info = nullptr) {
+  std::unique_ptr<Expr> e;
+  int error_count = 0;
+  ASSERT_EQ(0, parse_string(expr_str, &e, &error_count));
+  ASSERT_EQ(0, error_count);
+
+  State state(expr_str, info);
+
+  std::string result;
+  bool status = Evaluate(&state, e, &result);
+
+  if (expected == nullptr) {
+    ASSERT_FALSE(status);
+  } else {
+    ASSERT_TRUE(status);
+    ASSERT_STREQ(expected, result.c_str());
+  }
+
+  // Error code is set in updater/updater.cpp only, by parsing State.errmsg.
+  ASSERT_EQ(kNoError, state.error_code);
+
+  // Cause code should always be available.
+  ASSERT_EQ(cause_code, state.cause_code);
+}
+
+static std::string get_sha1(const std::string& content) {
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
+  return print_sha1(digest);
+}
+
+class UpdaterTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() override {
+    RegisterBuiltins();
+    RegisterInstallFunctions();
+    RegisterBlockImageFunctions();
+  }
+};
+
+TEST_F(UpdaterTest, getprop) {
+    expect(android::base::GetProperty("ro.product.device", "").c_str(),
+           "getprop(\"ro.product.device\")",
+           kNoCause);
+
+    expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(),
+           "getprop(\"ro.build.fingerprint\")",
+           kNoCause);
+
+    // getprop() accepts only one parameter.
+    expect(nullptr, "getprop()", kArgsParsingFailure);
+    expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure);
+}
+
+TEST_F(UpdaterTest, sha1_check) {
+    // sha1_check(data) returns the SHA-1 of the data.
+    expect("81fe8bfe87576c3ecb22426f8e57847382917acf", "sha1_check(\"abcd\")", kNoCause);
+    expect("da39a3ee5e6b4b0d3255bfef95601890afd80709", "sha1_check(\"\")", kNoCause);
+
+    // sha1_check(data, sha1_hex, [sha1_hex, ...]) returns the matched SHA-1.
+    expect("81fe8bfe87576c3ecb22426f8e57847382917acf",
+           "sha1_check(\"abcd\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")",
+           kNoCause);
+
+    expect("81fe8bfe87576c3ecb22426f8e57847382917acf",
+           "sha1_check(\"abcd\", \"wrong_sha1\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")",
+           kNoCause);
+
+    // Or "" if there's no match.
+    expect("",
+           "sha1_check(\"abcd\", \"wrong_sha1\")",
+           kNoCause);
+
+    expect("",
+           "sha1_check(\"abcd\", \"wrong_sha1\", \"wrong_sha2\")",
+           kNoCause);
+
+    // sha1_check() expects at least one argument.
+    expect(nullptr, "sha1_check()", kArgsParsingFailure);
+}
+
+TEST_F(UpdaterTest, apply_patch_check) {
+  // Zero-argument is not valid.
+  expect(nullptr, "apply_patch_check()", kArgsParsingFailure);
+
+  // File not found.
+  expect("", "apply_patch_check(\"/doesntexist\")", kNoCause);
+
+  std::string src_file = from_testdata_base("old.file");
+  std::string src_content;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file, &src_content));
+  size_t src_size = src_content.size();
+  std::string src_hash = get_sha1(src_content);
+
+  // One-argument with EMMC:file:size:sha1 should pass the check.
+  std::string filename = android::base::Join(
+      std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size), src_hash }, ":");
+  std::string cmd = "apply_patch_check(\"" + filename + "\")";
+  expect("t", cmd.c_str(), kNoCause);
+
+  // EMMC:file:(size-1):sha1:(size+1):sha1 should fail the check.
+  std::string filename_bad = android::base::Join(
+      std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1), src_hash,
+                                std::to_string(src_size + 1), src_hash },
+      ":");
+  cmd = "apply_patch_check(\"" + filename_bad + "\")";
+  expect("", cmd.c_str(), kNoCause);
+
+  // EMMC:file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
+  filename_bad =
+      android::base::Join(std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1),
+                                                    src_hash, std::to_string(src_size), src_hash,
+                                                    std::to_string(src_size + 1), src_hash },
+                          ":");
+  cmd = "apply_patch_check(\"" + filename_bad + "\")";
+  expect("t", cmd.c_str(), kNoCause);
+
+  // Multiple arguments.
+  cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"wrong_sha2\")";
+  expect("", cmd.c_str(), kNoCause);
+
+  cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"" + src_hash +
+        "\", \"wrong_sha2\")";
+  expect("t", cmd.c_str(), kNoCause);
+
+  cmd = "apply_patch_check(\"" + filename_bad + "\", \"wrong_sha1\", \"" + src_hash +
+        "\", \"wrong_sha2\")";
+  expect("t", cmd.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, file_getprop) {
+    // file_getprop() expects two arguments.
+    expect(nullptr, "file_getprop()", kArgsParsingFailure);
+    expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure);
+    expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+    // File doesn't exist.
+    expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFileGetPropFailure);
+
+    // Reject too large files (current limit = 65536).
+    TemporaryFile temp_file1;
+    std::string buffer(65540, '\0');
+    ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path));
+
+    // Read some keys.
+    TemporaryFile temp_file2;
+    std::string content("ro.product.name=tardis\n"
+                        "# comment\n\n\n"
+                        "ro.product.model\n"
+                        "ro.product.board =  magic \n");
+    ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path));
+
+    std::string script1("file_getprop(\"" + std::string(temp_file2.path) +
+                       "\", \"ro.product.name\")");
+    expect("tardis", script1.c_str(), kNoCause);
+
+    std::string script2("file_getprop(\"" + std::string(temp_file2.path) +
+                       "\", \"ro.product.board\")");
+    expect("magic", script2.c_str(), kNoCause);
+
+    // No match.
+    std::string script3("file_getprop(\"" + std::string(temp_file2.path) +
+                       "\", \"ro.product.wrong\")");
+    expect("", script3.c_str(), kNoCause);
+
+    std::string script4("file_getprop(\"" + std::string(temp_file2.path) +
+                       "\", \"ro.product.name=\")");
+    expect("", script4.c_str(), kNoCause);
+
+    std::string script5("file_getprop(\"" + std::string(temp_file2.path) +
+                       "\", \"ro.product.nam\")");
+    expect("", script5.c_str(), kNoCause);
+
+    std::string script6("file_getprop(\"" + std::string(temp_file2.path) +
+                       "\", \"ro.product.model\")");
+    expect("", script6.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, package_extract_dir) {
+  // package_extract_dir expects 2 arguments.
+  expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
+  expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Need to set up the ziphandle.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+
+  // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
+  TemporaryDir td;
+  std::string temp_dir(td.path);
+  std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  // Verify.
+  std::string data;
+  std::string file_c = temp_dir + "/c.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+
+  std::string file_d = temp_dir + "/d.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  // Modify the contents in order to retry. It's expected to be overwritten.
+  ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
+  ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
+
+  // Extract again and verify.
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  // Extracting "b/" (with slash) should give the same result.
+  script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  // Extracting "" is allowed. The entries will carry the path name.
+  script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  std::string file_a = temp_dir + "/a.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
+  ASSERT_EQ(kATxtContents, data);
+  std::string file_b = temp_dir + "/b.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
+  ASSERT_EQ(kBTxtContents, data);
+  std::string file_b_c = temp_dir + "/b/c.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  std::string file_b_d = temp_dir + "/b/d.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  ASSERT_EQ(0, unlink(file_a.c_str()));
+  ASSERT_EQ(0, unlink(file_b.c_str()));
+  ASSERT_EQ(0, unlink(file_b_c.c_str()));
+  ASSERT_EQ(0, unlink(file_b_d.c_str()));
+  ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
+
+  // Extracting non-existent entry should still give "t".
+  script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  // Only relative zip_path is allowed.
+  script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  // Only absolute dest_path is allowed.
+  script = "package_extract_dir(\"b\", \"path\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  CloseArchive(handle);
+}
+
+// TODO: Test extracting to block device.
+TEST_F(UpdaterTest, package_extract_file) {
+  // package_extract_file expects 1 or 2 arguments.
+  expect(nullptr, "package_extract_file()", kArgsParsingFailure);
+  expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Need to set up the ziphandle.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+
+  // Two-argument version.
+  TemporaryFile temp_file1;
+  std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  // Verify the extracted entry.
+  std::string data;
+  ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
+  ASSERT_EQ(kATxtContents, data);
+
+  // Now extract another entry to the same location, which should overwrite.
+  script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
+  ASSERT_EQ(kBTxtContents, data);
+
+  // Missing zip entry. The two-argument version doesn't abort.
+  script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  // Extract to /dev/full should fail.
+  script = "package_extract_file(\"a.txt\", \"/dev/full\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  // One-argument version.
+  script = "sha1_check(package_extract_file(\"a.txt\"))";
+  expect(kATxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+
+  script = "sha1_check(package_extract_file(\"b.txt\"))";
+  expect(kBTxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+
+  // Missing entry. The one-argument version aborts the evaluation.
+  script = "package_extract_file(\"doesntexist\")";
+  expect(nullptr, script.c_str(), kPackageExtractFileFailure, &updater_info);
+
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, write_value) {
+  // write_value() expects two arguments.
+  expect(nullptr, "write_value()", kArgsParsingFailure);
+  expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // filename cannot be empty.
+  expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure);
+
+  // Write some value to file.
+  TemporaryFile temp_file;
+  std::string value = "magicvalue";
+  std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")");
+  expect("t", script.c_str(), kNoCause);
+
+  // Verify the content.
+  std::string content;
+  ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
+  ASSERT_EQ(value, content);
+
+  // Allow writing empty string.
+  script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")";
+  expect("t", script.c_str(), kNoCause);
+
+  // Verify the content.
+  ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
+  ASSERT_EQ("", content);
+
+  // It should fail gracefully when write fails.
+  script = "write_value(\"value\", \"/proc/0/file1\")";
+  expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, get_stage) {
+  // get_stage() expects one argument.
+  expect(nullptr, "get_stage()", kArgsParsingFailure);
+  expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure);
+  expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // Set up a local file as BCB.
+  TemporaryFile tf;
+  std::string temp_file(tf.path);
+  bootloader_message boot;
+  strlcpy(boot.stage, "2/3", sizeof(boot.stage));
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
+
+  // Can read the stage value.
+  std::string script("get_stage(\"" + temp_file + "\")");
+  expect("2/3", script.c_str(), kNoCause);
+
+  // Bad BCB path.
+  script = "get_stage(\"doesntexist\")";
+  expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, set_stage) {
+  // set_stage() expects two arguments.
+  expect(nullptr, "set_stage()", kArgsParsingFailure);
+  expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // Set up a local file as BCB.
+  TemporaryFile tf;
+  std::string temp_file(tf.path);
+  bootloader_message boot;
+  strlcpy(boot.command, "command", sizeof(boot.command));
+  strlcpy(boot.stage, "2/3", sizeof(boot.stage));
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
+
+  // Write with set_stage().
+  std::string script("set_stage(\"" + temp_file + "\", \"1/3\")");
+  expect(tf.path, script.c_str(), kNoCause);
+
+  // Verify.
+  bootloader_message boot_verify;
+  ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err));
+
+  // Stage should be updated, with command part untouched.
+  ASSERT_STREQ("1/3", boot_verify.stage);
+  ASSERT_STREQ(boot.command, boot_verify.command);
+
+  // Bad BCB path.
+  script = "set_stage(\"doesntexist\", \"1/3\")";
+  expect("", script.c_str(), kNoCause);
+
+  script = "set_stage(\"/dev/full\", \"1/3\")";
+  expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, set_progress) {
+  // set_progress() expects one argument.
+  expect(nullptr, "set_progress()", kArgsParsingFailure);
+  expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
+
+  // Invalid progress argument.
+  expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure);
+  expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure);
+
+  TemporaryFile tf;
+  UpdaterInfo updater_info;
+  updater_info.cmd_pipe = fdopen(tf.fd, "w");
+  expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
+  fflush(updater_info.cmd_pipe);
+
+  std::string cmd;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
+  ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
+  // recovery-updater protocol expects 2 tokens ("set_progress <frac>").
+  ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
+}
+
+TEST_F(UpdaterTest, show_progress) {
+  // show_progress() expects two arguments.
+  expect(nullptr, "show_progress()", kArgsParsingFailure);
+  expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // Invalid progress arguments.
+  expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
+  expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure);
+  expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure);
+
+  TemporaryFile tf;
+  UpdaterInfo updater_info;
+  updater_info.cmd_pipe = fdopen(tf.fd, "w");
+  expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
+  fflush(updater_info.cmd_pipe);
+
+  std::string cmd;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
+  ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
+  // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
+  ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
+}
+
+TEST_F(UpdaterTest, block_image_update) {
+  // Create a zip file with new_data and patch_data.
+  TemporaryFile zip_file;
+  FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
+  ZipWriter zip_writer(zip_file_ptr);
+
+  // Add a dummy new data.
+  ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+
+  // Generate and add the patch data.
+  std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
+  std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
+  TemporaryFile patch_file;
+  ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()),
+      src_content.size(), reinterpret_cast<const uint8_t*>(tgt_content.data()),
+      tgt_content.size(), patch_file.path, nullptr));
+  std::string patch_content;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
+  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
+  ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+
+  // Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed.
+  std::string src_hash = get_sha1(src_content);
+  std::string tgt_hash = get_sha1(tgt_content);
+  std::vector<std::string> transfer_list = {
+    "4",
+    "2",
+    "0",
+    "2",
+    "stash " + src_hash + " 2,0,2",
+    android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(),
+                                src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
+    "free " + src_hash,
+  };
+  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
+  std::string commands = android::base::Join(transfer_list, '\n');
+  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+
+  // Stash and free some blocks, then fail the 2nd update intentionally.
+  std::vector<std::string> fail_transfer_list = {
+    "4",
+    "2",
+    "0",
+    "2",
+    "stash " + tgt_hash + " 2,0,2",
+    "free " + tgt_hash,
+    "fail",
+  };
+  ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0));
+  std::string fail_commands = android::base::Join(fail_transfer_list, '\n');
+  ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+  ASSERT_EQ(0, zip_writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  MemMapping map;
+  ASSERT_EQ(0, sysMapFile(zip_file.path, &map));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  // Execute the commands in the 1st transfer list.
+  TemporaryFile update_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+  std::string script = "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+  // The update_file should be patched correctly.
+  std::string updated_content;
+  ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
+  ASSERT_EQ(tgt_hash, get_sha1(updated_content));
+
+  // Expect the 2nd update to fail, but expect the stashed blocks to be freed.
+  script = "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))";
+  expect("", script.c_str(), kNoCause, &updater_info);
+  // Updater generates the stash name based on the input file name.
+  std::string name_digest = get_sha1(update_file.path);
+  std::string stash_base = "/cache/recovery/" + name_digest;
+  ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
+  ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK));
+  ASSERT_EQ(0, rmdir(stash_base.c_str()));
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index 780ff28..4c06487 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -22,108 +22,36 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <memory>
 #include <string>
 #include <vector>
 
-#include <openssl/sha.h>
-
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
 
-#include "common.h"
 #include "common/test_constants.h"
-#include "minzip/SysUtil.h"
-#include "ui.h"
+#include "otautil/SysUtil.h"
 #include "verifier.h"
 
-static const char* DATA_PATH = getenv("ANDROID_DATA");
-static const char* TESTDATA_PATH = "/recovery/testdata/";
-
-RecoveryUI* ui = NULL;
-
-class MockUI : public RecoveryUI {
-    void Init() { }
-    void SetStage(int, int) { }
-    void SetLocale(const char*) { }
-    void SetBackground(Icon icon) { }
-    void SetSystemUpdateText(bool security_update) { }
-
-    void SetProgressType(ProgressType determinate) { }
-    void ShowProgress(float portion, float seconds) { }
-    void SetProgress(float fraction) { }
-
-    void ShowText(bool visible) { }
-    bool IsTextVisible() { return false; }
-    bool WasTextEverVisible() { return false; }
-    void Print(const char* fmt, ...) {
-        va_list ap;
-        va_start(ap, fmt);
-        vfprintf(stderr, fmt, ap);
-        va_end(ap);
-    }
-    void PrintOnScreenOnly(const char* fmt, ...) {
-        va_list ap;
-        va_start(ap, fmt);
-        vfprintf(stderr, fmt, ap);
-        va_end(ap);
-    }
-    void ShowFile(const char*) { }
-
-    void StartMenu(const char* const * headers, const char* const * items,
-                           int initial_selection) { }
-    int SelectMenu(int sel) { return 0; }
-    void EndMenu() { }
-};
-
-void
-ui_print(const char* format, ...) {
-    va_list ap;
-    va_start(ap, format);
-    vfprintf(stdout, format, ap);
-    va_end(ap);
-}
+using namespace std::string_literals;
 
 class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
-  public:
-    MemMapping memmap;
-    std::vector<Certificate> certs;
-
-    virtual void SetUp() {
-        std::vector<std::string> args = GetParam();
-        std::string package =
-            android::base::StringPrintf("%s%s%s%s", DATA_PATH, NATIVE_TEST_PATH,
-                                        TESTDATA_PATH, args[0].c_str());
-        if (sysMapFile(package.c_str(), &memmap) != 0) {
-            FAIL() << "Failed to mmap " << package << ": " << strerror(errno)
-                   << "\n";
-        }
-
-        for (auto it = ++(args.cbegin()); it != args.cend(); ++it) {
-            if (it->substr(it->length() - 3, it->length()) == "256") {
-                if (certs.empty()) {
-                    FAIL() << "May only specify -sha256 after key type\n";
-                }
-                certs.back().hash_len = SHA256_DIGEST_LENGTH;
-            } else {
-                std::string public_key_file = android::base::StringPrintf(
-                    "%s%s%stest_key_%s.txt", DATA_PATH, NATIVE_TEST_PATH,
-                    TESTDATA_PATH, it->c_str());
-                ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
-                certs.back().hash_len = SHA_DIGEST_LENGTH;
-            }
-        }
-        if (certs.empty()) {
-            std::string public_key_file = android::base::StringPrintf(
-                "%s%s%stest_key_e3.txt", DATA_PATH, NATIVE_TEST_PATH,
-                TESTDATA_PATH);
-            ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
-            certs.back().hash_len = SHA_DIGEST_LENGTH;
-        }
+ protected:
+  void SetUp() override {
+    std::vector<std::string> args = GetParam();
+    std::string package = from_testdata_base(args[0]);
+    if (sysMapFile(package.c_str(), &memmap) != 0) {
+      FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
     }
 
-    static void SetUpTestCase() {
-        ui = new MockUI();
+    for (auto it = ++args.cbegin(); it != args.cend(); ++it) {
+      std::string public_key_file = from_testdata_base("testkey_" + *it + ".txt");
+      ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
     }
+  }
+
+  MemMapping memmap;
+  std::vector<Certificate> certs;
 };
 
 class VerifierSuccessTest : public VerifierTest {
@@ -132,47 +60,120 @@
 class VerifierFailureTest : public VerifierTest {
 };
 
+TEST(VerifierTest, load_keys_multiple_keys) {
+  std::string testkey_v4;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
+
+  std::string testkey_v3;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+
+  std::string keys = testkey_v4 + "," + testkey_v3 + "," + testkey_v4;
+  TemporaryFile key_file1;
+  ASSERT_TRUE(android::base::WriteStringToFile(keys, key_file1.path));
+  std::vector<Certificate> certs;
+  ASSERT_TRUE(load_keys(key_file1.path, certs));
+  ASSERT_EQ(3U, certs.size());
+}
+
+TEST(VerifierTest, load_keys_invalid_keys) {
+  std::vector<Certificate> certs;
+  ASSERT_FALSE(load_keys("/doesntexist", certs));
+
+  // Empty file.
+  TemporaryFile key_file1;
+  ASSERT_FALSE(load_keys(key_file1.path, certs));
+
+  // Invalid contents.
+  ASSERT_TRUE(android::base::WriteStringToFile("invalid", key_file1.path));
+  ASSERT_FALSE(load_keys(key_file1.path, certs));
+
+  std::string testkey_v4;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
+
+  // Invalid key version: "v4 ..." => "v6 ...".
+  std::string invalid_key2(testkey_v4);
+  invalid_key2[1] = '6';
+  TemporaryFile key_file2;
+  ASSERT_TRUE(android::base::WriteStringToFile(invalid_key2, key_file2.path));
+  ASSERT_FALSE(load_keys(key_file2.path, certs));
+
+  // Invalid key content: inserted extra bytes ",2209831334".
+  std::string invalid_key3(testkey_v4);
+  invalid_key3.insert(invalid_key2.size() - 2, ",2209831334");
+  TemporaryFile key_file3;
+  ASSERT_TRUE(android::base::WriteStringToFile(invalid_key3, key_file3.path));
+  ASSERT_FALSE(load_keys(key_file3.path, certs));
+
+  // Invalid key: the last key must not end with an extra ','.
+  std::string invalid_key4 = testkey_v4 + ",";
+  TemporaryFile key_file4;
+  ASSERT_TRUE(android::base::WriteStringToFile(invalid_key4, key_file4.path));
+  ASSERT_FALSE(load_keys(key_file4.path, certs));
+
+  // Invalid key separator.
+  std::string invalid_key5 = testkey_v4 + ";" + testkey_v4;
+  TemporaryFile key_file5;
+  ASSERT_TRUE(android::base::WriteStringToFile(invalid_key5, key_file5.path));
+  ASSERT_FALSE(load_keys(key_file5.path, certs));
+}
+
+TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) {
+  std::string testkey_v3;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+
+  TemporaryFile key_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file.path));
+  std::vector<Certificate> certs;
+  ASSERT_TRUE(load_keys(key_file.path, certs));
+
+  // Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369).
+  std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s;
+  ASSERT_EQ(VERIFY_FAILURE, verify_file(reinterpret_cast<const unsigned char*>(package.data()),
+                                        package.size(), certs));
+}
+
 TEST_P(VerifierSuccessTest, VerifySucceed) {
-    ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_SUCCESS);
+  ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS);
 }
 
 TEST_P(VerifierFailureTest, VerifyFailure) {
-    ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_FAILURE);
+  ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_FAILURE);
 }
 
 INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest,
-        ::testing::Values(
-            std::vector<std::string>({"otasigned.zip", "e3"}),
-            std::vector<std::string>({"otasigned_f4.zip", "f4"}),
-            std::vector<std::string>({"otasigned_sha256.zip", "e3", "sha256"}),
-            std::vector<std::string>({"otasigned_f4_sha256.zip", "f4", "sha256"}),
-            std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "ec", "sha256"})));
+    ::testing::Values(
+      std::vector<std::string>({"otasigned_v1.zip", "v1"}),
+      std::vector<std::string>({"otasigned_v2.zip", "v2"}),
+      std::vector<std::string>({"otasigned_v3.zip", "v3"}),
+      std::vector<std::string>({"otasigned_v4.zip", "v4"}),
+      std::vector<std::string>({"otasigned_v5.zip", "v5"})));
 
 INSTANTIATE_TEST_CASE_P(MultiKeySuccess, VerifierSuccessTest,
-        ::testing::Values(
-            std::vector<std::string>({"otasigned.zip", "f4", "e3"}),
-            std::vector<std::string>({"otasigned_f4.zip", "ec", "f4"}),
-            std::vector<std::string>({"otasigned_sha256.zip", "ec", "e3", "e3", "sha256"}),
-            std::vector<std::string>({"otasigned_f4_sha256.zip", "ec", "sha256", "e3", "f4", "sha256"}),
-            std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "f4", "sha256", "e3", "ec", "sha256"})));
+    ::testing::Values(
+      std::vector<std::string>({"otasigned_v1.zip", "v1", "v2"}),
+      std::vector<std::string>({"otasigned_v2.zip", "v5", "v2"}),
+      std::vector<std::string>({"otasigned_v3.zip", "v5", "v1", "v3"}),
+      std::vector<std::string>({"otasigned_v4.zip", "v5", "v1", "v4"}),
+      std::vector<std::string>({"otasigned_v5.zip", "v4", "v1", "v5"})));
 
 INSTANTIATE_TEST_CASE_P(WrongKey, VerifierFailureTest,
-        ::testing::Values(
-            std::vector<std::string>({"otasigned.zip", "f4"}),
-            std::vector<std::string>({"otasigned_f4.zip", "e3"}),
-            std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "e3", "sha256"})));
+    ::testing::Values(
+      std::vector<std::string>({"otasigned_v1.zip", "v2"}),
+      std::vector<std::string>({"otasigned_v2.zip", "v1"}),
+      std::vector<std::string>({"otasigned_v3.zip", "v5"}),
+      std::vector<std::string>({"otasigned_v4.zip", "v5"}),
+      std::vector<std::string>({"otasigned_v5.zip", "v3"})));
 
 INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest,
-        ::testing::Values(
-            std::vector<std::string>({"otasigned.zip", "e3", "sha256"}),
-            std::vector<std::string>({"otasigned_f4.zip", "f4", "sha256"}),
-            std::vector<std::string>({"otasigned_sha256.zip"}),
-            std::vector<std::string>({"otasigned_f4_sha256.zip", "f4"}),
-            std::vector<std::string>({"otasigned_ecdsa_sha256.zip"})));
+    ::testing::Values(
+      std::vector<std::string>({"otasigned_v1.zip", "v3"}),
+      std::vector<std::string>({"otasigned_v2.zip", "v4"}),
+      std::vector<std::string>({"otasigned_v3.zip", "v1"}),
+      std::vector<std::string>({"otasigned_v4.zip", "v2"})));
 
 INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest,
-        ::testing::Values(
-            std::vector<std::string>({"random.zip"}),
-            std::vector<std::string>({"fake-eocd.zip"}),
-            std::vector<std::string>({"alter-metadata.zip"}),
-            std::vector<std::string>({"alter-footer.zip"})));
+    ::testing::Values(
+      std::vector<std::string>({"random.zip", "v1"}),
+      std::vector<std::string>({"fake-eocd.zip", "v1"}),
+      std::vector<std::string>({"alter-metadata.zip", "v1"}),
+      std::vector<std::string>({"alter-footer.zip", "v1"})));
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
new file mode 100644
index 0000000..d36dd33
--- /dev/null
+++ b/tests/manual/recovery_test.cpp
@@ -0,0 +1,224 @@
+/*
+ * 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 agreed 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 <dirent.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android/log.h>
+#include <gtest/gtest.h>
+#include <png.h>
+#include <private/android_logger.h>
+
+#include "minui/minui.h"
+
+static const std::string myFilename = "/data/misc/recovery/inject.txt";
+static const std::string myContent = "Hello World\nWelcome to my recovery\n";
+static const std::string kLocale = "zu";
+static const std::string kResourceTestDir = "/data/nativetest/recovery/";
+
+// Failure is expected on systems that do not deliver either the
+// recovery-persist or recovery-refresh executables. Tests also require
+// a reboot sequence of test to truly verify.
+
+static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename,
+                         const char *buf, size_t len, void *arg) {
+  EXPECT_EQ(LOG_ID_SYSTEM, logId);
+  EXPECT_EQ(ANDROID_LOG_INFO, prio);
+  EXPECT_NE(std::string::npos, myFilename.find(filename));
+  EXPECT_EQ(myContent, buf);
+  EXPECT_EQ(myContent.size(), len);
+  EXPECT_EQ(nullptr, arg);
+  return len;
+}
+
+// recovery.refresh - May fail. Requires recovery.inject, two reboots,
+//                    then expect success after second reboot.
+TEST(recovery, refresh) {
+  EXPECT_EQ(0, access("/system/bin/recovery-refresh", F_OK));
+
+  ssize_t ret = __android_log_pmsg_file_read(
+      LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr);
+  if (ret == -ENOENT) {
+    EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
+        myFilename.c_str(), myContent.c_str(), myContent.size()));
+
+    fprintf(stderr, "injected test data, requires two intervening reboots "
+        "to check for replication\n");
+  }
+  EXPECT_EQ(static_cast<ssize_t>(myContent.size()), ret);
+}
+
+// recovery.persist - Requires recovery.inject, then a reboot, then
+//                    expect success after for this test on that boot.
+TEST(recovery, persist) {
+  EXPECT_EQ(0, access("/system/bin/recovery-persist", F_OK));
+
+  ssize_t ret = __android_log_pmsg_file_read(
+      LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr);
+  if (ret == -ENOENT) {
+    EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
+        myFilename.c_str(), myContent.c_str(), myContent.size()));
+
+    fprintf(stderr, "injected test data, requires intervening reboot "
+        "to check for storage\n");
+  }
+
+  std::string buf;
+  EXPECT_TRUE(android::base::ReadFileToString(myFilename, &buf));
+  EXPECT_EQ(myContent, buf);
+  if (access(myFilename.c_str(), F_OK) == 0) {
+    fprintf(stderr, "Removing persistent test data, "
+        "check if reconstructed on reboot\n");
+  }
+  EXPECT_EQ(0, unlink(myFilename.c_str()));
+}
+
+std::vector<std::string> image_dir {
+  "res-mdpi/images/",
+  "res-hdpi/images/",
+  "res-xhdpi/images/",
+  "res-xxhdpi/images/",
+  "res-xxxhdpi/images/"
+};
+
+static int png_filter(const dirent* de) {
+  if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
+    return 0;
+  }
+  return 1;
+}
+
+// Find out all png files to test under /data/nativetest/recovery/.
+static std::vector<std::string> add_files() {
+  std::vector<std::string> files;
+  for (const std::string& str : image_dir) {
+    std::string dir_path = kResourceTestDir + str;
+    dirent** namelist;
+    int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
+    if (n == -1) {
+      printf("Failed to scan dir %s: %s\n", kResourceTestDir.c_str(), strerror(errno));
+      return files;
+    }
+    if (n == 0) {
+      printf("No file is added for test in %s\n", kResourceTestDir.c_str());
+    }
+
+    while (n--) {
+      std::string file_path = dir_path + namelist[n]->d_name;
+      files.push_back(file_path);
+      free(namelist[n]);
+    }
+    free(namelist);
+  }
+  return files;
+}
+
+class ResourceTest : public testing::TestWithParam<std::string> {
+ public:
+  static std::vector<std::string> png_list;
+
+  // Parse a png file and test if it's qualified for the background text image
+  // under recovery.
+  void SetUp() override {
+    std::string file_path = GetParam();
+    fp = fopen(file_path.c_str(), "rb");
+    ASSERT_NE(nullptr, fp);
+
+    unsigned char header[8];
+    size_t bytesRead = fread(header, 1, sizeof(header), fp);
+    ASSERT_EQ(sizeof(header), bytesRead);
+    ASSERT_EQ(0, png_sig_cmp(header, 0, sizeof(header)));
+
+    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+    ASSERT_NE(nullptr, png_ptr);
+
+    info_ptr = png_create_info_struct(png_ptr);
+    ASSERT_NE(nullptr, info_ptr);
+
+    png_init_io(png_ptr, fp);
+    png_set_sig_bytes(png_ptr, sizeof(header));
+    png_read_info(png_ptr, info_ptr);
+
+    int color_type, bit_depth;
+    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr,
+                 nullptr);
+    ASSERT_EQ(PNG_COLOR_TYPE_GRAY, color_type) << "Recovery expects grayscale PNG file.";
+    ASSERT_LT(static_cast<png_uint_32>(5), width);
+    ASSERT_LT(static_cast<png_uint_32>(0), height);
+    if (bit_depth <= 8) {
+      // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
+      png_set_expand_gray_1_2_4_to_8(png_ptr);
+    }
+
+    png_byte channels = png_get_channels(png_ptr, info_ptr);
+    ASSERT_EQ(1, channels) << "Recovery background text images expects 1-channel PNG file.";
+  }
+
+  void TearDown() override {
+    if (png_ptr != nullptr && info_ptr != nullptr) {
+      png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
+    }
+
+    if (fp != nullptr) {
+      fclose(fp);
+    }
+  }
+
+ protected:
+  png_structp png_ptr;
+  png_infop info_ptr;
+  png_uint_32 width, height;
+
+  FILE* fp;
+};
+
+std::vector<std::string> ResourceTest::png_list = add_files();
+
+TEST_P(ResourceTest, ValidateLocale) {
+  std::vector<unsigned char> row(width);
+  for (png_uint_32 y = 0; y < height; ++y) {
+    png_read_row(png_ptr, row.data(), nullptr);
+    int w = (row[1] << 8) | row[0];
+    int h = (row[3] << 8) | row[2];
+    int len = row[4];
+    EXPECT_LT(0, w);
+    EXPECT_LT(0, h);
+    EXPECT_LT(0, len) << "Locale string should be non-empty.";
+    EXPECT_NE(0, row[5]) << "Locale string is missing.";
+
+    ASSERT_GT(height, y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
+    char* loc = reinterpret_cast<char*>(&row[5]);
+    if (matches_locale(loc, kLocale.c_str())) {
+      EXPECT_TRUE(android::base::StartsWith(loc, kLocale.c_str()));
+      break;
+    } else {
+      for (int i = 0; i < h; ++i, ++y) {
+        png_read_row(png_ptr, row.data(), nullptr);
+      }
+    }
+  }
+}
+
+INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourceTest,
+                        ::testing::ValuesIn(ResourceTest::png_list.cbegin(),
+                                            ResourceTest::png_list.cend()));
diff --git a/tests/testdata/bonus.file b/tests/testdata/bonus.file
new file mode 100644
index 0000000..918ef8a
--- /dev/null
+++ b/tests/testdata/bonus.file
Binary files differ
diff --git a/tests/testdata/boot.img b/tests/testdata/boot.img
new file mode 100644
index 0000000..dd48975
--- /dev/null
+++ b/tests/testdata/boot.img
Binary files differ
diff --git a/tests/testdata/otasigned.zip b/tests/testdata/otasigned_v1.zip
similarity index 100%
rename from tests/testdata/otasigned.zip
rename to tests/testdata/otasigned_v1.zip
Binary files differ
diff --git a/tests/testdata/otasigned_f4.zip b/tests/testdata/otasigned_v2.zip
similarity index 100%
rename from tests/testdata/otasigned_f4.zip
rename to tests/testdata/otasigned_v2.zip
Binary files differ
diff --git a/tests/testdata/otasigned_sha256.zip b/tests/testdata/otasigned_v3.zip
similarity index 100%
rename from tests/testdata/otasigned_sha256.zip
rename to tests/testdata/otasigned_v3.zip
Binary files differ
diff --git a/tests/testdata/otasigned_f4_sha256.zip b/tests/testdata/otasigned_v4.zip
similarity index 100%
rename from tests/testdata/otasigned_f4_sha256.zip
rename to tests/testdata/otasigned_v4.zip
Binary files differ
diff --git a/tests/testdata/otasigned_ecdsa_sha256.zip b/tests/testdata/otasigned_v5.zip
similarity index 100%
rename from tests/testdata/otasigned_ecdsa_sha256.zip
rename to tests/testdata/otasigned_v5.zip
Binary files differ
diff --git a/tests/testdata/recovery-from-boot-with-bonus.p b/tests/testdata/recovery-from-boot-with-bonus.p
new file mode 100644
index 0000000..08b6f55
--- /dev/null
+++ b/tests/testdata/recovery-from-boot-with-bonus.p
Binary files differ
diff --git a/tests/testdata/recovery-from-boot.p b/tests/testdata/recovery-from-boot.p
new file mode 100644
index 0000000..06f6c29
--- /dev/null
+++ b/tests/testdata/recovery-from-boot.p
Binary files differ
diff --git a/tests/testdata/recovery.img b/tests/testdata/recovery.img
new file mode 100644
index 0000000..b862e6f
--- /dev/null
+++ b/tests/testdata/recovery.img
Binary files differ
diff --git a/tests/testdata/testkey.pk8 b/tests/testdata/testkey_v1.pk8
similarity index 100%
rename from tests/testdata/testkey.pk8
rename to tests/testdata/testkey_v1.pk8
Binary files differ
diff --git a/tests/testdata/test_key_e3.txt b/tests/testdata/testkey_v1.txt
similarity index 100%
rename from tests/testdata/test_key_e3.txt
rename to tests/testdata/testkey_v1.txt
diff --git a/tests/testdata/testkey.x509.pem b/tests/testdata/testkey_v1.x509.pem
similarity index 100%
rename from tests/testdata/testkey.x509.pem
rename to tests/testdata/testkey_v1.x509.pem
diff --git a/tests/testdata/test_f4.pk8 b/tests/testdata/testkey_v2.pk8
similarity index 100%
rename from tests/testdata/test_f4.pk8
rename to tests/testdata/testkey_v2.pk8
Binary files differ
diff --git a/tests/testdata/test_key_f4.txt b/tests/testdata/testkey_v2.txt
similarity index 100%
rename from tests/testdata/test_key_f4.txt
rename to tests/testdata/testkey_v2.txt
diff --git a/tests/testdata/test_f4.x509.pem b/tests/testdata/testkey_v2.x509.pem
similarity index 100%
rename from tests/testdata/test_f4.x509.pem
rename to tests/testdata/testkey_v2.x509.pem
diff --git a/tests/testdata/testkey_v3.pk8 b/tests/testdata/testkey_v3.pk8
new file mode 120000
index 0000000..18ecf98
--- /dev/null
+++ b/tests/testdata/testkey_v3.pk8
@@ -0,0 +1 @@
+testkey_v1.pk8
\ No newline at end of file
diff --git a/tests/testdata/testkey_v3.txt b/tests/testdata/testkey_v3.txt
new file mode 100644
index 0000000..3208571
--- /dev/null
+++ b/tests/testdata/testkey_v3.txt
@@ -0,0 +1 @@
+v3 {64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}}
diff --git a/tests/testdata/testkey_sha256.x509.pem b/tests/testdata/testkey_v3.x509.pem
similarity index 100%
rename from tests/testdata/testkey_sha256.x509.pem
rename to tests/testdata/testkey_v3.x509.pem
diff --git a/tests/testdata/testkey_v4.pk8 b/tests/testdata/testkey_v4.pk8
new file mode 120000
index 0000000..683b9a3
--- /dev/null
+++ b/tests/testdata/testkey_v4.pk8
@@ -0,0 +1 @@
+testkey_v2.pk8
\ No newline at end of file
diff --git a/tests/testdata/testkey_v4.txt b/tests/testdata/testkey_v4.txt
new file mode 100644
index 0000000..532cbd5
--- /dev/null
+++ b/tests/testdata/testkey_v4.txt
@@ -0,0 +1 @@
+v4 {64,0xc9bd1f21,{293133087,3210546773,865313125,250921607,3158780490,943703457,1242806226,2986289859,2942743769,2457906415,2719374299,1783459420,149579627,3081531591,3440738617,2788543742,2758457512,1146764939,3699497403,2446203424,1744968926,1159130537,2370028300,3978231572,3392699980,1487782451,1180150567,2841334302,3753960204,961373345,3333628321,748825784,2978557276,1566596926,1613056060,2600292737,1847226629,50398611,1890374404,2878700735,2286201787,1401186359,619285059,731930817,2340993166,1156490245,2992241729,151498140,318782170,3480838990,2100383433,4223552555,3628927011,4247846280,1759029513,4215632601,2719154626,3490334597,1751299340,3487864726,3668753795,4217506054,3748782284,3150295088},{1772626313,445326068,3477676155,1758201194,2986784722,491035581,3922936562,702212696,2979856666,3324974564,2488428922,3056318590,1626954946,664714029,398585816,3964097931,3356701905,2298377729,2040082097,3025491477,539143308,3348777868,2995302452,3602465520,212480763,2691021393,1307177300,704008044,2031136606,1054106474,3838318865,2441343869,1477566916,700949900,2534790355,3353533667,336163563,4106790558,2701448228,1571536379,1103842411,3623110423,1635278839,1577828979,910322800,715583630,138128831,1017877531,2289162787,447994798,1897243165,4121561445,4150719842,2131821093,2262395396,3305771534,980753571,3256525190,3128121808,1072869975,3507939515,4229109952,118381341,2209831334}}
diff --git a/tests/testdata/test_f4_sha256.x509.pem b/tests/testdata/testkey_v4.x509.pem
similarity index 100%
rename from tests/testdata/test_f4_sha256.x509.pem
rename to tests/testdata/testkey_v4.x509.pem
diff --git a/tests/testdata/testkey_ecdsa.pk8 b/tests/testdata/testkey_v5.pk8
similarity index 100%
rename from tests/testdata/testkey_ecdsa.pk8
rename to tests/testdata/testkey_v5.pk8
Binary files differ
diff --git a/tests/testdata/test_key_ec.txt b/tests/testdata/testkey_v5.txt
similarity index 100%
rename from tests/testdata/test_key_ec.txt
rename to tests/testdata/testkey_v5.txt
diff --git a/tests/testdata/testkey_ecdsa.x509.pem b/tests/testdata/testkey_v5.x509.pem
similarity index 100%
rename from tests/testdata/testkey_ecdsa.x509.pem
rename to tests/testdata/testkey_v5.x509.pem
diff --git a/tests/testdata/ziptest_dummy-update.zip b/tests/testdata/ziptest_dummy-update.zip
new file mode 100644
index 0000000..6976bf1
--- /dev/null
+++ b/tests/testdata/ziptest_dummy-update.zip
Binary files differ
diff --git a/tests/testdata/ziptest_valid.zip b/tests/testdata/ziptest_valid.zip
new file mode 100644
index 0000000..9e7cb78
--- /dev/null
+++ b/tests/testdata/ziptest_valid.zip
Binary files differ
diff --git a/tests/unit/asn1_decoder_test.cpp b/tests/unit/asn1_decoder_test.cpp
index af96d87..b334a65 100644
--- a/tests/unit/asn1_decoder_test.cpp
+++ b/tests/unit/asn1_decoder_test.cpp
@@ -14,225 +14,188 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "asn1_decoder_test"
-
-#include <cutils/log.h>
-#include <gtest/gtest.h>
 #include <stdint.h>
-#include <unistd.h>
+
+#include <memory>
+
+#include <gtest/gtest.h>
 
 #include "asn1_decoder.h"
 
-namespace android {
+TEST(Asn1DecoderTest, Empty_Failure) {
+  uint8_t empty[] = {};
+  asn1_context ctx(empty, sizeof(empty));
 
-class Asn1DecoderTest : public testing::Test {
-};
+  ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
+  ASSERT_FALSE(ctx.asn1_constructed_skip_all());
+  ASSERT_EQ(0, ctx.asn1_constructed_type());
+  ASSERT_EQ(nullptr, ctx.asn1_sequence_get());
+  ASSERT_EQ(nullptr, ctx.asn1_set_get());
+  ASSERT_FALSE(ctx.asn1_sequence_next());
 
-TEST_F(Asn1DecoderTest, Empty_Failure) {
-    uint8_t empty[] = { };
-    asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty));
-
-    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
-    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
-    EXPECT_EQ(0, asn1_constructed_type(ctx));
-    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
-    EXPECT_EQ(NULL, asn1_set_get(ctx));
-    EXPECT_FALSE(asn1_sequence_next(ctx));
-
-    uint8_t* junk;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length));
-    EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length));
-
-    asn1_context_free(ctx);
+  const uint8_t* junk;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_oid_get(&junk, &length));
+  ASSERT_FALSE(ctx.asn1_octet_string_get(&junk, &length));
 }
 
-TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
-    uint8_t truncated[] = { 0xA0, 0x82, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
+  uint8_t truncated[] = { 0xA0, 0x82 };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
 }
 
-TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
-    uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A,
-                            0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
+  uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
 }
 
-TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
-    uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_constructed_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    EXPECT_EQ(5, asn1_constructed_type(ptr));
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
+  uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_constructed_get());
+  ASSERT_NE(nullptr, ptr);
+  ASSERT_EQ(5, ptr->asn1_constructed_type());
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, ConstructedGet_Success) {
-    uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_constructed_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    EXPECT_EQ(5, asn1_constructed_type(ptr));
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0x01U, *oid);
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedGet_Success) {
+  uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_constructed_get());
+  ASSERT_NE(nullptr, ptr);
+  ASSERT_EQ(5, ptr->asn1_constructed_type());
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0x01U, *oid);
 }
 
-TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
-    uint8_t truncated[] = { 0xA2, 0x82, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
+  uint8_t truncated[] = { 0xA2, 0x82 };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_FALSE(ctx.asn1_constructed_skip_all());
 }
 
-TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) {
-    uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01,
-                            0xA1, 0x03, 0x02, 0x01, 0x01,
-                            0x06, 0x01, 0xA5, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    ASSERT_TRUE(asn1_constructed_skip_all(ctx));
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0xA5U, *oid);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedSkipAll_Success) {
+  uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01, 0xA1, 0x03, 0x02, 0x01, 0x01, 0x06, 0x01, 0xA5 };
+  asn1_context ctx(data, sizeof(data));
+  ASSERT_TRUE(ctx.asn1_constructed_skip_all());
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ctx.asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0xA5U, *oid);
 }
 
-TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
-    uint8_t truncated[] = { 0x30, 0x82, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
+  uint8_t truncated[] = { 0x30, 0x82 };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_EQ(nullptr, ctx.asn1_sequence_get());
 }
 
-TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
-    uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_sequence_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
+  uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_sequence_get());
+  ASSERT_NE(nullptr, ptr);
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, SequenceGet_Success) {
-    uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_sequence_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0x01U, *oid);
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SequenceGet_Success) {
+  uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_sequence_get());
+  ASSERT_NE(nullptr, ptr);
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0x01U, *oid);
 }
 
-TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
-    uint8_t truncated[] = { 0x31, 0x82, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_EQ(NULL, asn1_set_get(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
+  uint8_t truncated[] = { 0x31, 0x82 };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_EQ(nullptr, ctx.asn1_set_get());
 }
 
-TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
-    uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_set_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
+  uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_set_get());
+  ASSERT_NE(nullptr, ptr);
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, SetGet_Success) {
-    uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_set_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0xBAU, *oid);
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SetGet_Success) {
+  uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_set_get());
+  ASSERT_NE(nullptr, ptr);
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0xBAU, *oid);
 }
 
-TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) {
-    uint8_t data[] = { 0x06, 0x00, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OidGet_LengthZero_Failure) {
+  uint8_t data[] = { 0x06, 0x00, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) {
-    uint8_t data[] = { 0x06, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OidGet_TooSmall_Failure) {
+  uint8_t data[] = { 0x06, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, OidGet_Success) {
-    uint8_t data[] = { 0x06, 0x01, 0x99, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0x99U, *oid);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OidGet_Success) {
+  uint8_t data[] = { 0x06, 0x01, 0x99 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ctx.asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0x99U, *oid);
 }
 
-TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
-    uint8_t data[] = { 0x04, 0x00, 0x55, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* string;
-    size_t length;
-    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
+  uint8_t data[] = { 0x04, 0x00, 0x55 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* string;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_octet_string_get(&string, &length));
 }
 
-TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
-    uint8_t data[] = { 0x04, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* string;
-    size_t length;
-    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
+  uint8_t data[] = { 0x04, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* string;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_octet_string_get(&string, &length));
 }
 
-TEST_F(Asn1DecoderTest, OctetStringGet_Success) {
-    uint8_t data[] = { 0x04, 0x01, 0xAA, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* string;
-    size_t length;
-    ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0xAAU, *string);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OctetStringGet_Success) {
+  uint8_t data[] = { 0x04, 0x01, 0xAA };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* string;
+  size_t length;
+  ASSERT_TRUE(ctx.asn1_octet_string_get(&string, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0xAAU, *string);
 }
-
-} // namespace android
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
new file mode 100644
index 0000000..5e2ae4f
--- /dev/null
+++ b/tests/unit/dirutil_test.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 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 agreed 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 <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/DirUtil.h>
+
+TEST(DirUtilTest, create_invalid) {
+  // Requesting to create an empty dir is invalid.
+  ASSERT_EQ(-1, dirCreateHierarchy("", 0755, nullptr, false, nullptr));
+  ASSERT_EQ(ENOENT, errno);
+
+  // Requesting to strip the name with no slash present.
+  ASSERT_EQ(-1, dirCreateHierarchy("abc", 0755, nullptr, true, nullptr));
+  ASSERT_EQ(ENOENT, errno);
+
+  // Creating a dir that already exists.
+  TemporaryDir td;
+  ASSERT_EQ(0, dirCreateHierarchy(td.path, 0755, nullptr, false, nullptr));
+
+  // "///" is a valid dir.
+  ASSERT_EQ(0, dirCreateHierarchy("///", 0755, nullptr, false, nullptr));
+
+  // Request to create a dir, but a file with the same name already exists.
+  TemporaryFile tf;
+  ASSERT_EQ(-1, dirCreateHierarchy(tf.path, 0755, nullptr, false, nullptr));
+  ASSERT_EQ(ENOTDIR, errno);
+}
+
+TEST(DirUtilTest, create_smoke) {
+  TemporaryDir td;
+  std::string prefix(td.path);
+  std::string path = prefix + "/a/b";
+  constexpr mode_t mode = 0755;
+  ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, nullptr, false, nullptr));
+
+  // Verify.
+  struct stat sb;
+  ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno);
+  ASSERT_TRUE(S_ISDIR(sb.st_mode));
+  constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
+  ASSERT_EQ(mode, sb.st_mode & mask);
+
+  // Clean up.
+  ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
+  ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
+}
+
+TEST(DirUtilTest, create_strip_filename) {
+  TemporaryDir td;
+  std::string prefix(td.path);
+  std::string path = prefix + "/a/b";
+  ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), 0755, nullptr, true, nullptr));
+
+  // Verify that "../a" exists but not "../a/b".
+  struct stat sb;
+  ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno);
+  ASSERT_TRUE(S_ISDIR(sb.st_mode));
+
+  ASSERT_EQ(-1, stat(path.c_str(), &sb));
+  ASSERT_EQ(ENOENT, errno);
+
+  // Clean up.
+  ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
+}
+
+TEST(DirUtilTest, create_mode_and_timestamp) {
+  TemporaryDir td;
+  std::string prefix(td.path);
+  std::string path = prefix + "/a/b";
+  // Set the timestamp to 8/1/2008.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
+  constexpr mode_t mode = 0751;
+  ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, &timestamp, false, nullptr));
+
+  // Verify the mode and timestamp for "../a/b".
+  struct stat sb;
+  ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno);
+  ASSERT_TRUE(S_ISDIR(sb.st_mode));
+  constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
+  ASSERT_EQ(mode, sb.st_mode & mask);
+
+  timespec time;
+  time.tv_sec = 1217592000;
+  time.tv_nsec = 0;
+
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  // Verify the mode for "../a". Note that the timestamp for intermediate directories (e.g. "../a")
+  // may not be 'timestamp' according to the current implementation.
+  ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno);
+  ASSERT_TRUE(S_ISDIR(sb.st_mode));
+  ASSERT_EQ(mode, sb.st_mode & mask);
+
+  // Clean up.
+  ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
+  ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
+}
+
+TEST(DirUtilTest, unlink_invalid) {
+  // File doesn't exist.
+  ASSERT_EQ(-1, dirUnlinkHierarchy("doesntexist"));
+
+  // Nonexistent directory.
+  TemporaryDir td;
+  std::string path(td.path);
+  ASSERT_EQ(-1, dirUnlinkHierarchy((path + "/a").c_str()));
+  ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(DirUtilTest, unlink_smoke) {
+  // Unlink a file.
+  TemporaryFile tf;
+  ASSERT_EQ(0, dirUnlinkHierarchy(tf.path));
+  ASSERT_EQ(-1, access(tf.path, F_OK));
+
+  TemporaryDir td;
+  std::string path(td.path);
+  constexpr mode_t mode = 0700;
+  ASSERT_EQ(0, mkdir((path + "/a").c_str(), mode));
+  ASSERT_EQ(0, mkdir((path + "/a/b").c_str(), mode));
+  ASSERT_EQ(0, mkdir((path + "/a/b/c").c_str(), mode));
+  ASSERT_EQ(0, mkdir((path + "/a/d").c_str(), mode));
+
+  // Remove "../a" recursively.
+  ASSERT_EQ(0, dirUnlinkHierarchy((path + "/a").c_str()));
+
+  // Verify it's gone.
+  ASSERT_EQ(-1, access((path + "/a").c_str(), F_OK));
+}
diff --git a/tests/unit/locale_test.cpp b/tests/unit/locale_test.cpp
index 0e515f8..cdaba0e 100644
--- a/tests/unit/locale_test.cpp
+++ b/tests/unit/locale_test.cpp
@@ -19,11 +19,15 @@
 #include "minui/minui.h"
 
 TEST(LocaleTest, Misc) {
-    EXPECT_TRUE(matches_locale("zh_CN", "zh_CN_#Hans"));
-    EXPECT_TRUE(matches_locale("zh", "zh_CN_#Hans"));
-    EXPECT_FALSE(matches_locale("zh_HK", "zh_CN_#Hans"));
-    EXPECT_TRUE(matches_locale("en_GB", "en_GB"));
-    EXPECT_TRUE(matches_locale("en", "en_GB"));
-    EXPECT_FALSE(matches_locale("en_GB", "en"));
-    EXPECT_FALSE(matches_locale("en_GB", "en_US"));
+  EXPECT_TRUE(matches_locale("zh-CN", "zh-Hans-CN"));
+  EXPECT_TRUE(matches_locale("zh", "zh-Hans-CN"));
+  EXPECT_FALSE(matches_locale("zh-HK", "zh-Hans-CN"));
+  EXPECT_TRUE(matches_locale("en-GB", "en-GB"));
+  EXPECT_TRUE(matches_locale("en", "en-GB"));
+  EXPECT_FALSE(matches_locale("en-GB", "en"));
+  EXPECT_FALSE(matches_locale("en-GB", "en-US"));
+  EXPECT_FALSE(matches_locale("en-US", ""));
+  // Empty locale prefix in the PNG file will match the input locale.
+  EXPECT_TRUE(matches_locale("", "en-US"));
+  EXPECT_TRUE(matches_locale("sr-Latn", "sr-Latn-BA"));
 }
diff --git a/tests/unit/recovery_test.cpp b/tests/unit/recovery_test.cpp
deleted file mode 100644
index f397f25..0000000
--- a/tests/unit/recovery_test.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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 agreed 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 <string.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <android/log.h>
-#include <gtest/gtest.h>
-#include <log/logger.h>
-#include <private/android_logger.h>
-
-static const char myFilename[] = "/data/misc/recovery/inject.txt";
-static const char myContent[] = "Hello World\nWelcome to my recovery\n";
-
-// Failure is expected on systems that do not deliver either the
-// recovery-persist or recovery-refresh executables. Tests also require
-// a reboot sequence of test to truly verify.
-
-static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename,
-                         const char *buf, size_t len, void *arg) {
-    EXPECT_EQ(LOG_ID_SYSTEM, logId);
-    EXPECT_EQ(ANDROID_LOG_INFO, prio);
-    EXPECT_EQ(0, NULL == strstr(myFilename,filename));
-    EXPECT_EQ(0, strcmp(myContent, buf));
-    EXPECT_EQ(sizeof(myContent), len);
-    EXPECT_EQ(0, NULL != arg);
-    return len;
-}
-
-// recovery.refresh - May fail. Requires recovery.inject, two reboots,
-//                    then expect success after second reboot.
-TEST(recovery, refresh) {
-    EXPECT_EQ(0, access("/system/bin/recovery-refresh", F_OK));
-
-    ssize_t ret = __android_log_pmsg_file_read(
-        LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, NULL);
-    if (ret == -ENOENT) {
-        EXPECT_LT(0, __android_log_pmsg_file_write(
-            LOG_ID_SYSTEM, ANDROID_LOG_INFO,
-            myFilename, myContent, sizeof(myContent)));
-        fprintf(stderr, "injected test data, "
-                        "requires two intervening reboots "
-                        "to check for replication\n");
-    }
-    EXPECT_EQ((ssize_t)sizeof(myContent), ret);
-}
-
-// recovery.persist - Requires recovery.inject, then a reboot, then
-//                    expect success after for this test on that boot.
-TEST(recovery, persist) {
-    EXPECT_EQ(0, access("/system/bin/recovery-persist", F_OK));
-
-    ssize_t ret = __android_log_pmsg_file_read(
-        LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, NULL);
-    if (ret == -ENOENT) {
-        EXPECT_LT(0, __android_log_pmsg_file_write(
-            LOG_ID_SYSTEM, ANDROID_LOG_INFO,
-            myFilename, myContent, sizeof(myContent)));
-        fprintf(stderr, "injected test data, "
-                        "requires intervening reboot "
-                        "to check for storage\n");
-    }
-
-    int fd = open(myFilename, O_RDONLY);
-    EXPECT_LE(0, fd);
-
-    char buf[sizeof(myContent) + 32];
-    ret = read(fd, buf, sizeof(buf));
-    close(fd);
-    EXPECT_EQ(ret, (ssize_t)sizeof(myContent));
-    EXPECT_EQ(0, strcmp(myContent, buf));
-    if (fd >= 0) {
-        fprintf(stderr, "Removing persistent test data, "
-                        "check if reconstructed on reboot\n");
-    }
-    EXPECT_EQ(0, unlink(myFilename));
-}
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
new file mode 100644
index 0000000..f469966
--- /dev/null
+++ b/tests/unit/sysutil_test.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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 agreed 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 <gtest/gtest.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+
+#include "otautil/SysUtil.h"
+
+TEST(SysUtilTest, InvalidArgs) {
+  MemMapping mapping;
+
+  // Invalid argument.
+  ASSERT_EQ(-1, sysMapFile(nullptr, &mapping));
+  ASSERT_EQ(-1, sysMapFile("/somefile", nullptr));
+}
+
+TEST(SysUtilTest, sysMapFileRegularFile) {
+  TemporaryFile temp_file1;
+  std::string content = "abc";
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file1.path));
+
+  // sysMapFile() should map the file to one range.
+  MemMapping mapping;
+  ASSERT_EQ(0, sysMapFile(temp_file1.path, &mapping));
+  ASSERT_NE(nullptr, mapping.addr);
+  ASSERT_EQ(content.size(), mapping.length);
+  ASSERT_EQ(1U, mapping.ranges.size());
+
+  sysReleaseMap(&mapping);
+  ASSERT_EQ(0U, mapping.ranges.size());
+}
+
+TEST(SysUtilTest, sysMapFileBlockMap) {
+  // Create a file that has 10 blocks.
+  TemporaryFile package;
+  std::string content;
+  constexpr size_t file_size = 4096 * 10;
+  content.reserve(file_size);
+  ASSERT_TRUE(android::base::WriteStringToFile(content, package.path));
+
+  TemporaryFile block_map_file;
+  std::string filename = std::string("@") + block_map_file.path;
+  MemMapping mapping;
+
+  // One range.
+  std::string block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n";
+  ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
+
+  ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_EQ(file_size, mapping.length);
+  ASSERT_EQ(1U, mapping.ranges.size());
+
+  // It's okay to not have the trailing '\n'.
+  block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10";
+  ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
+
+  ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_EQ(file_size, mapping.length);
+  ASSERT_EQ(1U, mapping.ranges.size());
+
+  // Or having multiple trailing '\n's.
+  block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n\n\n";
+  ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
+
+  ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_EQ(file_size, mapping.length);
+  ASSERT_EQ(1U, mapping.ranges.size());
+
+  // Multiple ranges.
+  block_map_content = std::string(package.path) + "\n40960 4096\n3\n0 3\n3 5\n5 10\n";
+  ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
+
+  ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_EQ(file_size, mapping.length);
+  ASSERT_EQ(3U, mapping.ranges.size());
+
+  sysReleaseMap(&mapping);
+  ASSERT_EQ(0U, mapping.ranges.size());
+}
+
+TEST(SysUtilTest, sysMapFileBlockMapInvalidBlockMap) {
+  MemMapping mapping;
+  TemporaryFile temp_file;
+  std::string filename = std::string("@") + temp_file.path;
+
+  // Block map file is too short.
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  // Block map file has unexpected number of lines.
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n1\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n2\n0 1\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  // Invalid size/blksize/range_count.
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\nabc 4096\n1\n0 1\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n\n0 1\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  // size/blksize/range_count don't match.
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n0 4096\n1\n0 1\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 0\n1\n0 1\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n0 1\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  // Invalid block dev path.
+  ASSERT_TRUE(android::base::WriteStringToFile("/doesntexist\n4096 4096\n1\n0 1\n", temp_file.path));
+  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+  sysReleaseMap(&mapping);
+  ASSERT_EQ(0U, mapping.ranges.size());
+}
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
new file mode 100644
index 0000000..4a1a49b
--- /dev/null
+++ b/tests/unit/zip_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 agreed 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 <errno.h>
+#include <unistd.h>
+
+#include <memory>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/SysUtil.h>
+#include <otautil/ZipUtil.h>
+#include <ziparchive/zip_archive.h>
+
+#include "common/test_constants.h"
+
+TEST(ZipTest, ExtractPackageRecursive) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract the whole package into a temp directory.
+  TemporaryDir td;
+  ASSERT_NE(nullptr, td.path);
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+  // Make sure all the files are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+  ASSERT_EQ(kATxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  CloseArchive(handle);
+
+  // Clean up.
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+}
+
+TEST(ZipTest, OpenFromMemory) {
+  MemMapping map;
+  std::string zip_path = from_testdata_base("ziptest_dummy-update.zip");
+  ASSERT_EQ(0, sysMapFile(zip_path.c_str(), &map));
+
+  // Map an update package into memory and open the archive from there.
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle));
+
+  static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary";
+  ZipString binary_path(BINARY_PATH);
+  ZipEntry binary_entry;
+  // Make sure the package opens correctly and its entry can be read.
+  ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry));
+
+  TemporaryFile tmp_binary;
+  ASSERT_NE(-1, tmp_binary.fd);
+  ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd));
+
+  CloseArchive(handle);
+  sysReleaseMap(&map);
+}
+
diff --git a/tests/unit/ziputil_test.cpp b/tests/unit/ziputil_test.cpp
new file mode 100644
index 0000000..14e5416
--- /dev/null
+++ b/tests/unit/ziputil_test.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 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 agreed 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 <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/ZipUtil.h>
+#include <ziparchive/zip_archive.h>
+
+#include "common/test_constants.h"
+
+TEST(ZipUtilTest, invalid_args) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // zip_path must be a relative path.
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr));
+
+  // dest_path must be an absolute path.
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr));
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_all) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract the whole package into a temp directory.
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+  // Make sure all the files are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+  ASSERT_EQ(kATxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_with_slash) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract all the entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+  // And the rest are not extracted.
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+  ASSERT_EQ(kCTxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_without_slash) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract all the file entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+  // And the rest are not extracted.
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+  ASSERT_EQ(kCTxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, set_timestamp) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Set the timestamp to 8/1/2008.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
+
+  // Extract all the entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b", td.path, &timestamp, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  std::string file_c = path + "/c.txt";
+  std::string file_d = path + "/d.txt";
+  ASSERT_EQ(0, access(file_c.c_str(), F_OK));
+  ASSERT_EQ(0, access(file_d.c_str(), F_OK));
+
+  // Verify the timestamp.
+  timespec time;
+  time.tv_sec = 1217592000;
+  time.tv_nsec = 0;
+
+  struct stat sb;
+  ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno);
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno);
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  CloseArchive(handle);
+}
diff --git a/tools/dumpkey/Android.mk b/tools/dumpkey/Android.mk
new file mode 100644
index 0000000..3154914
--- /dev/null
+++ b/tools/dumpkey/Android.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2008 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 agreed 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := dumpkey
+LOCAL_SRC_FILES := DumpPublicKey.java
+LOCAL_JAR_MANIFEST := DumpPublicKey.mf
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/dumpkey/DumpPublicKey.java b/tools/dumpkey/DumpPublicKey.java
new file mode 100644
index 0000000..3eb1398
--- /dev/null
+++ b/tools/dumpkey/DumpPublicKey.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2008 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 agreed 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.
+ */
+
+package com.android.dumpkey;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import java.io.FileInputStream;
+import java.math.BigInteger;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.KeyStore;
+import java.security.Key;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECPoint;
+
+/**
+ * Command line tool to extract RSA public keys from X.509 certificates
+ * and output source code with data initializers for the keys.
+ * @hide
+ */
+class DumpPublicKey {
+    /**
+     * @param key to perform sanity checks on
+     * @return version number of key.  Supported versions are:
+     *     1: 2048-bit RSA key with e=3 and SHA-1 hash
+     *     2: 2048-bit RSA key with e=65537 and SHA-1 hash
+     *     3: 2048-bit RSA key with e=3 and SHA-256 hash
+     *     4: 2048-bit RSA key with e=65537 and SHA-256 hash
+     * @throws Exception if the key has the wrong size or public exponent
+     */
+    static int checkRSA(RSAPublicKey key, boolean useSHA256) throws Exception {
+        BigInteger pubexp = key.getPublicExponent();
+        BigInteger modulus = key.getModulus();
+        int version;
+
+        if (pubexp.equals(BigInteger.valueOf(3))) {
+            version = useSHA256 ? 3 : 1;
+        } else if (pubexp.equals(BigInteger.valueOf(65537))) {
+            version = useSHA256 ? 4 : 2;
+        } else {
+            throw new Exception("Public exponent should be 3 or 65537 but is " +
+                                pubexp.toString(10) + ".");
+        }
+
+        if (modulus.bitLength() != 2048) {
+             throw new Exception("Modulus should be 2048 bits long but is " +
+                        modulus.bitLength() + " bits.");
+        }
+
+        return version;
+    }
+
+    /**
+     * @param key to perform sanity checks on
+     * @return version number of key.  Supported versions are:
+     *     5: 256-bit EC key with curve NIST P-256
+     * @throws Exception if the key has the wrong size or public exponent
+     */
+    static int checkEC(ECPublicKey key) throws Exception {
+        if (key.getParams().getCurve().getField().getFieldSize() != 256) {
+            throw new Exception("Curve must be NIST P-256");
+        }
+
+        return 5;
+    }
+
+    /**
+     * Perform sanity check on public key.
+     */
+    static int check(PublicKey key, boolean useSHA256) throws Exception {
+        if (key instanceof RSAPublicKey) {
+            return checkRSA((RSAPublicKey) key, useSHA256);
+        } else if (key instanceof ECPublicKey) {
+            if (!useSHA256) {
+                throw new Exception("Must use SHA-256 with EC keys!");
+            }
+            return checkEC((ECPublicKey) key);
+        } else {
+            throw new Exception("Unsupported key class: " + key.getClass().getName());
+        }
+    }
+
+    /**
+     * @param key to output
+     * @return a String representing this public key.  If the key is a
+     *    version 1 key, the string will be a C initializer; this is
+     *    not true for newer key versions.
+     */
+    static String printRSA(RSAPublicKey key, boolean useSHA256) throws Exception {
+        int version = check(key, useSHA256);
+
+        BigInteger N = key.getModulus();
+
+        StringBuilder result = new StringBuilder();
+
+        int nwords = N.bitLength() / 32;    // # of 32 bit integers in modulus
+
+        if (version > 1) {
+            result.append("v");
+            result.append(Integer.toString(version));
+            result.append(" ");
+        }
+
+        result.append("{");
+        result.append(nwords);
+
+        BigInteger B = BigInteger.valueOf(0x100000000L);  // 2^32
+        BigInteger N0inv = B.subtract(N.modInverse(B));   // -1 / N[0] mod 2^32
+
+        result.append(",0x");
+        result.append(N0inv.toString(16));
+
+        BigInteger R = BigInteger.valueOf(2).pow(N.bitLength());
+        BigInteger RR = R.multiply(R).mod(N);    // 2^4096 mod N
+
+        // Write out modulus as little endian array of integers.
+        result.append(",{");
+        for (int i = 0; i < nwords; ++i) {
+            long n = N.mod(B).longValue();
+            result.append(n);
+
+            if (i != nwords - 1) {
+                result.append(",");
+            }
+
+            N = N.divide(B);
+        }
+        result.append("}");
+
+        // Write R^2 as little endian array of integers.
+        result.append(",{");
+        for (int i = 0; i < nwords; ++i) {
+            long rr = RR.mod(B).longValue();
+            result.append(rr);
+
+            if (i != nwords - 1) {
+                result.append(",");
+            }
+
+            RR = RR.divide(B);
+        }
+        result.append("}");
+
+        result.append("}");
+        return result.toString();
+    }
+
+    /**
+     * @param key to output
+     * @return a String representing this public key.  If the key is a
+     *    version 1 key, the string will be a C initializer; this is
+     *    not true for newer key versions.
+     */
+    static String printEC(ECPublicKey key) throws Exception {
+        int version = checkEC(key);
+
+        StringBuilder result = new StringBuilder();
+
+        result.append("v");
+        result.append(Integer.toString(version));
+        result.append(" ");
+
+        BigInteger X = key.getW().getAffineX();
+        BigInteger Y = key.getW().getAffineY();
+        int nbytes = key.getParams().getCurve().getField().getFieldSize() / 8;    // # of 32 bit integers in X coordinate
+
+        result.append("{");
+        result.append(nbytes);
+
+        BigInteger B = BigInteger.valueOf(0x100L);  // 2^8
+
+        // Write out Y coordinate as array of characters.
+        result.append(",{");
+        for (int i = 0; i < nbytes; ++i) {
+            long n = X.mod(B).longValue();
+            result.append(n);
+
+            if (i != nbytes - 1) {
+                result.append(",");
+            }
+
+            X = X.divide(B);
+        }
+        result.append("}");
+
+        // Write out Y coordinate as array of characters.
+        result.append(",{");
+        for (int i = 0; i < nbytes; ++i) {
+            long n = Y.mod(B).longValue();
+            result.append(n);
+
+            if (i != nbytes - 1) {
+                result.append(",");
+            }
+
+            Y = Y.divide(B);
+        }
+        result.append("}");
+
+        result.append("}");
+        return result.toString();
+    }
+
+    static String print(PublicKey key, boolean useSHA256) throws Exception {
+        if (key instanceof RSAPublicKey) {
+            return printRSA((RSAPublicKey) key, useSHA256);
+        } else if (key instanceof ECPublicKey) {
+            return printEC((ECPublicKey) key);
+        } else {
+            throw new Exception("Unsupported key class: " + key.getClass().getName());
+        }
+    }
+
+    public static void main(String[] args) {
+        if (args.length < 1) {
+            System.err.println("Usage: DumpPublicKey certfile ... > source.c");
+            System.exit(1);
+        }
+        Security.addProvider(new BouncyCastleProvider());
+        try {
+            for (int i = 0; i < args.length; i++) {
+                FileInputStream input = new FileInputStream(args[i]);
+                CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
+
+                boolean useSHA256 = false;
+                String sigAlg = cert.getSigAlgName();
+                if ("SHA1withRSA".equals(sigAlg) || "MD5withRSA".equals(sigAlg)) {
+                    // SignApk has historically accepted "MD5withRSA"
+                    // certificates, but treated them as "SHA1withRSA"
+                    // anyway.  Continue to do so for backwards
+                    // compatibility.
+                  useSHA256 = false;
+                } else if ("SHA256withRSA".equals(sigAlg) || "SHA256withECDSA".equals(sigAlg)) {
+                  useSHA256 = true;
+                } else {
+                  System.err.println(args[i] + ": unsupported signature algorithm \"" +
+                                     sigAlg + "\"");
+                  System.exit(1);
+                }
+
+                PublicKey key = cert.getPublicKey();
+                check(key, useSHA256);
+                System.out.print(print(key, useSHA256));
+                System.out.println(i < args.length - 1 ? "," : "");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        System.exit(0);
+    }
+}
diff --git a/tools/dumpkey/DumpPublicKey.mf b/tools/dumpkey/DumpPublicKey.mf
new file mode 100644
index 0000000..7bb3bc8
--- /dev/null
+++ b/tools/dumpkey/DumpPublicKey.mf
@@ -0,0 +1 @@
+Main-Class: com.android.dumpkey.DumpPublicKey
diff --git a/tools/ota/Android.mk b/tools/ota/Android.mk
deleted file mode 100644
index 142c3b2..0000000
--- a/tools/ota/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2008 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 agreed 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_MODULE := add-property-tag
-LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_MODULE_TAGS := debug
-LOCAL_SRC_FILES := add-property-tag.c
-LOCAL_STATIC_LIBRARIES := libc
-include $(BUILD_EXECUTABLE)
-
-include $(CLEAR_VARS)
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_MODULE := check-lost+found
-LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_MODULE_TAGS := debug
-LOCAL_SRC_FILES := check-lost+found.c
-LOCAL_STATIC_LIBRARIES := libcutils libc
-include $(BUILD_EXECUTABLE)
diff --git a/tools/ota/add-property-tag.c b/tools/ota/add-property-tag.c
deleted file mode 100644
index aab30b2..0000000
--- a/tools/ota/add-property-tag.c
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2008 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 agreed 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 <ctype.h>
-#include <errno.h>
-#include <getopt.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-/*
- * Append a tag to a property value in a .prop file if it isn't already there.
- * Normally used to modify build properties to record incremental updates.
- */
-
-// Return nonzero if the tag should be added to this line.
-int should_tag(const char *line, const char *propname) {
-    const char *prop = strstr(line, propname);
-    if (prop == NULL) return 0;
-
-    // Make sure this is actually the property name (not an accidental hit)
-    const char *ptr;
-    for (ptr = line; ptr < prop && isspace(*ptr); ++ptr) ;
-    if (ptr != prop) return 0;  // Must be at the beginning of the line
-
-    for (ptr += strlen(propname); *ptr != '\0' && isspace(*ptr); ++ptr) ;
-    return (*ptr == '=');  // Must be followed by a '='
-}
-
-// Remove existing tags from the line, return the following number (if any)
-int remove_tag(char *line, const char *tag) {
-    char *pos = strstr(line, tag);
-    if (pos == NULL) return 0;
-
-    char *end;
-    int num = strtoul(pos + strlen(tag), &end, 10);
-    strcpy(pos, end);
-    return num;
-}
-
-// Write line to output with the tag added, adding a number (if >0)
-void write_tagged(FILE *out, const char *line, const char *tag, int number) {
-    const char *end = line + strlen(line);
-    while (end > line && isspace(end[-1])) --end;
-    if (number > 0) {
-        fprintf(out, "%.*s%s%d%s", (int)(end - line), line, tag, number, end);
-    } else {
-        fprintf(out, "%.*s%s%s", (int)(end - line), line, tag, end);
-    }
-}
-
-int main(int argc, char **argv) {
-    const char *filename = "/system/build.prop";
-    const char *propname = "ro.build.fingerprint";
-    const char *tag = NULL;
-    int do_remove = 0, do_number = 0;
-
-    int opt;
-    while ((opt = getopt(argc, argv, "f:p:rn")) != -1) {
-        switch (opt) {
-        case 'f': filename = optarg; break;
-        case 'p': propname = optarg; break;
-        case 'r': do_remove = 1; break;
-        case 'n': do_number = 1; break;
-        case '?': return 2;
-        }
-    }
-
-    if (argc != optind + 1) {
-        fprintf(stderr,
-            "usage: add-property-tag [flags] tag-to-add\n"
-            "flags: -f /dir/file.prop (default /system/build.prop)\n"
-            "       -p prop.name (default ro.build.fingerprint)\n"
-            "       -r (if set, remove the tag rather than adding it)\n"
-            "       -n (if set, add and increment a number after the tag)\n");
-        return 2;
-    }
-
-    tag = argv[optind];
-    FILE *input = fopen(filename, "r");
-    if (input == NULL) {
-        fprintf(stderr, "can't read %s: %s\n", filename, strerror(errno));
-        return 1;
-    }
-
-    char tmpname[PATH_MAX];
-    snprintf(tmpname, sizeof(tmpname), "%s.tmp", filename);
-    FILE *output = fopen(tmpname, "w");
-    if (output == NULL) {
-        fprintf(stderr, "can't write %s: %s\n", tmpname, strerror(errno));
-        return 1;
-    }
-
-    int found = 0;
-    char line[4096];
-    while (fgets(line, sizeof(line), input)) {
-        if (!should_tag(line, propname)) {
-            fputs(line, output);  // Pass through unmodified
-        } else {
-            found = 1;
-            int number = remove_tag(line, tag);
-            if (do_remove) {
-                fputs(line, output);  // Remove the tag but don't re-add it
-            } else {
-                write_tagged(output, line, tag, number + do_number);
-            }
-        }
-    }
-
-    fclose(input);
-    fclose(output);
-
-    if (!found) {
-        fprintf(stderr, "property %s not found in %s\n", propname, filename);
-        remove(tmpname);
-        return 1;
-    }
-
-    if (rename(tmpname, filename)) {
-        fprintf(stderr, "can't rename %s to %s: %s\n",
-            tmpname, filename, strerror(errno));
-        remove(tmpname);
-        return 1;
-    }
-
-    return 0;
-}
diff --git a/tools/ota/check-lost+found.c b/tools/ota/check-lost+found.c
deleted file mode 100644
index 8ce12d3..0000000
--- a/tools/ota/check-lost+found.c
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2008 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 agreed 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 <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/klog.h>
-#include <sys/reboot.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "private/android_filesystem_config.h"
-
-// Sentinel file used to track whether we've forced a reboot
-static const char *kMarkerFile = "/data/misc/check-lost+found-rebooted-2";
-
-// Output file in tombstones directory (first 8K will be uploaded)
-static const char *kOutputDir = "/data/tombstones";
-static const char *kOutputFile = "/data/tombstones/check-lost+found-log";
-
-// Partitions to check
-static const char *kPartitions[] = { "/system", "/data", "/cache", NULL };
-
-/*
- * 1. If /data/misc/forced-reboot is missing, touch it & force "unclean" boot.
- * 2. Write a log entry with the number of files in lost+found directories.
- */
-
-int main(int argc __attribute__((unused)), char **argv __attribute__((unused))) {
-    mkdir(kOutputDir, 0755);
-    chown(kOutputDir, AID_SYSTEM, AID_SYSTEM);
-    FILE *out = fopen(kOutputFile, "a");
-    if (out == NULL) {
-        fprintf(stderr, "Can't write %s: %s\n", kOutputFile, strerror(errno));
-        return 1;
-    }
-
-    // Note: only the first 8K of log will be uploaded, so be terse.
-    time_t start = time(NULL);
-    fprintf(out, "*** check-lost+found ***\nStarted: %s", ctime(&start));
-
-    struct stat st;
-    if (stat(kMarkerFile, &st)) {
-        // No reboot marker -- need to force an unclean reboot.
-        // But first, try to create the marker file.  If that fails,
-        // skip the reboot, so we don't get caught in an infinite loop.
-
-        int fd = open(kMarkerFile, O_WRONLY|O_CREAT, 0444);
-        if (fd >= 0 && close(fd) == 0) {
-            fprintf(out, "Wrote %s, rebooting\n", kMarkerFile);
-            fflush(out);
-            sync();  // Make sure the marker file is committed to disk
-
-            // If possible, dirty each of these partitions before rebooting,
-            // to make sure the filesystem has to do a scan on mount.
-            int i;
-            for (i = 0; kPartitions[i] != NULL; ++i) {
-                char fn[PATH_MAX];
-                snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "dirty");
-                fd = open(fn, O_WRONLY|O_CREAT, 0444);
-                if (fd >= 0) {  // Don't sweat it if we can't write the file.
-                    TEMP_FAILURE_RETRY(write(fd, fn, sizeof(fn)));  // write, you know, some data
-                    close(fd);
-                    unlink(fn);
-                }
-            }
-
-            reboot(RB_AUTOBOOT);  // reboot immediately, with dirty filesystems
-            fprintf(out, "Reboot failed?!\n");
-            exit(1);
-        } else {
-            fprintf(out, "Can't write %s: %s\n", kMarkerFile, strerror(errno));
-        }
-    } else {
-        fprintf(out, "Found %s\n", kMarkerFile);
-    }
-
-    int i;
-    for (i = 0; kPartitions[i] != NULL; ++i) {
-        char fn[PATH_MAX];
-        snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "lost+found");
-        DIR *dir = opendir(fn);
-        if (dir == NULL) {
-            fprintf(out, "Can't open %s: %s\n", fn, strerror(errno));
-        } else {
-            int count = 0;
-            struct dirent *ent;
-            while ((ent = readdir(dir))) {
-                if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, ".."))
-                    ++count;
-            }
-            closedir(dir);
-            if (count > 0) {
-                fprintf(out, "OMGZ FOUND %d FILES IN %s\n", count, fn);
-            } else {
-                fprintf(out, "%s is clean\n", fn);
-            }
-        }
-    }
-
-    char dmesg[131073];
-    int len = klogctl(KLOG_READ_ALL, dmesg, sizeof(dmesg) - 1);
-    if (len < 0) {
-        fprintf(out, "Can't read kernel log: %s\n", strerror(errno));
-    } else {  // To conserve space, only write lines with certain keywords
-        fprintf(out, "--- Kernel log ---\n");
-        dmesg[len] = '\0';
-        char *saveptr, *line;
-        int in_yaffs = 0;
-        for (line = strtok_r(dmesg, "\n", &saveptr); line != NULL;
-             line = strtok_r(NULL, "\n", &saveptr)) {
-            if (strstr(line, "yaffs: dev is")) in_yaffs = 1;
-
-            if (in_yaffs ||
-                    strstr(line, "yaffs") ||
-                    strstr(line, "mtd") ||
-                    strstr(line, "msm_nand")) {
-                fprintf(out, "%s\n", line);
-            }
-
-            if (strstr(line, "yaffs_read_super: isCheckpointed")) in_yaffs = 0;
-        }
-    }
-
-    return 0;
-}
diff --git a/tools/ota/convert-to-bmp.py b/tools/ota/convert-to-bmp.py
deleted file mode 100644
index 446c09d..0000000
--- a/tools/ota/convert-to-bmp.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/python2.4
-
-"""A simple script to convert asset images to BMP files, that supports
-RGBA image."""
-
-import struct
-import Image
-import sys
-
-infile = sys.argv[1]
-outfile = sys.argv[2]
-
-if not outfile.endswith(".bmp"):
-  print >> sys.stderr, "Warning: I'm expecting to write BMP files."
-
-im = Image.open(infile)
-if im.mode == 'RGB':
-  im.save(outfile)
-elif im.mode == 'RGBA':
-  # Python Imaging Library doesn't write RGBA BMP files, so we roll
-  # our own.
-
-  BMP_HEADER_FMT = ("<"      # little-endian
-                    "H"      # signature
-                    "L"      # file size
-                    "HH"     # reserved (set to 0)
-                    "L"      # offset to start of bitmap data)
-                    )
-
-  BITMAPINFO_HEADER_FMT= ("<"      # little-endian
-                          "L"      # size of this struct
-                          "L"      # width
-                          "L"      # height
-                          "H"      # planes (set to 1)
-                          "H"      # bit count
-                          "L"      # compression (set to 0 for minui)
-                          "L"      # size of image data (0 if uncompressed)
-                          "L"      # x pixels per meter (1)
-                          "L"      # y pixels per meter (1)
-                          "L"      # colors used (0)
-                          "L"      # important colors (0)
-                          )
-
-  fileheadersize = struct.calcsize(BMP_HEADER_FMT)
-  infoheadersize = struct.calcsize(BITMAPINFO_HEADER_FMT)
-
-  header = struct.pack(BMP_HEADER_FMT,
-                       0x4d42,   # "BM" in little-endian
-                       (fileheadersize + infoheadersize +
-                        im.size[0] * im.size[1] * 4),
-                       0, 0,
-                       fileheadersize + infoheadersize)
-
-  info = struct.pack(BITMAPINFO_HEADER_FMT,
-                     infoheadersize,
-                     im.size[0],
-                     im.size[1],
-                     1,
-                     32,
-                     0,
-                     0,
-                     1,
-                     1,
-                     0,
-                     0)
-
-  f = open(outfile, "wb")
-  f.write(header)
-  f.write(info)
-  data = im.tostring()
-  for j in range(im.size[1]-1, -1, -1):   # rows bottom-to-top
-    for i in range(j*im.size[0]*4, (j+1)*im.size[0]*4, 4):
-      f.write(data[i+2])    # B
-      f.write(data[i+1])    # G
-      f.write(data[i+0])    # R
-      f.write(data[i+3])    # A
-  f.close()
-else:
-  print >> sys.stderr, "Don't know how to handle image mode '%s'." % (im.mode,)
diff --git a/tools/recovery_l10n/README.md b/tools/recovery_l10n/README.md
new file mode 100644
index 0000000..e9e85d6
--- /dev/null
+++ b/tools/recovery_l10n/README.md
@@ -0,0 +1,36 @@
+# Steps to regenerate background text images under res-*dpi/images/
+
+1.  Build the recovery_l10n app:
+
+    cd bootable/recovery && mma -j32
+
+2.  Install the app on the device (or emulator) with the intended dpi.
+
+    *   For example, we can use Nexus 5 to generate the text images under
+        res-xxhdpi.
+    *   We can set up the maximum width of the final png image in res/layout/main.xml
+        Currently, the image width is 1200px for xxxhdpi, 900px for xxhdpi and
+        480px for xhdpi/hdpi/mdpi.
+    *   When using the emulator, make sure the NDK version matches the current
+        repository. Otherwise, the app may not work properly.
+
+    adb install $PATH_TO_APP
+
+3.  Run the app, select the string to translate and press the 'go' button.
+
+4.  After the app goes through the strings for all locales, pull the output png
+    file from the device.
+
+    adb root && adb pull /data/data/com.android.recovery_l10n/files/text-out.png
+
+5.  Compress the output file put it under the corresponding directory.
+
+    *   "pngcrush -c 0 ..." converts "text-out.png" into a 1-channel image,
+        which is accepted by Recovery. This also compresses the image file by
+        ~60%.
+    *   zopflipng could further compress the png files by ~10%, more details
+        in https://github.com/google/zopfli/blob/master/README.zopflipng
+    *   If you're using other png compression tools, make sure the final text
+        image works by running graphic tests under the recovery mode.
+
+    pngcrush -c 0 text-out.png $OUTPUT_PNG
diff --git a/tools/recovery_l10n/res/values-az-rAZ/strings.xml b/tools/recovery_l10n/res/values-az/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-az-rAZ/strings.xml
rename to tools/recovery_l10n/res/values-az/strings.xml
diff --git a/tools/recovery_l10n/res/values-be-rBY/strings.xml b/tools/recovery_l10n/res/values-be/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-be-rBY/strings.xml
rename to tools/recovery_l10n/res/values-be/strings.xml
diff --git a/tools/recovery_l10n/res/values-bn-rBD/strings.xml b/tools/recovery_l10n/res/values-bn/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-bn-rBD/strings.xml
rename to tools/recovery_l10n/res/values-bn/strings.xml
diff --git a/tools/recovery_l10n/res/values-bs-rBA/strings.xml b/tools/recovery_l10n/res/values-bs/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-bs-rBA/strings.xml
rename to tools/recovery_l10n/res/values-bs/strings.xml
diff --git a/tools/recovery_l10n/res/values-et-rEE/strings.xml b/tools/recovery_l10n/res/values-et/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-et-rEE/strings.xml
rename to tools/recovery_l10n/res/values-et/strings.xml
diff --git a/tools/recovery_l10n/res/values-eu-rES/strings.xml b/tools/recovery_l10n/res/values-eu/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-eu-rES/strings.xml
rename to tools/recovery_l10n/res/values-eu/strings.xml
diff --git a/tools/recovery_l10n/res/values-gl-rES/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-gl-rES/strings.xml
rename to tools/recovery_l10n/res/values-gl/strings.xml
diff --git a/tools/recovery_l10n/res/values-gu-rIN/strings.xml b/tools/recovery_l10n/res/values-gu/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-gu-rIN/strings.xml
rename to tools/recovery_l10n/res/values-gu/strings.xml
diff --git a/tools/recovery_l10n/res/values-hy-rAM/strings.xml b/tools/recovery_l10n/res/values-hy/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-hy-rAM/strings.xml
rename to tools/recovery_l10n/res/values-hy/strings.xml
diff --git a/tools/recovery_l10n/res/values-is-rIS/strings.xml b/tools/recovery_l10n/res/values-is/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-is-rIS/strings.xml
rename to tools/recovery_l10n/res/values-is/strings.xml
diff --git a/tools/recovery_l10n/res/values-ka-rGE/strings.xml b/tools/recovery_l10n/res/values-ka/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-ka-rGE/strings.xml
rename to tools/recovery_l10n/res/values-ka/strings.xml
diff --git a/tools/recovery_l10n/res/values-kk-rKZ/strings.xml b/tools/recovery_l10n/res/values-kk/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-kk-rKZ/strings.xml
rename to tools/recovery_l10n/res/values-kk/strings.xml
diff --git a/tools/recovery_l10n/res/values-km-rKH/strings.xml b/tools/recovery_l10n/res/values-km/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-km-rKH/strings.xml
rename to tools/recovery_l10n/res/values-km/strings.xml
diff --git a/tools/recovery_l10n/res/values-kn-rIN/strings.xml b/tools/recovery_l10n/res/values-kn/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-kn-rIN/strings.xml
rename to tools/recovery_l10n/res/values-kn/strings.xml
diff --git a/tools/recovery_l10n/res/values-ky-rKG/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-ky-rKG/strings.xml
rename to tools/recovery_l10n/res/values-ky/strings.xml
diff --git a/tools/recovery_l10n/res/values-lo-rLA/strings.xml b/tools/recovery_l10n/res/values-lo/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-lo-rLA/strings.xml
rename to tools/recovery_l10n/res/values-lo/strings.xml
diff --git a/tools/recovery_l10n/res/values-mk-rMK/strings.xml b/tools/recovery_l10n/res/values-mk/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-mk-rMK/strings.xml
rename to tools/recovery_l10n/res/values-mk/strings.xml
diff --git a/tools/recovery_l10n/res/values-ml-rIN/strings.xml b/tools/recovery_l10n/res/values-ml/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-ml-rIN/strings.xml
rename to tools/recovery_l10n/res/values-ml/strings.xml
diff --git a/tools/recovery_l10n/res/values-mn-rMN/strings.xml b/tools/recovery_l10n/res/values-mn/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-mn-rMN/strings.xml
rename to tools/recovery_l10n/res/values-mn/strings.xml
diff --git a/tools/recovery_l10n/res/values-mr-rIN/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-mr-rIN/strings.xml
rename to tools/recovery_l10n/res/values-mr/strings.xml
diff --git a/tools/recovery_l10n/res/values-ms-rMY/strings.xml b/tools/recovery_l10n/res/values-ms/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-ms-rMY/strings.xml
rename to tools/recovery_l10n/res/values-ms/strings.xml
diff --git a/tools/recovery_l10n/res/values-my-rMM/strings.xml b/tools/recovery_l10n/res/values-my/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-my-rMM/strings.xml
rename to tools/recovery_l10n/res/values-my/strings.xml
diff --git a/tools/recovery_l10n/res/values-ne-rNP/strings.xml b/tools/recovery_l10n/res/values-ne/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-ne-rNP/strings.xml
rename to tools/recovery_l10n/res/values-ne/strings.xml
diff --git a/tools/recovery_l10n/res/values-pa-rIN/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-pa-rIN/strings.xml
rename to tools/recovery_l10n/res/values-pa/strings.xml
diff --git a/tools/recovery_l10n/res/values-si-rLK/strings.xml b/tools/recovery_l10n/res/values-si/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-si-rLK/strings.xml
rename to tools/recovery_l10n/res/values-si/strings.xml
diff --git a/tools/recovery_l10n/res/values-sq-rAL/strings.xml b/tools/recovery_l10n/res/values-sq/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-sq-rAL/strings.xml
rename to tools/recovery_l10n/res/values-sq/strings.xml
diff --git a/tools/recovery_l10n/res/values-ta-rIN/strings.xml b/tools/recovery_l10n/res/values-ta/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-ta-rIN/strings.xml
rename to tools/recovery_l10n/res/values-ta/strings.xml
diff --git a/tools/recovery_l10n/res/values-te-rIN/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-te-rIN/strings.xml
rename to tools/recovery_l10n/res/values-te/strings.xml
diff --git a/tools/recovery_l10n/res/values-ur-rPK/strings.xml b/tools/recovery_l10n/res/values-ur/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-ur-rPK/strings.xml
rename to tools/recovery_l10n/res/values-ur/strings.xml
diff --git a/tools/recovery_l10n/res/values-uz-rUZ/strings.xml b/tools/recovery_l10n/res/values-uz/strings.xml
similarity index 100%
rename from tools/recovery_l10n/res/values-uz-rUZ/strings.xml
rename to tools/recovery_l10n/res/values-uz/strings.xml
diff --git a/tools/recovery_l10n/res/values/strings.xml b/tools/recovery_l10n/res/values/strings.xml
index 971e038..d56d073 100644
--- a/tools/recovery_l10n/res/values/strings.xml
+++ b/tools/recovery_l10n/res/values/strings.xml
@@ -9,6 +9,7 @@
     <item>erasing</item>
     <item>no_command</item>
     <item>error</item>
+    <item>installing_security</item>
   </string-array>
 
   <!-- Displayed on the screen beneath the animated android while the
diff --git a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
index 817a3ad..30d45f6 100644
--- a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
+++ b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
@@ -38,6 +38,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Locale;
 
@@ -139,6 +140,7 @@
                     case 1: mStringId = R.string.recovery_erasing; break;
                     case 2: mStringId = R.string.recovery_no_command; break;
                     case 3: mStringId = R.string.recovery_error; break;
+                    case 4: mStringId = R.string.recovery_installing_security; break;
                 }
             }
             @Override public void onNothingSelected(AdapterView parent) { }
@@ -147,11 +149,28 @@
         mText = (TextView) findViewById(R.id.text);
 
         String[] localeNames = getAssets().getLocales();
-        Arrays.sort(localeNames);
+        Arrays.sort(localeNames, new Comparator<String>() {
+        // Override the string comparator so that en is sorted behind en_US.
+        // As a result, en_US will be matched first in recovery.
+            @Override
+            public int compare(String s1, String s2) {
+                if (s1.equals(s2)) {
+                    return 0;
+                } else if (s1.startsWith(s2)) {
+                    return -1;
+                } else if (s2.startsWith(s1)) {
+                    return 1;
+                }
+                return s1.compareTo(s2);
+            }
+        });
+
         ArrayList<Locale> locales = new ArrayList<Locale>();
         for (String localeName : localeNames) {
             Log.i(TAG, "locale = " + localeName);
-            locales.add(Locale.forLanguageTag(localeName));
+            if (!localeName.isEmpty()) {
+                locales.add(Locale.forLanguageTag(localeName));
+            }
         }
 
         final Runnable seq = buildSequence(locales.toArray(new Locale[0]));
diff --git a/ui.cpp b/ui.cpp
index 2efb759..cad7449 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "ui.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/input.h>
@@ -28,32 +30,44 @@
 #include <time.h>
 #include <unistd.h>
 
-#include <cutils/properties.h>
+#include <functional>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <cutils/android_reboot.h>
+#include <minui/minui.h>
 
 #include "common.h"
 #include "roots.h"
 #include "device.h"
-#include "minui/minui.h"
-#include "screen_ui.h"
-#include "ui.h"
 
-#define UI_WAIT_KEY_TIMEOUT_SEC    120
+static constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120;
+static constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness";
+static constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness";
 
 RecoveryUI::RecoveryUI()
-        : key_queue_len(0),
-          key_last_down(-1),
-          key_long_press(false),
-          key_down_count(0),
-          enable_reboot(true),
-          consecutive_power_keys(0),
-          last_key(-1),
-          has_power_key(false),
-          has_up_key(false),
-          has_down_key(false) {
-    pthread_mutex_init(&key_queue_mutex, nullptr);
-    pthread_cond_init(&key_queue_cond, nullptr);
-    memset(key_pressed, 0, sizeof(key_pressed));
+    : locale_(""),
+      rtl_locale_(false),
+      brightness_normal_(50),
+      brightness_dimmed_(25),
+      key_queue_len(0),
+      key_last_down(-1),
+      key_long_press(false),
+      key_down_count(0),
+      enable_reboot(true),
+      consecutive_power_keys(0),
+      last_key(-1),
+      has_power_key(false),
+      has_up_key(false),
+      has_down_key(false),
+      screensaver_state_(ScreensaverState::DISABLED) {
+  pthread_mutex_init(&key_queue_mutex, nullptr);
+  pthread_cond_init(&key_queue_cond, nullptr);
+  memset(key_pressed, 0, sizeof(key_pressed));
 }
 
 void RecoveryUI::OnKeyDetected(int key_code) {
@@ -66,10 +80,6 @@
     }
 }
 
-int RecoveryUI::InputCallback(int fd, uint32_t epevents, void* data) {
-    return reinterpret_cast<RecoveryUI*>(data)->OnInputEvent(fd, epevents);
-}
-
 // Reads input events, handles special hot keys, and adds to the key queue.
 static void* InputThreadLoop(void*) {
     while (true) {
@@ -80,12 +90,54 @@
     return nullptr;
 }
 
-void RecoveryUI::Init() {
-    ev_init(InputCallback, this);
+bool RecoveryUI::InitScreensaver() {
+  // Disabled.
+  if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) {
+    return false;
+  }
 
-    ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+  // Set the initial brightness level based on the max brightness. Note that reading the initial
+  // value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so
+  // we don't have a good way to query the default value.
+  std::string content;
+  if (!android::base::ReadFileToString(MAX_BRIGHTNESS_FILE, &content)) {
+    PLOG(WARNING) << "Failed to read max brightness";
+    return false;
+  }
 
-    pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
+  unsigned int max_value;
+  if (!android::base::ParseUint(android::base::Trim(content), &max_value)) {
+    LOG(WARNING) << "Failed to parse max brightness: " << content;
+    return false;
+  }
+
+  brightness_normal_value_ = max_value * brightness_normal_ / 100.0;
+  brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0;
+  if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
+                                        BRIGHTNESS_FILE)) {
+    PLOG(WARNING) << "Failed to set brightness";
+    return false;
+  }
+
+  LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)";
+  screensaver_state_ = ScreensaverState::NORMAL;
+  return true;
+}
+
+bool RecoveryUI::Init(const std::string& locale) {
+  // Set up the locale info.
+  SetLocale(locale);
+
+  ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));
+
+  ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+
+  if (!InitScreensaver()) {
+    LOG(INFO) << "Screensaver disabled";
+  }
+
+  pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
+  return true;
 }
 
 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
@@ -175,7 +227,7 @@
 
           case RecoveryUI::REBOOT:
             if (reboot_enabled) {
-                property_set(ANDROID_RB_PROPERTY, "reboot,");
+                reboot("reboot,");
                 while (true) { pause(); }
             }
             break;
@@ -188,7 +240,7 @@
 }
 
 void* RecoveryUI::time_key_helper(void* cookie) {
-    key_timer_t* info = (key_timer_t*) cookie;
+    key_timer_t* info = static_cast<key_timer_t*>(cookie);
     info->ui->time_key(info->key_code, info->count);
     delete info;
     return nullptr;
@@ -216,31 +268,65 @@
 }
 
 int RecoveryUI::WaitKey() {
-    pthread_mutex_lock(&key_queue_mutex);
+  pthread_mutex_lock(&key_queue_mutex);
 
-    // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
-    // plugged in.
-    do {
-        struct timeval now;
-        struct timespec timeout;
-        gettimeofday(&now, nullptr);
-        timeout.tv_sec = now.tv_sec;
-        timeout.tv_nsec = now.tv_usec * 1000;
-        timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
+  // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
+  // plugged in.
+  do {
+    struct timeval now;
+    struct timespec timeout;
+    gettimeofday(&now, nullptr);
+    timeout.tv_sec = now.tv_sec;
+    timeout.tv_nsec = now.tv_usec * 1000;
+    timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
 
-        int rc = 0;
-        while (key_queue_len == 0 && rc != ETIMEDOUT) {
-            rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout);
-        }
-    } while (IsUsbConnected() && key_queue_len == 0);
-
-    int key = -1;
-    if (key_queue_len > 0) {
-        key = key_queue[0];
-        memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+    int rc = 0;
+    while (key_queue_len == 0 && rc != ETIMEDOUT) {
+      rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout);
     }
-    pthread_mutex_unlock(&key_queue_mutex);
-    return key;
+
+    if (screensaver_state_ != ScreensaverState::DISABLED) {
+      if (rc == ETIMEDOUT) {
+        // Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
+        if (screensaver_state_ == ScreensaverState::NORMAL) {
+          if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_),
+                                               BRIGHTNESS_FILE)) {
+            LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_
+                      << "%)";
+            screensaver_state_ = ScreensaverState::DIMMED;
+          }
+        } else if (screensaver_state_ == ScreensaverState::DIMMED) {
+          if (android::base::WriteStringToFile("0", BRIGHTNESS_FILE)) {
+            LOG(INFO) << "Brightness: 0 (off)";
+            screensaver_state_ = ScreensaverState::OFF;
+          }
+        }
+      } else if (screensaver_state_ != ScreensaverState::NORMAL) {
+        // Drop the first key if it's changing from OFF to NORMAL.
+        if (screensaver_state_ == ScreensaverState::OFF) {
+          if (key_queue_len > 0) {
+            memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+          }
+        }
+
+        // Reset the brightness to normal.
+        if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
+                                             BRIGHTNESS_FILE)) {
+          screensaver_state_ = ScreensaverState::NORMAL;
+          LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
+                    << "%)";
+        }
+      }
+    }
+  } while (IsUsbConnected() && key_queue_len == 0);
+
+  int key = -1;
+  if (key_queue_len > 0) {
+    key = key_queue[0];
+    memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+  }
+  pthread_mutex_unlock(&key_queue_mutex);
+  return key;
 }
 
 bool RecoveryUI::IsUsbConnected() {
@@ -326,7 +412,7 @@
     }
 
     last_key = key;
-    return IsTextVisible() ? ENQUEUE : IGNORE;
+    return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE;
 }
 
 void RecoveryUI::KeyLongPress(int) {
@@ -337,3 +423,23 @@
     enable_reboot = enabled;
     pthread_mutex_unlock(&key_queue_mutex);
 }
+
+void RecoveryUI::SetLocale(const std::string& new_locale) {
+  this->locale_ = new_locale;
+  this->rtl_locale_ = false;
+
+  if (!new_locale.empty()) {
+    size_t underscore = new_locale.find('_');
+    // lang has the language prefix prior to '_', or full string if '_' doesn't exist.
+    std::string lang = new_locale.substr(0, underscore);
+
+    // A bit cheesy: keep an explicit list of supported RTL languages.
+    if (lang == "ar" ||  // Arabic
+        lang == "fa" ||  // Persian (Farsi)
+        lang == "he" ||  // Hebrew (new language code)
+        lang == "iw" ||  // Hebrew (old language code)
+        lang == "ur") {  // Urdu
+      rtl_locale_ = true;
+    }
+  }
+}
diff --git a/ui.h b/ui.h
index 82d95a3..823eb65 100644
--- a/ui.h
+++ b/ui.h
@@ -21,6 +21,8 @@
 #include <pthread.h>
 #include <time.h>
 
+#include <string>
+
 // Abstract class for controlling the user interface during recovery.
 class RecoveryUI {
   public:
@@ -28,14 +30,13 @@
 
     virtual ~RecoveryUI() { }
 
-    // Initialize the object; called before anything else.
-    virtual void Init();
+    // Initialize the object; called before anything else. UI texts will be
+    // initialized according to the given locale. Returns true on success.
+    virtual bool Init(const std::string& locale);
+
     // Show a stage indicator.  Call immediately after Init().
     virtual void SetStage(int current, int max) = 0;
 
-    // After calling Init(), you can tell the UI what locale it is operating in.
-    virtual void SetLocale(const char* locale) = 0;
-
     // Set the overall recovery state ("background image").
     enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
     virtual void SetBackground(Icon icon) = 0;
@@ -122,10 +123,21 @@
     // statements will be displayed.
     virtual void EndMenu() = 0;
 
-protected:
+  protected:
     void EnqueueKey(int key_code);
 
-private:
+    // The locale that's used to show the rendered texts.
+    std::string locale_;
+    bool rtl_locale_;
+
+    // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25%
+    // of the max_brightness). Because the absolute values may vary across devices. These two
+    // values can be configured via subclassing. Setting brightness_normal_ to 0 to disable
+    // screensaver.
+    unsigned int brightness_normal_;
+    unsigned int brightness_dimmed_;
+
+  private:
     // Key event input queue
     pthread_mutex_t key_queue_mutex;
     pthread_cond_t key_queue_cond;
@@ -153,8 +165,6 @@
     pthread_t input_thread_;
 
     void OnKeyDetected(int key_code);
-
-    static int InputCallback(int fd, uint32_t epevents, void* data);
     int OnInputEvent(int fd, uint32_t epevents);
     void ProcessKey(int key_code, int updown);
 
@@ -162,6 +172,16 @@
 
     static void* time_key_helper(void* cookie);
     void time_key(int key_code, int count);
+
+    void SetLocale(const std::string&);
+
+    enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF };
+    ScreensaverState screensaver_state_;
+    // The following two contain the absolute values computed from brightness_normal_ and
+    // brightness_dimmed_ respectively.
+    unsigned int brightness_normal_value_;
+    unsigned int brightness_dimmed_value_;
+    bool InitScreensaver();
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk
index bb276ed..59084b0 100644
--- a/uncrypt/Android.mk
+++ b/uncrypt/Android.mk
@@ -17,16 +17,16 @@
 include $(CLEAR_VARS)
 
 LOCAL_CLANG := true
-
 LOCAL_SRC_FILES := uncrypt.cpp
-
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
-
 LOCAL_MODULE := uncrypt
-
-LOCAL_STATIC_LIBRARIES := libbootloader_message libbase \
-                          liblog libfs_mgr libcutils \
-
+LOCAL_STATIC_LIBRARIES := \
+    libbootloader_message \
+    libbase \
+    liblog \
+    libfs_mgr \
+    libcutils
+LOCAL_CFLAGS := -Werror
 LOCAL_INIT_RC := uncrypt.rc
 
 include $(BUILD_EXECUTABLE)
diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index 280568d..ad3bdce 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -107,21 +107,19 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <bootloader_message/bootloader_message.h>
 #include <cutils/android_reboot.h>
-#include <cutils/properties.h>
 #include <cutils/sockets.h>
 #include <fs_mgr.h>
 
-#define LOG_TAG "uncrypt"
-#include <log/log.h>
-
 #include "error_code.h"
-#include "unique_fd.h"
 
-#define WINDOW_SIZE 5
+static constexpr int WINDOW_SIZE = 5;
+static constexpr int FIBMAP_RETRY_LIMIT = 3;
 
 // uncrypt provides three services: SETUP_BCB, CLEAR_BCB and UNCRYPT.
 //
@@ -142,11 +140,11 @@
 
 static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) {
     if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) {
-        ALOGE("error seeking to offset %" PRId64 ": %s", offset, strerror(errno));
+        PLOG(ERROR) << "error seeking to offset " << offset;
         return -1;
     }
     if (!android::base::WriteFully(wfd, buffer, size)) {
-        ALOGE("error writing offset %" PRId64 ": %s", offset, strerror(errno));
+        PLOG(ERROR) << "error writing offset " << offset;
         return -1;
     }
     return 0;
@@ -165,18 +163,9 @@
 }
 
 static struct fstab* read_fstab() {
-    fstab = NULL;
-
-    // The fstab path is always "/fstab.${ro.hardware}".
-    char fstab_path[PATH_MAX+1] = "/fstab.";
-    if (!property_get("ro.hardware", fstab_path+strlen(fstab_path), "")) {
-        ALOGE("failed to get ro.hardware");
-        return NULL;
-    }
-
-    fstab = fs_mgr_read_fstab(fstab_path);
+    fstab = fs_mgr_read_fstab_default();
     if (!fstab) {
-        ALOGE("failed to read %s", fstab_path);
+        LOG(ERROR) << "failed to read default fstab";
         return NULL;
     }
 
@@ -199,9 +188,7 @@
             *encryptable = false;
             if (fs_mgr_is_encryptable(v) || fs_mgr_is_file_encrypted(v)) {
                 *encryptable = true;
-                char buffer[PROPERTY_VALUE_MAX+1];
-                if (property_get("ro.crypto.state", buffer, "") &&
-                    strcmp(buffer, "encrypted") == 0) {
+                if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") {
                     *encrypted = true;
                 }
             }
@@ -213,6 +200,11 @@
 }
 
 static bool write_status_to_socket(int status, int socket) {
+    // If socket equals -1, uncrypt is in debug mode without socket communication.
+    // Skip writing and return success.
+    if (socket == -1) {
+        return true;
+    }
     int status_out = htonl(status);
     return android::base::WriteFully(socket, &status_out, sizeof(int));
 }
@@ -222,7 +214,7 @@
     CHECK(package_name != nullptr);
     std::string uncrypt_path;
     if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) {
-        ALOGE("failed to open \"%s\": %s", uncrypt_path_file.c_str(), strerror(errno));
+        PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\"";
         return false;
     }
 
@@ -231,43 +223,65 @@
     return true;
 }
 
+static int retry_fibmap(const int fd, const char* name, int* block, const int head_block) {
+    CHECK(block != nullptr);
+    for (size_t i = 0; i < FIBMAP_RETRY_LIMIT; i++) {
+        if (fsync(fd) == -1) {
+            PLOG(ERROR) << "failed to fsync \"" << name << "\"";
+            return kUncryptFileSyncError;
+        }
+        if (ioctl(fd, FIBMAP, block) != 0) {
+            PLOG(ERROR) << "failed to find block " << head_block;
+            return kUncryptIoctlError;
+        }
+        if (*block != 0) {
+            return kUncryptNoError;
+        }
+        sleep(1);
+    }
+    LOG(ERROR) << "fibmap of " << head_block << "always returns 0";
+    return kUncryptIoctlError;
+}
+
 static int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
                              bool encrypted, int socket) {
     std::string err;
     if (!android::base::RemoveFileIfExists(map_file, &err)) {
-        ALOGE("failed to remove the existing map file %s: %s", map_file, err.c_str());
+        LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err;
         return kUncryptFileRemoveError;
     }
     std::string tmp_map_file = std::string(map_file) + ".tmp";
-    unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
-    if (!mapfd) {
-        ALOGE("failed to open %s: %s\n", tmp_map_file.c_str(), strerror(errno));
+    android::base::unique_fd mapfd(open(tmp_map_file.c_str(),
+                                        O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
+    if (mapfd == -1) {
+        PLOG(ERROR) << "failed to open " << tmp_map_file;
         return kUncryptFileOpenError;
     }
 
     // Make sure we can write to the socket.
     if (!write_status_to_socket(0, socket)) {
-        ALOGE("failed to write to socket %d\n", socket);
+        LOG(ERROR) << "failed to write to socket " << socket;
         return kUncryptSocketWriteError;
     }
 
     struct stat sb;
     if (stat(path, &sb) != 0) {
-        ALOGE("failed to stat %s", path);
+        LOG(ERROR) << "failed to stat " << path;
         return kUncryptFileStatError;
     }
 
-    ALOGI(" block size: %ld bytes", static_cast<long>(sb.st_blksize));
+    LOG(INFO) << " block size: " << sb.st_blksize << " bytes";
 
     int blocks = ((sb.st_size-1) / sb.st_blksize) + 1;
-    ALOGI("  file size: %" PRId64 " bytes, %d blocks", sb.st_size, blocks);
+    LOG(INFO) << "  file size: " << sb.st_size << " bytes, " << blocks << " blocks";
 
     std::vector<int> ranges;
 
-    std::string s = android::base::StringPrintf("%s\n%" PRId64 " %ld\n",
-                       blk_dev, sb.st_size, static_cast<long>(sb.st_blksize));
-    if (!android::base::WriteStringToFd(s, mapfd.get())) {
-        ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno));
+    std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n",
+                       blk_dev, static_cast<int64_t>(sb.st_size),
+                       static_cast<int64_t>(sb.st_blksize));
+    if (!android::base::WriteStringToFd(s, mapfd)) {
+        PLOG(ERROR) << "failed to write " << tmp_map_file;
         return kUncryptWriteError;
     }
 
@@ -278,17 +292,17 @@
     int head_block = 0;
     int head = 0, tail = 0;
 
-    unique_fd fd(open(path, O_RDONLY));
-    if (!fd) {
-        ALOGE("failed to open %s for reading: %s", path, strerror(errno));
+    android::base::unique_fd fd(open(path, O_RDONLY));
+    if (fd == -1) {
+        PLOG(ERROR) << "failed to open " << path << " for reading";
         return kUncryptFileOpenError;
     }
 
-    unique_fd wfd(-1);
+    android::base::unique_fd wfd;
     if (encrypted) {
-        wfd = open(blk_dev, O_WRONLY);
-        if (!wfd) {
-            ALOGE("failed to open fd for writing: %s", strerror(errno));
+        wfd.reset(open(blk_dev, O_WRONLY));
+        if (wfd == -1) {
+            PLOG(ERROR) << "failed to open " << blk_dev << " for writing";
             return kUncryptBlockOpenError;
         }
     }
@@ -306,14 +320,23 @@
         if ((tail+1) % WINDOW_SIZE == head) {
             // write out head buffer
             int block = head_block;
-            if (ioctl(fd.get(), FIBMAP, &block) != 0) {
-                ALOGE("failed to find block %d", head_block);
+            if (ioctl(fd, FIBMAP, &block) != 0) {
+                PLOG(ERROR) << "failed to find block " << head_block;
                 return kUncryptIoctlError;
             }
+
+            if (block == 0) {
+                LOG(ERROR) << "failed to find block " << head_block << ", retrying";
+                int error = retry_fibmap(fd, path, &block, head_block);
+                if (error != kUncryptNoError) {
+                    return error;
+                }
+            }
+
             add_block_to_ranges(ranges, block);
             if (encrypted) {
-                if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd.get(),
-                        static_cast<off64_t>(sb.st_blksize) * block) != 0) {
+                if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd,
+                                    static_cast<off64_t>(sb.st_blksize) * block) != 0) {
                     return kUncryptWriteError;
                 }
             }
@@ -325,8 +348,8 @@
         if (encrypted) {
             size_t to_read = static_cast<size_t>(
                     std::min(static_cast<off64_t>(sb.st_blksize), sb.st_size - pos));
-            if (!android::base::ReadFully(fd.get(), buffers[tail].data(), to_read)) {
-                ALOGE("failed to read: %s", strerror(errno));
+            if (!android::base::ReadFully(fd, buffers[tail].data(), to_read)) {
+                PLOG(ERROR) << "failed to read " << path;
                 return kUncryptReadError;
             }
             pos += to_read;
@@ -342,14 +365,23 @@
     while (head != tail) {
         // write out head buffer
         int block = head_block;
-        if (ioctl(fd.get(), FIBMAP, &block) != 0) {
-            ALOGE("failed to find block %d", head_block);
+        if (ioctl(fd, FIBMAP, &block) != 0) {
+            PLOG(ERROR) << "failed to find block " << head_block;
             return kUncryptIoctlError;
         }
+
+        if (block == 0) {
+            LOG(ERROR) << "failed to find block " << head_block << ", retrying";
+            int error = retry_fibmap(fd, path, &block, head_block);
+            if (error != kUncryptNoError) {
+                return error;
+            }
+        }
+
         add_block_to_ranges(ranges, block);
         if (encrypted) {
-            if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd.get(),
-                    static_cast<off64_t>(sb.st_blksize) * block) != 0) {
+            if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd,
+                                static_cast<off64_t>(sb.st_blksize) * block) != 0) {
                 return kUncryptWriteError;
             }
         }
@@ -358,72 +390,69 @@
     }
 
     if (!android::base::WriteStringToFd(
-            android::base::StringPrintf("%zu\n", ranges.size() / 2), mapfd.get())) {
-        ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno));
+            android::base::StringPrintf("%zu\n", ranges.size() / 2), mapfd)) {
+        PLOG(ERROR) << "failed to write " << tmp_map_file;
         return kUncryptWriteError;
     }
     for (size_t i = 0; i < ranges.size(); i += 2) {
         if (!android::base::WriteStringToFd(
-                android::base::StringPrintf("%d %d\n", ranges[i], ranges[i+1]), mapfd.get())) {
-            ALOGE("failed to write %s: %s", tmp_map_file.c_str(), strerror(errno));
+                android::base::StringPrintf("%d %d\n", ranges[i], ranges[i+1]), mapfd)) {
+            PLOG(ERROR) << "failed to write " << tmp_map_file;
             return kUncryptWriteError;
         }
     }
 
-    if (fsync(mapfd.get()) == -1) {
-        ALOGE("failed to fsync \"%s\": %s", tmp_map_file.c_str(), strerror(errno));
+    if (fsync(mapfd) == -1) {
+        PLOG(ERROR) << "failed to fsync \"" << tmp_map_file << "\"";
         return kUncryptFileSyncError;
     }
-    if (close(mapfd.get()) == -1) {
-        ALOGE("failed to close %s: %s", tmp_map_file.c_str(), strerror(errno));
+    if (close(mapfd.release()) == -1) {
+        PLOG(ERROR) << "failed to close " << tmp_map_file;
         return kUncryptFileCloseError;
     }
-    mapfd = -1;
 
     if (encrypted) {
-        if (fsync(wfd.get()) == -1) {
-            ALOGE("failed to fsync \"%s\": %s", blk_dev, strerror(errno));
+        if (fsync(wfd) == -1) {
+            PLOG(ERROR) << "failed to fsync \"" << blk_dev << "\"";
             return kUncryptFileSyncError;
         }
-        if (close(wfd.get()) == -1) {
-            ALOGE("failed to close %s: %s", blk_dev, strerror(errno));
+        if (close(wfd.release()) == -1) {
+            PLOG(ERROR) << "failed to close " << blk_dev;
             return kUncryptFileCloseError;
         }
-        wfd = -1;
     }
 
     if (rename(tmp_map_file.c_str(), map_file) == -1) {
-        ALOGE("failed to rename %s to %s: %s", tmp_map_file.c_str(), map_file, strerror(errno));
+        PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file;
         return kUncryptFileRenameError;
     }
     // Sync dir to make rename() result written to disk.
     std::string file_name = map_file;
     std::string dir_name = dirname(&file_name[0]);
-    unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY));
-    if (!dfd) {
-        ALOGE("failed to open dir %s: %s", dir_name.c_str(), strerror(errno));
+    android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY));
+    if (dfd == -1) {
+        PLOG(ERROR) << "failed to open dir " << dir_name;
         return kUncryptFileOpenError;
     }
-    if (fsync(dfd.get()) == -1) {
-        ALOGE("failed to fsync %s: %s", dir_name.c_str(), strerror(errno));
+    if (fsync(dfd) == -1) {
+        PLOG(ERROR) << "failed to fsync " << dir_name;
         return kUncryptFileSyncError;
     }
-    if (close(dfd.get()) == -1) {
-        ALOGE("failed to close %s: %s", dir_name.c_str(), strerror(errno));
+    if (close(dfd.release()) == -1) {
+        PLOG(ERROR) << "failed to close " << dir_name;
         return kUncryptFileCloseError;
     }
-    dfd = -1;
     return 0;
 }
 
 static int uncrypt(const char* input_path, const char* map_file, const int socket) {
-    ALOGI("update package is \"%s\"", input_path);
+    LOG(INFO) << "update package is \"" << input_path << "\"";
 
     // Turn the name of the file we're supposed to convert into an
     // absolute path, so we can find what filesystem it's on.
     char path[PATH_MAX+1];
     if (realpath(input_path, path) == NULL) {
-        ALOGE("failed to convert \"%s\" to absolute path: %s", input_path, strerror(errno));
+        PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path";
         return 1;
     }
 
@@ -431,15 +460,15 @@
     bool encrypted;
     const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
     if (blk_dev == NULL) {
-        ALOGE("failed to find block device for %s", path);
+        LOG(ERROR) << "failed to find block device for " << path;
         return 1;
     }
 
     // If the filesystem it's on isn't encrypted, we only produce the
     // block map, we don't rewrite the file contents (it would be
     // pointless to do so).
-    ALOGI("encryptable: %s", encryptable ? "yes" : "no");
-    ALOGI("  encrypted: %s", encrypted ? "yes" : "no");
+    LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no");
+    LOG(INFO) << "  encrypted: " << (encrypted ? "yes" : "no");
 
     // Recovery supports installing packages from 3 paths: /cache,
     // /data, and /sdcard.  (On a particular device, other locations
@@ -449,7 +478,7 @@
     // can read the package without mounting the partition.  On /cache
     // and /sdcard we leave the file alone.
     if (strncmp(path, "/data/", 6) == 0) {
-        ALOGI("writing block map %s", map_file);
+        LOG(INFO) << "writing block map " << map_file;
         return produce_block_map(path, map_file, blk_dev, encrypted, socket);
     }
 
@@ -459,7 +488,7 @@
 static void log_uncrypt_error_code(UncryptErrorCode error_code) {
     if (!android::base::WriteStringToFile(android::base::StringPrintf(
             "uncrypt_error: %d\n", error_code), UNCRYPT_STATUS)) {
-        ALOGW("failed to write to %s: %s", UNCRYPT_STATUS.c_str(), strerror(errno));
+        PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
     }
 }
 
@@ -489,7 +518,7 @@
         // Log the time cost and error code if uncrypt fails.
         uncrypt_message += android::base::StringPrintf("uncrypt_error: %d\n", status);
         if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) {
-            ALOGW("failed to write to %s: %s", UNCRYPT_STATUS.c_str(), strerror(errno));
+            PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
         }
 
         write_status_to_socket(-1, socket);
@@ -497,7 +526,7 @@
     }
 
     if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) {
-        ALOGW("failed to write to %s: %s", UNCRYPT_STATUS.c_str(), strerror(errno));
+        PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
     }
 
     write_status_to_socket(100, socket);
@@ -508,7 +537,7 @@
 static bool clear_bcb(const int socket) {
     std::string err;
     if (!clear_bootloader_message(&err)) {
-        ALOGE("failed to clear bootloader message: %s", err.c_str());
+        LOG(ERROR) << "failed to clear bootloader message: " << err;
         write_status_to_socket(-1, socket);
         return false;
     }
@@ -520,7 +549,7 @@
     // c5. receive message length
     int length;
     if (!android::base::ReadFully(socket, &length, 4)) {
-        ALOGE("failed to read the length: %s", strerror(errno));
+        PLOG(ERROR) << "failed to read the length";
         return false;
     }
     length = ntohl(length);
@@ -529,17 +558,17 @@
     std::string content;
     content.resize(length);
     if (!android::base::ReadFully(socket, &content[0], length)) {
-        ALOGE("failed to read the length: %s", strerror(errno));
+        PLOG(ERROR) << "failed to read the message";
         return false;
     }
-    ALOGI("  received command: [%s] (%zu)", content.c_str(), content.size());
+    LOG(INFO) << "  received command: [" << content << "] (" << content.size() << ")";
     std::vector<std::string> options = android::base::Split(content, "\n");
     std::string wipe_package;
     for (auto& option : options) {
         if (android::base::StartsWith(option, "--wipe_package=")) {
             std::string path = option.substr(strlen("--wipe_package="));
             if (!android::base::ReadFileToString(path, &wipe_package)) {
-                ALOGE("failed to read %s: %s", path.c_str(), strerror(errno));
+                PLOG(ERROR) << "failed to read " << path;
                 return false;
             }
             option = android::base::StringPrintf("--wipe_package_size=%zu", wipe_package.size());
@@ -549,12 +578,12 @@
     // c8. setup the bcb command
     std::string err;
     if (!write_bootloader_message(options, &err)) {
-        ALOGE("failed to set bootloader message: %s", err.c_str());
+        LOG(ERROR) << "failed to set bootloader message: " << err;
         write_status_to_socket(-1, socket);
         return false;
     }
     if (!wipe_package.empty() && !write_wipe_package(wipe_package, &err)) {
-        ALOGE("failed to set wipe package: %s", err.c_str());
+        PLOG(ERROR) << "failed to set wipe package: " << err;
         write_status_to_socket(-1, socket);
         return false;
     }
@@ -571,7 +600,7 @@
 }
 
 int main(int argc, char** argv) {
-    enum { UNCRYPT, SETUP_BCB, CLEAR_BCB } action;
+    enum { UNCRYPT, SETUP_BCB, CLEAR_BCB, UNCRYPT_DEBUG } action;
     const char* input_path = nullptr;
     const char* map_file = CACHE_BLOCK_MAP.c_str();
 
@@ -584,7 +613,7 @@
     } else if (argc == 3) {
         input_path = argv[1];
         map_file = argv[2];
-        action = UNCRYPT;
+        action = UNCRYPT_DEBUG;
     } else {
         usage(argv[0]);
         return 2;
@@ -595,25 +624,36 @@
         return 1;
     }
 
+    if (action == UNCRYPT_DEBUG) {
+        LOG(INFO) << "uncrypt called in debug mode, skip socket communication";
+        bool success = uncrypt_wrapper(input_path, map_file, -1);
+        if (success) {
+            LOG(INFO) << "uncrypt succeeded";
+        } else{
+            LOG(INFO) << "uncrypt failed";
+        }
+        return success ? 0 : 1;
+    }
+
     // c3. The socket is created by init when starting the service. uncrypt
     // will use the socket to communicate with its caller.
-    unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str()));
-    if (!service_socket) {
-        ALOGE("failed to open socket \"%s\": %s", UNCRYPT_SOCKET.c_str(), strerror(errno));
+    android::base::unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str()));
+    if (service_socket == -1) {
+        PLOG(ERROR) << "failed to open socket \"" << UNCRYPT_SOCKET << "\"";
         log_uncrypt_error_code(kUncryptSocketOpenError);
         return 1;
     }
-    fcntl(service_socket.get(), F_SETFD, FD_CLOEXEC);
+    fcntl(service_socket, F_SETFD, FD_CLOEXEC);
 
-    if (listen(service_socket.get(), 1) == -1) {
-        ALOGE("failed to listen on socket %d: %s", service_socket.get(), strerror(errno));
+    if (listen(service_socket, 1) == -1) {
+        PLOG(ERROR) << "failed to listen on socket " << service_socket.get();
         log_uncrypt_error_code(kUncryptSocketListenError);
         return 1;
     }
 
-    unique_fd socket_fd(accept4(service_socket.get(), nullptr, nullptr, SOCK_CLOEXEC));
-    if (!socket_fd) {
-        ALOGE("failed to accept on socket %d: %s", service_socket.get(), strerror(errno));
+    android::base::unique_fd socket_fd(accept4(service_socket, nullptr, nullptr, SOCK_CLOEXEC));
+    if (socket_fd == -1) {
+        PLOG(ERROR) << "failed to accept on socket " << service_socket.get();
         log_uncrypt_error_code(kUncryptSocketAcceptError);
         return 1;
     }
@@ -621,16 +661,16 @@
     bool success = false;
     switch (action) {
         case UNCRYPT:
-            success = uncrypt_wrapper(input_path, map_file, socket_fd.get());
+            success = uncrypt_wrapper(input_path, map_file, socket_fd);
             break;
         case SETUP_BCB:
-            success = setup_bcb(socket_fd.get());
+            success = setup_bcb(socket_fd);
             break;
         case CLEAR_BCB:
-            success = clear_bcb(socket_fd.get());
+            success = clear_bcb(socket_fd);
             break;
         default:  // Should never happen.
-            ALOGE("Invalid uncrypt action code: %d", action);
+            LOG(ERROR) << "Invalid uncrypt action code: " << action;
             return 1;
     }
 
@@ -638,10 +678,10 @@
     // ensure the client to receive the last status code before the socket gets
     // destroyed.
     int code;
-    if (android::base::ReadFully(socket_fd.get(), &code, 4)) {
-        ALOGI("  received %d, exiting now", code);
+    if (android::base::ReadFully(socket_fd, &code, 4)) {
+        LOG(INFO) << "  received " << code << ", exiting now";
     } else {
-        ALOGE("failed to read the code: %s", strerror(errno));
+        PLOG(ERROR) << "failed to read the code";
     }
     return success ? 0 : 1;
 }
diff --git a/unique_fd.h b/unique_fd.h
deleted file mode 100644
index cc85383..0000000
--- a/unique_fd.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 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 agreed 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 UNIQUE_FD_H
-#define UNIQUE_FD_H
-
-#include <stdio.h>
-
-#include <memory>
-
-class unique_fd {
-  public:
-    unique_fd(int fd) : fd_(fd) { }
-
-    unique_fd(unique_fd&& uf) {
-        fd_ = uf.fd_;
-        uf.fd_ = -1;
-    }
-
-    ~unique_fd() {
-        if (fd_ != -1) {
-            close(fd_);
-        }
-    }
-
-    int get() {
-        return fd_;
-    }
-
-    // Movable.
-    unique_fd& operator=(unique_fd&& uf) {
-        fd_ = uf.fd_;
-        uf.fd_ = -1;
-        return *this;
-    }
-
-    explicit operator bool() const {
-        return fd_ != -1;
-    }
-
-  private:
-    int fd_;
-
-    // Non-copyable.
-    unique_fd(const unique_fd&) = delete;
-    unique_fd& operator=(const unique_fd&) = delete;
-};
-
-#endif  // UNIQUE_FD_H
diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk
index 2bfd016..1acd5ec 100644
--- a/update_verifier/Android.mk
+++ b/update_verifier/Android.mk
@@ -20,8 +20,22 @@
 LOCAL_SRC_FILES := update_verifier.cpp
 
 LOCAL_MODULE := update_verifier
-LOCAL_SHARED_LIBRARIES := libbase libcutils libhardware liblog
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libcutils \
+    libhardware \
+    liblog \
+    libutils \
+    libhidlbase \
+    android.hardware.boot@1.0
 
+LOCAL_CFLAGS := -Werror
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
 
+LOCAL_INIT_RC := update_verifier.rc
+
+ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
+    LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
+endif
+
 include $(BUILD_EXECUTABLE)
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index 5cff8be..350020f 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -19,88 +19,146 @@
  * update. It gets invoked by init, and will only perform the verification if
  * it's the first boot post an A/B OTA update.
  *
- * It relies on dm-verity to capture any corruption on the partitions being
- * verified. dm-verity must be in enforcing mode, so that it will reboot the
- * device on dm-verity failures. When that happens, the bootloader should
+ * Update_verifier relies on dm-verity to capture any corruption on the partitions
+ * being verified. And its behavior varies depending on the dm-verity mode.
+ * Upon detection of failures:
+ *   enforcing mode: dm-verity reboots the device
+ *   eio mode: dm-verity fails the read and update_verifier reboots the device
+ *   other mode: not supported and update_verifier reboots the device
+ *
+ * After a predefined number of failing boot attempts, the bootloader should
  * mark the slot as unbootable and stops trying. Other dm-verity modes (
  * for example, veritymode=EIO) are not accepted and simply lead to a
  * verification failure.
  *
  * The current slot will be marked as having booted successfully if the
  * verifier reaches the end after the verification.
- *
  */
 
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
 
 #include <string>
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <cutils/properties.h>
-#include <hardware/boot_control.h>
-#define LOG_TAG       "update_verifier"
-#include <log/log.h>
+#include <android/hardware/boot/1.0/IBootControl.h>
+#include <cutils/android_reboot.h>
+
+using android::sp;
+using android::hardware::boot::V1_0::IBootControl;
+using android::hardware::boot::V1_0::BoolResult;
+using android::hardware::boot::V1_0::CommandResult;
 
 constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
+constexpr auto DM_PATH_PREFIX = "/sys/block/";
+constexpr auto DM_PATH_SUFFIX = "/dm/name";
+constexpr auto DEV_PATH = "/dev/block/";
 constexpr int BLOCKSIZE = 4096;
 
-static bool read_blocks(const std::string& blk_device_prefix, const std::string& range_str) {
-    char slot_suffix[PROPERTY_VALUE_MAX];
-    property_get("ro.boot.slot_suffix", slot_suffix, "");
-    std::string blk_device = blk_device_prefix + std::string(slot_suffix);
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY)));
-    if (fd.get() == -1) {
-        SLOGE("Error reading partition %s: %s\n", blk_device.c_str(), strerror(errno));
-        return false;
+// Find directories in format of "/sys/block/dm-X".
+static int dm_name_filter(const dirent* de) {
+  if (android::base::StartsWith(de->d_name, "dm-")) {
+    return 1;
+  }
+  return 0;
+}
+
+static bool read_blocks(const std::string& partition, const std::string& range_str) {
+  if (partition != "system" && partition != "vendor") {
+    LOG(ERROR) << "partition name must be system or vendor: " << partition;
+    return false;
+  }
+  // Iterate the content of "/sys/block/dm-X/dm/name". If it matches "system"
+  // (or "vendor"), then dm-X is a dm-wrapped system/vendor partition.
+  // Afterwards, update_verifier will read every block on the care_map_file of
+  // "/dev/block/dm-X" to ensure the partition's integrity.
+  dirent** namelist;
+  int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort);
+  if (n == -1) {
+    PLOG(ERROR) << "Failed to scan dir " << DM_PATH_PREFIX;
+    return false;
+  }
+  if (n == 0) {
+    LOG(ERROR) << "dm block device not found for " << partition;
+    return false;
+  }
+
+  std::string dm_block_device;
+  while (n--) {
+    std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX;
+    std::string content;
+    if (!android::base::ReadFileToString(path, &content)) {
+      PLOG(WARNING) << "Failed to read " << path;
+    } else if (android::base::Trim(content) == partition) {
+      dm_block_device = DEV_PATH + std::string(namelist[n]->d_name);
+      while (n--) {
+        free(namelist[n]);
+      }
+      break;
+    }
+    free(namelist[n]);
+  }
+  free(namelist);
+
+  if (dm_block_device.empty()) {
+    LOG(ERROR) << "Failed to find dm block device for " << partition;
+    return false;
+  }
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
+  if (fd.get() == -1) {
+    PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
+    return false;
+  }
+
+  // For block range string, first integer 'count' equals 2 * total number of valid ranges,
+  // followed by 'count' number comma separated integers. Every two integers reprensent a
+  // block range with the first number included in range but second number not included.
+  // For example '4,64536,65343,74149,74150' represents: [64536,65343) and [74149,74150).
+  std::vector<std::string> ranges = android::base::Split(range_str, ",");
+  size_t range_count;
+  bool status = android::base::ParseUint(ranges[0], &range_count);
+  if (!status || (range_count == 0) || (range_count % 2 != 0) ||
+      (range_count != ranges.size() - 1)) {
+    LOG(ERROR) << "Error in parsing range string.";
+    return false;
+  }
+
+  size_t blk_count = 0;
+  for (size_t i = 1; i < ranges.size(); i += 2) {
+    unsigned int range_start, range_end;
+    bool parse_status = android::base::ParseUint(ranges[i], &range_start);
+    parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
+    if (!parse_status || range_start >= range_end) {
+      LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
+      return false;
     }
 
-    // For block range string, first integer 'count' equals 2 * total number of valid ranges,
-    // followed by 'count' number comma separated integers. Every two integers reprensent a
-    // block range with the first number included in range but second number not included.
-    // For example '4,64536,65343,74149,74150' represents: [64536,65343) and [74149,74150).
-    std::vector<std::string> ranges = android::base::Split(range_str, ",");
-    size_t range_count;
-    bool status = android::base::ParseUint(ranges[0].c_str(), &range_count);
-    if (!status || (range_count == 0) || (range_count % 2 != 0) ||
-            (range_count != ranges.size()-1)) {
-        SLOGE("Error in parsing range string.\n");
-        return false;
+    if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) {
+      PLOG(ERROR) << "lseek to " << range_start << " failed";
+      return false;
     }
 
-    size_t blk_count = 0;
-    for (size_t i = 1; i < ranges.size(); i += 2) {
-        unsigned int range_start, range_end;
-        bool parse_status = android::base::ParseUint(ranges[i].c_str(), &range_start);
-        parse_status = parse_status && android::base::ParseUint(ranges[i+1].c_str(), &range_end);
-        if (!parse_status || range_start >= range_end) {
-            SLOGE("Invalid range pair %s, %s.\n", ranges[i].c_str(), ranges[i+1].c_str());
-            return false;
-        }
-
-        if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) {
-            SLOGE("lseek to %u failed: %s.\n", range_start, strerror(errno));
-            return false;
-        }
-
-        size_t size = (range_end - range_start) * BLOCKSIZE;
-        std::vector<uint8_t> buf(size);
-        if (!android::base::ReadFully(fd.get(), buf.data(), size)) {
-            SLOGE("Failed to read blocks %u to %u: %s.\n", range_start, range_end,
-                  strerror(errno));
-            return false;
-        }
-        blk_count += (range_end - range_start);
+    size_t size = (range_end - range_start) * BLOCKSIZE;
+    std::vector<uint8_t> buf(size);
+    if (!android::base::ReadFully(fd.get(), buf.data(), size)) {
+      PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
+      return false;
     }
+    blk_count += (range_end - range_start);
+  }
 
-    SLOGI("Finished reading %zu blocks on %s.\n", blk_count, blk_device.c_str());
-    return true;
+  LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
+  return true;
 }
 
 static bool verify_image(const std::string& care_map_name) {
@@ -109,24 +167,24 @@
     // in /data/ota_package. To allow the device to continue booting in this situation,
     // we should print a warning and skip the block verification.
     if (care_map_fd.get() == -1) {
-        SLOGI("Warning: care map %s not found.\n", care_map_name.c_str());
+        PLOG(WARNING) << "Failed to open " << care_map_name;
         return true;
     }
     // Care map file has four lines (two lines if vendor partition is not present):
-    // First line has the block device name, e.g./dev/block/.../by-name/system.
+    // First line has the block partition name (system/vendor).
     // Second line holds all ranges of blocks to verify.
     // The next two lines have the same format but for vendor partition.
     std::string file_content;
     if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) {
-        SLOGE("Error reading care map contents to string.\n");
+        LOG(ERROR) << "Error reading care map contents to string.";
         return false;
     }
 
     std::vector<std::string> lines;
     lines = android::base::Split(android::base::Trim(file_content), "\n");
     if (lines.size() != 2 && lines.size() != 4) {
-        SLOGE("Invalid lines in care_map: found %zu lines, expecting 2 or 4 lines.\n",
-              lines.size());
+        LOG(ERROR) << "Invalid lines in care_map: found " << lines.size()
+                   << " lines, expecting 2 or 4 lines.";
         return false;
     }
 
@@ -139,51 +197,64 @@
     return true;
 }
 
-int main(int argc, char** argv) {
-  for (int i = 1; i < argc; i++) {
-    SLOGI("Started with arg %d: %s\n", i, argv[i]);
-  }
-
-  const hw_module_t* hw_module;
-  if (hw_get_module("bootctrl", &hw_module) != 0) {
-    SLOGE("Error getting bootctrl module.\n");
+static int reboot_device() {
+  if (android_reboot(ANDROID_RB_RESTART2, 0, nullptr) == -1) {
+    LOG(ERROR) << "Failed to reboot.";
     return -1;
   }
+  while (true) pause();
+}
 
-  boot_control_module_t* module = reinterpret_cast<boot_control_module_t*>(
-      const_cast<hw_module_t*>(hw_module));
-  module->init(module);
-
-  unsigned current_slot = module->getCurrentSlot(module);
-  int is_successful= module->isSlotMarkedSuccessful(module, current_slot);
-  SLOGI("Booting slot %u: isSlotMarkedSuccessful=%d\n", current_slot, is_successful);
-  if (is_successful == 0) {
-    // The current slot has not booted successfully.
-    char verity_mode[PROPERTY_VALUE_MAX];
-    if (property_get("ro.boot.veritymode", verity_mode, "") == -1) {
-      SLOGE("Failed to get dm-verity mode");
-      return -1;
-    } else if (strcasecmp(verity_mode, "eio") == 0) {
-      // We shouldn't see verity in EIO mode if the current slot hasn't booted
-      // successfully before. Therefore, fail the verification when veritymode=eio.
-      SLOGE("Found dm-verity in EIO mode, skip verification.");
-      return -1;
-    } else if (strcmp(verity_mode, "enforcing") != 0) {
-      SLOGE("Unexpected dm-verity mode : %s, expecting enforcing.", verity_mode);
-      return -1;
-    } else if (!verify_image(CARE_MAP_FILE)) {
-      SLOGE("Failed to verify all blocks in care map file.\n");
-      return -1;
-    }
-
-    int ret = module->markBootSuccessful(module);
-    if (ret != 0) {
-      SLOGE("Error marking booted successfully: %s\n", strerror(-ret));
-      return -1;
-    }
-    SLOGI("Marked slot %u as booted successfully.\n", current_slot);
+int main(int argc, char** argv) {
+  for (int i = 1; i < argc; i++) {
+    LOG(INFO) << "Started with arg " << i << ": " << argv[i];
   }
 
-  SLOGI("Leaving update_verifier.\n");
+  sp<IBootControl> module = IBootControl::getService();
+  if (module == nullptr) {
+    LOG(ERROR) << "Error getting bootctrl module.";
+    return reboot_device();
+  }
+
+  uint32_t current_slot = module->getCurrentSlot();
+  BoolResult is_successful = module->isSlotMarkedSuccessful(current_slot);
+  LOG(INFO) << "Booting slot " << current_slot << ": isSlotMarkedSuccessful="
+            << static_cast<int32_t>(is_successful);
+
+  if (is_successful == BoolResult::FALSE) {
+    // The current slot has not booted successfully.
+
+#ifdef PRODUCT_SUPPORTS_VERITY
+    std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", "");
+    if (verity_mode.empty()) {
+      LOG(ERROR) << "Failed to get dm-verity mode.";
+      return reboot_device();
+    } else if (android::base::EqualsIgnoreCase(verity_mode, "eio")) {
+      // We shouldn't see verity in EIO mode if the current slot hasn't booted successfully before.
+      // Continue the verification until we fail to read some blocks.
+      LOG(WARNING) << "Found dm-verity in EIO mode.";
+    } else if (verity_mode != "enforcing") {
+      LOG(ERROR) << "Unexpected dm-verity mode : " << verity_mode << ", expecting enforcing.";
+      return reboot_device();
+    }
+
+    if (!verify_image(CARE_MAP_FILE)) {
+      LOG(ERROR) << "Failed to verify all blocks in care map file.";
+      return reboot_device();
+    }
+#else
+    LOG(WARNING) << "dm-verity not enabled; marking without verification.";
+#endif
+
+    CommandResult cr;
+    module->markBootSuccessful([&cr](CommandResult result) { cr = result; });
+    if (!cr.success) {
+      LOG(ERROR) << "Error marking booted successfully: " << cr.errMsg;
+      return reboot_device();
+    }
+    LOG(INFO) << "Marked slot " << current_slot << " as booted successfully.";
+  }
+
+  LOG(INFO) << "Leaving update_verifier.";
   return 0;
 }
diff --git a/update_verifier/update_verifier.rc b/update_verifier/update_verifier.rc
new file mode 100644
index 0000000..862b062
--- /dev/null
+++ b/update_verifier/update_verifier.rc
@@ -0,0 +1,11 @@
+service update_verifier_nonencrypted /system/bin/update_verifier nonencrypted
+    user root
+    group cache system
+    priority -20
+    ioprio rt 0
+
+service update_verifier /system/bin/update_verifier ${vold.decrypt}
+    user root
+    group cache system
+    priority -20
+    ioprio rt 0
diff --git a/updater/Android.mk b/updater/Android.mk
index d7aa613..a113fe8 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -14,52 +14,90 @@
 
 LOCAL_PATH := $(call my-dir)
 
-updater_src_files := \
-	install.cpp \
-	blockimg.cpp \
-	updater.cpp
+tune2fs_static_libraries := \
+    libext2_com_err \
+    libext2_blkid \
+    libext2_quota \
+    libext2_uuid \
+    libext2_e2p \
+    libext2fs
 
-#
-# Build a statically-linked binary to include in OTA packages
-#
+updater_common_static_libraries := \
+    libapplypatch \
+    libbspatch \
+    libedify \
+    libziparchive \
+    libotautil \
+    libbootloader_message \
+    libutils \
+    libmounts \
+    libotafault \
+    libext4_utils \
+    libfec \
+    libfec_rs \
+    libfs_mgr \
+    liblog \
+    libselinux \
+    libsparse \
+    libsquashfs_utils \
+    libbz \
+    libz \
+    libbase \
+    libcrypto \
+    libcrypto_utils \
+    libcutils \
+    libtune2fs \
+    $(tune2fs_static_libraries)
+
+# libupdater (static library)
+# ===============================
 include $(CLEAR_VARS)
 
-# Build only in eng, so we don't end up with a copy of this in /system
-# on user builds.  (TODO: find a better way to build device binaries
-# needed only for OTA packages.)
-LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE := libupdater
 
-LOCAL_CLANG := true
+LOCAL_SRC_FILES := \
+    install.cpp \
+    blockimg.cpp
 
-LOCAL_SRC_FILES := $(updater_src_files)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/.. \
+    $(LOCAL_PATH)/include \
+    external/e2fsprogs/misc
 
-LOCAL_STATIC_LIBRARIES += libfec libfec_rs libext4_utils_static libsquashfs_utils libcrypto_static
+LOCAL_CFLAGS := \
+    -Wno-unused-parameter \
+    -Werror
 
-ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
-LOCAL_CFLAGS += -DUSE_EXT4
-LOCAL_CFLAGS += -Wno-unused-parameter
-LOCAL_C_INCLUDES += system/extras/ext4_utils
-LOCAL_STATIC_LIBRARIES += \
-    libsparse_static \
-    libz
-endif
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+    $(LOCAL_PATH)/include
 
-LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS)
-LOCAL_STATIC_LIBRARIES += libapplypatch libbase libotafault libedify libmtdutils libminzip libz
-LOCAL_STATIC_LIBRARIES += libbz
-LOCAL_STATIC_LIBRARIES += libcutils liblog libc
-LOCAL_STATIC_LIBRARIES += libselinux
-tune2fs_static_libraries := \
- libext2_com_err \
- libext2_blkid \
- libext2_quota \
- libext2_uuid_static \
- libext2_e2p \
- libext2fs
-LOCAL_STATIC_LIBRARIES += libtune2fs $(tune2fs_static_libraries)
+LOCAL_STATIC_LIBRARIES := \
+    $(updater_common_static_libraries)
 
-LOCAL_C_INCLUDES += external/e2fsprogs/misc
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+include $(BUILD_STATIC_LIBRARY)
+
+# updater (static executable)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := updater
+
+LOCAL_SRC_FILES := \
+    updater.cpp
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/.. \
+    $(LOCAL_PATH)/include
+
+LOCAL_CFLAGS := \
+    -Wno-unused-parameter \
+    -Werror
+
+LOCAL_STATIC_LIBRARIES := \
+    libupdater \
+    $(TARGET_RECOVERY_UPDATER_LIBS) \
+    $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \
+    $(updater_common_static_libraries)
 
 # Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
 # named "Register_<libname>()".  Here we emit a little C function that
@@ -72,21 +110,11 @@
 # any subsidiary static libraries required for your registered
 # extension libs.
 
-inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc
-
-# Encode the value of TARGET_RECOVERY_UPDATER_LIBS into the filename of the dependency.
-# So if TARGET_RECOVERY_UPDATER_LIBS is changed, a new dependency file will be generated.
-# Note that we have to remove any existing depency files before creating new one,
-# so no obsolete dependecy file gets used if you switch back to an old value.
-inc_dep_file := $(inc).dep.$(subst $(space),-,$(sort $(TARGET_RECOVERY_UPDATER_LIBS)))
-$(inc_dep_file): stem := $(inc).dep
-$(inc_dep_file) :
-	$(hide) mkdir -p $(dir $@)
-	$(hide) rm -f $(stem).*
-	$(hide) touch $@
+LOCAL_MODULE_CLASS := EXECUTABLES
+inc := $(call local-generated-sources-dir)/register.inc
 
 $(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
-$(inc) : $(inc_dep_file)
+$(inc) :
 	$(hide) mkdir -p $(dir $@)
 	$(hide) echo "" > $@
 	$(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;)
@@ -94,13 +122,9 @@
 	$(hide) $(foreach lib,$(libs),echo "  Register_$(lib)();" >> $@;)
 	$(hide) echo "}" >> $@
 
-$(call intermediates-dir-for,EXECUTABLES,updater,,,$(TARGET_PREFER_32_BIT))/updater.o : $(inc)
-LOCAL_C_INCLUDES += $(dir $(inc))
+LOCAL_GENERATED_SOURCES := $(inc)
 
 inc :=
-inc_dep_file :=
-
-LOCAL_MODULE := updater
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index a80180a..c614ccc 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -18,7 +18,6 @@
 #include <errno.h>
 #include <dirent.h>
 #include <fcntl.h>
-#include <inttypes.h>
 #include <linux/fs.h>
 #include <pthread.h>
 #include <stdarg.h>
@@ -33,114 +32,130 @@
 #include <unistd.h>
 #include <fec/io.h>
 
-#include <map>
+#include <functional>
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
+#include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <applypatch/applypatch.h>
+#include <openssl/sha.h>
+#include <private/android_filesystem_config.h>
+#include <ziparchive/zip_archive.h>
 
-#include "applypatch/applypatch.h"
 #include "edify/expr.h"
 #include "error_code.h"
-#include "install.h"
-#include "openssl/sha.h"
-#include "minzip/Hash.h"
+#include "updater/install.h"
 #include "ota_io.h"
 #include "print_sha1.h"
-#include "unique_fd.h"
-#include "updater.h"
-
-#define BLOCKSIZE 4096
+#include "updater/updater.h"
 
 // Set this to 0 to interpret 'erase' transfers to mean do a
 // BLKDISCARD ioctl (the normal behavior).  Set to 1 to interpret
 // erase to mean fill the region with zeroes.
 #define DEBUG_ERASE  0
 
-#define STASH_DIRECTORY_BASE "/cache/recovery"
-#define STASH_DIRECTORY_MODE 0700
-#define STASH_FILE_MODE 0600
+static constexpr size_t BLOCKSIZE = 4096;
+static constexpr const char* STASH_DIRECTORY_BASE = "/cache/recovery";
+static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
+static constexpr mode_t STASH_FILE_MODE = 0600;
 
 struct RangeSet {
-    size_t count;             // Limit is INT_MAX.
-    size_t size;
-    std::vector<size_t> pos;  // Actual limit is INT_MAX.
+  size_t count;  // Limit is INT_MAX.
+  size_t size;
+  std::vector<size_t> pos;  // Actual limit is INT_MAX.
+
+  // Get the block number for the ith(starting from 0) block in the range set.
+  int get_block(size_t idx) const {
+    if (idx >= size) {
+      LOG(ERROR) << "index: " << idx << " is greater than range set size: " << size;
+      return -1;
+    }
+    for (size_t i = 0; i < pos.size(); i += 2) {
+      if (idx < pos[i + 1] - pos[i]) {
+        return pos[i] + idx;
+      }
+      idx -= (pos[i + 1] - pos[i]);
+    }
+    return -1;
+  }
 };
 
 static CauseCode failure_type = kNoCause;
 static bool is_retry = false;
-static std::map<std::string, RangeSet> stash_map;
+static std::unordered_map<std::string, RangeSet> stash_map;
 
-static void parse_range(const std::string& range_text, RangeSet& rs) {
+static RangeSet parse_range(const std::string& range_text) {
+  RangeSet rs;
 
-    std::vector<std::string> pieces = android::base::Split(range_text, ",");
-    if (pieces.size() < 3) {
-        goto err;
+  std::vector<std::string> pieces = android::base::Split(range_text, ",");
+  if (pieces.size() < 3) {
+    goto err;
+  }
+
+  size_t num;
+  if (!android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) {
+    goto err;
+  }
+
+  if (num == 0 || num % 2) {
+    goto err;  // must be even
+  } else if (num != pieces.size() - 1) {
+    goto err;
+  }
+
+  rs.pos.resize(num);
+  rs.count = num / 2;
+  rs.size = 0;
+
+  for (size_t i = 0; i < num; i += 2) {
+    if (!android::base::ParseUint(pieces[i + 1], &rs.pos[i], static_cast<size_t>(INT_MAX))) {
+      goto err;
     }
 
-    size_t num;
-    if (!android::base::ParseUint(pieces[0].c_str(), &num, static_cast<size_t>(INT_MAX))) {
-        goto err;
+    if (!android::base::ParseUint(pieces[i + 2], &rs.pos[i + 1], static_cast<size_t>(INT_MAX))) {
+      goto err;
     }
 
-    if (num == 0 || num % 2) {
-        goto err; // must be even
-    } else if (num != pieces.size() - 1) {
-        goto err;
+    if (rs.pos[i] >= rs.pos[i + 1]) {
+      goto err;  // empty or negative range
     }
 
-    rs.pos.resize(num);
-    rs.count = num / 2;
-    rs.size = 0;
-
-    for (size_t i = 0; i < num; i += 2) {
-        if (!android::base::ParseUint(pieces[i+1].c_str(), &rs.pos[i],
-                                      static_cast<size_t>(INT_MAX))) {
-            goto err;
-        }
-
-        if (!android::base::ParseUint(pieces[i+2].c_str(), &rs.pos[i+1],
-                                      static_cast<size_t>(INT_MAX))) {
-            goto err;
-        }
-
-        if (rs.pos[i] >= rs.pos[i+1]) {
-            goto err; // empty or negative range
-        }
-
-        size_t sz = rs.pos[i+1] - rs.pos[i];
-        if (rs.size > SIZE_MAX - sz) {
-            goto err; // overflow
-        }
-
-        rs.size += sz;
+    size_t sz = rs.pos[i + 1] - rs.pos[i];
+    if (rs.size > SIZE_MAX - sz) {
+      goto err;  // overflow
     }
 
-    return;
+    rs.size += sz;
+  }
+
+  return rs;
 
 err:
-    fprintf(stderr, "failed to parse range '%s'\n", range_text.c_str());
-    exit(1);
+  LOG(ERROR) << "failed to parse range '" << range_text << "'";
+  exit(EXIT_FAILURE);
 }
 
 static bool range_overlaps(const RangeSet& r1, const RangeSet& r2) {
-    for (size_t i = 0; i < r1.count; ++i) {
-        size_t r1_0 = r1.pos[i * 2];
-        size_t r1_1 = r1.pos[i * 2 + 1];
+  for (size_t i = 0; i < r1.count; ++i) {
+    size_t r1_0 = r1.pos[i * 2];
+    size_t r1_1 = r1.pos[i * 2 + 1];
 
-        for (size_t j = 0; j < r2.count; ++j) {
-            size_t r2_0 = r2.pos[j * 2];
-            size_t r2_1 = r2.pos[j * 2 + 1];
+    for (size_t j = 0; j < r2.count; ++j) {
+      size_t r2_0 = r2.pos[j * 2];
+      size_t r2_1 = r2.pos[j * 2 + 1];
 
-            if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) {
-                return true;
-            }
-        }
+      if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) {
+        return true;
+      }
     }
+  }
 
-    return false;
+  return false;
 }
 
 static int read_all(int fd, uint8_t* data, size_t size) {
@@ -149,7 +164,11 @@
         ssize_t r = TEMP_FAILURE_RETRY(ota_read(fd, data+so_far, size-so_far));
         if (r == -1) {
             failure_type = kFreadFailure;
-            fprintf(stderr, "read failed: %s\n", strerror(errno));
+            PLOG(ERROR) << "read failed";
+            return -1;
+        } else if (r == 0) {
+            failure_type = kFreadFailure;
+            LOG(ERROR) << "read reached unexpected EOF.";
             return -1;
         }
         so_far += r;
@@ -167,7 +186,7 @@
         ssize_t w = TEMP_FAILURE_RETRY(ota_write(fd, data+written, size-written));
         if (w == -1) {
             failure_type = kFwriteFailure;
-            fprintf(stderr, "write failed: %s\n", strerror(errno));
+            PLOG(ERROR) << "write failed";
             return -1;
         }
         written += w;
@@ -189,7 +208,7 @@
     uint64_t args[2] = {static_cast<uint64_t>(offset), size};
     int status = ioctl(fd, BLKDISCARD, &args);
     if (status == -1) {
-        fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno));
+        PLOG(ERROR) << "BLKDISCARD ioctl failed";
         return false;
     }
     return true;
@@ -199,7 +218,7 @@
     off64_t rc = TEMP_FAILURE_RETRY(lseek64(fd, offset, whence));
     if (rc == -1) {
         failure_type = kLseekFailure;
-        fprintf(stderr, "lseek64 failed: %s\n", strerror(errno));
+        PLOG(ERROR) << "lseek64 failed";
         return false;
     }
     return true;
@@ -213,7 +232,7 @@
 }
 
 struct RangeSinkState {
-    RangeSinkState(RangeSet& rs) : tgt(rs) { };
+    explicit RangeSinkState(RangeSet& rs) : tgt(rs) { };
 
     int fd;
     const RangeSet& tgt;
@@ -225,7 +244,7 @@
     RangeSinkState* rss = reinterpret_cast<RangeSinkState*>(token);
 
     if (rss->p_remain == 0) {
-        fprintf(stderr, "range sink write overrun");
+        LOG(ERROR) << "range sink write overrun";
         return 0;
     }
 
@@ -296,8 +315,8 @@
 // rss and signals the condition again.
 
 struct NewThreadInfo {
-    ZipArchive* za;
-    const ZipEntry* entry;
+    ZipArchiveHandle za;
+    ZipEntry entry;
 
     RangeSinkState* rss;
 
@@ -305,7 +324,7 @@
     pthread_cond_t cv;
 };
 
-static bool receive_new_data(const unsigned char* data, int size, void* cookie) {
+static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) {
     NewThreadInfo* nti = reinterpret_cast<NewThreadInfo*>(cookie);
 
     while (size > 0) {
@@ -337,8 +356,8 @@
 }
 
 static void* unzip_new_data(void* cookie) {
-    NewThreadInfo* nti = (NewThreadInfo*) cookie;
-    mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti);
+    NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
+    ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti);
     return nullptr;
 }
 
@@ -398,7 +417,7 @@
     std::string stashbase;
     bool canwrite;
     int createdstash;
-    int fd;
+    android::base::unique_fd fd;
     bool foundwrites;
     bool isunresumable;
     int version;
@@ -410,34 +429,109 @@
     uint8_t* patch_start;
 };
 
-// Do a source/target load for move/bsdiff/imgdiff in version 1.
-// We expect to parse the remainder of the parameter tokens as:
-//
-//    <src_range> <tgt_range>
-//
-// The source range is loaded into the provided buffer, reallocating
-// it to make it larger if necessary.
+// Print the hash in hex for corrupted source blocks (excluding the stashed blocks which is
+// handled separately).
+static void PrintHashForCorruptedSourceBlocks(const CommandParameters& params,
+                                              const std::vector<uint8_t>& buffer) {
+  LOG(INFO) << "unexpected contents of source blocks in cmd:\n" << params.cmdline;
+  CHECK(params.tokens[0] == "move" || params.tokens[0] == "bsdiff" ||
+        params.tokens[0] == "imgdiff");
 
-static int LoadSrcTgtVersion1(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
-        std::vector<uint8_t>& buffer, int fd) {
-
-    if (params.cpos + 1 >= params.tokens.size()) {
-        fprintf(stderr, "invalid parameters\n");
-        return -1;
+  size_t pos = 0;
+  // Command example:
+  // move <onehash> <tgt_range> <src_blk_count> <src_range> [<loc_range> <stashed_blocks>]
+  // bsdiff <offset> <len> <src_hash> <tgt_hash> <tgt_range> <src_blk_count> <src_range>
+  //        [<loc_range> <stashed_blocks>]
+  if (params.tokens[0] == "move") {
+    // src_range for move starts at the 4th position.
+    if (params.tokens.size() < 5) {
+      LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline;
+      return;
     }
+    pos = 4;
+  } else {
+    // src_range for diff starts at the 7th position.
+    if (params.tokens.size() < 8) {
+      LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline;
+      return;
+    }
+    pos = 7;
+  }
 
-    // <src_range>
-    RangeSet src;
-    parse_range(params.tokens[params.cpos++], src);
+  // Source blocks in stash only, no work to do.
+  if (params.tokens[pos] == "-") {
+    return;
+  }
 
-    // <tgt_range>
-    parse_range(params.tokens[params.cpos++], tgt);
+  RangeSet src = parse_range(params.tokens[pos++]);
 
-    allocate(src.size * BLOCKSIZE, buffer);
-    int rc = ReadBlocks(src, buffer, fd);
-    src_blocks = src.size;
+  RangeSet locs;
+  // If there's no stashed blocks, content in the buffer is consecutive and has the same
+  // order as the source blocks.
+  if (pos == params.tokens.size()) {
+    locs.count = 1;
+    locs.size = src.size;
+    locs.pos = { 0, src.size };
+  } else {
+    // Otherwise, the next token is the offset of the source blocks in the target range.
+    // Example: for the tokens <4,63946,63947,63948,63979> <4,6,7,8,39> <stashed_blocks>;
+    // We want to print SHA-1 for the data in buffer[6], buffer[8], buffer[9] ... buffer[38];
+    // this corresponds to the 32 src blocks #63946, #63948, #63949 ... #63978.
+    locs = parse_range(params.tokens[pos++]);
+    CHECK_EQ(src.size, locs.size);
+    CHECK_EQ(locs.pos.size() % 2, static_cast<size_t>(0));
+  }
 
-    return rc;
+  LOG(INFO) << "printing hash in hex for " << src.size << " source blocks";
+  for (size_t i = 0; i < src.size; i++) {
+    int block_num = src.get_block(i);
+    CHECK_NE(block_num, -1);
+    int buffer_index = locs.get_block(i);
+    CHECK_NE(buffer_index, -1);
+    CHECK_LE((buffer_index + 1) * BLOCKSIZE, buffer.size());
+
+    uint8_t digest[SHA_DIGEST_LENGTH];
+    SHA1(buffer.data() + buffer_index * BLOCKSIZE, BLOCKSIZE, digest);
+    std::string hexdigest = print_sha1(digest);
+    LOG(INFO) << "  block number: " << block_num << ", SHA-1: " << hexdigest;
+  }
+}
+
+// If the calculated hash for the whole stash doesn't match the stash id, print the SHA-1
+// in hex for each block.
+static void PrintHashForCorruptedStashedBlocks(const std::string& id,
+                                               const std::vector<uint8_t>& buffer,
+                                               const RangeSet& src) {
+  LOG(INFO) << "printing hash in hex for stash_id: " << id;
+  CHECK_EQ(src.size * BLOCKSIZE, buffer.size());
+
+  for (size_t i = 0; i < src.size; i++) {
+    int block_num = src.get_block(i);
+    CHECK_NE(block_num, -1);
+
+    uint8_t digest[SHA_DIGEST_LENGTH];
+    SHA1(buffer.data() + i * BLOCKSIZE, BLOCKSIZE, digest);
+    std::string hexdigest = print_sha1(digest);
+    LOG(INFO) << "  block number: " << block_num << ", SHA-1: " << hexdigest;
+  }
+}
+
+// If the stash file doesn't exist, read the source blocks this stash contains and print the
+// SHA-1 for these blocks.
+static void PrintHashForMissingStashedBlocks(const std::string& id, int fd) {
+  if (stash_map.find(id) == stash_map.end()) {
+    LOG(ERROR) << "No stash saved for id: " << id;
+    return;
+  }
+
+  LOG(INFO) << "print hash in hex for source blocks in missing stash: " << id;
+  const RangeSet& src = stash_map[id];
+  std::vector<uint8_t> buffer(src.size * BLOCKSIZE);
+  if (ReadBlocks(src, buffer, fd) == -1) {
+      LOG(ERROR) << "failed to read source blocks for stash: " << id;
+      return;
+  }
+  PrintHashForCorruptedStashedBlocks(id, buffer, src);
 }
 
 static int VerifyBlocks(const std::string& expected, const std::vector<uint8_t>& buffer,
@@ -451,8 +545,8 @@
 
     if (hexdigest != expected) {
         if (printerror) {
-            fprintf(stderr, "failed to verify blocks (expected %s, read %s)\n",
-                    expected.c_str(), hexdigest.c_str());
+            LOG(ERROR) << "failed to verify blocks (expected " << expected << ", read "
+                       << hexdigest << ")";
         }
         return -1;
     }
@@ -472,92 +566,58 @@
     return fn;
 }
 
-typedef void (*StashCallback)(const std::string&, void*);
+// Does a best effort enumeration of stash files. Ignores possible non-file items in the stash
+// directory and continues despite of errors. Calls the 'callback' function for each file.
+static void EnumerateStash(const std::string& dirname,
+                           const std::function<void(const std::string&)>& callback) {
+  if (dirname.empty()) return;
 
-// Does a best effort enumeration of stash files. Ignores possible non-file
-// items in the stash directory and continues despite of errors. Calls the
-// 'callback' function for each file and passes 'data' to the function as a
-// parameter.
+  std::unique_ptr<DIR, decltype(&closedir)> directory(opendir(dirname.c_str()), closedir);
 
-static void EnumerateStash(const std::string& dirname, StashCallback callback, void* data) {
-    if (dirname.empty() || callback == nullptr) {
-        return;
+  if (directory == nullptr) {
+    if (errno != ENOENT) {
+      PLOG(ERROR) << "opendir \"" << dirname << "\" failed";
     }
+    return;
+  }
 
-    std::unique_ptr<DIR, int(*)(DIR*)> directory(opendir(dirname.c_str()), closedir);
-
-    if (directory == nullptr) {
-        if (errno != ENOENT) {
-            fprintf(stderr, "opendir \"%s\" failed: %s\n", dirname.c_str(), strerror(errno));
-        }
-        return;
-    }
-
-    struct dirent* item;
-    while ((item = readdir(directory.get())) != nullptr) {
-        if (item->d_type != DT_REG) {
-            continue;
-        }
-
-        std::string fn = dirname + "/" + std::string(item->d_name);
-        callback(fn, data);
-    }
-}
-
-static void UpdateFileSize(const std::string& fn, void* data) {
-    if (fn.empty() || !data) {
-        return;
-    }
-
-    struct stat sb;
-    if (stat(fn.c_str(), &sb) == -1) {
-        fprintf(stderr, "stat \"%s\" failed: %s\n", fn.c_str(), strerror(errno));
-        return;
-    }
-
-    int* size = reinterpret_cast<int*>(data);
-    *size += sb.st_size;
+  dirent* item;
+  while ((item = readdir(directory.get())) != nullptr) {
+    if (item->d_type != DT_REG) continue;
+    callback(dirname + "/" + item->d_name);
+  }
 }
 
 // Deletes the stash directory and all files in it. Assumes that it only
 // contains files. There is nothing we can do about unlikely, but possible
 // errors, so they are merely logged.
+static void DeleteFile(const std::string& fn) {
+  if (fn.empty()) return;
 
-static void DeleteFile(const std::string& fn, void* /* data */) {
-    if (!fn.empty()) {
-        fprintf(stderr, "deleting %s\n", fn.c_str());
+  LOG(INFO) << "deleting " << fn;
 
-        if (unlink(fn.c_str()) == -1 && errno != ENOENT) {
-            fprintf(stderr, "unlink \"%s\" failed: %s\n", fn.c_str(), strerror(errno));
-        }
-    }
-}
-
-static void DeletePartial(const std::string& fn, void* data) {
-    if (android::base::EndsWith(fn, ".partial")) {
-        DeleteFile(fn, data);
-    }
+  if (unlink(fn.c_str()) == -1 && errno != ENOENT) {
+    PLOG(ERROR) << "unlink \"" << fn << "\" failed";
+  }
 }
 
 static void DeleteStash(const std::string& base) {
-    if (base.empty()) {
-        return;
+  if (base.empty()) return;
+
+  LOG(INFO) << "deleting stash " << base;
+
+  std::string dirname = GetStashFileName(base, "", "");
+  EnumerateStash(dirname, DeleteFile);
+
+  if (rmdir(dirname.c_str()) == -1) {
+    if (errno != ENOENT && errno != ENOTDIR) {
+      PLOG(ERROR) << "rmdir \"" << dirname << "\" failed";
     }
-
-    fprintf(stderr, "deleting stash %s\n", base.c_str());
-
-    std::string dirname = GetStashFileName(base, "", "");
-    EnumerateStash(dirname, DeleteFile, nullptr);
-
-    if (rmdir(dirname.c_str()) == -1) {
-        if (errno != ENOENT && errno != ENOTDIR) {
-            fprintf(stderr, "rmdir \"%s\" failed: %s\n", dirname.c_str(), strerror(errno));
-        }
-    }
+  }
 }
 
-static int LoadStash(CommandParameters& params, const std::string& base, const std::string& id,
-        bool verify, size_t* blocks, std::vector<uint8_t>& buffer, bool printnoent) {
+static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks,
+                     std::vector<uint8_t>& buffer, bool printnoent) {
     // In verify mode, if source range_set was saved for the given hash,
     // check contents in the source blocks first. If the check fails,
     // search for the stashed files on /cache as usual.
@@ -567,52 +627,47 @@
             allocate(src.size * BLOCKSIZE, buffer);
 
             if (ReadBlocks(src, buffer, params.fd) == -1) {
-                fprintf(stderr, "failed to read source blocks in stash map.\n");
+                LOG(ERROR) << "failed to read source blocks in stash map.";
                 return -1;
             }
             if (VerifyBlocks(id, buffer, src.size, true) != 0) {
-                fprintf(stderr, "failed to verify loaded source blocks in stash map.\n");
+                LOG(ERROR) << "failed to verify loaded source blocks in stash map.";
+                PrintHashForCorruptedStashedBlocks(id, buffer, src);
                 return -1;
             }
             return 0;
         }
     }
 
-    if (base.empty()) {
-        return -1;
-    }
-
     size_t blockcount = 0;
 
     if (!blocks) {
         blocks = &blockcount;
     }
 
-    std::string fn = GetStashFileName(base, id, "");
+    std::string fn = GetStashFileName(params.stashbase, id, "");
 
     struct stat sb;
     int res = stat(fn.c_str(), &sb);
 
     if (res == -1) {
         if (errno != ENOENT || printnoent) {
-            fprintf(stderr, "stat \"%s\" failed: %s\n", fn.c_str(), strerror(errno));
+            PLOG(ERROR) << "stat \"" << fn << "\" failed";
+            PrintHashForMissingStashedBlocks(id, params.fd);
         }
         return -1;
     }
 
-    fprintf(stderr, " loading %s\n", fn.c_str());
+    LOG(INFO) << " loading " << fn;
 
     if ((sb.st_size % BLOCKSIZE) != 0) {
-        fprintf(stderr, "%s size %" PRId64 " not multiple of block size %d",
-                fn.c_str(), static_cast<int64_t>(sb.st_size), BLOCKSIZE);
+        LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE;
         return -1;
     }
 
-    int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY));
-    unique_fd fd_holder(fd);
-
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY)));
     if (fd == -1) {
-        fprintf(stderr, "open \"%s\" failed: %s\n", fn.c_str(), strerror(errno));
+        PLOG(ERROR) << "open \"" << fn << "\" failed";
         return -1;
     }
 
@@ -625,8 +680,15 @@
     *blocks = sb.st_size / BLOCKSIZE;
 
     if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) {
-        fprintf(stderr, "unexpected contents in %s\n", fn.c_str());
-        DeleteFile(fn, nullptr);
+        LOG(ERROR) << "unexpected contents in " << fn;
+        if (stash_map.find(id) == stash_map.end()) {
+            LOG(ERROR) << "failed to find source blocks number for stash " << id
+                       << " when executing command: " << params.cmdname;
+        } else {
+            const RangeSet& src = stash_map[id];
+            PrintHashForCorruptedStashedBlocks(id, buffer, src);
+        }
+        DeleteFile(fn);
         return -1;
     }
 
@@ -634,13 +696,13 @@
 }
 
 static int WriteStash(const std::string& base, const std::string& id, int blocks,
-        std::vector<uint8_t>& buffer, bool checkspace, bool *exists) {
+                      std::vector<uint8_t>& buffer, bool checkspace, bool *exists) {
     if (base.empty()) {
         return -1;
     }
 
     if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) {
-        fprintf(stderr, "not enough space to write stash\n");
+        LOG(ERROR) << "not enough space to write stash";
         return -1;
     }
 
@@ -655,7 +717,7 @@
             // The file already exists and since the name is the hash of the contents,
             // it's safe to assume the contents are identical (accidental hash collisions
             // are unlikely)
-            fprintf(stderr, " skipping %d existing blocks in %s\n", blocks, cn.c_str());
+            LOG(INFO) << " skipping " << blocks << " existing blocks in " << cn;
             *exists = true;
             return 0;
         }
@@ -663,13 +725,17 @@
         *exists = false;
     }
 
-    fprintf(stderr, " writing %d blocks to %s\n", blocks, cn.c_str());
+    LOG(INFO) << " writing " << blocks << " blocks to " << cn;
 
-    int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE));
-    unique_fd fd_holder(fd);
-
+    android::base::unique_fd fd(
+        TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE)));
     if (fd == -1) {
-        fprintf(stderr, "failed to create \"%s\": %s\n", fn.c_str(), strerror(errno));
+        PLOG(ERROR) << "failed to create \"" << fn << "\"";
+        return -1;
+    }
+
+    if (fchown(fd, AID_SYSTEM, AID_SYSTEM) != 0) {  // system user
+        PLOG(ERROR) << "failed to chown \"" << fn << "\"";
         return -1;
     }
 
@@ -679,29 +745,27 @@
 
     if (ota_fsync(fd) == -1) {
         failure_type = kFsyncFailure;
-        fprintf(stderr, "fsync \"%s\" failed: %s\n", fn.c_str(), strerror(errno));
+        PLOG(ERROR) << "fsync \"" << fn << "\" failed";
         return -1;
     }
 
     if (rename(fn.c_str(), cn.c_str()) == -1) {
-        fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", fn.c_str(), cn.c_str(),
-                strerror(errno));
+        PLOG(ERROR) << "rename(\"" << fn << "\", \"" << cn << "\") failed";
         return -1;
     }
 
     std::string dname = GetStashFileName(base, "", "");
-    int dfd = TEMP_FAILURE_RETRY(open(dname.c_str(), O_RDONLY | O_DIRECTORY));
-    unique_fd dfd_holder(dfd);
-
+    android::base::unique_fd dfd(TEMP_FAILURE_RETRY(ota_open(dname.c_str(),
+                                                             O_RDONLY | O_DIRECTORY)));
     if (dfd == -1) {
         failure_type = kFileOpenFailure;
-        fprintf(stderr, "failed to open \"%s\" failed: %s\n", dname.c_str(), strerror(errno));
+        PLOG(ERROR) << "failed to open \"" << dname << "\" failed";
         return -1;
     }
 
     if (ota_fsync(dfd) == -1) {
         failure_type = kFsyncFailure;
-        fprintf(stderr, "fsync \"%s\" failed: %s\n", dname.c_str(), strerror(errno));
+        PLOG(ERROR) << "fsync \"" << dname << "\" failed";
         return -1;
     }
 
@@ -712,121 +776,94 @@
 // hash enough space for the expected amount of blocks we need to store. Returns
 // >0 if we created the directory, zero if it existed already, and <0 of failure.
 
-static int CreateStash(State* state, int maxblocks, const char* blockdev, std::string& base) {
-    if (blockdev == nullptr) {
-        return -1;
+static int CreateStash(State* state, size_t maxblocks, const std::string& blockdev,
+                       std::string& base) {
+  if (blockdev.empty()) {
+    return -1;
+  }
+
+  // Stash directory should be different for each partition to avoid conflicts
+  // when updating multiple partitions at the same time, so we use the hash of
+  // the block device name as the base directory
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1(reinterpret_cast<const uint8_t*>(blockdev.data()), blockdev.size(), digest);
+  base = print_sha1(digest);
+
+  std::string dirname = GetStashFileName(base, "", "");
+  struct stat sb;
+  int res = stat(dirname.c_str(), &sb);
+  size_t max_stash_size = maxblocks * BLOCKSIZE;
+
+  if (res == -1 && errno != ENOENT) {
+    ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n", dirname.c_str(),
+               strerror(errno));
+    return -1;
+  } else if (res != 0) {
+    LOG(INFO) << "creating stash " << dirname;
+    res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
+
+    if (res != 0) {
+      ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n", dirname.c_str(),
+                 strerror(errno));
+      return -1;
     }
 
-    // Stash directory should be different for each partition to avoid conflicts
-    // when updating multiple partitions at the same time, so we use the hash of
-    // the block device name as the base directory
-    uint8_t digest[SHA_DIGEST_LENGTH];
-    SHA1(reinterpret_cast<const uint8_t*>(blockdev), strlen(blockdev), digest);
-    base = print_sha1(digest);
+    if (chown(dirname.c_str(), AID_SYSTEM, AID_SYSTEM) != 0) {  // system user
+      ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s\n", dirname.c_str(),
+                 strerror(errno));
+      return -1;
+    }
 
-    std::string dirname = GetStashFileName(base, "", "");
+    if (CacheSizeCheck(max_stash_size) != 0) {
+      ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)\n",
+                 max_stash_size);
+      return -1;
+    }
+
+    return 1;  // Created directory
+  }
+
+  LOG(INFO) << "using existing stash " << dirname;
+
+  // If the directory already exists, calculate the space already allocated to stash files and check
+  // if there's enough for all required blocks. Delete any partially completed stash files first.
+  EnumerateStash(dirname, [](const std::string& fn) {
+    if (android::base::EndsWith(fn, ".partial")) {
+      DeleteFile(fn);
+    }
+  });
+
+  size_t existing = 0;
+  EnumerateStash(dirname, [&existing](const std::string& fn) {
+    if (fn.empty()) return;
     struct stat sb;
-    int res = stat(dirname.c_str(), &sb);
-
-    if (res == -1 && errno != ENOENT) {
-        ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n",
-                   dirname.c_str(), strerror(errno));
-        return -1;
-    } else if (res != 0) {
-        fprintf(stderr, "creating stash %s\n", dirname.c_str());
-        res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
-
-        if (res != 0) {
-            ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n",
-                       dirname.c_str(), strerror(errno));
-            return -1;
-        }
-
-        if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) {
-            ErrorAbort(state, kStashCreationFailure, "not enough space for stash\n");
-            return -1;
-        }
-
-        return 1;  // Created directory
+    if (stat(fn.c_str(), &sb) == -1) {
+      PLOG(ERROR) << "stat \"" << fn << "\" failed";
+      return;
     }
+    existing += static_cast<size_t>(sb.st_size);
+  });
 
-    fprintf(stderr, "using existing stash %s\n", dirname.c_str());
-
-    // If the directory already exists, calculate the space already allocated to
-    // stash files and check if there's enough for all required blocks. Delete any
-    // partially completed stash files first.
-
-    EnumerateStash(dirname, DeletePartial, nullptr);
-    int size = 0;
-    EnumerateStash(dirname, UpdateFileSize, &size);
-
-    size = maxblocks * BLOCKSIZE - size;
-
-    if (size > 0 && CacheSizeCheck(size) != 0) {
-        ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%d more needed)\n",
-                   size);
-        return -1;
+  if (max_stash_size > existing) {
+    size_t needed = max_stash_size - existing;
+    if (CacheSizeCheck(needed) != 0) {
+      ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)\n",
+                 needed);
+      return -1;
     }
+  }
 
-    return 0; // Using existing directory
-}
-
-static int SaveStash(CommandParameters& params, const std::string& base,
-        std::vector<uint8_t>& buffer, int fd, bool usehash) {
-
-    // <stash_id> <src_range>
-    if (params.cpos + 1 >= params.tokens.size()) {
-        fprintf(stderr, "missing id and/or src range fields in stash command\n");
-        return -1;
-    }
-    const std::string& id = params.tokens[params.cpos++];
-
-    size_t blocks = 0;
-    if (usehash && LoadStash(params, base, id, true, &blocks, buffer, false) == 0) {
-        // Stash file already exists and has expected contents. Do not
-        // read from source again, as the source may have been already
-        // overwritten during a previous attempt.
-        return 0;
-    }
-
-    RangeSet src;
-    parse_range(params.tokens[params.cpos++], src);
-
-    allocate(src.size * BLOCKSIZE, buffer);
-    if (ReadBlocks(src, buffer, fd) == -1) {
-        return -1;
-    }
-    blocks = src.size;
-
-    if (usehash && VerifyBlocks(id, buffer, blocks, true) != 0) {
-        // Source blocks have unexpected contents. If we actually need this
-        // data later, this is an unrecoverable error. However, the command
-        // that uses the data may have already completed previously, so the
-        // possible failure will occur during source block verification.
-        fprintf(stderr, "failed to load source blocks for stash %s\n", id.c_str());
-        return 0;
-    }
-
-    // In verify mode, save source range_set instead of stashing blocks.
-    if (!params.canwrite && usehash) {
-        stash_map[id] = src;
-        return 0;
-    }
-
-    fprintf(stderr, "stashing %zu blocks to %s\n", blocks, id.c_str());
-    params.stashed += blocks;
-    return WriteStash(base, id, blocks, buffer, false, nullptr);
+  return 0;  // Using existing directory
 }
 
 static int FreeStash(const std::string& base, const std::string& id) {
-    if (base.empty() || id.empty()) {
-        return -1;
-    }
+  if (base.empty() || id.empty()) {
+    return -1;
+  }
 
-    std::string fn = GetStashFileName(base, id, "");
-    DeleteFile(fn, nullptr);
+  DeleteFile(GetStashFileName(base, id, ""));
 
-    return 0;
+  return 0;
 }
 
 static void MoveRange(std::vector<uint8_t>& dest, const RangeSet& locs,
@@ -858,41 +895,39 @@
 //    <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...>
 //        (loads data from both source image and stashes)
 //
-// On return, buffer is filled with the loaded source data (rearranged
-// and combined with stashed data as necessary).  buffer may be
-// reallocated if needed to accommodate the source data.  *tgt is the
-// target RangeSet.  Any stashes required are loaded using LoadStash.
+// On return, params.buffer is filled with the loaded source data (rearranged and combined with
+// stashed data as necessary). buffer may be reallocated if needed to accommodate the source data.
+// *tgt is the target RangeSet. Any stashes required are loaded using LoadStash.
 
 static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
-        std::vector<uint8_t>& buffer, int fd, const std::string& stashbase, bool* overlap) {
+                              bool* overlap) {
 
     // At least it needs to provide three parameters: <tgt_range>,
     // <src_block_count> and "-"/<src_range>.
     if (params.cpos + 2 >= params.tokens.size()) {
-        fprintf(stderr, "invalid parameters\n");
+        LOG(ERROR) << "invalid parameters";
         return -1;
     }
 
     // <tgt_range>
-    parse_range(params.tokens[params.cpos++], tgt);
+    tgt = parse_range(params.tokens[params.cpos++]);
 
     // <src_block_count>
     const std::string& token = params.tokens[params.cpos++];
     if (!android::base::ParseUint(token.c_str(), &src_blocks)) {
-        fprintf(stderr, "invalid src_block_count \"%s\"\n", token.c_str());
+        LOG(ERROR) << "invalid src_block_count \"" << token << "\"";
         return -1;
     }
 
-    allocate(src_blocks * BLOCKSIZE, buffer);
+    allocate(src_blocks * BLOCKSIZE, params.buffer);
 
     // "-" or <src_range> [<src_loc>]
     if (params.tokens[params.cpos] == "-") {
         // no source ranges, only stashes
         params.cpos++;
     } else {
-        RangeSet src;
-        parse_range(params.tokens[params.cpos++], src);
-        int res = ReadBlocks(src, buffer, fd);
+        RangeSet src = parse_range(params.tokens[params.cpos++]);
+        int res = ReadBlocks(src, params.buffer, params.fd);
 
         if (overlap) {
             *overlap = range_overlaps(src, tgt);
@@ -907,9 +942,8 @@
             return 0;
         }
 
-        RangeSet locs;
-        parse_range(params.tokens[params.cpos++], locs);
-        MoveRange(buffer, locs, buffer);
+        RangeSet locs = parse_range(params.tokens[params.cpos++]);
+        MoveRange(params.buffer, locs, params.buffer);
     }
 
     // <[stash_id:stash_range]>
@@ -919,51 +953,59 @@
         // stashed data should go.
         std::vector<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":");
         if (tokens.size() != 2) {
-            fprintf(stderr, "invalid parameter\n");
+            LOG(ERROR) << "invalid parameter";
             return -1;
         }
 
         std::vector<uint8_t> stash;
-        int res = LoadStash(params, stashbase, tokens[0], false, nullptr, stash, true);
+        int res = LoadStash(params, tokens[0], false, nullptr, stash, true);
 
         if (res == -1) {
             // These source blocks will fail verification if used later, but we
             // will let the caller decide if this is a fatal failure
-            fprintf(stderr, "failed to load stash %s\n", tokens[0].c_str());
+            LOG(ERROR) << "failed to load stash " << tokens[0];
             continue;
         }
 
-        RangeSet locs;
-        parse_range(tokens[1], locs);
+        RangeSet locs = parse_range(tokens[1]);
 
-        MoveRange(buffer, locs, stash);
+        MoveRange(params.buffer, locs, stash);
     }
 
     return 0;
 }
 
-// Do a source/target load for move/bsdiff/imgdiff in version 3.
-//
-// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which
-// tells the function whether to expect separate source and targe block hashes, or
-// if they are both the same and only one hash should be expected, and
-// 'isunresumable', which receives a non-zero value if block verification fails in
-// a way that the update cannot be resumed anymore.
-//
-// If the function is unable to load the necessary blocks or their contents don't
-// match the hashes, the return value is -1 and the command should be aborted.
-//
-// If the return value is 1, the command has already been completed according to
-// the contents of the target blocks, and should not be performed again.
-//
-// If the return value is 0, source blocks have expected content and the command
-// can be performed.
-
+/**
+ * Do a source/target load for move/bsdiff/imgdiff in version 3.
+ *
+ * We expect to parse the remainder of the parameter tokens as one of:
+ *
+ *    <tgt_range> <src_block_count> <src_range>
+ *        (loads data from source image only)
+ *
+ *    <tgt_range> <src_block_count> - <[stash_id:stash_range] ...>
+ *        (loads data from stashes only)
+ *
+ *    <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...>
+ *        (loads data from both source image and stashes)
+ *
+ * Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which tells the function
+ * whether to expect separate source and targe block hashes, or if they are both the same and only
+ * one hash should be expected, and 'isunresumable', which receives a non-zero value if block
+ * verification fails in a way that the update cannot be resumed anymore.
+ *
+ * If the function is unable to load the necessary blocks or their contents don't match the hashes,
+ * the return value is -1 and the command should be aborted.
+ *
+ * If the return value is 1, the command has already been completed according to the contents of the
+ * target blocks, and should not be performed again.
+ *
+ * If the return value is 0, source blocks have expected content and the command can be performed.
+ */
 static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
-        bool onehash, bool& overlap) {
-
+                              bool onehash, bool& overlap) {
     if (params.cpos >= params.tokens.size()) {
-        fprintf(stderr, "missing source hash\n");
+        LOG(ERROR) << "missing source hash";
         return -1;
     }
 
@@ -974,14 +1016,13 @@
         tgthash = srchash;
     } else {
         if (params.cpos >= params.tokens.size()) {
-            fprintf(stderr, "missing target hash\n");
+            LOG(ERROR) << "missing target hash";
             return -1;
         }
         tgthash = params.tokens[params.cpos++];
     }
 
-    if (LoadSrcTgtVersion2(params, tgt, src_blocks, params.buffer, params.fd, params.stashbase,
-            &overlap) == -1) {
+    if (LoadSrcTgtVersion2(params, tgt, src_blocks, &overlap) == -1) {
         return -1;
     }
 
@@ -992,7 +1033,7 @@
     }
 
     if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) {
-        // Target blocks already have expected content, command should be skipped
+        // Target blocks already have expected content, command should be skipped.
         return 1;
     }
 
@@ -1001,128 +1042,150 @@
         // resume from possible write errors. In verify mode, we can skip stashing
         // because the source blocks won't be overwritten.
         if (overlap && params.canwrite) {
-            fprintf(stderr, "stashing %zu overlapping blocks to %s\n", src_blocks,
-                    srchash.c_str());
+            LOG(INFO) << "stashing " << src_blocks << " overlapping blocks to " << srchash;
 
             bool stash_exists = false;
             if (WriteStash(params.stashbase, srchash, src_blocks, params.buffer, true,
                            &stash_exists) != 0) {
-                fprintf(stderr, "failed to stash overlapping source blocks\n");
+                LOG(ERROR) << "failed to stash overlapping source blocks";
                 return -1;
             }
 
             params.stashed += src_blocks;
-            // Can be deleted when the write has completed
+            // Can be deleted when the write has completed.
             if (!stash_exists) {
                 params.freestash = srchash;
             }
         }
 
-        // Source blocks have expected content, command can proceed
+        // Source blocks have expected content, command can proceed.
         return 0;
     }
 
-    if (overlap && LoadStash(params, params.stashbase, srchash, true, nullptr, params.buffer,
-                             true) == 0) {
+    if (overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) {
         // Overlapping source blocks were previously stashed, command can proceed.
         // We are recovering from an interrupted command, so we don't know if the
         // stash can safely be deleted after this command.
         return 0;
     }
 
-    // Valid source data not available, update cannot be resumed
-    fprintf(stderr, "partition has unexpected contents\n");
+    // Valid source data not available, update cannot be resumed.
+    LOG(ERROR) << "partition has unexpected contents";
+    PrintHashForCorruptedSourceBlocks(params, params.buffer);
+
     params.isunresumable = true;
 
     return -1;
 }
 
 static int PerformCommandMove(CommandParameters& params) {
-    size_t blocks = 0;
-    bool overlap = false;
-    int status = 0;
-    RangeSet tgt;
+  size_t blocks = 0;
+  bool overlap = false;
+  RangeSet tgt;
+  int status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap);
 
-    if (params.version == 1) {
-        status = LoadSrcTgtVersion1(params, tgt, blocks, params.buffer, params.fd);
-    } else if (params.version == 2) {
-        status = LoadSrcTgtVersion2(params, tgt, blocks, params.buffer, params.fd,
-                params.stashbase, nullptr);
-    } else if (params.version >= 3) {
-        status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap);
-    }
+  if (status == -1) {
+    LOG(ERROR) << "failed to read blocks for move";
+    return -1;
+  }
 
-    if (status == -1) {
-        fprintf(stderr, "failed to read blocks for move\n");
-        return -1;
-    }
+  if (status == 0) {
+    params.foundwrites = true;
+  } else if (params.foundwrites) {
+    LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+  }
 
+  if (params.canwrite) {
     if (status == 0) {
-        params.foundwrites = true;
-    } else if (params.foundwrites) {
-        fprintf(stderr, "warning: commands executed out of order [%s]\n", params.cmdname);
+      LOG(INFO) << "  moving " << blocks << " blocks";
+
+      if (WriteBlocks(tgt, params.buffer, params.fd) == -1) {
+        return -1;
+      }
+    } else {
+      LOG(INFO) << "skipping " << blocks << " already moved blocks";
     }
+  }
 
-    if (params.canwrite) {
-        if (status == 0) {
-            fprintf(stderr, "  moving %zu blocks\n", blocks);
+  if (!params.freestash.empty()) {
+    FreeStash(params.stashbase, params.freestash);
+    params.freestash.clear();
+  }
 
-            if (WriteBlocks(tgt, params.buffer, params.fd) == -1) {
-                return -1;
-            }
-        } else {
-            fprintf(stderr, "skipping %zu already moved blocks\n", blocks);
-        }
+  params.written += tgt.size;
 
-    }
-
-    if (!params.freestash.empty()) {
-        FreeStash(params.stashbase, params.freestash);
-        params.freestash.clear();
-    }
-
-    params.written += tgt.size;
-
-    return 0;
+  return 0;
 }
 
 static int PerformCommandStash(CommandParameters& params) {
-    return SaveStash(params, params.stashbase, params.buffer, params.fd,
-            (params.version >= 3));
+  // <stash_id> <src_range>
+  if (params.cpos + 1 >= params.tokens.size()) {
+    LOG(ERROR) << "missing id and/or src range fields in stash command";
+    return -1;
+  }
+
+  const std::string& id = params.tokens[params.cpos++];
+  size_t blocks = 0;
+  if (LoadStash(params, id, true, &blocks, params.buffer, false) == 0) {
+    // Stash file already exists and has expected contents. Do not read from source again, as the
+    // source may have been already overwritten during a previous attempt.
+    return 0;
+  }
+
+  RangeSet src = parse_range(params.tokens[params.cpos++]);
+
+  allocate(src.size * BLOCKSIZE, params.buffer);
+  if (ReadBlocks(src, params.buffer, params.fd) == -1) {
+    return -1;
+  }
+  blocks = src.size;
+  stash_map[id] = src;
+
+  if (VerifyBlocks(id, params.buffer, blocks, true) != 0) {
+    // Source blocks have unexpected contents. If we actually need this data later, this is an
+    // unrecoverable error. However, the command that uses the data may have already completed
+    // previously, so the possible failure will occur during source block verification.
+    LOG(ERROR) << "failed to load source blocks for stash " << id;
+    return 0;
+  }
+
+  // In verify mode, we don't need to stash any blocks.
+  if (!params.canwrite) {
+    return 0;
+  }
+
+  LOG(INFO) << "stashing " << blocks << " blocks to " << id;
+  params.stashed += blocks;
+  return WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr);
 }
 
 static int PerformCommandFree(CommandParameters& params) {
-    // <stash_id>
-    if (params.cpos >= params.tokens.size()) {
-        fprintf(stderr, "missing stash id in free command\n");
-        return -1;
-    }
+  // <stash_id>
+  if (params.cpos >= params.tokens.size()) {
+    LOG(ERROR) << "missing stash id in free command";
+    return -1;
+  }
 
-    const std::string& id = params.tokens[params.cpos++];
+  const std::string& id = params.tokens[params.cpos++];
+  stash_map.erase(id);
 
-    if (!params.canwrite && stash_map.find(id) != stash_map.end()) {
-        stash_map.erase(id);
-        return 0;
-    }
+  if (params.createdstash || params.canwrite) {
+    return FreeStash(params.stashbase, id);
+  }
 
-    if (params.createdstash || params.canwrite) {
-        return FreeStash(params.stashbase, id);
-    }
-
-    return 0;
+  return 0;
 }
 
 static int PerformCommandZero(CommandParameters& params) {
 
     if (params.cpos >= params.tokens.size()) {
-        fprintf(stderr, "missing target blocks for zero\n");
+        LOG(ERROR) << "missing target blocks for zero";
         return -1;
     }
 
-    RangeSet tgt;
-    parse_range(params.tokens[params.cpos++], tgt);
+    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
 
-    fprintf(stderr, "  zeroing %zu blocks\n", tgt.size);
+    LOG(INFO) << "  zeroing " << tgt.size << " blocks";
 
     allocate(BLOCKSIZE, params.buffer);
     memset(params.buffer.data(), 0, BLOCKSIZE);
@@ -1159,15 +1222,14 @@
 static int PerformCommandNew(CommandParameters& params) {
 
     if (params.cpos >= params.tokens.size()) {
-        fprintf(stderr, "missing target blocks for new\n");
+        LOG(ERROR) << "missing target blocks for new";
         return -1;
     }
 
-    RangeSet tgt;
-    parse_range(params.tokens[params.cpos++], tgt);
+    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
 
     if (params.canwrite) {
-        fprintf(stderr, " writing %zu blocks of new data\n", tgt.size);
+        LOG(INFO) << " writing " << tgt.size << " blocks of new data";
 
         RangeSinkState rss(tgt);
         rss.fd = params.fd;
@@ -1203,55 +1265,43 @@
 
     // <offset> <length>
     if (params.cpos + 1 >= params.tokens.size()) {
-        fprintf(stderr, "missing patch offset or length for %s\n", params.cmdname);
+        LOG(ERROR) << "missing patch offset or length for " << params.cmdname;
         return -1;
     }
 
     size_t offset;
     if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &offset)) {
-        fprintf(stderr, "invalid patch offset\n");
+        LOG(ERROR) << "invalid patch offset";
         return -1;
     }
 
     size_t len;
     if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &len)) {
-        fprintf(stderr, "invalid patch offset\n");
+        LOG(ERROR) << "invalid patch len";
         return -1;
     }
 
     RangeSet tgt;
     size_t blocks = 0;
     bool overlap = false;
-    int status = 0;
-    if (params.version == 1) {
-        status = LoadSrcTgtVersion1(params, tgt, blocks, params.buffer, params.fd);
-    } else if (params.version == 2) {
-        status = LoadSrcTgtVersion2(params, tgt, blocks, params.buffer, params.fd,
-                params.stashbase, nullptr);
-    } else if (params.version >= 3) {
-        status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap);
-    }
+    int status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap);
 
     if (status == -1) {
-        fprintf(stderr, "failed to read blocks for diff\n");
+        LOG(ERROR) << "failed to read blocks for diff";
         return -1;
     }
 
     if (status == 0) {
         params.foundwrites = true;
     } else if (params.foundwrites) {
-        fprintf(stderr, "warning: commands executed out of order [%s]\n", params.cmdname);
+        LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
     }
 
     if (params.canwrite) {
         if (status == 0) {
-            fprintf(stderr, "patching %zu blocks to %zu\n", blocks, tgt.size);
-
-            Value patch_value;
-            patch_value.type = VAL_BLOB;
-            patch_value.size = len;
-            patch_value.data = (char*) (params.patch_start + offset);
-
+            LOG(INFO) << "patching " << blocks << " blocks to " << tgt.size;
+            Value patch_value(VAL_BLOB,
+                    std::string(reinterpret_cast<const char*>(params.patch_start + offset), len));
             RangeSinkState rss(tgt);
             rss.fd = params.fd;
             rss.p_block = 0;
@@ -1269,24 +1319,24 @@
             if (params.cmdname[0] == 'i') {      // imgdiff
                 if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value,
                         &RangeSinkWrite, &rss, nullptr, nullptr) != 0) {
-                    fprintf(stderr, "Failed to apply image patch.\n");
+                    LOG(ERROR) << "Failed to apply image patch.";
                     return -1;
                 }
             } else {
                 if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value,
                         0, &RangeSinkWrite, &rss, nullptr) != 0) {
-                    fprintf(stderr, "Failed to apply bsdiff patch.\n");
+                    LOG(ERROR) << "Failed to apply bsdiff patch.";
                     return -1;
                 }
             }
 
             // We expect the output of the patcher to fill the tgt ranges exactly.
             if (rss.p_block != tgt.count || rss.p_remain != 0) {
-                fprintf(stderr, "range sink underrun?\n");
+                LOG(ERROR) << "range sink underrun?";
             }
         } else {
-            fprintf(stderr, "skipping %zu blocks already patched to %zu [%s]\n",
-                blocks, tgt.size, params.cmdline);
+            LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.size
+                      << " [" << params.cmdline << "]";
         }
     }
 
@@ -1307,25 +1357,24 @@
 
     struct stat sb;
     if (fstat(params.fd, &sb) == -1) {
-        fprintf(stderr, "failed to fstat device to erase: %s\n", strerror(errno));
+        PLOG(ERROR) << "failed to fstat device to erase";
         return -1;
     }
 
     if (!S_ISBLK(sb.st_mode)) {
-        fprintf(stderr, "not a block device; skipping erase\n");
+        LOG(ERROR) << "not a block device; skipping erase";
         return -1;
     }
 
     if (params.cpos >= params.tokens.size()) {
-        fprintf(stderr, "missing target blocks for erase\n");
+        LOG(ERROR) << "missing target blocks for erase";
         return -1;
     }
 
-    RangeSet tgt;
-    parse_range(params.tokens[params.cpos++], tgt);
+    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
 
     if (params.canwrite) {
-        fprintf(stderr, " erasing %zu blocks\n", tgt.size);
+        LOG(INFO) << " erasing " << tgt.size << " blocks";
 
         for (size_t i = 0; i < tgt.count; ++i) {
             uint64_t blocks[2];
@@ -1335,7 +1384,7 @@
             blocks[1] = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * (uint64_t) BLOCKSIZE;
 
             if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) {
-                fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno));
+                PLOG(ERROR) << "BLKDISCARD ioctl failed";
                 return -1;
             }
         }
@@ -1352,339 +1401,292 @@
     CommandFunction f;
 };
 
-// CompareCommands and CompareCommandNames are for the hash table
-
-static int CompareCommands(const void* c1, const void* c2) {
-    return strcmp(((const Command*) c1)->name, ((const Command*) c2)->name);
-}
-
-static int CompareCommandNames(const void* c1, const void* c2) {
-    return strcmp(((const Command*) c1)->name, (const char*) c2);
-}
-
-// HashString is used to hash command names for the hash table
-
-static unsigned int HashString(const char *s) {
-    unsigned int hash = 0;
-    if (s) {
-        while (*s) {
-            hash = hash * 33 + *s++;
-        }
-    }
-    return hash;
-}
-
 // args:
 //    - block device (or file) to modify in-place
 //    - transfer list (blob)
 //    - new data stream (filename within package.zip)
 //    - patch stream (filename within package.zip, must be uncompressed)
 
-static Value* PerformBlockImageUpdate(const char* name, State* state, int /* argc */, Expr* argv[],
-        const Command* commands, size_t cmdcount, bool dryrun) {
-    CommandParameters params;
-    memset(&params, 0, sizeof(params));
-    params.canwrite = !dryrun;
+static Value* PerformBlockImageUpdate(const char* name, State* state,
+                                      const std::vector<std::unique_ptr<Expr>>& argv,
+                                      const Command* commands, size_t cmdcount, bool dryrun) {
+  CommandParameters params = {};
+  params.canwrite = !dryrun;
 
-    fprintf(stderr, "performing %s\n", dryrun ? "verification" : "update");
-    if (state->is_retry) {
-        is_retry = true;
-        fprintf(stderr, "This update is a retry.\n");
+  LOG(INFO) << "performing " << (dryrun ? "verification" : "update");
+  if (state->is_retry) {
+    is_retry = true;
+    LOG(INFO) << "This update is a retry.";
+  }
+  if (argv.size() != 4) {
+    ErrorAbort(state, kArgsParsingFailure, "block_image_update expects 4 arguments, got %zu",
+               argv.size());
+    return StringValue("");
+  }
+
+  std::vector<std::unique_ptr<Value>> args;
+  if (!ReadValueArgs(state, argv, &args)) {
+    return nullptr;
+  }
+
+  const Value* blockdev_filename = args[0].get();
+  const Value* transfer_list_value = args[1].get();
+  const Value* new_data_fn = args[2].get();
+  const Value* patch_data_fn = args[3].get();
+
+  if (blockdev_filename->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name);
+    return StringValue("");
+  }
+  if (transfer_list_value->type != VAL_BLOB) {
+    ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name);
+    return StringValue("");
+  }
+  if (new_data_fn->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name);
+    return StringValue("");
+  }
+  if (patch_data_fn->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string", name);
+    return StringValue("");
+  }
+
+  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
+  if (ui == nullptr) {
+    return StringValue("");
+  }
+
+  FILE* cmd_pipe = ui->cmd_pipe;
+  ZipArchiveHandle za = ui->package_zip;
+
+  if (cmd_pipe == nullptr || za == nullptr) {
+    return StringValue("");
+  }
+
+  ZipString path_data(patch_data_fn->data.c_str());
+  ZipEntry patch_entry;
+  if (FindEntry(za, path_data, &patch_entry) != 0) {
+    LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package";
+    return StringValue("");
+  }
+
+  params.patch_start = ui->package_zip_addr + patch_entry.offset;
+  ZipString new_data(new_data_fn->data.c_str());
+  ZipEntry new_entry;
+  if (FindEntry(za, new_data, &new_entry) != 0) {
+    LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package";
+    return StringValue("");
+  }
+
+  params.fd.reset(TEMP_FAILURE_RETRY(ota_open(blockdev_filename->data.c_str(), O_RDWR)));
+  if (params.fd == -1) {
+    PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
+    return StringValue("");
+  }
+
+  if (params.canwrite) {
+    params.nti.za = za;
+    params.nti.entry = new_entry;
+
+    pthread_mutex_init(&params.nti.mu, nullptr);
+    pthread_cond_init(&params.nti.cv, nullptr);
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+    int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
+    if (error != 0) {
+      PLOG(ERROR) << "pthread_create failed";
+      return StringValue("");
+    }
+  }
+
+  std::vector<std::string> lines = android::base::Split(transfer_list_value->data, "\n");
+  if (lines.size() < 2) {
+    ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n",
+               lines.size());
+    return StringValue("");
+  }
+
+  // First line in transfer list is the version number.
+  if (!android::base::ParseInt(lines[0], &params.version, 3, 4)) {
+    LOG(ERROR) << "unexpected transfer list version [" << lines[0] << "]";
+    return StringValue("");
+  }
+
+  LOG(INFO) << "blockimg version is " << params.version;
+
+  // Second line in transfer list is the total number of blocks we expect to write.
+  size_t total_blocks;
+  if (!android::base::ParseUint(lines[1], &total_blocks)) {
+    ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str());
+    return StringValue("");
+  }
+
+  if (total_blocks == 0) {
+    return StringValue("t");
+  }
+
+  size_t start = 2;
+  if (lines.size() < 4) {
+    ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
+               lines.size());
+    return StringValue("");
+  }
+
+  // Third line is how many stash entries are needed simultaneously.
+  LOG(INFO) << "maximum stash entries " << lines[2];
+
+  // Fourth line is the maximum number of blocks that will be stashed simultaneously
+  size_t stash_max_blocks;
+  if (!android::base::ParseUint(lines[3], &stash_max_blocks)) {
+    ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n",
+               lines[3].c_str());
+    return StringValue("");
+  }
+
+  int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase);
+  if (res == -1) {
+    return StringValue("");
+  }
+
+  params.createdstash = res;
+
+  start += 2;
+
+  // Build a map of the available commands
+  std::unordered_map<std::string, const Command*> cmd_map;
+  for (size_t i = 0; i < cmdcount; ++i) {
+    if (cmd_map.find(commands[i].name) != cmd_map.end()) {
+      LOG(ERROR) << "Error: command [" << commands[i].name << "] already exists in the cmd map.";
+      return StringValue(strdup(""));
+    }
+    cmd_map[commands[i].name] = &commands[i];
+  }
+
+  int rc = -1;
+
+  // Subsequent lines are all individual transfer commands
+  for (auto it = lines.cbegin() + start; it != lines.cend(); it++) {
+    const std::string& line(*it);
+    if (line.empty()) continue;
+
+    params.tokens = android::base::Split(line, " ");
+    params.cpos = 0;
+    params.cmdname = params.tokens[params.cpos++].c_str();
+    params.cmdline = line.c_str();
+
+    if (cmd_map.find(params.cmdname) == cmd_map.end()) {
+      LOG(ERROR) << "unexpected command [" << params.cmdname << "]";
+      goto pbiudone;
     }
 
-    Value* blockdev_filename = nullptr;
-    Value* transfer_list_value = nullptr;
-    Value* new_data_fn = nullptr;
-    Value* patch_data_fn = nullptr;
-    if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value,
-            &new_data_fn, &patch_data_fn) < 0) {
-        return StringValue(strdup(""));
-    }
-    std::unique_ptr<Value, decltype(&FreeValue)> blockdev_filename_holder(blockdev_filename,
-            FreeValue);
-    std::unique_ptr<Value, decltype(&FreeValue)> transfer_list_value_holder(transfer_list_value,
-            FreeValue);
-    std::unique_ptr<Value, decltype(&FreeValue)> new_data_fn_holder(new_data_fn, FreeValue);
-    std::unique_ptr<Value, decltype(&FreeValue)> patch_data_fn_holder(patch_data_fn, FreeValue);
+    const Command* cmd = cmd_map[params.cmdname];
 
-    if (blockdev_filename->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string",
-                   name);
-        return StringValue(strdup(""));
-    }
-    if (transfer_list_value->type != VAL_BLOB) {
-        ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name);
-        return StringValue(strdup(""));
-    }
-    if (new_data_fn->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name);
-        return StringValue(strdup(""));
-    }
-    if (patch_data_fn->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string",
-                   name);
-        return StringValue(strdup(""));
-    }
-
-    UpdaterInfo* ui = reinterpret_cast<UpdaterInfo*>(state->cookie);
-
-    if (ui == nullptr) {
-        return StringValue(strdup(""));
-    }
-
-    FILE* cmd_pipe = ui->cmd_pipe;
-    ZipArchive* za = ui->package_zip;
-
-    if (cmd_pipe == nullptr || za == nullptr) {
-        return StringValue(strdup(""));
-    }
-
-    const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data);
-    if (patch_entry == nullptr) {
-        fprintf(stderr, "%s(): no file \"%s\" in package", name, patch_data_fn->data);
-        return StringValue(strdup(""));
-    }
-
-    params.patch_start = ui->package_zip_addr + mzGetZipEntryOffset(patch_entry);
-    const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data);
-    if (new_entry == nullptr) {
-        fprintf(stderr, "%s(): no file \"%s\" in package", name, new_data_fn->data);
-        return StringValue(strdup(""));
-    }
-
-    params.fd = TEMP_FAILURE_RETRY(open(blockdev_filename->data, O_RDWR));
-    unique_fd fd_holder(params.fd);
-
-    if (params.fd == -1) {
-        fprintf(stderr, "open \"%s\" failed: %s\n", blockdev_filename->data, strerror(errno));
-        return StringValue(strdup(""));
+    if (cmd->f != nullptr && cmd->f(params) == -1) {
+      LOG(ERROR) << "failed to execute command [" << line << "]";
+      goto pbiudone;
     }
 
     if (params.canwrite) {
-        params.nti.za = za;
-        params.nti.entry = new_entry;
-
-        pthread_mutex_init(&params.nti.mu, nullptr);
-        pthread_cond_init(&params.nti.cv, nullptr);
-        pthread_attr_t attr;
-        pthread_attr_init(&attr);
-        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
-
-        int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
-        if (error != 0) {
-            fprintf(stderr, "pthread_create failed: %s\n", strerror(error));
-            return StringValue(strdup(""));
-        }
+      if (ota_fsync(params.fd) == -1) {
+        failure_type = kFsyncFailure;
+        PLOG(ERROR) << "fsync failed";
+        goto pbiudone;
+      }
+      fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks);
+      fflush(cmd_pipe);
     }
+  }
 
-    // Copy all the lines in transfer_list_value into std::string for
-    // processing.
-    const std::string transfer_list(transfer_list_value->data, transfer_list_value->size);
-    std::vector<std::string> lines = android::base::Split(transfer_list, "\n");
-    if (lines.size() < 2) {
-        ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n",
-                   lines.size());
-        return StringValue(strdup(""));
+  if (params.canwrite) {
+    pthread_join(params.thread, nullptr);
+
+    LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
+    LOG(INFO) << "stashed " << params.stashed << " blocks";
+    LOG(INFO) << "max alloc needed was " << params.buffer.size();
+
+    const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+    if (partition != nullptr && *(partition + 1) != 0) {
+      fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
+      fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
+      fflush(cmd_pipe);
     }
+    // Delete stash only after successfully completing the update, as it may contain blocks needed
+    // to complete the update later.
+    DeleteStash(params.stashbase);
+  } else {
+    LOG(INFO) << "verified partition contents; update may be resumed";
+  }
 
-    // First line in transfer list is the version number
-    if (!android::base::ParseInt(lines[0].c_str(), &params.version, 1, 4)) {
-        fprintf(stderr, "unexpected transfer list version [%s]\n", lines[0].c_str());
-        return StringValue(strdup(""));
-    }
-
-    fprintf(stderr, "blockimg version is %d\n", params.version);
-
-    // Second line in transfer list is the total number of blocks we expect to write
-    int total_blocks;
-    if (!android::base::ParseInt(lines[1].c_str(), &total_blocks, 0)) {
-        ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str());
-        return StringValue(strdup(""));
-    }
-
-    if (total_blocks == 0) {
-        return StringValue(strdup("t"));
-    }
-
-    size_t start = 2;
-    if (params.version >= 2) {
-        if (lines.size() < 4) {
-            ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
-                       lines.size());
-            return StringValue(strdup(""));
-        }
-
-        // Third line is how many stash entries are needed simultaneously
-        fprintf(stderr, "maximum stash entries %s\n", lines[2].c_str());
-
-        // Fourth line is the maximum number of blocks that will be stashed simultaneously
-        int stash_max_blocks;
-        if (!android::base::ParseInt(lines[3].c_str(), &stash_max_blocks, 0)) {
-            ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n",
-                       lines[3].c_str());
-            return StringValue(strdup(""));
-        }
-
-        int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase);
-        if (res == -1) {
-            return StringValue(strdup(""));
-        }
-
-        params.createdstash = res;
-
-        start += 2;
-    }
-
-    // Build a hash table of the available commands
-    HashTable* cmdht = mzHashTableCreate(cmdcount, nullptr);
-    std::unique_ptr<HashTable, decltype(&mzHashTableFree)> cmdht_holder(cmdht, mzHashTableFree);
-
-    for (size_t i = 0; i < cmdcount; ++i) {
-        unsigned int cmdhash = HashString(commands[i].name);
-        mzHashTableLookup(cmdht, cmdhash, (void*) &commands[i], CompareCommands, true);
-    }
-
-    int rc = -1;
-
-    // Subsequent lines are all individual transfer commands
-    for (auto it = lines.cbegin() + start; it != lines.cend(); it++) {
-        const std::string& line_str(*it);
-        if (line_str.empty()) {
-            continue;
-        }
-
-        params.tokens = android::base::Split(line_str, " ");
-        params.cpos = 0;
-        params.cmdname = params.tokens[params.cpos++].c_str();
-        params.cmdline = line_str.c_str();
-
-        unsigned int cmdhash = HashString(params.cmdname);
-        const Command* cmd = reinterpret_cast<const Command*>(mzHashTableLookup(cmdht, cmdhash,
-                const_cast<char*>(params.cmdname), CompareCommandNames,
-                false));
-
-        if (cmd == nullptr) {
-            fprintf(stderr, "unexpected command [%s]\n", params.cmdname);
-            goto pbiudone;
-        }
-
-        if (cmd->f != nullptr && cmd->f(params) == -1) {
-            fprintf(stderr, "failed to execute command [%s]\n", line_str.c_str());
-            goto pbiudone;
-        }
-
-        if (params.canwrite) {
-            if (ota_fsync(params.fd) == -1) {
-                failure_type = kFsyncFailure;
-                fprintf(stderr, "fsync failed: %s\n", strerror(errno));
-                goto pbiudone;
-            }
-            fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks);
-            fflush(cmd_pipe);
-        }
-    }
-
-    if (params.canwrite) {
-        pthread_join(params.thread, nullptr);
-
-        fprintf(stderr, "wrote %zu blocks; expected %d\n", params.written, total_blocks);
-        fprintf(stderr, "stashed %zu blocks\n", params.stashed);
-        fprintf(stderr, "max alloc needed was %zu\n", params.buffer.size());
-
-        const char* partition = strrchr(blockdev_filename->data, '/');
-        if (partition != nullptr && *(partition+1) != 0) {
-            fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1,
-                    params.written * BLOCKSIZE);
-            fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1,
-                    params.stashed * BLOCKSIZE);
-            fflush(cmd_pipe);
-        }
-        // Delete stash only after successfully completing the update, as it
-        // may contain blocks needed to complete the update later.
-        DeleteStash(params.stashbase);
-    } else {
-        fprintf(stderr, "verified partition contents; update may be resumed\n");
-    }
-
-    rc = 0;
+  rc = 0;
 
 pbiudone:
-    if (ota_fsync(params.fd) == -1) {
-        failure_type = kFsyncFailure;
-        fprintf(stderr, "fsync failed: %s\n", strerror(errno));
-    }
-    // params.fd will be automatically closed because of the fd_holder above.
+  if (ota_fsync(params.fd) == -1) {
+    failure_type = kFsyncFailure;
+    PLOG(ERROR) << "fsync failed";
+  }
+  // params.fd will be automatically closed because it's a unique_fd.
 
-    // Only delete the stash if the update cannot be resumed, or it's
-    // a verification run and we created the stash.
-    if (params.isunresumable || (!params.canwrite && params.createdstash)) {
-        DeleteStash(params.stashbase);
-    }
+  // Only delete the stash if the update cannot be resumed, or it's a verification run and we
+  // created the stash.
+  if (params.isunresumable || (!params.canwrite && params.createdstash)) {
+    DeleteStash(params.stashbase);
+  }
 
-    if (failure_type != kNoCause && state->cause_code == kNoCause) {
-        state->cause_code = failure_type;
-    }
+  if (failure_type != kNoCause && state->cause_code == kNoCause) {
+    state->cause_code = failure_type;
+  }
 
-    return StringValue(rc == 0 ? strdup("t") : strdup(""));
+  return StringValue(rc == 0 ? "t" : "");
 }
 
-// The transfer list is a text file containing commands to
-// transfer data from one place to another on the target
-// partition.  We parse it and execute the commands in order:
-//
-//    zero [rangeset]
-//      - fill the indicated blocks with zeros
-//
-//    new [rangeset]
-//      - fill the blocks with data read from the new_data file
-//
-//    erase [rangeset]
-//      - mark the given blocks as empty
-//
-//    move <...>
-//    bsdiff <patchstart> <patchlen> <...>
-//    imgdiff <patchstart> <patchlen> <...>
-//      - read the source blocks, apply a patch (or not in the
-//        case of move), write result to target blocks.  bsdiff or
-//        imgdiff specifies the type of patch; move means no patch
-//        at all.
-//
-//        The format of <...> differs between versions 1 and 2;
-//        see the LoadSrcTgtVersion{1,2}() functions for a
-//        description of what's expected.
-//
-//    stash <stash_id> <src_range>
-//      - (version 2+ only) load the given source range and stash
-//        the data in the given slot of the stash table.
-//
-//    free <stash_id>
-//      - (version 3+ only) free the given stash data.
-//
-// The creator of the transfer list will guarantee that no block
-// is read (ie, used as the source for a patch or move) after it
-// has been written.
-//
-// In version 2, the creator will guarantee that a given stash is
-// loaded (with a stash command) before it's used in a
-// move/bsdiff/imgdiff command.
-//
-// Within one command the source and target ranges may overlap so
-// in general we need to read the entire source into memory before
-// writing anything to the target blocks.
-//
-// All the patch data is concatenated into one patch_data file in
-// the update package.  It must be stored uncompressed because we
-// memory-map it in directly from the archive.  (Since patches are
-// already compressed, we lose very little by not compressing
-// their concatenation.)
-//
-// In version 3, commands that read data from the partition (i.e.
-// move/bsdiff/imgdiff/stash) have one or more additional hashes
-// before the range parameters, which are used to check if the
-// command has already been completed and verify the integrity of
-// the source data.
-
-Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) {
+/**
+ * The transfer list is a text file containing commands to transfer data from one place to another
+ * on the target partition. We parse it and execute the commands in order:
+ *
+ *    zero [rangeset]
+ *      - Fill the indicated blocks with zeros.
+ *
+ *    new [rangeset]
+ *      - Fill the blocks with data read from the new_data file.
+ *
+ *    erase [rangeset]
+ *      - Mark the given blocks as empty.
+ *
+ *    move <...>
+ *    bsdiff <patchstart> <patchlen> <...>
+ *    imgdiff <patchstart> <patchlen> <...>
+ *      - Read the source blocks, apply a patch (or not in the case of move), write result to target
+ *      blocks.  bsdiff or imgdiff specifies the type of patch; move means no patch at all.
+ *
+ *        See the comments in LoadSrcTgtVersion3() for a description of the <...> format.
+ *
+ *    stash <stash_id> <src_range>
+ *      - Load the given source range and stash the data in the given slot of the stash table.
+ *
+ *    free <stash_id>
+ *      - Free the given stash data.
+ *
+ * The creator of the transfer list will guarantee that no block is read (ie, used as the source for
+ * a patch or move) after it has been written.
+ *
+ * The creator will guarantee that a given stash is loaded (with a stash command) before it's used
+ * in a move/bsdiff/imgdiff command.
+ *
+ * Within one command the source and target ranges may overlap so in general we need to read the
+ * entire source into memory before writing anything to the target blocks.
+ *
+ * All the patch data is concatenated into one patch_data file in the update package. It must be
+ * stored uncompressed because we memory-map it in directly from the archive. (Since patches are
+ * already compressed, we lose very little by not compressing their concatenation.)
+ *
+ * Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more
+ * additional hashes before the range parameters, which are used to check if the command has already
+ * been completed and verify the integrity of the source data.
+ */
+Value* BlockImageVerifyFn(const char* name, State* state,
+                          const std::vector<std::unique_ptr<Expr>>& argv) {
     // Commands which are not tested are set to nullptr to skip them completely
     const Command commands[] = {
         { "bsdiff",     PerformCommandDiff  },
@@ -1698,11 +1700,12 @@
     };
 
     // Perform a dry run without writing to test if an update can proceed
-    return PerformBlockImageUpdate(name, state, argc, argv, commands,
+    return PerformBlockImageUpdate(name, state, argv, commands,
                 sizeof(commands) / sizeof(commands[0]), true);
 }
 
-Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* BlockImageUpdateFn(const char* name, State* state,
+                          const std::vector<std::unique_ptr<Expr>>& argv) {
     const Command commands[] = {
         { "bsdiff",     PerformCommandDiff  },
         { "erase",      PerformCommandErase },
@@ -1714,41 +1717,43 @@
         { "zero",       PerformCommandZero  }
     };
 
-    return PerformBlockImageUpdate(name, state, argc, argv, commands,
+    return PerformBlockImageUpdate(name, state, argv, commands,
                 sizeof(commands) / sizeof(commands[0]), false);
 }
 
-Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[]) {
-    Value* blockdev_filename;
-    Value* ranges;
-
-    if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) {
-        return StringValue(strdup(""));
+Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2) {
+        ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu",
+                   argv.size());
+        return StringValue("");
     }
-    std::unique_ptr<Value, decltype(&FreeValue)> ranges_holder(ranges, FreeValue);
-    std::unique_ptr<Value, decltype(&FreeValue)> blockdev_filename_holder(blockdev_filename,
-            FreeValue);
+
+    std::vector<std::unique_ptr<Value>> args;
+    if (!ReadValueArgs(state, argv, &args)) {
+        return nullptr;
+    }
+
+    const Value* blockdev_filename = args[0].get();
+    const Value* ranges = args[1].get();
 
     if (blockdev_filename->type != VAL_STRING) {
         ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string",
                    name);
-        return StringValue(strdup(""));
+        return StringValue("");
     }
     if (ranges->type != VAL_STRING) {
         ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
-        return StringValue(strdup(""));
+        return StringValue("");
     }
 
-    int fd = open(blockdev_filename->data, O_RDWR);
-    unique_fd fd_holder(fd);
-    if (fd < 0) {
-        ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", blockdev_filename->data,
-                   strerror(errno));
-        return StringValue(strdup(""));
+    android::base::unique_fd fd(ota_open(blockdev_filename->data.c_str(), O_RDWR));
+    if (fd == -1) {
+        ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s",
+                   blockdev_filename->data.c_str(), strerror(errno));
+        return StringValue("");
     }
 
-    RangeSet rs;
-    parse_range(ranges->data, rs);
+    RangeSet rs = parse_range(ranges->data);
 
     SHA_CTX ctx;
     SHA1_Init(&ctx);
@@ -1756,16 +1761,16 @@
     std::vector<uint8_t> buffer(BLOCKSIZE);
     for (size_t i = 0; i < rs.count; ++i) {
         if (!check_lseek(fd, (off64_t)rs.pos[i*2] * BLOCKSIZE, SEEK_SET)) {
-            ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data,
-                       strerror(errno));
-            return StringValue(strdup(""));
+            ErrorAbort(state, kLseekFailure, "failed to seek %s: %s",
+                       blockdev_filename->data.c_str(), strerror(errno));
+            return StringValue("");
         }
 
         for (size_t j = rs.pos[i*2]; j < rs.pos[i*2+1]; ++j) {
             if (read_all(fd, buffer, BLOCKSIZE) == -1) {
-                ErrorAbort(state, kFreadFailure, "failed to read %s: %s", blockdev_filename->data,
-                        strerror(errno));
-                return StringValue(strdup(""));
+                ErrorAbort(state, kFreadFailure, "failed to read %s: %s",
+                           blockdev_filename->data.c_str(), strerror(errno));
+                return StringValue("");
             }
 
             SHA1_Update(&ctx, buffer.data(), BLOCKSIZE);
@@ -1774,7 +1779,7 @@
     uint8_t digest[SHA_DIGEST_LENGTH];
     SHA1_Final(digest, &ctx);
 
-    return StringValue(strdup(print_sha1(digest).c_str()));
+    return StringValue(print_sha1(digest));
 }
 
 // This function checks if a device has been remounted R/W prior to an incremental
@@ -1782,34 +1787,40 @@
 // 1st block of each partition and check for mounting time/count. It return string "t"
 // if executes successfully and an empty string otherwise.
 
-Value* CheckFirstBlockFn(const char* name, State* state, int argc, Expr* argv[]) {
-    Value* arg_filename;
+Value* CheckFirstBlockFn(const char* name, State* state,
+                         const std::vector<std::unique_ptr<Expr>>& argv) {
+     if (argv.size() != 1) {
+        ErrorAbort(state, kArgsParsingFailure, "check_first_block expects 1 argument, got %zu",
+                   argv.size());
+        return StringValue("");
+    }
 
-    if (ReadValueArgs(state, argv, 1, &arg_filename) < 0) {
+    std::vector<std::unique_ptr<Value>> args;
+    if (!ReadValueArgs(state, argv, &args)) {
         return nullptr;
     }
-    std::unique_ptr<Value, decltype(&FreeValue)> filename(arg_filename, FreeValue);
 
-    if (filename->type != VAL_STRING) {
+    const Value* arg_filename = args[0].get();
+
+    if (arg_filename->type != VAL_STRING) {
         ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name);
-        return StringValue(strdup(""));
+        return StringValue("");
     }
 
-    int fd = open(arg_filename->data, O_RDONLY);
-    unique_fd fd_holder(fd);
+    android::base::unique_fd fd(ota_open(arg_filename->data.c_str(), O_RDONLY));
     if (fd == -1) {
-        ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data,
+        ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data.c_str(),
                    strerror(errno));
-        return StringValue(strdup(""));
+        return StringValue("");
     }
 
     RangeSet blk0 {1 /*count*/, 1/*size*/, std::vector<size_t> {0, 1}/*position*/};
     std::vector<uint8_t> block0_buffer(BLOCKSIZE);
 
     if (ReadBlocks(blk0, block0_buffer, fd) == -1) {
-        ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data,
+        ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(),
                 strerror(errno));
-        return StringValue(strdup(""));
+        return StringValue("");
     }
 
     // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
@@ -1827,56 +1838,60 @@
         uiPrintf(state, "Last remount happened on %s", ctime(&mount_time));
     }
 
-    return StringValue(strdup("t"));
+    return StringValue("t");
 }
 
 
-Value* BlockImageRecoverFn(const char* name, State* state, int argc, Expr* argv[]) {
-    Value* arg_filename;
-    Value* arg_ranges;
-
-    if (ReadValueArgs(state, argv, 2, &arg_filename, &arg_ranges) < 0) {
-        return NULL;
+Value* BlockImageRecoverFn(const char* name, State* state,
+                           const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2) {
+        ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu",
+                   argv.size());
+        return StringValue("");
     }
 
-    std::unique_ptr<Value, decltype(&FreeValue)> filename(arg_filename, FreeValue);
-    std::unique_ptr<Value, decltype(&FreeValue)> ranges(arg_ranges, FreeValue);
+    std::vector<std::unique_ptr<Value>> args;
+    if (!ReadValueArgs(state, argv, &args)) {
+        return nullptr;
+    }
+
+    const Value* filename = args[0].get();
+    const Value* ranges = args[1].get();
 
     if (filename->type != VAL_STRING) {
         ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name);
-        return StringValue(strdup(""));
+        return StringValue("");
     }
     if (ranges->type != VAL_STRING) {
         ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
-        return StringValue(strdup(""));
+        return StringValue("");
     }
 
     // Output notice to log when recover is attempted
-    fprintf(stderr, "%s image corrupted, attempting to recover...\n", filename->data);
+    LOG(INFO) << filename->data << " image corrupted, attempting to recover...";
 
     // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read
-    fec::io fh(filename->data, O_RDWR);
+    fec::io fh(filename->data.c_str(), O_RDWR);
 
     if (!fh) {
-        ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data,
+        ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(),
                    strerror(errno));
-        return StringValue(strdup(""));
+        return StringValue("");
     }
 
     if (!fh.has_ecc() || !fh.has_verity()) {
         ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors");
-        return StringValue(strdup(""));
+        return StringValue("");
     }
 
     fec_status status;
 
     if (!fh.get_status(status)) {
         ErrorAbort(state, kLibfecFailure, "failed to read FEC status");
-        return StringValue(strdup(""));
+        return StringValue("");
     }
 
-    RangeSet rs;
-    parse_range(ranges->data, rs);
+    RangeSet rs = parse_range(ranges->data);
 
     uint8_t buffer[BLOCKSIZE];
 
@@ -1889,8 +1904,8 @@
 
             if (fh.pread(buffer, BLOCKSIZE, (off64_t)j * BLOCKSIZE) != BLOCKSIZE) {
                 ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s",
-                           filename->data, j, strerror(errno));
-                return StringValue(strdup(""));
+                           filename->data.c_str(), j, strerror(errno));
+                return StringValue("");
             }
 
             // If we want to be able to recover from a situation where rewriting a corrected
@@ -1905,8 +1920,8 @@
             //     read and check if the errors field value has increased.
         }
     }
-    fprintf(stderr, "...%s image recovered successfully.\n", filename->data);
-    return StringValue(strdup("t"));
+    LOG(INFO) << "..." << filename->data << " image recovered successfully.";
+    return StringValue("t");
 }
 
 void RegisterBlockImageFunctions() {
diff --git a/updater/blockimg.h b/updater/include/updater/blockimg.h
similarity index 100%
rename from updater/blockimg.h
rename to updater/include/updater/blockimg.h
diff --git a/updater/install.h b/updater/include/updater/install.h
similarity index 85%
rename from updater/install.h
rename to updater/include/updater/install.h
index 70e3434..8d6ca47 100644
--- a/updater/install.h
+++ b/updater/include/updater/install.h
@@ -17,11 +17,12 @@
 #ifndef _UPDATER_INSTALL_H_
 #define _UPDATER_INSTALL_H_
 
+struct State;
+
 void RegisterInstallFunctions();
 
 // uiPrintf function prints msg to screen as well as logs
-void uiPrintf(State* state, const char* format, ...);
-
-static int make_parents(char* name);
+void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...)
+    __attribute__((__format__(printf, 2, 3)));
 
 #endif
diff --git a/updater/updater.h b/updater/include/updater/updater.h
similarity index 88%
rename from updater/updater.h
rename to updater/include/updater/updater.h
index d1dfdd0..f4a2fe8 100644
--- a/updater/updater.h
+++ b/updater/include/updater/updater.h
@@ -18,20 +18,18 @@
 #define _UPDATER_UPDATER_H_
 
 #include <stdio.h>
-#include "minzip/Zip.h"
-
-#include <selinux/selinux.h>
-#include <selinux/label.h>
+#include <ziparchive/zip_archive.h>
 
 typedef struct {
     FILE* cmd_pipe;
-    ZipArchive* package_zip;
+    ZipArchiveHandle package_zip;
     int version;
 
     uint8_t* package_zip_addr;
     size_t package_zip_len;
 } UpdaterInfo;
 
+struct selabel_handle;
 extern struct selabel_handle *sehandle;
 
 #endif
diff --git a/updater/install.cpp b/updater/install.cpp
index 74feb56..84cf5d6 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -14,1336 +14,749 @@
  * limitations under the License.
  */
 
+#include "updater/install.h"
+
 #include <ctype.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <inttypes.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/capability.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <time.h>
-#include <selinux/selinux.h>
-#include <ftw.h>
-#include <sys/capability.h>
 #include <sys/xattr.h>
-#include <linux/xattr.h>
-#include <inttypes.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
 
 #include <memory>
 #include <string>
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parsedouble.h>
 #include <android-base/parseint.h>
-#include <android-base/strings.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <applypatch/applypatch.h>
+#include <bootloader_message/bootloader_message.h>
+#include <cutils/android_reboot.h>
+#include <ext4_utils/make_ext4fs.h>
+#include <ext4_utils/wipe.h>
+#include <openssl/sha.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <ziparchive/zip_archive.h>
 
-#include "bootloader.h"
-#include "applypatch/applypatch.h"
-#include "cutils/android_reboot.h"
-#include "cutils/misc.h"
-#include "cutils/properties.h"
 #include "edify/expr.h"
 #include "error_code.h"
-#include "minzip/DirUtil.h"
-#include "mtdutils/mounts.h"
-#include "mtdutils/mtdutils.h"
-#include "openssl/sha.h"
+#include "mounts.h"
 #include "ota_io.h"
-#include "updater.h"
-#include "install.h"
+#include "otautil/DirUtil.h"
+#include "otautil/ZipUtil.h"
+#include "print_sha1.h"
 #include "tune2fs.h"
-
-#ifdef USE_EXT4
-#include "make_ext4fs.h"
-#include "wipe.h"
-#endif
+#include "updater/updater.h"
 
 // Send over the buffer to recovery though the command pipe.
 static void uiPrint(State* state, const std::string& buffer) {
-    UpdaterInfo* ui = reinterpret_cast<UpdaterInfo*>(state->cookie);
+  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
 
-    // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
-    // So skip sending empty strings to UI.
-    std::vector<std::string> lines = android::base::Split(buffer, "\n");
-    for (auto& line: lines) {
-        if (!line.empty()) {
-            fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str());
-            fprintf(ui->cmd_pipe, "ui_print\n");
-        }
+  // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
+  // So skip sending empty strings to UI.
+  std::vector<std::string> lines = android::base::Split(buffer, "\n");
+  for (auto& line : lines) {
+    if (!line.empty()) {
+      fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str());
     }
+  }
 
-    // On the updater side, we need to dump the contents to stderr (which has
-    // been redirected to the log file). Because the recovery will only print
-    // the contents to screen when processing pipe command ui_print.
-    fprintf(stderr, "%s", buffer.c_str());
+  // On the updater side, we need to dump the contents to stderr (which has
+  // been redirected to the log file). Because the recovery will only print
+  // the contents to screen when processing pipe command ui_print.
+  LOG(INFO) << buffer;
 }
 
-__attribute__((__format__(printf, 2, 3))) __nonnull((2))
-void uiPrintf(State* state, const char* format, ...) {
-    std::string error_msg;
+void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) {
+  std::string error_msg;
 
-    va_list ap;
-    va_start(ap, format);
-    android::base::StringAppendV(&error_msg, format, ap);
-    va_end(ap);
+  va_list ap;
+  va_start(ap, format);
+  android::base::StringAppendV(&error_msg, format, ap);
+  va_end(ap);
 
-    uiPrint(state, error_msg);
+  uiPrint(state, error_msg);
 }
 
-// Take a sha-1 digest and return it as a newly-allocated hex string.
-char* PrintSha1(const uint8_t* digest) {
-    char* buffer = reinterpret_cast<char*>(malloc(SHA_DIGEST_LENGTH*2 + 1));
-    const char* alphabet = "0123456789abcdef";
-    size_t i;
-    for (i = 0; i < SHA_DIGEST_LENGTH; ++i) {
-        buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf];
-        buffer[i*2+1] = alphabet[digest[i] & 0xf];
-    }
-    buffer[i*2] = '\0';
-    return buffer;
-}
-
-// mount(fs_type, partition_type, location, mount_point)
-//
-//    fs_type="yaffs2" partition_type="MTD"     location=partition
-//    fs_type="ext4"   partition_type="EMMC"    location=device
-Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* result = NULL;
-    if (argc != 4 && argc != 5) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %d", name, argc);
-    }
-    char* fs_type;
-    char* partition_type;
-    char* location;
-    char* mount_point;
-    char* mount_options;
-    bool has_mount_options;
-    if (argc == 5) {
-        has_mount_options = true;
-        if (ReadArgs(state, argv, 5, &fs_type, &partition_type,
-                 &location, &mount_point, &mount_options) < 0) {
-            return NULL;
-        }
-    } else {
-        has_mount_options = false;
-        if (ReadArgs(state, argv, 4, &fs_type, &partition_type,
-                 &location, &mount_point) < 0) {
-            return NULL;
-        }
-    }
-
-    if (strlen(fs_type) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name);
-        goto done;
-    }
-    if (strlen(partition_type) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty",
-                   name);
-        goto done;
-    }
-    if (strlen(location) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name);
-        goto done;
-    }
-    if (strlen(mount_point) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty",
-                   name);
-        goto done;
-    }
-
-    {
-        char *secontext = NULL;
-
-        if (sehandle) {
-            selabel_lookup(sehandle, &secontext, mount_point, 0755);
-            setfscreatecon(secontext);
-        }
-
-        mkdir(mount_point, 0755);
-
-        if (secontext) {
-            freecon(secontext);
-            setfscreatecon(NULL);
-        }
-    }
-
-    if (strcmp(partition_type, "MTD") == 0) {
-        mtd_scan_partitions();
-        const MtdPartition* mtd;
-        mtd = mtd_find_partition_by_name(location);
-        if (mtd == NULL) {
-            uiPrintf(state, "%s: no mtd partition named \"%s\"\n",
-                    name, location);
-            result = strdup("");
-            goto done;
-        }
-        if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) {
-            uiPrintf(state, "mtd mount of %s failed: %s\n",
-                    location, strerror(errno));
-            result = strdup("");
-            goto done;
-        }
-        result = mount_point;
-    } else {
-        if (mount(location, mount_point, fs_type,
-                  MS_NOATIME | MS_NODEV | MS_NODIRATIME,
-                  has_mount_options ? mount_options : "") < 0) {
-            uiPrintf(state, "%s: failed to mount %s at %s: %s\n",
-                    name, location, mount_point, strerror(errno));
-            result = strdup("");
-        } else {
-            result = mount_point;
-        }
-    }
-
-done:
-    free(fs_type);
-    free(partition_type);
-    free(location);
-    if (result != mount_point) free(mount_point);
-    if (has_mount_options) free(mount_options);
-    return StringValue(result);
-}
-
-
-// is_mounted(mount_point)
-Value* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* result = NULL;
-    if (argc != 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
-    }
-    char* mount_point;
-    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
-        return NULL;
-    }
-    if (strlen(mount_point) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "mount_point argument to unmount() can't be empty");
-        goto done;
-    }
-
-    scan_mounted_volumes();
-    {
-        const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
-        if (vol == NULL) {
-            result = strdup("");
-        } else {
-            result = mount_point;
-        }
-    }
-
-done:
-    if (result != mount_point) free(mount_point);
-    return StringValue(result);
-}
-
-
-Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* result = NULL;
-    if (argc != 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
-    }
-    char* mount_point;
-    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
-        return NULL;
-    }
-    if (strlen(mount_point) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "mount_point argument to unmount() can't be empty");
-        goto done;
-    }
-
-    scan_mounted_volumes();
-    {
-        const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
-        if (vol == NULL) {
-            uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point);
-            result = strdup("");
-        } else {
-            int ret = unmount_mounted_volume(vol);
-            if (ret != 0) {
-                uiPrintf(state, "unmount of %s failed (%d): %s\n",
-                         mount_point, ret, strerror(errno));
-            }
-            result = mount_point;
-        }
-    }
-
-done:
-    if (result != mount_point) free(mount_point);
-    return StringValue(result);
-}
-
-static int exec_cmd(const char* path, char* const argv[]) {
-    int status;
-    pid_t child;
-    if ((child = vfork()) == 0) {
-        execv(path, argv);
-        _exit(-1);
-    }
-    waitpid(child, &status, 0);
-    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-        printf("%s failed with status %d\n", path, WEXITSTATUS(status));
-    }
-    return WEXITSTATUS(status);
-}
-
-
-// format(fs_type, partition_type, location, fs_size, mount_point)
-//
-//    fs_type="yaffs2" partition_type="MTD"     location=partition fs_size=<bytes> mount_point=<location>
-//    fs_type="ext4"   partition_type="EMMC"    location=device    fs_size=<bytes> mount_point=<location>
-//    fs_type="f2fs"   partition_type="EMMC"    location=device    fs_size=<bytes> mount_point=<location>
-//    if fs_size == 0, then make fs uses the entire partition.
-//    if fs_size > 0, that is the size to use
-//    if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs")
-Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* result = NULL;
-    if (argc != 5) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %d", name, argc);
-    }
-    char* fs_type;
-    char* partition_type;
-    char* location;
-    char* fs_size;
-    char* mount_point;
-
-    if (ReadArgs(state, argv, 5, &fs_type, &partition_type, &location, &fs_size, &mount_point) < 0) {
-        return NULL;
-    }
-
-    if (strlen(fs_type) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name);
-        goto done;
-    }
-    if (strlen(partition_type) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty",
-                   name);
-        goto done;
-    }
-    if (strlen(location) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name);
-        goto done;
-    }
-
-    if (strlen(mount_point) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty",
-                   name);
-        goto done;
-    }
-
-    if (strcmp(partition_type, "MTD") == 0) {
-        mtd_scan_partitions();
-        const MtdPartition* mtd = mtd_find_partition_by_name(location);
-        if (mtd == NULL) {
-            printf("%s: no mtd partition named \"%s\"",
-                    name, location);
-            result = strdup("");
-            goto done;
-        }
-        MtdWriteContext* ctx = mtd_write_partition(mtd);
-        if (ctx == NULL) {
-            printf("%s: can't write \"%s\"", name, location);
-            result = strdup("");
-            goto done;
-        }
-        if (mtd_erase_blocks(ctx, -1) == -1) {
-            mtd_write_close(ctx);
-            printf("%s: failed to erase \"%s\"", name, location);
-            result = strdup("");
-            goto done;
-        }
-        if (mtd_write_close(ctx) != 0) {
-            printf("%s: failed to close \"%s\"", name, location);
-            result = strdup("");
-            goto done;
-        }
-        result = location;
-#ifdef USE_EXT4
-    } else if (strcmp(fs_type, "ext4") == 0) {
-        int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle);
-        if (status != 0) {
-            printf("%s: make_ext4fs failed (%d) on %s",
-                    name, status, location);
-            result = strdup("");
-            goto done;
-        }
-        result = location;
-    } else if (strcmp(fs_type, "f2fs") == 0) {
-        char *num_sectors;
-        if (asprintf(&num_sectors, "%lld", atoll(fs_size) / 512) <= 0) {
-            printf("format_volume: failed to create %s command for %s\n", fs_type, location);
-            result = strdup("");
-            goto done;
-        }
-        const char *f2fs_path = "/sbin/mkfs.f2fs";
-        const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", location, num_sectors, NULL};
-        int status = exec_cmd(f2fs_path, (char* const*)f2fs_argv);
-        free(num_sectors);
-        if (status != 0) {
-            printf("%s: mkfs.f2fs failed (%d) on %s",
-                    name, status, location);
-            result = strdup("");
-            goto done;
-        }
-        result = location;
-#endif
-    } else {
-        printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"",
-                name, fs_type, partition_type);
-    }
-
-done:
-    free(fs_type);
-    free(partition_type);
-    if (result != location) free(location);
-    return StringValue(result);
-}
-
-Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* result = NULL;
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
-
-    char* src_name;
-    char* dst_name;
-
-    if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) {
-        return NULL;
-    }
-    if (strlen(src_name) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "src_name argument to %s() can't be empty", name);
-        goto done;
-    }
-    if (strlen(dst_name) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "dst_name argument to %s() can't be empty", name);
-        goto done;
-    }
-    if (make_parents(dst_name) != 0) {
-        ErrorAbort(state, kFileRenameFailure, "Creating parent of %s failed, error %s",
-          dst_name, strerror(errno));
-    } else if (access(dst_name, F_OK) == 0 && access(src_name, F_OK) != 0) {
-        // File was already moved
-        result = dst_name;
-    } else if (rename(src_name, dst_name) != 0) {
-        ErrorAbort(state, kFileRenameFailure, "Rename of %s to %s failed, error %s",
-          src_name, dst_name, strerror(errno));
-    } else {
-        result = dst_name;
-    }
-
-done:
-    free(src_name);
-    if (result != dst_name) free(dst_name);
-    return StringValue(result);
-}
-
-Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char** paths = reinterpret_cast<char**>(malloc(argc * sizeof(char*)));
-    for (int i = 0; i < argc; ++i) {
-        paths[i] = Evaluate(state, argv[i]);
-        if (paths[i] == NULL) {
-            for (int j = 0; j < i; ++j) {
-                free(paths[j]);
-            }
-            free(paths);
-            return NULL;
-        }
-    }
-
-    bool recursive = (strcmp(name, "delete_recursive") == 0);
-
-    int success = 0;
-    for (int i = 0; i < argc; ++i) {
-        if ((recursive ? dirUnlinkHierarchy(paths[i]) : unlink(paths[i])) == 0)
-            ++success;
-        free(paths[i]);
-    }
-    free(paths);
-
-    char buffer[10];
-    sprintf(buffer, "%d", success);
-    return StringValue(strdup(buffer));
-}
-
-
-Value* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
-    char* frac_str;
-    char* sec_str;
-    if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) {
-        return NULL;
-    }
-
-    double frac = strtod(frac_str, NULL);
-    int sec;
-    android::base::ParseInt(sec_str, &sec);
-
-    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
-    fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
-
-    free(sec_str);
-    return StringValue(frac_str);
-}
-
-Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
-    }
-    char* frac_str;
-    if (ReadArgs(state, argv, 1, &frac_str) < 0) {
-        return NULL;
-    }
-
-    double frac = strtod(frac_str, NULL);
-
-    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
-    fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
-
-    return StringValue(frac_str);
-}
-
-// package_extract_dir(package_path, destination_path)
-Value* PackageExtractDirFn(const char* name, State* state,
-                          int argc, Expr* argv[]) {
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
-    char* zip_path;
-    char* dest_path;
-    if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
-
-    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
-
-    // To create a consistent system image, never use the clock for timestamps.
-    struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
-
-    bool success = mzExtractRecursive(za, zip_path, dest_path,
-                                      &timestamp,
-                                      NULL, NULL, sehandle);
-    free(zip_path);
-    free(dest_path);
-    return StringValue(strdup(success ? "t" : ""));
-}
-
-
-// package_extract_file(package_path, destination_path)
-//   or
-// package_extract_file(package_path)
-//   to return the entire contents of the file as the result of this
-//   function (the char* returned is actually a FileContents*).
-Value* PackageExtractFileFn(const char* name, State* state,
-                           int argc, Expr* argv[]) {
-    if (argc < 1 || argc > 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %d",
-                          name, argc);
-    }
-    bool success = false;
-
-    if (argc == 2) {
-        // The two-argument version extracts to a file.
-
-        ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
-
-        char* zip_path;
-        char* dest_path;
-        if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
-
-        const ZipEntry* entry = mzFindZipEntry(za, zip_path);
-        if (entry == NULL) {
-            printf("%s: no %s in package\n", name, zip_path);
-            goto done2;
-        }
-
-        {
-            int fd = TEMP_FAILURE_RETRY(ota_open(dest_path, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC,
-                  S_IRUSR | S_IWUSR));
-            if (fd == -1) {
-                printf("%s: can't open %s for write: %s\n", name, dest_path, strerror(errno));
-                goto done2;
-            }
-            success = mzExtractZipEntryToFile(za, entry, fd);
-            if (ota_fsync(fd) == -1) {
-                printf("fsync of \"%s\" failed: %s\n", dest_path, strerror(errno));
-                success = false;
-            }
-            if (ota_close(fd) == -1) {
-                printf("close of \"%s\" failed: %s\n", dest_path, strerror(errno));
-                success = false;
-            }
-        }
-
-      done2:
-        free(zip_path);
-        free(dest_path);
-        return StringValue(strdup(success ? "t" : ""));
-    } else {
-        // The one-argument version returns the contents of the file
-        // as the result.
-
-        char* zip_path;
-        if (ReadArgs(state, argv, 1, &zip_path) < 0) return NULL;
-
-        Value* v = reinterpret_cast<Value*>(malloc(sizeof(Value)));
-        v->type = VAL_BLOB;
-        v->size = -1;
-        v->data = NULL;
-
-        ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
-        const ZipEntry* entry = mzFindZipEntry(za, zip_path);
-        if (entry == NULL) {
-            printf("%s: no %s in package\n", name, zip_path);
-            goto done1;
-        }
-
-        v->size = mzGetZipEntryUncompLen(entry);
-        v->data = reinterpret_cast<char*>(malloc(v->size));
-        if (v->data == NULL) {
-            printf("%s: failed to allocate %ld bytes for %s\n",
-                    name, (long)v->size, zip_path);
-            goto done1;
-        }
-
-        success = mzExtractZipEntryToBuffer(za, entry,
-                                            (unsigned char *)v->data);
-
-      done1:
-        free(zip_path);
-        if (!success) {
-            free(v->data);
-            v->data = NULL;
-            v->size = -1;
-        }
-        return v;
-    }
+static bool is_dir(const std::string& dirpath) {
+  struct stat st;
+  return stat(dirpath.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
 }
 
 // Create all parent directories of name, if necessary.
-static int make_parents(char* name) {
-    char* p;
-    for (p = name + (strlen(name)-1); p > name; --p) {
-        if (*p != '/') continue;
-        *p = '\0';
-        if (make_parents(name) < 0) return -1;
-        int result = mkdir(name, 0700);
-        if (result == 0) printf("created [%s]\n", name);
-        *p = '/';
-        if (result == 0 || errno == EEXIST) {
-            // successfully created or already existed; we're done
-            return 0;
-        } else {
-            printf("failed to mkdir %s: %s\n", name, strerror(errno));
-            return -1;
-        }
+static bool make_parents(const std::string& name) {
+  size_t prev_end = 0;
+  while (prev_end < name.size()) {
+    size_t next_end = name.find('/', prev_end + 1);
+    if (next_end == std::string::npos) {
+      break;
     }
-    return 0;
+    std::string dir_path = name.substr(0, next_end);
+    if (!is_dir(dir_path)) {
+      int result = mkdir(dir_path.c_str(), 0700);
+      if (result != 0) {
+        PLOG(ERROR) << "failed to mkdir " << dir_path << " when make parents for " << name;
+        return false;
+      }
+
+      LOG(INFO) << "created [" << dir_path << "]";
+    }
+    prev_end = next_end;
+  }
+  return true;
 }
 
-// symlink target src1 src2 ...
-//    unlinks any previously existing src1, src2, etc before creating symlinks.
-Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc == 0) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1+ args, got %d", name, argc);
-    }
-    char* target;
-    target = Evaluate(state, argv[0]);
-    if (target == NULL) return NULL;
+// mount(fs_type, partition_type, location, mount_point)
+// mount(fs_type, partition_type, location, mount_point, mount_options)
 
-    char** srcs = ReadVarArgs(state, argc-1, argv+1);
-    if (srcs == NULL) {
-        free(target);
-        return NULL;
+//    fs_type="ext4"   partition_type="EMMC"    location=device
+Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 4 && argv.size() != 5) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& fs_type = args[0];
+  const std::string& partition_type = args[1];
+  const std::string& location = args[2];
+  const std::string& mount_point = args[3];
+  std::string mount_options;
+
+  if (argv.size() == 5) {
+    mount_options = args[4];
+  }
+
+  if (fs_type.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name);
+  }
+  if (partition_type.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty",
+                      name);
+  }
+  if (location.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name);
+  }
+  if (mount_point.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty",
+                      name);
+  }
+
+  {
+    char* secontext = nullptr;
+
+    if (sehandle) {
+      selabel_lookup(sehandle, &secontext, mount_point.c_str(), 0755);
+      setfscreatecon(secontext);
     }
 
-    int bad = 0;
-    int i;
-    for (i = 0; i < argc-1; ++i) {
-        if (unlink(srcs[i]) < 0) {
-            if (errno != ENOENT) {
-                printf("%s: failed to remove %s: %s\n",
-                        name, srcs[i], strerror(errno));
-                ++bad;
-            }
-        }
-        if (make_parents(srcs[i])) {
-            printf("%s: failed to symlink %s to %s: making parents failed\n",
-                    name, srcs[i], target);
-            ++bad;
-        }
-        if (symlink(target, srcs[i]) < 0) {
-            printf("%s: failed to symlink %s to %s: %s\n",
-                    name, srcs[i], target, strerror(errno));
-            ++bad;
-        }
-        free(srcs[i]);
+    mkdir(mount_point.c_str(), 0755);
+
+    if (secontext) {
+      freecon(secontext);
+      setfscreatecon(nullptr);
     }
-    free(srcs);
-    if (bad) {
-        return ErrorAbort(state, kSymlinkFailure, "%s: some symlinks failed", name);
-    }
-    return StringValue(strdup(""));
+  }
+
+  if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(),
+            MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) {
+    uiPrintf(state, "%s: failed to mount %s at %s: %s\n", name, location.c_str(),
+             mount_point.c_str(), strerror(errno));
+    return StringValue("");
+  }
+
+  return StringValue(mount_point);
 }
 
-struct perm_parsed_args {
-    bool has_uid;
-    uid_t uid;
-    bool has_gid;
-    gid_t gid;
-    bool has_mode;
-    mode_t mode;
-    bool has_fmode;
-    mode_t fmode;
-    bool has_dmode;
-    mode_t dmode;
-    bool has_selabel;
-    char* selabel;
-    bool has_capabilities;
-    uint64_t capabilities;
-};
+// is_mounted(mount_point)
+Value* IsMountedFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
+  }
 
-static struct perm_parsed_args ParsePermArgs(State * state, int argc, char** args) {
-    int i;
-    struct perm_parsed_args parsed;
-    int bad = 0;
-    static int max_warnings = 20;
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& mount_point = args[0];
+  if (mount_point.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure,
+                      "mount_point argument to unmount() can't be empty");
+  }
 
-    memset(&parsed, 0, sizeof(parsed));
+  scan_mounted_volumes();
+  MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
+  if (vol == nullptr) {
+    return StringValue("");
+  }
 
-    for (i = 1; i < argc; i += 2) {
-        if (strcmp("uid", args[i]) == 0) {
-            int64_t uid;
-            if (sscanf(args[i+1], "%" SCNd64, &uid) == 1) {
-                parsed.uid = uid;
-                parsed.has_uid = true;
-            } else {
-                uiPrintf(state, "ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]);
-                bad++;
-            }
-            continue;
-        }
-        if (strcmp("gid", args[i]) == 0) {
-            int64_t gid;
-            if (sscanf(args[i+1], "%" SCNd64, &gid) == 1) {
-                parsed.gid = gid;
-                parsed.has_gid = true;
-            } else {
-                uiPrintf(state, "ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]);
-                bad++;
-            }
-            continue;
-        }
-        if (strcmp("mode", args[i]) == 0) {
-            int32_t mode;
-            if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
-                parsed.mode = mode;
-                parsed.has_mode = true;
-            } else {
-                uiPrintf(state, "ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]);
-                bad++;
-            }
-            continue;
-        }
-        if (strcmp("dmode", args[i]) == 0) {
-            int32_t mode;
-            if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
-                parsed.dmode = mode;
-                parsed.has_dmode = true;
-            } else {
-                uiPrintf(state, "ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]);
-                bad++;
-            }
-            continue;
-        }
-        if (strcmp("fmode", args[i]) == 0) {
-            int32_t mode;
-            if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
-                parsed.fmode = mode;
-                parsed.has_fmode = true;
-            } else {
-                uiPrintf(state, "ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]);
-                bad++;
-            }
-            continue;
-        }
-        if (strcmp("capabilities", args[i]) == 0) {
-            int64_t capabilities;
-            if (sscanf(args[i+1], "%" SCNi64, &capabilities) == 1) {
-                parsed.capabilities = capabilities;
-                parsed.has_capabilities = true;
-            } else {
-                uiPrintf(state, "ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]);
-                bad++;
-            }
-            continue;
-        }
-        if (strcmp("selabel", args[i]) == 0) {
-            if (args[i+1][0] != '\0') {
-                parsed.selabel = args[i+1];
-                parsed.has_selabel = true;
-            } else {
-                uiPrintf(state, "ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]);
-                bad++;
-            }
-            continue;
-        }
-        if (max_warnings != 0) {
-            printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i]);
-            max_warnings--;
-            if (max_warnings == 0) {
-                printf("ParsedPermArgs: suppressing further warnings\n");
-            }
-        }
-    }
-    return parsed;
+  return StringValue(mount_point);
 }
 
-static int ApplyParsedPerms(
-        State * state,
-        const char* filename,
-        const struct stat *statptr,
-        struct perm_parsed_args parsed)
-{
-    int bad = 0;
+Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
+  }
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& mount_point = args[0];
+  if (mount_point.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure,
+                      "mount_point argument to unmount() can't be empty");
+  }
 
-    if (parsed.has_selabel) {
-        if (lsetfilecon(filename, parsed.selabel) != 0) {
-            uiPrintf(state, "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n",
-                    filename, parsed.selabel, strerror(errno));
-            bad++;
-        }
+  scan_mounted_volumes();
+  MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
+  if (vol == nullptr) {
+    uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point.c_str());
+    return nullptr;
+  } else {
+    int ret = unmount_mounted_volume(vol);
+    if (ret != 0) {
+      uiPrintf(state, "unmount of %s failed (%d): %s\n", mount_point.c_str(), ret, strerror(errno));
     }
+  }
 
-    /* ignore symlinks */
-    if (S_ISLNK(statptr->st_mode)) {
-        return bad;
-    }
-
-    if (parsed.has_uid) {
-        if (chown(filename, parsed.uid, -1) < 0) {
-            uiPrintf(state, "ApplyParsedPerms: chown of %s to %d failed: %s\n",
-                    filename, parsed.uid, strerror(errno));
-            bad++;
-        }
-    }
-
-    if (parsed.has_gid) {
-        if (chown(filename, -1, parsed.gid) < 0) {
-            uiPrintf(state, "ApplyParsedPerms: chgrp of %s to %d failed: %s\n",
-                    filename, parsed.gid, strerror(errno));
-            bad++;
-        }
-    }
-
-    if (parsed.has_mode) {
-        if (chmod(filename, parsed.mode) < 0) {
-            uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
-                    filename, parsed.mode, strerror(errno));
-            bad++;
-        }
-    }
-
-    if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) {
-        if (chmod(filename, parsed.dmode) < 0) {
-            uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
-                    filename, parsed.dmode, strerror(errno));
-            bad++;
-        }
-    }
-
-    if (parsed.has_fmode && S_ISREG(statptr->st_mode)) {
-        if (chmod(filename, parsed.fmode) < 0) {
-            uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
-                   filename, parsed.fmode, strerror(errno));
-            bad++;
-        }
-    }
-
-    if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) {
-        if (parsed.capabilities == 0) {
-            if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) {
-                // Report failure unless it's ENODATA (attribute not set)
-                uiPrintf(state, "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n",
-                       filename, parsed.capabilities, strerror(errno));
-                bad++;
-            }
-        } else {
-            struct vfs_cap_data cap_data;
-            memset(&cap_data, 0, sizeof(cap_data));
-            cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE;
-            cap_data.data[0].permitted = (uint32_t) (parsed.capabilities & 0xffffffff);
-            cap_data.data[0].inheritable = 0;
-            cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32);
-            cap_data.data[1].inheritable = 0;
-            if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
-                uiPrintf(state, "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n",
-                        filename, parsed.capabilities, strerror(errno));
-                bad++;
-            }
-        }
-    }
-
-    return bad;
+  return StringValue(mount_point);
 }
 
-// nftw doesn't allow us to pass along context, so we need to use
-// global variables.  *sigh*
-static struct perm_parsed_args recursive_parsed_args;
-static State* recursive_state;
+static int exec_cmd(const char* path, char* const argv[]) {
+  pid_t child;
+  if ((child = vfork()) == 0) {
+    execv(path, argv);
+    _exit(EXIT_FAILURE);
+  }
 
-static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr,
-        int fileflags, struct FTW *pfwt) {
-    return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args);
+  int status;
+  waitpid(child, &status, 0);
+  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+    LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status);
+  }
+  return WEXITSTATUS(status);
 }
 
-static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) {
-    int bad = 0;
-    struct stat sb;
-    Value* result = NULL;
+// format(fs_type, partition_type, location, fs_size, mount_point)
+//
+//    fs_type="ext4"  partition_type="EMMC"  location=device  fs_size=<bytes> mount_point=<location>
+//    fs_type="f2fs"  partition_type="EMMC"  location=device  fs_size=<bytes> mount_point=<location>
+//    if fs_size == 0, then make fs uses the entire partition.
+//    if fs_size > 0, that is the size to use
+//    if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs")
+Value* FormatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 5) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %zu", name,
+                      argv.size());
+  }
 
-    bool recursive = (strcmp(name, "set_metadata_recursive") == 0);
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& fs_type = args[0];
+  const std::string& partition_type = args[1];
+  const std::string& location = args[2];
+  const std::string& fs_size = args[3];
+  const std::string& mount_point = args[4];
 
-    if ((argc % 2) != 1) {
-        return ErrorAbort(state, kArgsParsingFailure,
-                          "%s() expects an odd number of arguments, got %d", name, argc);
+  if (fs_type.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name);
+  }
+  if (partition_type.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty",
+                      name);
+  }
+  if (location.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name);
+  }
+  if (mount_point.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty",
+                      name);
+  }
+
+  int64_t size;
+  if (!android::base::ParseInt(fs_size, &size)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name,
+                      fs_size.c_str());
+  }
+
+  if (fs_type == "ext4") {
+    int status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle);
+    if (status != 0) {
+      LOG(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location;
+      return StringValue("");
     }
-
-    char** args = ReadVarArgs(state, argc, argv);
-    if (args == NULL) return NULL;
-
-    if (lstat(args[0], &sb) == -1) {
-        result = ErrorAbort(state, kSetMetadataFailure, "%s: Error on lstat of \"%s\": %s",
-                            name, args[0], strerror(errno));
-        goto done;
+    return StringValue(location);
+  } else if (fs_type == "f2fs") {
+    if (size < 0) {
+      LOG(ERROR) << name << ": fs_size can't be negative for f2fs: " << fs_size;
+      return StringValue("");
     }
+    std::string num_sectors = std::to_string(size / 512);
 
-    {
-        struct perm_parsed_args parsed = ParsePermArgs(state, argc, args);
-
-        if (recursive) {
-            recursive_parsed_args = parsed;
-            recursive_state = state;
-            bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS);
-            memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args));
-            recursive_state = NULL;
-        } else {
-            bad += ApplyParsedPerms(state, args[0], &sb, parsed);
-        }
+    const char* f2fs_path = "/sbin/mkfs.f2fs";
+    const char* const f2fs_argv[] = { "mkfs.f2fs", "-t", "-d1", location.c_str(),
+                                      num_sectors.c_str(), nullptr };
+    int status = exec_cmd(f2fs_path, const_cast<char* const*>(f2fs_argv));
+    if (status != 0) {
+      LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location;
+      return StringValue("");
     }
+    return StringValue(location);
+  } else {
+    LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \""
+               << partition_type << "\"";
+  }
 
-done:
-    for (int i = 0; i < argc; ++i) {
-        free(args[i]);
-    }
-    free(args);
-
-    if (result != NULL) {
-        return result;
-    }
-
-    if (bad > 0) {
-        return ErrorAbort(state, kSetMetadataFailure, "%s: some changes failed", name);
-    }
-
-    return StringValue(strdup(""));
+  return nullptr;
 }
 
-Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
+Value* ShowProgressFn(const char* name, State* state,
+                      const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& frac_str = args[0];
+  const std::string& sec_str = args[1];
+
+  double frac;
+  if (!android::base::ParseDouble(frac_str.c_str(), &frac)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name,
+                      frac_str.c_str());
+  }
+  int sec;
+  if (!android::base::ParseInt(sec_str.c_str(), &sec)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name,
+                      sec_str.c_str());
+  }
+
+  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
+  fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
+
+  return StringValue(frac_str);
+}
+
+Value* SetProgressFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& frac_str = args[0];
+
+  double frac;
+  if (!android::base::ParseDouble(frac_str.c_str(), &frac)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name,
+                      frac_str.c_str());
+  }
+
+  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
+  fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
+
+  return StringValue(frac_str);
+}
+
+// package_extract_dir(package_dir, dest_dir)
+//   Extracts all files from the package underneath package_dir and writes them to the
+//   corresponding tree beneath dest_dir. Any existing files are overwritten.
+//   Example: package_extract_dir("system", "/system")
+//
+//   Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path.
+Value* PackageExtractDirFn(const char* name, State* state,
+                           const std::vector<std::unique_ptr<Expr>>&argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& zip_path = args[0];
+  const std::string& dest_path = args[1];
+
+  ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+
+  // To create a consistent system image, never use the clock for timestamps.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
+
+  bool success = ExtractPackageRecursive(za, zip_path, dest_path, &timestamp, sehandle);
+
+  return StringValue(success ? "t" : "");
+}
+
+// package_extract_file(package_file[, dest_file])
+//   Extracts a single package_file from the update package and writes it to dest_file,
+//   overwriting existing files if necessary. Without the dest_file argument, returns the
+//   contents of the package file as a binary blob.
+Value* PackageExtractFileFn(const char* name, State* state,
+                            const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1 || argv.size() > 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  if (argv.size() == 2) {
+    // The two-argument version extracts to a file.
+
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+                        argv.size());
     }
-    char* key = Evaluate(state, argv[0]);
-    if (key == NULL) return NULL;
+    const std::string& zip_path = args[0];
+    const std::string& dest_path = args[1];
 
-    char value[PROPERTY_VALUE_MAX];
-    property_get(key, value, "");
-    free(key);
+    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+    ZipString zip_string_path(zip_path.c_str());
+    ZipEntry entry;
+    if (FindEntry(za, zip_string_path, &entry) != 0) {
+      LOG(ERROR) << name << ": no " << zip_path << " in package";
+      return StringValue("");
+    }
 
-    return StringValue(strdup(value));
+    unique_fd fd(TEMP_FAILURE_RETRY(
+        ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
+    if (fd == -1) {
+      PLOG(ERROR) << name << ": can't open " << dest_path << " for write";
+      return StringValue("");
+    }
+
+    bool success = true;
+    int32_t ret = ExtractEntryToFile(za, &entry, fd);
+    if (ret != 0) {
+      LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" ("
+                 << entry.uncompressed_length << " bytes) to \"" << dest_path
+                 << "\": " << ErrorCodeString(ret);
+      success = false;
+    }
+    if (ota_fsync(fd) == -1) {
+      PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed";
+      success = false;
+    }
+    if (ota_close(fd) == -1) {
+      PLOG(ERROR) << "close of \"" << dest_path << "\" failed";
+      success = false;
+    }
+
+    return StringValue(success ? "t" : "");
+  } else {
+    // The one-argument version returns the contents of the file as the result.
+
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+                        argv.size());
+    }
+    const std::string& zip_path = args[0];
+
+    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+    ZipString zip_string_path(zip_path.c_str());
+    ZipEntry entry;
+    if (FindEntry(za, zip_string_path, &entry) != 0) {
+      return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
+                        zip_path.c_str());
+    }
+
+    std::string buffer;
+    buffer.resize(entry.uncompressed_length);
+
+    int32_t ret = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size());
+    if (ret != 0) {
+      return ErrorAbort(state, kPackageExtractFileFailure,
+                        "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name,
+                        zip_path.c_str(), buffer.size(), ErrorCodeString(ret));
+    }
+
+    return new Value(VAL_BLOB, buffer);
+  }
+}
+
+Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
+  }
+  std::string key;
+  if (!Evaluate(state, argv[0], &key)) {
+    return nullptr;
+  }
+  std::string value = android::base::GetProperty(key, "");
+
+  return StringValue(value);
 }
 
 // file_getprop(file, key)
 //
 //   interprets 'file' as a getprop-style file (key=value pairs, one
-//   per line. # comment lines,blank lines, lines without '=' ignored),
+//   per line. # comment lines, blank lines, lines without '=' ignored),
 //   and returns the value for 'key' (or "" if it isn't defined).
-Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* result = NULL;
-    char* buffer = NULL;
-    char* filename;
-    char* key;
-    if (ReadArgs(state, argv, 2, &filename, &key) < 0) {
-        return NULL;
+Value* FileGetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+  const std::string& key = args[1];
+
+  struct stat st;
+  if (stat(filename.c_str(), &st) < 0) {
+    return ErrorAbort(state, kFileGetPropFailure, "%s: failed to stat \"%s\": %s", name,
+                      filename.c_str(), strerror(errno));
+  }
+
+  constexpr off_t MAX_FILE_GETPROP_SIZE = 65536;
+  if (st.st_size > MAX_FILE_GETPROP_SIZE) {
+    return ErrorAbort(state, kFileGetPropFailure, "%s too large for %s (max %lld)",
+                      filename.c_str(), name, static_cast<long long>(MAX_FILE_GETPROP_SIZE));
+  }
+
+  std::string buffer(st.st_size, '\0');
+  unique_file f(ota_fopen(filename.c_str(), "rb"));
+  if (f == nullptr) {
+    return ErrorAbort(state, kFileOpenFailure, "%s: failed to open %s: %s", name, filename.c_str(),
+                      strerror(errno));
+  }
+
+  if (ota_fread(&buffer[0], 1, st.st_size, f.get()) != static_cast<size_t>(st.st_size)) {
+    ErrorAbort(state, kFreadFailure, "%s: failed to read %zu bytes from %s", name,
+               static_cast<size_t>(st.st_size), filename.c_str());
+    return nullptr;
+  }
+
+  ota_fclose(f);
+
+  std::vector<std::string> lines = android::base::Split(buffer, "\n");
+  for (size_t i = 0; i < lines.size(); i++) {
+    std::string line = android::base::Trim(lines[i]);
+
+    // comment or blank line: skip to next line
+    if (line.empty() || line[0] == '#') {
+      continue;
+    }
+    size_t equal_pos = line.find('=');
+    if (equal_pos == std::string::npos) {
+      continue;
     }
 
-    struct stat st;
-    if (stat(filename, &st) < 0) {
-        ErrorAbort(state, kFileGetPropFailure, "%s: failed to stat \"%s\": %s", name, filename,
-                   strerror(errno));
-        goto done;
-    }
+    // trim whitespace between key and '='
+    std::string str = android::base::Trim(line.substr(0, equal_pos));
 
-#define MAX_FILE_GETPROP_SIZE    65536
+    // not the key we're looking for
+    if (key != str) continue;
 
-    if (st.st_size > MAX_FILE_GETPROP_SIZE) {
-        ErrorAbort(state, kFileGetPropFailure, "%s too large for %s (max %d)", filename, name,
-                   MAX_FILE_GETPROP_SIZE);
-        goto done;
-    }
+    return StringValue(android::base::Trim(line.substr(equal_pos + 1)));
+  }
 
-    buffer = reinterpret_cast<char*>(malloc(st.st_size+1));
-    if (buffer == NULL) {
-        ErrorAbort(state, kFileGetPropFailure, "%s: failed to alloc %lld bytes", name,
-                   (long long)st.st_size+1);
-        goto done;
-    }
-
-    FILE* f;
-    f = fopen(filename, "rb");
-    if (f == NULL) {
-        ErrorAbort(state, kFileOpenFailure, "%s: failed to open %s: %s", name, filename,
-                   strerror(errno));
-        goto done;
-    }
-
-    if (ota_fread(buffer, 1, st.st_size, f) != static_cast<size_t>(st.st_size)) {
-        ErrorAbort(state, kFreadFailure, "%s: failed to read %lld bytes from %s",
-                   name, (long long)st.st_size+1, filename);
-        fclose(f);
-        goto done;
-    }
-    buffer[st.st_size] = '\0';
-
-    fclose(f);
-
-    char* line;
-    line = strtok(buffer, "\n");
-    do {
-        // skip whitespace at start of line
-        while (*line && isspace(*line)) ++line;
-
-        // comment or blank line: skip to next line
-        if (*line == '\0' || *line == '#') continue;
-
-        char* equal = strchr(line, '=');
-        if (equal == NULL) {
-            continue;
-        }
-
-        // trim whitespace between key and '='
-        char* key_end = equal-1;
-        while (key_end > line && isspace(*key_end)) --key_end;
-        key_end[1] = '\0';
-
-        // not the key we're looking for
-        if (strcmp(key, line) != 0) continue;
-
-        // skip whitespace after the '=' to the start of the value
-        char* val_start = equal+1;
-        while(*val_start && isspace(*val_start)) ++val_start;
-
-        // trim trailing whitespace
-        char* val_end = val_start + strlen(val_start)-1;
-        while (val_end > val_start && isspace(*val_end)) --val_end;
-        val_end[1] = '\0';
-
-        result = strdup(val_start);
-        break;
-
-    } while ((line = strtok(NULL, "\n")));
-
-    if (result == NULL) result = strdup("");
-
-  done:
-    free(filename);
-    free(key);
-    free(buffer);
-    return StringValue(result);
-}
-
-// write_raw_image(filename_or_blob, partition)
-Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char* result = NULL;
-
-    Value* partition_value;
-    Value* contents;
-    if (ReadValueArgs(state, argv, 2, &contents, &partition_value) < 0) {
-        return NULL;
-    }
-
-    char* partition = NULL;
-    if (partition_value->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "partition argument to %s must be string", name);
-        goto done;
-    }
-    partition = partition_value->data;
-    if (strlen(partition) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "partition argument to %s can't be empty", name);
-        goto done;
-    }
-    if (contents->type == VAL_STRING && strlen((char*) contents->data) == 0) {
-        ErrorAbort(state, kArgsParsingFailure, "file argument to %s can't be empty", name);
-        goto done;
-    }
-
-    mtd_scan_partitions();
-    const MtdPartition* mtd;
-    mtd = mtd_find_partition_by_name(partition);
-    if (mtd == NULL) {
-        printf("%s: no mtd partition named \"%s\"\n", name, partition);
-        result = strdup("");
-        goto done;
-    }
-
-    MtdWriteContext* ctx;
-    ctx = mtd_write_partition(mtd);
-    if (ctx == NULL) {
-        printf("%s: can't write mtd partition \"%s\"\n",
-                name, partition);
-        result = strdup("");
-        goto done;
-    }
-
-    bool success;
-
-    if (contents->type == VAL_STRING) {
-        // we're given a filename as the contents
-        char* filename = contents->data;
-        FILE* f = ota_fopen(filename, "rb");
-        if (f == NULL) {
-            printf("%s: can't open %s: %s\n", name, filename, strerror(errno));
-            result = strdup("");
-            goto done;
-        }
-
-        success = true;
-        char* buffer = reinterpret_cast<char*>(malloc(BUFSIZ));
-        int read;
-        while (success && (read = ota_fread(buffer, 1, BUFSIZ, f)) > 0) {
-            int wrote = mtd_write_data(ctx, buffer, read);
-            success = success && (wrote == read);
-        }
-        free(buffer);
-        ota_fclose(f);
-    } else {
-        // we're given a blob as the contents
-        ssize_t wrote = mtd_write_data(ctx, contents->data, contents->size);
-        success = (wrote == contents->size);
-    }
-    if (!success) {
-        printf("mtd_write_data to %s failed: %s\n",
-                partition, strerror(errno));
-    }
-
-    if (mtd_erase_blocks(ctx, -1) == -1) {
-        printf("%s: error erasing blocks of %s\n", name, partition);
-    }
-    if (mtd_write_close(ctx) != 0) {
-        printf("%s: error closing write of %s\n", name, partition);
-    }
-
-    printf("%s %s partition\n",
-           success ? "wrote" : "failed to write", partition);
-
-    result = success ? partition : strdup("");
-
-done:
-    if (result != partition) FreeValue(partition_value);
-    FreeValue(contents);
-    return StringValue(result);
+  return StringValue("");
 }
 
 // apply_patch_space(bytes)
-Value* ApplyPatchSpaceFn(const char* name, State* state,
-                         int argc, Expr* argv[]) {
-    char* bytes_str;
-    if (ReadArgs(state, argv, 1, &bytes_str) < 0) {
-        return NULL;
-    }
+Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name,
+                      argv.size());
+  }
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& bytes_str = args[0];
 
-    size_t bytes;
-    if (!android::base::ParseUint(bytes_str, &bytes)) {
-        ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count\n\n",
-                   name, bytes_str);
-        free(bytes_str);
-        return nullptr;
-    }
+  size_t bytes;
+  if (!android::base::ParseUint(bytes_str.c_str(), &bytes)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count\n\n",
+                      name, bytes_str.c_str());
+  }
 
-    return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t"));
+  return StringValue(CacheSizeCheck(bytes) ? "" : "t");
 }
 
-// apply_patch(file, size, init_sha1, tgt_sha1, patch)
-
-Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc < 6 || (argc % 2) == 1) {
+// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...])
+//   Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the
+//   same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1
+//   hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a
+//   40-character hex string) and a blob. The blob is the patch to be applied when the source
+//   file's current contents have the given SHA1.
+//
+//   The patching is done in a safe manner that guarantees the target file either has the desired
+//   SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate
+//   state. If the process is interrupted during patching, the target file may be in an intermediate
+//   state; a copy exists in the cache partition so restarting the update can successfully update
+//   the file.
+Value* ApplyPatchFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() < 6 || (argv.size() % 2) == 1) {
         return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 6 args and an "
-                                 "even number, got %d", name, argc);
+                          "even number, got %zu", name, argv.size());
     }
 
-    char* source_filename;
-    char* target_filename;
-    char* target_sha1;
-    char* target_size_str;
-    if (ReadArgs(state, argv, 4, &source_filename, &target_filename,
-                 &target_sha1, &target_size_str) < 0) {
-        return NULL;
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args, 0, 4)) {
+        return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
     }
+    const std::string& source_filename = args[0];
+    const std::string& target_filename = args[1];
+    const std::string& target_sha1 = args[2];
+    const std::string& target_size_str = args[3];
 
     size_t target_size;
-    if (!android::base::ParseUint(target_size_str, &target_size)) {
-        ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count",
-                   name, target_size_str);
-        free(source_filename);
-        free(target_filename);
-        free(target_sha1);
-        free(target_size_str);
+    if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) {
+        return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count",
+                          name, target_size_str.c_str());
+    }
+
+    int patchcount = (argv.size()-4) / 2;
+    std::vector<std::unique_ptr<Value>> arg_values;
+    if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) {
         return nullptr;
     }
 
-    int patchcount = (argc-4) / 2;
-    std::unique_ptr<Value*, decltype(&free)> arg_values(ReadValueVarArgs(state, argc-4, argv+4),
-                                                        free);
-    if (!arg_values) {
-        return nullptr;
-    }
-    std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> patch_shas;
-    std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> patches;
-    // Protect values by unique_ptrs first to get rid of memory leak.
-    for (int i = 0; i < patchcount * 2; i += 2) {
-        patch_shas.emplace_back(arg_values.get()[i], FreeValue);
-        patches.emplace_back(arg_values.get()[i+1], FreeValue);
-    }
-
     for (int i = 0; i < patchcount; ++i) {
-        if (patch_shas[i]->type != VAL_STRING) {
-            ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, i);
-            return nullptr;
+        if (arg_values[i * 2]->type != VAL_STRING) {
+            return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name,
+                              i * 2);
         }
-        if (patches[i]->type != VAL_BLOB) {
-            ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, i);
-            return nullptr;
+        if (arg_values[i * 2 + 1]->type != VAL_BLOB) {
+            return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name,
+                              i * 2 + 1);
         }
     }
 
-    std::vector<char*> patch_sha_str;
-    std::vector<Value*> patch_ptrs;
+    std::vector<std::string> patch_sha_str;
+    std::vector<std::unique_ptr<Value>> patches;
     for (int i = 0; i < patchcount; ++i) {
-        patch_sha_str.push_back(patch_shas[i]->data);
-        patch_ptrs.push_back(patches[i].get());
+        patch_sha_str.push_back(arg_values[i * 2]->data);
+        patches.push_back(std::move(arg_values[i * 2 + 1]));
     }
 
-    int result = applypatch(source_filename, target_filename,
-                            target_sha1, target_size,
-                            patchcount, patch_sha_str.data(), patch_ptrs.data(), NULL);
+    int result = applypatch(source_filename.c_str(), target_filename.c_str(),
+                            target_sha1.c_str(), target_size,
+                            patch_sha_str, patches, nullptr);
 
-    return StringValue(strdup(result == 0 ? "t" : ""));
+    return StringValue(result == 0 ? "t" : "");
 }
 
-// apply_patch_check(file, [sha1_1, ...])
-Value* ApplyPatchCheckFn(const char* name, State* state,
-                         int argc, Expr* argv[]) {
-    if (argc < 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %d",
-                          name, argc);
-    }
+// apply_patch_check(filename, [sha1, ...])
+//   Returns true if the contents of filename or the temporary copy in the cache partition (if
+//   present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are
+//   specified as 40 hex digits. This function differs from sha1_check(read_file(filename),
+//   sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will
+//   succeed even if the file was corrupted by an interrupted apply_patch() update.
+Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name,
+                      argv.size());
+  }
 
-    char* filename;
-    if (ReadArgs(state, argv, 1, &filename) < 0) {
-        return NULL;
-    }
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args, 0, 1)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
 
-    int patchcount = argc-1;
-    char** sha1s = ReadVarArgs(state, argc-1, argv+1);
+  std::vector<std::string> sha1s;
+  if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  int result = applypatch_check(filename.c_str(), sha1s);
 
-    int result = applypatch_check(filename, patchcount, sha1s);
-
-    int i;
-    for (i = 0; i < patchcount; ++i) {
-        free(sha1s[i]);
-    }
-    free(sha1s);
-
-    return StringValue(strdup(result == 0 ? "t" : ""));
+  return StringValue(result == 0 ? "t" : "");
 }
 
 // This is the updater side handler for ui_print() in edify script. Contents
 // will be sent over to the recovery side for on-screen display.
-Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
-    char** args = ReadVarArgs(state, argc, argv);
-    if (args == NULL) {
-        return NULL;
-    }
+Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
 
-    std::string buffer;
-    for (int i = 0; i < argc; ++i) {
-        buffer += args[i];
-        free(args[i]);
-    }
-    free(args);
-
-    buffer += "\n";
-    uiPrint(state, buffer);
-    return StringValue(strdup(buffer.c_str()));
+  std::string buffer = android::base::Join(args, "") + "\n";
+  uiPrint(state, buffer);
+  return StringValue(buffer);
 }
 
-Value* WipeCacheFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 0) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %d", name, argc);
-    }
-    fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "wipe_cache\n");
-    return StringValue(strdup("t"));
+Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (!argv.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
+                      argv.size());
+  }
+  fprintf(static_cast<UpdaterInfo*>(state->cookie)->cmd_pipe, "wipe_cache\n");
+  return StringValue("t");
 }
 
-Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc < 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
+Value* RunProgramFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+
+  char* args2[argv.size() + 1];
+  for (size_t i = 0; i < argv.size(); i++) {
+    args2[i] = &args[i][0];
+  }
+  args2[argv.size()] = nullptr;
+
+  LOG(INFO) << "about to run program [" << args2[0] << "] with " << argv.size() << " args";
+
+  pid_t child = fork();
+  if (child == 0) {
+    execv(args2[0], args2);
+    PLOG(ERROR) << "run_program: execv failed";
+    _exit(EXIT_FAILURE);
+  }
+
+  int status;
+  waitpid(child, &status, 0);
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != 0) {
+      LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
     }
-    char** args = ReadVarArgs(state, argc, argv);
-    if (args == NULL) {
-        return NULL;
-    }
+  } else if (WIFSIGNALED(status)) {
+    LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
+  }
 
-    char** args2 = reinterpret_cast<char**>(malloc(sizeof(char*) * (argc+1)));
-    memcpy(args2, args, sizeof(char*) * argc);
-    args2[argc] = NULL;
-
-    printf("about to run program [%s] with %d args\n", args2[0], argc);
-
-    pid_t child = fork();
-    if (child == 0) {
-        execv(args2[0], args2);
-        printf("run_program: execv failed: %s\n", strerror(errno));
-        _exit(1);
-    }
-    int status;
-    waitpid(child, &status, 0);
-    if (WIFEXITED(status)) {
-        if (WEXITSTATUS(status) != 0) {
-            printf("run_program: child exited with status %d\n",
-                    WEXITSTATUS(status));
-        }
-    } else if (WIFSIGNALED(status)) {
-        printf("run_program: child terminated by signal %d\n",
-                WTERMSIG(status));
-    }
-
-    int i;
-    for (i = 0; i < argc; ++i) {
-        free(args[i]);
-    }
-    free(args);
-    free(args2);
-
-    char buffer[20];
-    sprintf(buffer, "%d", status);
-
-    return StringValue(strdup(buffer));
+  return StringValue(std::to_string(status));
 }
 
 // sha1_check(data)
@@ -1354,104 +767,92 @@
 //    returns the sha1 of the file if it matches any of the hex
 //    strings passed, or "" if it does not equal any of them.
 //
-Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc < 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
-    }
+Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
+  }
 
-    std::unique_ptr<Value*, decltype(&free)> arg_values(ReadValueVarArgs(state, argc, argv), free);
-    if (arg_values == nullptr) {
-        return nullptr;
-    }
-    std::vector<std::unique_ptr<Value, decltype(&FreeValue)>> args;
-    for (int i = 0; i < argc; ++i) {
-        args.emplace_back(arg_values.get()[i], FreeValue);
-    }
+  std::vector<std::unique_ptr<Value>> args;
+  if (!ReadValueArgs(state, argv, &args)) {
+    return nullptr;
+  }
 
-    if (args[0]->size < 0) {
-        return StringValue(strdup(""));
-    }
-    uint8_t digest[SHA_DIGEST_LENGTH];
-    SHA1(reinterpret_cast<uint8_t*>(args[0]->data), args[0]->size, digest);
+  if (args[0]->type == VAL_INVALID) {
+    return StringValue("");
+  }
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest);
 
-    if (argc == 1) {
-        return StringValue(PrintSha1(digest));
-    }
+  if (argv.size() == 1) {
+    return StringValue(print_sha1(digest));
+  }
 
-    int i;
+  for (size_t i = 1; i < argv.size(); ++i) {
     uint8_t arg_digest[SHA_DIGEST_LENGTH];
-    for (i = 1; i < argc; ++i) {
-        if (args[i]->type != VAL_STRING) {
-            printf("%s(): arg %d is not a string; skipping",
-                    name, i);
-        } else if (ParseSha1(args[i]->data, arg_digest) != 0) {
-            // Warn about bad args and skip them.
-            printf("%s(): error parsing \"%s\" as sha-1; skipping",
-                   name, args[i]->data);
-        } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) {
-            break;
-        }
+    if (args[i]->type != VAL_STRING) {
+      LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping";
+    } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) {
+      // Warn about bad args and skip them.
+      LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping";
+    } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) {
+      // Found a match.
+      return args[i].release();
     }
-    if (i >= argc) {
-        // Didn't match any of the hex strings; return false.
-        return StringValue(strdup(""));
-    }
-    // Found a match.
-    return args[i].release();
+  }
+
+  // Didn't match any of the hex strings; return false.
+  return StringValue("");
 }
 
 // Read a local file and return its contents (the Value* returned
 // is actually a FileContents*).
-Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
-    }
-    char* filename;
-    if (ReadArgs(state, argv, 1, &filename) < 0) return NULL;
+Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
+  }
 
-    Value* v = static_cast<Value*>(malloc(sizeof(Value)));
-    if (v == nullptr) {
-        return nullptr;
-    }
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+
+  Value* v = new Value(VAL_INVALID, "");
+
+  FileContents fc;
+  if (LoadFileContents(filename.c_str(), &fc) == 0) {
     v->type = VAL_BLOB;
-    v->size = -1;
-    v->data = nullptr;
-
-    FileContents fc;
-    if (LoadFileContents(filename, &fc) == 0) {
-        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();
-        }
-    }
-    free(filename);
-    return v;
+    v->data = std::string(fc.data.begin(), fc.data.end());
+  }
+  return v;
 }
 
 // write_value(value, filename)
 //   Writes 'value' to 'filename'.
 //   Example: write_value("960000", "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq")
-Value* WriteValueFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
+Value* WriteValueFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
 
-    char* value;
-    char* filename;
-    if (ReadArgs(state, argv, 2, &value, &filename) < 0) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)",
-                          name);
-    }
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+  }
 
-    bool ret = android::base::WriteStringToFile(value, filename);
-    if (!ret) {
-        printf("%s: Failed to write to \"%s\": %s\n", name, filename, strerror(errno));
-    }
+  const std::string& filename = args[1];
+  if (filename.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Filename cannot be empty", name);
+  }
 
-    free(value);
-    free(filename);
-    return StringValue(strdup(ret ? "t" : ""));
+  const std::string& value = args[0];
+  if (!android::base::WriteStringToFile(value, filename)) {
+    PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\"";
+    return StringValue("");
+  } else {
+    return StringValue("t");
+  }
 }
 
 // Immediately reboot the device.  Recovery is not finished normally,
@@ -1463,36 +864,40 @@
 // property.  It can be "recovery" to boot from the recovery
 // partition, or "" (empty string) to boot from the regular boot
 // partition.
-Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
+Value* RebootNowFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
 
-    char* filename;
-    char* property;
-    if (ReadArgs(state, argv, 2, &filename, &property) < 0) return NULL;
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+  const std::string& property = args[1];
 
-    char buffer[80];
+  // Zero out the 'command' field of the bootloader message. Leave the rest intact.
+  bootloader_message boot;
+  std::string err;
+  if (!read_bootloader_message_from(&boot, filename, &err)) {
+    LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err;
+    return StringValue("");
+  }
+  memset(boot.command, 0, sizeof(boot.command));
+  if (!write_bootloader_message_to(boot, filename, &err)) {
+    LOG(ERROR) << name << "(): Failed to write to \"" << filename << "\": " << err;
+    return StringValue("");
+  }
 
-    // zero out the 'command' field of the bootloader message.
-    memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command));
-    FILE* f = fopen(filename, "r+b");
-    fseek(f, offsetof(struct bootloader_message, command), SEEK_SET);
-    ota_fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f);
-    fclose(f);
-    free(filename);
+  std::string reboot_cmd = "reboot," + property;
+  if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+    reboot_cmd += ",quiescent";
+  }
+  android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd);
 
-    strcpy(buffer, "reboot,");
-    if (property != NULL) {
-        strncat(buffer, property, sizeof(buffer)-10);
-    }
-
-    property_set(ANDROID_RB_PROPERTY, buffer);
-
-    sleep(5);
-    free(property);
-    ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
-    return NULL;
+  sleep(5);
+  return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
 }
 
 // Store a string value somewhere that future invocations of recovery
@@ -1505,166 +910,157 @@
 // ("/misc" in the fstab), which is where this value is stored.  The
 // second argument is the string to store; it should not exceed 31
 // bytes.
-Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
+Value* SetStageFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
 
-    char* filename;
-    char* stagestr;
-    if (ReadArgs(state, argv, 2, &filename, &stagestr) < 0) return NULL;
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+  const std::string& stagestr = args[1];
 
-    // Store this value in the misc partition, immediately after the
-    // bootloader message that the main recovery uses to save its
-    // arguments in case of the device restarting midway through
-    // package installation.
-    FILE* f = fopen(filename, "r+b");
-    fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET);
-    int to_write = strlen(stagestr)+1;
-    int max_size = sizeof(((struct bootloader_message*)0)->stage);
-    if (to_write > max_size) {
-        to_write = max_size;
-        stagestr[max_size-1] = 0;
-    }
-    ota_fwrite(stagestr, to_write, 1, f);
-    fclose(f);
+  // Store this value in the misc partition, immediately after the
+  // bootloader message that the main recovery uses to save its
+  // arguments in case of the device restarting midway through
+  // package installation.
+  bootloader_message boot;
+  std::string err;
+  if (!read_bootloader_message_from(&boot, filename, &err)) {
+    LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err;
+    return StringValue("");
+  }
+  strlcpy(boot.stage, stagestr.c_str(), sizeof(boot.stage));
+  if (!write_bootloader_message_to(boot, filename, &err)) {
+    LOG(ERROR) << name << "(): Failed to write to \"" << filename << "\": " << err;
+    return StringValue("");
+  }
 
-    free(stagestr);
-    return StringValue(filename);
+  return StringValue(filename);
 }
 
 // Return the value most recently saved with SetStageFn.  The argument
 // is the block device for the misc partition.
-Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
-    }
+Value* GetStageFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
+  }
 
-    char* filename;
-    if (ReadArgs(state, argv, 1, &filename) < 0) return NULL;
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
 
-    char buffer[sizeof(((struct bootloader_message*)0)->stage)];
-    FILE* f = fopen(filename, "rb");
-    fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET);
-    ota_fread(buffer, sizeof(buffer), 1, f);
-    fclose(f);
-    buffer[sizeof(buffer)-1] = '\0';
+  bootloader_message boot;
+  std::string err;
+  if (!read_bootloader_message_from(&boot, filename, &err)) {
+    LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err;
+    return StringValue("");
+  }
 
-    return StringValue(strdup(buffer));
+  return StringValue(boot.stage);
 }
 
-Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
+Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
 
-    char* filename;
-    char* len_str;
-    if (ReadArgs(state, argv, 2, &filename, &len_str) < 0) return NULL;
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+  const std::string& len_str = args[1];
 
-    size_t len;
-    android::base::ParseUint(len_str, &len);
-    int fd = ota_open(filename, O_WRONLY, 0644);
-    int success = wipe_block_device(fd, len);
-
-    free(filename);
-    free(len_str);
-
-    ota_close(fd);
-
-    return StringValue(strdup(success ? "t" : ""));
+  size_t len;
+  if (!android::base::ParseUint(len_str.c_str(), &len)) {
+    return nullptr;
+  }
+  unique_fd fd(ota_open(filename.c_str(), O_WRONLY, 0644));
+  // The wipe_block_device function in ext4_utils returns 0 on success and 1
+  // for failure.
+  int status = wipe_block_device(fd, len);
+  return StringValue((status == 0) ? "t" : "");
 }
 
-Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 0) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %d", name, argc);
-    }
-    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
-    fprintf(ui->cmd_pipe, "enable_reboot\n");
-    return StringValue(strdup("t"));
+Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (!argv.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
+                      argv.size());
+  }
+  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
+  fprintf(ui->cmd_pipe, "enable_reboot\n");
+  return StringValue("t");
 }
 
-Value* Tune2FsFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc == 0) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %d", name, argc);
-    }
+Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %zu", name, argv.size());
+  }
 
-    char** args = ReadVarArgs(state, argc, argv);
-    if (args == NULL) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name);
-    }
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name);
+  }
 
-    char** args2 = reinterpret_cast<char**>(malloc(sizeof(char*) * (argc+1)));
-    // Tune2fs expects the program name as its args[0]
-    args2[0] = strdup(name);
-    for (int i = 0; i < argc; ++i) {
-       args2[i + 1] = args[i];
-    }
-    int result = tune2fs_main(argc + 1, args2);
-    for (int i = 0; i < argc; ++i) {
-        free(args[i]);
-    }
-    free(args);
+  char* args2[argv.size() + 1];
+  // Tune2fs expects the program name as its args[0]
+  args2[0] = const_cast<char*>(name);
+  if (args2[0] == nullptr) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < argv.size(); ++i) {
+    args2[i + 1] = &args[i][0];
+  }
 
-    free(args2[0]);
-    free(args2);
-    if (result != 0) {
-        return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d",
-                          name, result);
-    }
-    return StringValue(strdup("t"));
+  // tune2fs changes the file system parameters on an ext2 file system; it
+  // returns 0 on success.
+  int result = tune2fs_main(argv.size() + 1, args2);
+  if (result != 0) {
+    return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
+  }
+  return StringValue("t");
 }
 
 void RegisterInstallFunctions() {
-    RegisterFunction("mount", MountFn);
-    RegisterFunction("is_mounted", IsMountedFn);
-    RegisterFunction("unmount", UnmountFn);
-    RegisterFunction("format", FormatFn);
-    RegisterFunction("show_progress", ShowProgressFn);
-    RegisterFunction("set_progress", SetProgressFn);
-    RegisterFunction("delete", DeleteFn);
-    RegisterFunction("delete_recursive", DeleteFn);
-    RegisterFunction("package_extract_dir", PackageExtractDirFn);
-    RegisterFunction("package_extract_file", PackageExtractFileFn);
-    RegisterFunction("symlink", SymlinkFn);
+  RegisterFunction("mount", MountFn);
+  RegisterFunction("is_mounted", IsMountedFn);
+  RegisterFunction("unmount", UnmountFn);
+  RegisterFunction("format", FormatFn);
+  RegisterFunction("show_progress", ShowProgressFn);
+  RegisterFunction("set_progress", SetProgressFn);
+  RegisterFunction("package_extract_dir", PackageExtractDirFn);
+  RegisterFunction("package_extract_file", PackageExtractFileFn);
 
-    // Usage:
-    //   set_metadata("filename", "key1", "value1", "key2", "value2", ...)
-    // Example:
-    //   set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
-    RegisterFunction("set_metadata", SetMetadataFn);
+  RegisterFunction("getprop", GetPropFn);
+  RegisterFunction("file_getprop", FileGetPropFn);
 
-    // Usage:
-    //   set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...)
-    // Example:
-    //   set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
-    RegisterFunction("set_metadata_recursive", SetMetadataFn);
+  RegisterFunction("apply_patch", ApplyPatchFn);
+  RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
+  RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
 
-    RegisterFunction("getprop", GetPropFn);
-    RegisterFunction("file_getprop", FileGetPropFn);
-    RegisterFunction("write_raw_image", WriteRawImageFn);
+  RegisterFunction("wipe_block_device", WipeBlockDeviceFn);
 
-    RegisterFunction("apply_patch", ApplyPatchFn);
-    RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
-    RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
+  RegisterFunction("read_file", ReadFileFn);
+  RegisterFunction("sha1_check", Sha1CheckFn);
+  RegisterFunction("write_value", WriteValueFn);
 
-    RegisterFunction("wipe_block_device", WipeBlockDeviceFn);
+  RegisterFunction("wipe_cache", WipeCacheFn);
 
-    RegisterFunction("read_file", ReadFileFn);
-    RegisterFunction("sha1_check", Sha1CheckFn);
-    RegisterFunction("rename", RenameFn);
-    RegisterFunction("write_value", WriteValueFn);
+  RegisterFunction("ui_print", UIPrintFn);
 
-    RegisterFunction("wipe_cache", WipeCacheFn);
+  RegisterFunction("run_program", RunProgramFn);
 
-    RegisterFunction("ui_print", UIPrintFn);
+  RegisterFunction("reboot_now", RebootNowFn);
+  RegisterFunction("get_stage", GetStageFn);
+  RegisterFunction("set_stage", SetStageFn);
 
-    RegisterFunction("run_program", RunProgramFn);
-
-    RegisterFunction("reboot_now", RebootNowFn);
-    RegisterFunction("get_stage", GetStageFn);
-    RegisterFunction("set_stage", SetStageFn);
-
-    RegisterFunction("enable_reboot", EnableRebootFn);
-    RegisterFunction("tune2fs", Tune2FsFn);
+  RegisterFunction("enable_reboot", EnableRebootFn);
+  RegisterFunction("tune2fs", Tune2FsFn);
 }
diff --git a/updater/updater.cpp b/updater/updater.cpp
index e956dd5..f3e2820 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -14,18 +14,28 @@
  * limitations under the License.
  */
 
+#include "updater/updater.h"
+
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "edify/expr.h"
-#include "updater.h"
-#include "install.h"
-#include "blockimg.h"
-#include "minzip/Zip.h"
-#include "minzip/SysUtil.h"
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <selinux/android.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <ziparchive/zip_archive.h>
+
 #include "config.h"
+#include "edify/expr.h"
+#include "otautil/DirUtil.h"
+#include "otautil/SysUtil.h"
+#include "updater/blockimg.h"
+#include "updater/install.h"
 
 // Generated by the makefile, this function defines the
 // RegisterDeviceExtensions() function, which calls all the
@@ -34,170 +44,176 @@
 
 // Where in the package we expect to find the edify script to execute.
 // (Note it's "updateR-script", not the older "update-script".)
-#define SCRIPT_NAME "META-INF/com/google/android/updater-script"
+static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
 
 extern bool have_eio_error;
 
 struct selabel_handle *sehandle;
 
+static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
+                          const char* /* tag */, const char* /* file */, unsigned int /* line */,
+                          const char* message) {
+  fprintf(stdout, "%s\n", message);
+}
+
 int main(int argc, char** argv) {
-    // Various things log information to stdout or stderr more or less
-    // at random (though we've tried to standardize on stdout).  The
-    // log file makes more sense if buffering is turned off so things
-    // appear in the right order.
-    setbuf(stdout, NULL);
-    setbuf(stderr, NULL);
+  // Various things log information to stdout or stderr more or less
+  // at random (though we've tried to standardize on stdout).  The
+  // log file makes more sense if buffering is turned off so things
+  // appear in the right order.
+  setbuf(stdout, nullptr);
+  setbuf(stderr, nullptr);
 
-    if (argc != 4 && argc != 5) {
-        printf("unexpected number of arguments (%d)\n", argc);
-        return 1;
-    }
+  // We don't have logcat yet under recovery. Update logs will always be written to stdout
+  // (which is redirected to recovery.log).
+  android::base::InitLogging(argv, &UpdaterLogger);
 
-    char* version = argv[1];
-    if ((version[0] != '1' && version[0] != '2' && version[0] != '3') ||
-        version[1] != '\0') {
-        // We support version 1, 2, or 3.
-        printf("wrong updater binary API; expected 1, 2, or 3; "
-                        "got %s\n",
-                argv[1]);
-        return 2;
-    }
+  if (argc != 4 && argc != 5) {
+    LOG(ERROR) << "unexpected number of arguments: " << argc;
+    return 1;
+  }
 
-    // Set up the pipe for sending commands back to the parent process.
+  char* version = argv[1];
+  if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
+    // We support version 1, 2, or 3.
+    LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
+    return 2;
+  }
 
-    int fd = atoi(argv[2]);
-    FILE* cmd_pipe = fdopen(fd, "wb");
-    setlinebuf(cmd_pipe);
+  // Set up the pipe for sending commands back to the parent process.
 
-    // Extract the script from the package.
+  int fd = atoi(argv[2]);
+  FILE* cmd_pipe = fdopen(fd, "wb");
+  setlinebuf(cmd_pipe);
 
-    const char* package_filename = argv[3];
-    MemMapping map;
-    if (sysMapFile(package_filename, &map) != 0) {
-        printf("failed to map package %s\n", argv[3]);
-        return 3;
-    }
-    ZipArchive za;
-    int err;
-    err = mzOpenZipArchive(map.addr, map.length, &za);
-    if (err != 0) {
-        printf("failed to open package %s: %s\n",
-               argv[3], strerror(err));
-        return 3;
-    }
-    ota_io_init(&za);
+  // Extract the script from the package.
 
-    const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
-    if (script_entry == NULL) {
-        printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename);
-        return 4;
-    }
+  const char* package_filename = argv[3];
+  MemMapping map;
+  if (sysMapFile(package_filename, &map) != 0) {
+    LOG(ERROR) << "failed to map package " << argv[3];
+    return 3;
+  }
+  ZipArchiveHandle za;
+  int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
+  if (open_err != 0) {
+    LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
+    CloseArchive(za);
+    return 3;
+  }
 
-    char* script = reinterpret_cast<char*>(malloc(script_entry->uncompLen+1));
-    if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
-        printf("failed to read script from package\n");
-        return 5;
-    }
-    script[script_entry->uncompLen] = '\0';
+  ZipString script_name(SCRIPT_NAME);
+  ZipEntry script_entry;
+  int find_err = FindEntry(za, script_name, &script_entry);
+  if (find_err != 0) {
+    LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
+               << ErrorCodeString(find_err);
+    CloseArchive(za);
+    return 4;
+  }
 
-    // Configure edify's functions.
+  std::string script;
+  script.resize(script_entry.uncompressed_length);
+  int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
+                                    script_entry.uncompressed_length);
+  if (extract_err != 0) {
+    LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
+    CloseArchive(za);
+    return 5;
+  }
 
-    RegisterBuiltins();
-    RegisterInstallFunctions();
-    RegisterBlockImageFunctions();
-    RegisterDeviceExtensions();
-    FinishRegistration();
+  // Configure edify's functions.
 
-    // Parse the script.
+  RegisterBuiltins();
+  RegisterInstallFunctions();
+  RegisterBlockImageFunctions();
+  RegisterDeviceExtensions();
 
-    Expr* root;
-    int error_count = 0;
-    int error = parse_string(script, &root, &error_count);
-    if (error != 0 || error_count > 0) {
-        printf("%d parse errors\n", error_count);
-        return 6;
-    }
+  // Parse the script.
 
-    struct selinux_opt seopts[] = {
-      { SELABEL_OPT_PATH, "/file_contexts" }
-    };
+  std::unique_ptr<Expr> root;
+  int error_count = 0;
+  int error = parse_string(script.c_str(), &root, &error_count);
+  if (error != 0 || error_count > 0) {
+    LOG(ERROR) << error_count << " parse errors";
+    CloseArchive(za);
+    return 6;
+  }
 
-    sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
+  sehandle = selinux_android_file_context_handle();
+  selinux_android_set_sehandle(sehandle);
 
-    if (!sehandle) {
-        fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
-    }
+  if (!sehandle) {
+    fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
+  }
 
-    // Evaluate the parsed script.
+  // Evaluate the parsed script.
 
-    UpdaterInfo updater_info;
-    updater_info.cmd_pipe = cmd_pipe;
-    updater_info.package_zip = &za;
-    updater_info.version = atoi(version);
-    updater_info.package_zip_addr = map.addr;
-    updater_info.package_zip_len = map.length;
+  UpdaterInfo updater_info;
+  updater_info.cmd_pipe = cmd_pipe;
+  updater_info.package_zip = za;
+  updater_info.version = atoi(version);
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
 
-    State state;
-    state.cookie = &updater_info;
-    state.script = script;
-    state.errmsg = NULL;
+  State state(script, &updater_info);
 
-    if (argc == 5) {
-        if (strcmp(argv[4], "retry") == 0) {
-            state.is_retry = true;
-        } else {
-            printf("unexpected argument: %s", argv[4]);
-        }
-    }
-
-    char* result = Evaluate(&state, root);
-
-    if (have_eio_error) {
-        fprintf(cmd_pipe, "retry_update\n");
-    }
-
-    if (result == NULL) {
-        if (state.errmsg == NULL) {
-            printf("script aborted (no error message)\n");
-            fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
-        } else {
-            printf("script aborted: %s\n", state.errmsg);
-            char* line = strtok(state.errmsg, "\n");
-            while (line) {
-                // Parse the error code in abort message.
-                // Example: "E30: This package is for bullhead devices."
-                if (*line == 'E') {
-                    if (sscanf(line, "E%u: ", &state.error_code) != 1) {
-                         printf("Failed to parse error code: [%s]\n", line);
-                    }
-                }
-                fprintf(cmd_pipe, "ui_print %s\n", line);
-                line = strtok(NULL, "\n");
-            }
-            fprintf(cmd_pipe, "ui_print\n");
-        }
-
-        if (state.error_code != kNoError) {
-            fprintf(cmd_pipe, "log error: %d\n", state.error_code);
-            // Cause code should provide additional information about the abort;
-            // report only when an error exists.
-            if (state.cause_code != kNoCause) {
-                fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
-            }
-        }
-
-        free(state.errmsg);
-        return 7;
+  if (argc == 5) {
+    if (strcmp(argv[4], "retry") == 0) {
+      state.is_retry = true;
     } else {
-        fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result);
-        free(result);
+      printf("unexpected argument: %s", argv[4]);
+    }
+  }
+  ota_io_init(za, state.is_retry);
+
+  std::string result;
+  bool status = Evaluate(&state, root, &result);
+
+  if (have_eio_error) {
+    fprintf(cmd_pipe, "retry_update\n");
+  }
+
+  if (!status) {
+    if (state.errmsg.empty()) {
+      LOG(ERROR) << "script aborted (no error message)";
+      fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
+    } else {
+      LOG(ERROR) << "script aborted: " << state.errmsg;
+      const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
+      for (const std::string& line : lines) {
+        // Parse the error code in abort message.
+        // Example: "E30: This package is for bullhead devices."
+        if (!line.empty() && line[0] == 'E') {
+          if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
+            LOG(ERROR) << "Failed to parse error code: [" << line << "]";
+          }
+        }
+        fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
+      }
+    }
+
+    if (state.error_code != kNoError) {
+      fprintf(cmd_pipe, "log error: %d\n", state.error_code);
+      // Cause code should provide additional information about the abort;
+      // report only when an error exists.
+      if (state.cause_code != kNoCause) {
+        fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
+      }
     }
 
     if (updater_info.package_zip) {
-        mzCloseZipArchive(updater_info.package_zip);
+      CloseArchive(updater_info.package_zip);
     }
-    sysReleaseMap(&map);
-    free(script);
+    return 7;
+  } else {
+    fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
+  }
 
-    return 0;
+  if (updater_info.package_zip) {
+    CloseArchive(updater_info.package_zip);
+  }
+  sysReleaseMap(&map);
+
+  return 0;
 }
diff --git a/verifier.cpp b/verifier.cpp
index 2d1b0e7..2ef9c4c 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -14,24 +14,25 @@
  * limitations under the License.
  */
 
+#include "verifier.h"
+
 #include <errno.h>
-#include <malloc.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include <algorithm>
+#include <functional>
 #include <memory>
+#include <vector>
 
+#include <android-base/logging.h>
+#include <openssl/bn.h>
 #include <openssl/ecdsa.h>
 #include <openssl/obj_mac.h>
 
 #include "asn1_decoder.h"
-#include "common.h"
 #include "print_sha1.h"
-#include "ui.h"
-#include "verifier.h"
-
-extern RecoveryUI* ui;
 
 static constexpr size_t MiB = 1024 * 1024;
 
@@ -60,255 +61,246 @@
  *             SEQUENCE (SignatureAlgorithmIdentifier)
  *             OCTET STRING (SignatureValue)
  */
-static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der,
-        size_t* sig_der_length) {
-    asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len);
-    if (ctx == NULL) {
-        return false;
-    }
+static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len,
+                       std::vector<uint8_t>* sig_der) {
+  CHECK(sig_der != nullptr);
+  sig_der->clear();
 
-    asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx);
-    if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) {
-        asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq);
-        if (signed_data_app != NULL) {
-            asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app);
-            if (signed_data_seq != NULL
-                    && asn1_sequence_next(signed_data_seq)
-                    && asn1_sequence_next(signed_data_seq)
-                    && asn1_sequence_next(signed_data_seq)
-                    && asn1_constructed_skip_all(signed_data_seq)) {
-                asn1_context_t *sig_set = asn1_set_get(signed_data_seq);
-                if (sig_set != NULL) {
-                    asn1_context_t* sig_seq = asn1_sequence_get(sig_set);
-                    if (sig_seq != NULL
-                            && asn1_sequence_next(sig_seq)
-                            && asn1_sequence_next(sig_seq)
-                            && asn1_sequence_next(sig_seq)
-                            && asn1_sequence_next(sig_seq)) {
-                        uint8_t* sig_der_ptr;
-                        if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) {
-                            *sig_der = (uint8_t*) malloc(*sig_der_length);
-                            if (*sig_der != NULL) {
-                                memcpy(*sig_der, sig_der_ptr, *sig_der_length);
-                            }
-                        }
-                        asn1_context_free(sig_seq);
-                    }
-                    asn1_context_free(sig_set);
-                }
-                asn1_context_free(signed_data_seq);
-            }
-            asn1_context_free(signed_data_app);
-        }
-        asn1_context_free(pkcs7_seq);
-    }
-    asn1_context_free(ctx);
+  asn1_context ctx(pkcs7_der, pkcs7_der_len);
 
-    return *sig_der != NULL;
+  std::unique_ptr<asn1_context> pkcs7_seq(ctx.asn1_sequence_get());
+  if (pkcs7_seq == nullptr || !pkcs7_seq->asn1_sequence_next()) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> signed_data_app(pkcs7_seq->asn1_constructed_get());
+  if (signed_data_app == nullptr) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> signed_data_seq(signed_data_app->asn1_sequence_get());
+  if (signed_data_seq == nullptr ||
+      !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_constructed_skip_all()) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> sig_set(signed_data_seq->asn1_set_get());
+  if (sig_set == nullptr) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> sig_seq(sig_set->asn1_sequence_get());
+  if (sig_seq == nullptr ||
+      !sig_seq->asn1_sequence_next() ||
+      !sig_seq->asn1_sequence_next() ||
+      !sig_seq->asn1_sequence_next() ||
+      !sig_seq->asn1_sequence_next()) {
+    return false;
+  }
+
+  const uint8_t* sig_der_ptr;
+  size_t sig_der_length;
+  if (!sig_seq->asn1_octet_string_get(&sig_der_ptr, &sig_der_length)) {
+    return false;
+  }
+
+  sig_der->resize(sig_der_length);
+  std::copy(sig_der_ptr, sig_der_ptr + sig_der_length, sig_der->begin());
+  return true;
 }
 
-// Look for an RSA signature embedded in the .ZIP file comment given
-// the path to the zip.  Verify it matches one of the given public
-// keys.
-//
-// Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered
-// or no key matches the signature).
+/*
+ * Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip. Verifies
+ * that it matches one of the given public keys. A callback function can be optionally provided for
+ * posting the progress.
+ *
+ * Returns VERIFY_SUCCESS or VERIFY_FAILURE (if any error is encountered or no key matches the
+ * signature).
+ */
+int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys,
+                const std::function<void(float)>& set_progress) {
+  if (set_progress) {
+    set_progress(0.0);
+  }
 
-int verify_file(unsigned char* addr, size_t length,
-                const std::vector<Certificate>& keys) {
-    ui->SetProgress(0.0);
-
-    // An archive with a whole-file signature will end in six bytes:
-    //
-    //   (2-byte signature start) $ff $ff (2-byte comment size)
-    //
-    // (As far as the ZIP format is concerned, these are part of the
-    // archive comment.)  We start by reading this footer, this tells
-    // us how far back from the end we have to start reading to find
-    // the whole comment.
+  // An archive with a whole-file signature will end in six bytes:
+  //
+  //   (2-byte signature start) $ff $ff (2-byte comment size)
+  //
+  // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by
+  // reading this footer, this tells us how far back from the end we have to start reading to find
+  // the whole comment.
 
 #define FOOTER_SIZE 6
 
-    if (length < FOOTER_SIZE) {
-        LOGE("not big enough to contain footer\n");
-        return VERIFY_FAILURE;
-    }
+  if (length < FOOTER_SIZE) {
+    LOG(ERROR) << "not big enough to contain footer";
+    return VERIFY_FAILURE;
+  }
 
-    unsigned char* footer = addr + length - FOOTER_SIZE;
+  const unsigned char* footer = addr + length - FOOTER_SIZE;
 
-    if (footer[2] != 0xff || footer[3] != 0xff) {
-        LOGE("footer is wrong\n");
-        return VERIFY_FAILURE;
-    }
+  if (footer[2] != 0xff || footer[3] != 0xff) {
+    LOG(ERROR) << "footer is wrong";
+    return VERIFY_FAILURE;
+  }
 
-    size_t comment_size = footer[4] + (footer[5] << 8);
-    size_t signature_start = footer[0] + (footer[1] << 8);
-    LOGI("comment is %zu bytes; signature %zu bytes from end\n",
-         comment_size, signature_start);
+  size_t comment_size = footer[4] + (footer[5] << 8);
+  size_t signature_start = footer[0] + (footer[1] << 8);
+  LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start
+            << " bytes from end";
 
-    if (signature_start > comment_size) {
-        LOGE("signature start: %zu is larger than comment size: %zu\n", signature_start,
-             comment_size);
-        return VERIFY_FAILURE;
-    }
+  if (signature_start > comment_size) {
+    LOG(ERROR) << "signature start: " << signature_start << " is larger than comment size: "
+               << comment_size;
+    return VERIFY_FAILURE;
+  }
 
-    if (signature_start <= FOOTER_SIZE) {
-        LOGE("Signature start is in the footer");
-        return VERIFY_FAILURE;
-    }
+  if (signature_start <= FOOTER_SIZE) {
+    LOG(ERROR) << "Signature start is in the footer";
+    return VERIFY_FAILURE;
+  }
 
 #define EOCD_HEADER_SIZE 22
 
-    // The end-of-central-directory record is 22 bytes plus any
-    // comment length.
-    size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
+  // The end-of-central-directory record is 22 bytes plus any comment length.
+  size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
 
-    if (length < eocd_size) {
-        LOGE("not big enough to contain EOCD\n");
-        return VERIFY_FAILURE;
-    }
-
-    // Determine how much of the file is covered by the signature.
-    // This is everything except the signature data and length, which
-    // includes all of the EOCD except for the comment length field (2
-    // bytes) and the comment data.
-    size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;
-
-    unsigned char* eocd = addr + length - eocd_size;
-
-    // If this is really is the EOCD record, it will begin with the
-    // magic number $50 $4b $05 $06.
-    if (eocd[0] != 0x50 || eocd[1] != 0x4b ||
-        eocd[2] != 0x05 || eocd[3] != 0x06) {
-        LOGE("signature length doesn't match EOCD marker\n");
-        return VERIFY_FAILURE;
-    }
-
-    for (size_t i = 4; i < eocd_size-3; ++i) {
-        if (eocd[i  ] == 0x50 && eocd[i+1] == 0x4b &&
-            eocd[i+2] == 0x05 && eocd[i+3] == 0x06) {
-            // if the sequence $50 $4b $05 $06 appears anywhere after
-            // the real one, minzip will find the later (wrong) one,
-            // which could be exploitable.  Fail verification if
-            // this sequence occurs anywhere after the real one.
-            LOGE("EOCD marker occurs after start of EOCD\n");
-            return VERIFY_FAILURE;
-        }
-    }
-
-    bool need_sha1 = false;
-    bool need_sha256 = false;
-    for (const auto& key : keys) {
-        switch (key.hash_len) {
-            case SHA_DIGEST_LENGTH: need_sha1 = true; break;
-            case SHA256_DIGEST_LENGTH: need_sha256 = true; break;
-        }
-    }
-
-    SHA_CTX sha1_ctx;
-    SHA256_CTX sha256_ctx;
-    SHA1_Init(&sha1_ctx);
-    SHA256_Init(&sha256_ctx);
-
-    double frac = -1.0;
-    size_t so_far = 0;
-    while (so_far < signed_len) {
-        // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a
-        // 1196MiB full OTA and 60% for an 89MiB incremental OTA.
-        // http://b/28135231.
-        size_t size = std::min(signed_len - so_far, 16 * MiB);
-
-        if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size);
-        if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size);
-        so_far += size;
-
-        double f = so_far / (double)signed_len;
-        if (f > frac + 0.02 || size == so_far) {
-            ui->SetProgress(f);
-            frac = f;
-        }
-    }
-
-    uint8_t sha1[SHA_DIGEST_LENGTH];
-    SHA1_Final(sha1, &sha1_ctx);
-    uint8_t sha256[SHA256_DIGEST_LENGTH];
-    SHA256_Final(sha256, &sha256_ctx);
-
-    uint8_t* sig_der = nullptr;
-    size_t sig_der_length = 0;
-
-    uint8_t* signature = eocd + eocd_size - signature_start;
-    size_t signature_size = signature_start - FOOTER_SIZE;
-
-    LOGI("signature (offset: 0x%zx, length: %zu): %s\n",
-            length - signature_start, signature_size,
-            print_hex(signature, signature_size).c_str());
-
-    if (!read_pkcs7(signature, signature_size, &sig_der, &sig_der_length)) {
-        LOGE("Could not find signature DER block\n");
-        return VERIFY_FAILURE;
-    }
-
-    /*
-     * Check to make sure at least one of the keys matches the signature. Since
-     * any key can match, we need to try each before determining a verification
-     * failure has happened.
-     */
-    size_t i = 0;
-    for (const auto& key : keys) {
-        const uint8_t* hash;
-        int hash_nid;
-        switch (key.hash_len) {
-            case SHA_DIGEST_LENGTH:
-                hash = sha1;
-                hash_nid = NID_sha1;
-                break;
-            case SHA256_DIGEST_LENGTH:
-                hash = sha256;
-                hash_nid = NID_sha256;
-                break;
-            default:
-                continue;
-        }
-
-        // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
-        // the signing tool appends after the signature itself.
-        if (key.key_type == Certificate::KEY_TYPE_RSA) {
-            if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der,
-                            sig_der_length, key.rsa.get())) {
-                LOGI("failed to verify against RSA key %zu\n", i);
-                continue;
-            }
-
-            LOGI("whole-file signature verified against RSA key %zu\n", i);
-            free(sig_der);
-            return VERIFY_SUCCESS;
-        } else if (key.key_type == Certificate::KEY_TYPE_EC
-                && key.hash_len == SHA256_DIGEST_LENGTH) {
-            if (!ECDSA_verify(0, hash, key.hash_len, sig_der,
-                              sig_der_length, key.ec.get())) {
-                LOGI("failed to verify against EC key %zu\n", i);
-                continue;
-            }
-
-            LOGI("whole-file signature verified against EC key %zu\n", i);
-            free(sig_der);
-            return VERIFY_SUCCESS;
-        } else {
-            LOGI("Unknown key type %d\n", key.key_type);
-        }
-        i++;
-    }
-
-    if (need_sha1) {
-        LOGI("SHA-1 digest: %s\n", print_hex(sha1, SHA_DIGEST_LENGTH).c_str());
-    }
-    if (need_sha256) {
-        LOGI("SHA-256 digest: %s\n", print_hex(sha256, SHA256_DIGEST_LENGTH).c_str());
-    }
-    free(sig_der);
-    LOGE("failed to verify whole-file signature\n");
+  if (length < eocd_size) {
+    LOG(ERROR) << "not big enough to contain EOCD";
     return VERIFY_FAILURE;
+  }
+
+  // Determine how much of the file is covered by the signature. This is everything except the
+  // signature data and length, which includes all of the EOCD except for the comment length field
+  // (2 bytes) and the comment data.
+  size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;
+
+  const unsigned char* eocd = addr + length - eocd_size;
+
+  // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06.
+  if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) {
+    LOG(ERROR) << "signature length doesn't match EOCD marker";
+    return VERIFY_FAILURE;
+  }
+
+  for (size_t i = 4; i < eocd_size-3; ++i) {
+    if (eocd[i] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) {
+      // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will
+      // find the later (wrong) one, which could be exploitable. Fail the verification if this
+      // sequence occurs anywhere after the real one.
+      LOG(ERROR) << "EOCD marker occurs after start of EOCD";
+      return VERIFY_FAILURE;
+    }
+  }
+
+  bool need_sha1 = false;
+  bool need_sha256 = false;
+  for (const auto& key : keys) {
+    switch (key.hash_len) {
+      case SHA_DIGEST_LENGTH: need_sha1 = true; break;
+      case SHA256_DIGEST_LENGTH: need_sha256 = true; break;
+    }
+  }
+
+  SHA_CTX sha1_ctx;
+  SHA256_CTX sha256_ctx;
+  SHA1_Init(&sha1_ctx);
+  SHA256_Init(&sha256_ctx);
+
+  double frac = -1.0;
+  size_t so_far = 0;
+  while (so_far < signed_len) {
+    // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a
+    // 1196MiB full OTA and 60% for an 89MiB incremental OTA.
+    // http://b/28135231.
+    size_t size = std::min(signed_len - so_far, 16 * MiB);
+
+    if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size);
+    if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size);
+    so_far += size;
+
+    if (set_progress) {
+      double f = so_far / (double)signed_len;
+      if (f > frac + 0.02 || size == so_far) {
+        set_progress(f);
+        frac = f;
+      }
+    }
+  }
+
+  uint8_t sha1[SHA_DIGEST_LENGTH];
+  SHA1_Final(sha1, &sha1_ctx);
+  uint8_t sha256[SHA256_DIGEST_LENGTH];
+  SHA256_Final(sha256, &sha256_ctx);
+
+  const uint8_t* signature = eocd + eocd_size - signature_start;
+  size_t signature_size = signature_start - FOOTER_SIZE;
+
+  LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start) << ", length: "
+            << signature_size << "): " << print_hex(signature, signature_size);
+
+  std::vector<uint8_t> sig_der;
+  if (!read_pkcs7(signature, signature_size, &sig_der)) {
+    LOG(ERROR) << "Could not find signature DER block";
+    return VERIFY_FAILURE;
+  }
+
+  // Check to make sure at least one of the keys matches the signature. Since any key can match,
+  // we need to try each before determining a verification failure has happened.
+  size_t i = 0;
+  for (const auto& key : keys) {
+    const uint8_t* hash;
+    int hash_nid;
+    switch (key.hash_len) {
+      case SHA_DIGEST_LENGTH:
+        hash = sha1;
+        hash_nid = NID_sha1;
+        break;
+      case SHA256_DIGEST_LENGTH:
+        hash = sha256;
+        hash_nid = NID_sha256;
+        break;
+      default:
+        continue;
+    }
+
+    // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends
+    // after the signature itself.
+    if (key.key_type == Certificate::KEY_TYPE_RSA) {
+      if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der.data(), sig_der.size(),
+                      key.rsa.get())) {
+        LOG(INFO) << "failed to verify against RSA key " << i;
+        continue;
+      }
+
+      LOG(INFO) << "whole-file signature verified against RSA key " << i;
+      return VERIFY_SUCCESS;
+    } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) {
+      if (!ECDSA_verify(0, hash, key.hash_len, sig_der.data(), sig_der.size(), key.ec.get())) {
+        LOG(INFO) << "failed to verify against EC key " << i;
+        continue;
+      }
+
+      LOG(INFO) << "whole-file signature verified against EC key " << i;
+      return VERIFY_SUCCESS;
+    } else {
+      LOG(INFO) << "Unknown key type " << key.key_type;
+    }
+    i++;
+  }
+
+  if (need_sha1) {
+    LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH);
+  }
+  if (need_sha256) {
+    LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH);
+  }
+  LOG(ERROR) << "failed to verify whole-file signature";
+  return VERIFY_FAILURE;
 }
 
 std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) {
@@ -328,7 +320,7 @@
     }
 
     if (key_len_words > 8192 / 32) {
-        LOGE("key length (%d) too large\n", key_len_words);
+        LOG(ERROR) << "key length (" << key_len_words << ") too large";
         return nullptr;
     }
 
@@ -384,7 +376,7 @@
 }
 
 struct BNDeleter {
-  void operator()(BIGNUM* bn) {
+  void operator()(BIGNUM* bn) const {
     BN_free(bn);
   }
 };
@@ -484,7 +476,7 @@
 bool load_keys(const char* filename, std::vector<Certificate>& certs) {
     std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "r"), fclose);
     if (!f) {
-        LOGE("opening %s: %s\n", filename, strerror(errno));
+        PLOG(ERROR) << "error opening " << filename;
         return false;
     }
 
@@ -534,14 +526,14 @@
               return false;
             }
 
-            LOGI("read key e=%d hash=%d\n", exponent, cert.hash_len);
+            LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len;
         } else if (cert.key_type == Certificate::KEY_TYPE_EC) {
             cert.ec = parse_ec_key(f.get());
             if (!cert.ec) {
               return false;
             }
         } else {
-            LOGE("Unknown key type %d\n", cert.key_type);
+            LOG(ERROR) << "Unknown key type " << cert.key_type;
             return false;
         }
 
@@ -553,7 +545,7 @@
         } else if (ch == EOF) {
             break;
         } else {
-            LOGE("unexpected character between keys\n");
+            LOG(ERROR) << "unexpected character between keys";
             return false;
         }
     }
diff --git a/verifier.h b/verifier.h
index 58083fe..6fa8f2b 100644
--- a/verifier.h
+++ b/verifier.h
@@ -17,6 +17,7 @@
 #ifndef _RECOVERY_VERIFIER_H
 #define _RECOVERY_VERIFIER_H
 
+#include <functional>
 #include <memory>
 #include <vector>
 
@@ -25,13 +26,13 @@
 #include <openssl/sha.h>
 
 struct RSADeleter {
-  void operator()(RSA* rsa) {
+  void operator()(RSA* rsa) const {
     RSA_free(rsa);
   }
 };
 
 struct ECKEYDeleter {
-  void operator()(EC_KEY* ec_key) {
+  void operator()(EC_KEY* ec_key) const {
     EC_KEY_free(ec_key);
   }
 };
@@ -58,13 +59,14 @@
     std::unique_ptr<EC_KEY, ECKEYDeleter> ec;
 };
 
-/* addr and length define a an update package file that has been
- * loaded (or mmap'ed, or whatever) into memory.  Verify that the file
- * is signed and the signature matches one of the given keys.  Return
- * one of the constants below.
+/*
+ * 'addr' and 'length' define an update package file that has been loaded (or mmap'ed, or
+ * whatever) into memory. Verifies that the file is signed and the signature matches one of the
+ * given keys. It optionally accepts a callback function for posting the progress to. Returns one
+ * of the constants of VERIFY_SUCCESS and VERIFY_FAILURE.
  */
-int verify_file(unsigned char* addr, size_t length,
-                const std::vector<Certificate>& keys);
+int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys,
+                const std::function<void(float)>& set_progress = nullptr);
 
 bool load_keys(const char* filename, std::vector<Certificate>& certs);
 
diff --git a/wear_touch.cpp b/wear_touch.cpp
index f22d40b..e2ab44d 100644
--- a/wear_touch.cpp
+++ b/wear_touch.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#include "common.h"
-#include "wear_touch.h"
-
 #include <dirent.h>
 #include <fcntl.h>
 #include <stdio.h>
@@ -25,8 +22,11 @@
 #include <errno.h>
 #include <string.h>
 
+#include <android-base/logging.h>
 #include <linux/input.h>
 
+#include "wear_touch.h"
+
 #define DEVICE_PATH "/dev/input"
 
 WearSwipeDetector::WearSwipeDetector(int low, int high, OnSwipeCallback callback, void* cookie):
@@ -49,11 +49,11 @@
     } else if (abs(dx) < mLowThreshold && abs(dy) > mHighThreshold) {
         direction = dy < 0 ? UP : DOWN;
     } else {
-        LOGD("Ignore %d %d\n", dx, dy);
+        LOG(DEBUG) << "Ignore " << dx << " " << dy;
         return;
     }
 
-    LOGD("Swipe direction=%d\n", direction);
+    LOG(DEBUG) << "Swipe direction=" << direction;
     mCallback(mCookie, direction);
 }
 
@@ -105,7 +105,7 @@
 void WearSwipeDetector::run() {
     int fd = findDevice(DEVICE_PATH);
     if (fd < 0) {
-        LOGE("no input devices found\n");
+        LOG(ERROR) << "no input devices found";
         return;
     }
 
@@ -118,23 +118,23 @@
 }
 
 void* WearSwipeDetector::touch_thread(void* cookie) {
-    ((WearSwipeDetector*)cookie)->run();
+    (static_cast<WearSwipeDetector*>(cookie))->run();
     return NULL;
 }
 
-#define test_bit(bit, array)    (array[bit/8] & (1<<(bit%8)))
+#define test_bit(bit, array)    ((array)[(bit)/8] & (1<<((bit)%8)))
 
 int WearSwipeDetector::openDevice(const char *device) {
     int fd = open(device, O_RDONLY);
     if (fd < 0) {
-        LOGE("could not open %s, %s\n", device, strerror(errno));
+        PLOG(ERROR) << "could not open " << device;
         return false;
     }
 
     char name[80];
     name[sizeof(name) - 1] = '\0';
     if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
-        LOGE("could not get device name for %s, %s\n", device, strerror(errno));
+        PLOG(ERROR) << "could not get device name for " << device;
         name[0] = '\0';
     }
 
@@ -143,7 +143,7 @@
     int ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits);
     if (ret > 0) {
         if (test_bit(ABS_MT_POSITION_X, bits) && test_bit(ABS_MT_POSITION_Y, bits)) {
-            LOGD("Found %s %s\n", device, name);
+            LOG(DEBUG) << "Found " << device << " " << name;
             return fd;
         }
     }
@@ -155,7 +155,7 @@
 int WearSwipeDetector::findDevice(const char* path) {
     DIR* dir = opendir(path);
     if (dir == NULL) {
-        LOGE("Could not open directory %s", path);
+        PLOG(ERROR) << "Could not open directory " << path;
         return false;
     }
 
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 660a078..6c02865 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "wear_ui.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <stdarg.h>
@@ -25,14 +27,16 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <string>
 #include <vector>
 
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/stringprintf.h>
+#include <minui/minui.h>
+
 #include "common.h"
 #include "device.h"
-#include "wear_ui.h"
-#include "cutils/properties.h"
-#include "android-base/strings.h"
-#include "android-base/stringprintf.h"
 
 // There's only (at most) one of these objects, and global callbacks
 // (for pthread_create, and the input event system) need to find it,
@@ -119,8 +123,8 @@
         int y = outer_height;
         int x = outer_width;
         if (show_menu) {
-            char recovery_fingerprint[PROPERTY_VALUE_MAX];
-            property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, "");
+            std::string recovery_fingerprint =
+                    android::base::GetProperty("ro.bootimage.build.fingerprint", "");
             SetColor(HEADER);
             DrawTextLine(x + 4, &y, "Android Recovery", true);
             for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) {
@@ -190,8 +194,10 @@
     gr_flip();
 }
 
-void WearRecoveryUI::InitTextParams() {
-    ScreenRecoveryUI::InitTextParams();
+bool WearRecoveryUI::InitTextParams() {
+    if (!ScreenRecoveryUI::InitTextParams()) {
+        return false;
+    }
 
     text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_;
 
@@ -199,49 +205,51 @@
     if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
 
     visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_;
+    return true;
 }
 
-void WearRecoveryUI::Init() {
-    ScreenRecoveryUI::Init();
+bool WearRecoveryUI::Init(const std::string& locale) {
+  if (!ScreenRecoveryUI::Init(locale)) {
+    return false;
+  }
 
-    LoadBitmap("icon_error", &backgroundIcon[ERROR]);
-    backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
+  LoadBitmap("icon_error", &backgroundIcon[ERROR]);
+  backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
 
-    // This leaves backgroundIcon[INSTALLING_UPDATE] and backgroundIcon[ERASING]
-    // as NULL which is fine since draw_background_locked() doesn't use them.
+  // This leaves backgroundIcon[INSTALLING_UPDATE] and backgroundIcon[ERASING]
+  // as NULL which is fine since draw_background_locked() doesn't use them.
+
+  return true;
 }
 
-void WearRecoveryUI::SetStage(int current, int max)
-{
-}
+void WearRecoveryUI::SetStage(int current, int max) {}
 
-void WearRecoveryUI::Print(const char *fmt, ...)
-{
-    char buf[256];
-    va_list ap;
-    va_start(ap, fmt);
-    vsnprintf(buf, 256, fmt, ap);
-    va_end(ap);
+void WearRecoveryUI::Print(const char* fmt, ...) {
+  char buf[256];
+  va_list ap;
+  va_start(ap, fmt);
+  vsnprintf(buf, 256, fmt, ap);
+  va_end(ap);
 
-    fputs(buf, stdout);
+  fputs(buf, stdout);
 
-    // This can get called before ui_init(), so be careful.
-    pthread_mutex_lock(&updateMutex);
-    if (text_rows_ > 0 && text_cols_ > 0) {
-        char *ptr;
-        for (ptr = buf; *ptr != '\0'; ++ptr) {
-            if (*ptr == '\n' || text_col_ >= text_cols_) {
-                text_[text_row_][text_col_] = '\0';
-                text_col_ = 0;
-                text_row_ = (text_row_ + 1) % text_rows_;
-                if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
-            }
-            if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
-        }
+  // This can get called before ui_init(), so be careful.
+  pthread_mutex_lock(&updateMutex);
+  if (text_rows_ > 0 && text_cols_ > 0) {
+    char* ptr;
+    for (ptr = buf; *ptr != '\0'; ++ptr) {
+      if (*ptr == '\n' || text_col_ >= text_cols_) {
         text_[text_row_][text_col_] = '\0';
-        update_screen_locked();
+        text_col_ = 0;
+        text_row_ = (text_row_ + 1) % text_rows_;
+        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
+      }
+      if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
     }
-    pthread_mutex_unlock(&updateMutex);
+    text_[text_row_][text_col_] = '\0';
+    update_screen_locked();
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
@@ -294,8 +302,8 @@
 }
 
 void WearRecoveryUI::ShowFile(FILE* fp) {
-    std::vector<long> offsets;
-    offsets.push_back(ftell(fp));
+    std::vector<off_t> offsets;
+    offsets.push_back(ftello(fp));
     ClearText();
 
     struct stat sb;
@@ -305,7 +313,7 @@
     while (true) {
         if (show_prompt) {
             Print("--(%d%% of %d bytes)--",
-                  static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))),
+                  static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
                   static_cast<int>(sb.st_size));
             Redraw();
             while (show_prompt) {
@@ -324,7 +332,7 @@
                     if (feof(fp)) {
                         return;
                     }
-                    offsets.push_back(ftell(fp));
+                    offsets.push_back(ftello(fp));
                 }
             }
             ClearText();
diff --git a/wear_ui.h b/wear_ui.h
index 9351d41..4cd852f 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -19,11 +19,13 @@
 
 #include "screen_ui.h"
 
+#include <string>
+
 class WearRecoveryUI : public ScreenRecoveryUI {
   public:
     WearRecoveryUI();
 
-    void Init() override;
+    bool Init(const std::string& locale) override;
 
     void SetStage(int current, int max) override;
 
@@ -52,7 +54,7 @@
 
     int GetProgressBaseline() override;
 
-    void InitTextParams() override;
+    bool InitTextParams() override;
 
     void update_progress_locked() override;