Fix the input parameter for "set_retry_bootloader_message"
am: 539b08cfcf

Change-Id: Idd730e8e9ae3dbf27bc20b4ff2c5a3e73e28ce5d
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..f919ebc
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,3 @@
+subdirs = [
+    "otautil",
+]
diff --git a/Android.mk b/Android.mk
index 408b146..1f69d5d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,20 +22,23 @@
 # ===============================
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := fuse_sideload.cpp
-LOCAL_CLANG := true
-LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter -Werror
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
 LOCAL_MODULE := libfusesideload
-LOCAL_STATIC_LIBRARIES := libcutils libc libcrypto
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbase
 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_CFLAGS := \
+    -Wall \
+    -Werror
 LOCAL_MODULE := libmounts
+LOCAL_STATIC_LIBRARIES := libbase
 include $(BUILD_STATIC_LIBRARY)
 
 # librecovery (static library)
@@ -43,7 +46,7 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := \
     install.cpp
-LOCAL_CFLAGS := -Wno-unused-parameter -Werror
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 
 ifeq ($(AB_OTA_UPDATER),true)
@@ -66,7 +69,6 @@
 
 LOCAL_SRC_FILES := \
     adb_install.cpp \
-    asn1_decoder.cpp \
     device.cpp \
     fuse_sdcard_provider.cpp \
     recovery.cpp \
@@ -74,7 +76,6 @@
     rotate_logs.cpp \
     screen_ui.cpp \
     ui.cpp \
-    verifier.cpp \
     wear_ui.cpp \
     wear_touch.cpp \
 
@@ -90,14 +91,13 @@
 
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 LOCAL_CFLAGS += -Wno-unused-parameter -Werror
-LOCAL_CLANG := true
 
 LOCAL_C_INCLUDES += \
     system/vold \
-    system/core/adb \
 
 LOCAL_STATIC_LIBRARIES := \
     librecovery \
+    libverifier \
     libbatterymonitor \
     libbootloader_message \
     libext4_utils \
@@ -172,7 +172,6 @@
 # ===============================
 include $(CLEAR_VARS)
 LOCAL_MODULE := libverifier
-LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := \
     asn1_decoder.cpp \
     verifier.cpp
@@ -185,12 +184,12 @@
 
 include \
     $(LOCAL_PATH)/applypatch/Android.mk \
+    $(LOCAL_PATH)/boot_control/Android.mk \
     $(LOCAL_PATH)/bootloader_message/Android.mk \
     $(LOCAL_PATH)/edify/Android.mk \
     $(LOCAL_PATH)/minadbd/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/adb_install.cpp b/adb_install.cpp
index 79b8df9..ac01306 100644
--- a/adb_install.cpp
+++ b/adb_install.cpp
@@ -14,124 +14,130 @@
  * limitations under the License.
  */
 
-#include <unistd.h>
-#include <dirent.h>
+#include "adb_install.h"
+
 #include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
-#include <sys/stat.h>
-#include <signal.h>
-#include <fcntl.h>
+#include <unistd.h>
 
-#include "ui.h"
-#include "install.h"
-#include "common.h"
-#include "adb_install.h"
-#include "minadbd/fuse_adb_provider.h"
-#include "fuse_sideload.h"
-
+#include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <android-base/unique_fd.h>
 
-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));
-        return;
-    }
-    if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) {
-        ui->Print("failed to set driver control: %s\n", strerror(errno));
-    }
-    if (close(fd) < 0) {
-        ui->Print("failed to close driver control: %s\n", strerror(errno));
-    }
+#include "common.h"
+#include "fuse_sideload.h"
+#include "install.h"
+#include "ui.h"
+
+static void set_usb_driver(bool enabled) {
+  // USB configfs doesn't use /s/c/a/a/enable.
+  if (android::base::GetBoolProperty("sys.usb.configfs", false)) {
+    return;
+  }
+
+  static constexpr const char* USB_DRIVER_CONTROL = "/sys/class/android_usb/android0/enable";
+  android::base::unique_fd fd(open(USB_DRIVER_CONTROL, O_WRONLY));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open driver control";
+    return;
+  }
+  // Not using android::base::WriteStringToFile since that will open with O_CREAT and give EPERM
+  // when USB_DRIVER_CONTROL doesn't exist. When it gives EPERM, we don't know whether that's due
+  // to non-existent USB_DRIVER_CONTROL or indeed a permission issue.
+  if (!android::base::WriteStringToFd(enabled ? "1" : "0", fd)) {
+    PLOG(ERROR) << "Failed to set driver control";
+  }
 }
 
-static void stop_adbd(RecoveryUI* ui) {
-    ui->Print("Stopping adbd...\n");
-    android::base::SetProperty("ctl.stop", "adbd");
-    set_usb_driver(ui, false);
+static void stop_adbd() {
+  ui->Print("Stopping adbd...\n");
+  android::base::SetProperty("ctl.stop", "adbd");
+  set_usb_driver(false);
 }
 
-static void maybe_restart_adbd(RecoveryUI* ui) {
-    if (is_ro_debuggable()) {
-        ui->Print("Restarting adbd...\n");
-        set_usb_driver(ui, true);
-        android::base::SetProperty("ctl.start", "adbd");
-    }
+static void maybe_restart_adbd() {
+  if (is_ro_debuggable()) {
+    ui->Print("Restarting adbd...\n");
+    set_usb_driver(true);
+    android::base::SetProperty("ctl.start", "adbd");
+  }
 }
 
-// How long (in seconds) we wait for the host to start sending us a
-// package, before timing out.
-#define ADB_INSTALL_TIMEOUT 300
+int apply_from_adb(bool* wipe_cache, const char* install_file) {
+  modified_flash = true;
 
-int apply_from_adb(RecoveryUI* ui, bool* wipe_cache, const char* install_file) {
-    modified_flash = true;
+  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");
 
-    ui->Print("\n\nNow send the package you want to apply\n"
-              "to the device with \"adb sideload <filename>\"...\n");
+  pid_t child;
+  if ((child = fork()) == 0) {
+    execl("/sbin/recovery", "recovery", "--adbd", nullptr);
+    _exit(EXIT_FAILURE);
+  }
 
-    pid_t child;
-    if ((child = fork()) == 0) {
-        execl("/sbin/recovery", "recovery", "--adbd", NULL);
-        _exit(EXIT_FAILURE);
+  // How long (in seconds) we wait for the host to start sending us a package, before timing out.
+  static constexpr int ADB_INSTALL_TIMEOUT = 300;
+
+  // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host connects and starts serving a
+  // package. Poll for its appearance. (Note that inotify doesn't work with FUSE.)
+  int result = INSTALL_ERROR;
+  int status;
+  bool waited = false;
+  for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
+    if (waitpid(child, &status, WNOHANG) != 0) {
+      result = INSTALL_ERROR;
+      waited = true;
+      break;
     }
 
-    // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host
-    // connects and starts serving a package.  Poll for its
-    // appearance.  (Note that inotify doesn't work with FUSE.)
-    int result = INSTALL_ERROR;
-    int status;
-    bool waited = false;
     struct stat st;
-    for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
-        if (waitpid(child, &status, WNOHANG) != 0) {
-            result = INSTALL_ERROR;
-            waited = true;
-            break;
-        }
-
-        if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
-            if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) {
-                sleep(1);
-                continue;
-            } else {
-                ui->Print("\nTimed out waiting for package.\n\n");
-                result = INSTALL_ERROR;
-                kill(child, SIGKILL);
-                break;
-            }
-        }
-        result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false, 0);
+    if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
+      if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) {
+        sleep(1);
+        continue;
+      } else {
+        ui->Print("\nTimed out waiting for package.\n\n");
+        result = INSTALL_ERROR;
+        kill(child, SIGKILL);
         break;
+      }
     }
+    result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false, 0);
+    break;
+  }
 
-    if (!waited) {
-        // Calling stat() on this magic filename signals the minadbd
-        // subprocess to shut down.
-        stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+  if (!waited) {
+    // Calling stat() on this magic filename signals the minadbd subprocess to shut down.
+    struct stat st;
+    stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
 
-        // TODO(dougz): there should be a way to cancel waiting for a
-        // package (by pushing some button combo on the device).  For now
-        // you just have to 'adb sideload' a file that's not a valid
-        // package, like "/dev/null".
-        waitpid(child, &status, 0);
+    // TODO: there should be a way to cancel waiting for a package (by pushing some button combo on
+    // the device). For now you just have to 'adb sideload' a file that's not a valid package, like
+    // "/dev/null".
+    waitpid(child, &status, 0);
+  }
+
+  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+    if (WEXITSTATUS(status) == 3) {
+      ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n");
+    } else if (!WIFSIGNALED(status)) {
+      ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status));
     }
+  }
 
-    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-        if (WEXITSTATUS(status) == 3) {
-            ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n");
-        } else if (!WIFSIGNALED(status)) {
-            ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status));
-        }
-    }
+  set_usb_driver(false);
+  maybe_restart_adbd();
 
-    set_usb_driver(ui, false);
-    maybe_restart_adbd(ui);
-
-    return result;
+  return result;
 }
diff --git a/adb_install.h b/adb_install.h
index efad436..e654c89 100644
--- a/adb_install.h
+++ b/adb_install.h
@@ -17,8 +17,6 @@
 #ifndef _ADB_INSTALL_H
 #define _ADB_INSTALL_H
 
-class RecoveryUI;
-
-int apply_from_adb(RecoveryUI* h, bool* wipe_cache, const char* install_file);
+int apply_from_adb(bool* wipe_cache, const char* install_file);
 
 #endif
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 7be3fdb..51bf393 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -27,6 +27,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <functional>
 #include <memory>
 #include <string>
 #include <utility>
@@ -42,7 +43,7 @@
 #include "print_sha1.h"
 
 static int LoadPartitionContents(const std::string& filename, FileContents* file);
-static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token);
+static size_t FileSink(const unsigned char* data, size_t len, int fd);
 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);
@@ -194,8 +195,8 @@
     return -1;
   }
 
-  ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd);
-  if (bytes_written != static_cast<ssize_t>(file->data.size())) {
+  size_t bytes_written = FileSink(file->data.data(), file->data.size(), fd);
+  if (bytes_written != file->data.size()) {
     printf("short write of \"%s\" (%zd bytes of %zu): %s\n", filename, bytes_written,
            file->data.size(), strerror(errno));
     return -1;
@@ -433,25 +434,17 @@
     return 0;
 }
 
-ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) {
-    int fd = *static_cast<int*>(token);
-    ssize_t done = 0;
-    ssize_t wrote;
-    while (done < len) {
-        wrote = TEMP_FAILURE_RETRY(ota_write(fd, data+done, len-done));
-        if (wrote == -1) {
-            printf("error writing %zd bytes: %s\n", (len-done), strerror(errno));
-            return done;
-        }
-        done += wrote;
+static size_t FileSink(const unsigned char* data, size_t len, int fd) {
+  size_t done = 0;
+  while (done < len) {
+    ssize_t wrote = TEMP_FAILURE_RETRY(ota_write(fd, data + done, len - done));
+    if (wrote == -1) {
+      printf("error writing %zd bytes: %s\n", (len - done), strerror(errno));
+      return done;
     }
-    return done;
-}
-
-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;
+    done += wrote;
+  }
+  return done;
 }
 
 // Return the amount of free space (in bytes) on the filesystem
@@ -647,9 +640,11 @@
   }
 
   // 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;
+  SinkFn sink = [&memory_sink_str](const unsigned char* data, size_t len) {
+    memory_sink_str.append(reinterpret_cast<const char*>(data), len);
+    return len;
+  };
 
   SHA_CTX ctx;
   SHA1_Init(&ctx);
@@ -657,10 +652,10 @@
   int result;
   if (use_bsdiff) {
     result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch.get(), 0,
-                              sink, token, &ctx);
+                              sink, &ctx);
   } else {
     result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink,
-                             token, &ctx, bonus_data);
+                             &ctx, bonus_data);
   }
 
   if (result != 0) {
diff --git a/applypatch/applypatch.sh b/applypatch/applypatch.sh
deleted file mode 100755
index 8ea68a1..0000000
--- a/applypatch/applypatch.sh
+++ /dev/null
@@ -1,350 +0,0 @@
-#!/bin/bash
-#
-# A test suite for applypatch.  Run in a client where you have done
-# envsetup, choosecombo, etc.
-#
-# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT.  It will mess up your
-# system partition.
-#
-#
-# TODO: find some way to get this run regularly along with the rest of
-# the tests.
-
-EMULATOR_PORT=5580
-DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/applypatch/testdata
-
-# This must be the filename that applypatch uses for its copies.
-CACHE_TEMP_SOURCE=/cache/saved.file
-
-# Put all binaries and files here.  We use /cache because it's a
-# temporary filesystem in the emulator; it's created fresh each time
-# the emulator starts.
-WORK_DIR=/system
-
-# partition that WORK_DIR is located on, without the leading slash
-WORK_FS=system
-
-# set to 0 to use a device instead
-USE_EMULATOR=1
-
-# ------------------------
-
-tmpdir=$(mktemp -d)
-
-if [ "$USE_EMULATOR" == 1 ]; then
-  emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT &
-  pid_emulator=$!
-  ADB="adb -s emulator-$EMULATOR_PORT "
-else
-  ADB="adb -d "
-fi
-
-echo "waiting to connect to device"
-$ADB wait-for-device
-echo "device is available"
-$ADB remount
-# free up enough space on the system partition for the test to run.
-$ADB shell rm -r /system/media
-
-# run a command on the device; exit with the exit status of the device
-# command.
-run_command() {
-  $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}'
-}
-
-testname() {
-  echo
-  echo "$1"...
-  testname="$1"
-}
-
-fail() {
-  echo
-  echo FAIL: $testname
-  echo
-  [ "$open_pid" == "" ] || kill $open_pid
-  [ "$pid_emulator" == "" ] || kill $pid_emulator
-  exit 1
-}
-
-sha1() {
-  sha1sum $1 | awk '{print $1}'
-}
-
-free_space() {
-  run_command df | awk "/$1/ {print gensub(/K/, \"\", \"g\", \$6)}"
-}
-
-cleanup() {
-  # not necessary if we're about to kill the emulator, but nice for
-  # running on real devices or already-running emulators.
-  testname "removing test files"
-  run_command rm $WORK_DIR/bloat.dat
-  run_command rm $WORK_DIR/old.file
-  run_command rm $WORK_DIR/foo
-  run_command rm $WORK_DIR/patch.bsdiff
-  run_command rm $WORK_DIR/applypatch
-  run_command rm $CACHE_TEMP_SOURCE
-  run_command rm /cache/bloat*.dat
-
-  [ "$pid_emulator" == "" ] || kill $pid_emulator
-
-  if [ $# == 0 ]; then
-    rm -rf $tmpdir
-  fi
-}
-
-cleanup leave_tmp
-
-$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch
-
-BAD1_SHA1=$(printf "%040x" $RANDOM)
-BAD2_SHA1=$(printf "%040x" $RANDOM)
-OLD_SHA1=$(sha1 $DATA_DIR/old.file)
-NEW_SHA1=$(sha1 $DATA_DIR/new.file)
-NEW_SIZE=$(stat -c %s $DATA_DIR/new.file)
-
-# --------------- basic execution ----------------------
-
-testname "usage message"
-run_command $WORK_DIR/applypatch && fail
-
-testname "display license"
-run_command $WORK_DIR/applypatch -l | grep -q -i copyright || fail
-
-
-# --------------- check mode ----------------------
-
-$ADB push $DATA_DIR/old.file $WORK_DIR
-
-testname "check mode single"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
-
-testname "check mode multiple"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
-
-testname "check mode failure"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
-
-$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
-# put some junk in the old file
-run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
-
-testname "check mode cache (corrupted) single"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
-
-testname "check mode cache (corrupted) multiple"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
-
-testname "check mode cache (corrupted) failure"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
-
-# remove the old file entirely
-run_command rm $WORK_DIR/old.file
-
-testname "check mode cache (missing) single"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail
-
-testname "check mode cache (missing) multiple"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail
-
-testname "check mode cache (missing) failure"
-run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail
-
-
-# --------------- apply patch ----------------------
-
-$ADB push $DATA_DIR/old.file $WORK_DIR
-$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
-echo hello > $tmpdir/foo
-$ADB push $tmpdir/foo $WORK_DIR
-
-# Check that the partition has enough space to apply the patch without
-# copying.  If it doesn't, we'll be testing the low-space condition
-# when we intend to test the not-low-space condition.
-testname "apply patches (with enough space)"
-free_kb=$(free_space $WORK_FS)
-echo "${free_kb}kb free on /$WORK_FS."
-if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then
-  echo "Not enough space on /$WORK_FS to patch test file."
-  echo
-  echo "This doesn't mean that applypatch is necessarily broken;"
-  echo "just that /$WORK_FS doesn't have enough free space to"
-  echo "properly run this test."
-  exit 1
-fi
-
-testname "apply bsdiff patch"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/old.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-testname "reapply bsdiff patch"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/old.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-
-# --------------- apply patch in new location ----------------------
-
-$ADB push $DATA_DIR/old.file $WORK_DIR
-$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
-
-# Check that the partition has enough space to apply the patch without
-# copying.  If it doesn't, we'll be testing the low-space condition
-# when we intend to test the not-low-space condition.
-testname "apply patch to new location (with enough space)"
-free_kb=$(free_space $WORK_FS)
-echo "${free_kb}kb free on /$WORK_FS."
-if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then
-  echo "Not enough space on /$WORK_FS to patch test file."
-  echo
-  echo "This doesn't mean that applypatch is necessarily broken;"
-  echo "just that /$WORK_FS doesn't have enough free space to"
-  echo "properly run this test."
-  exit 1
-fi
-
-run_command rm $WORK_DIR/new.file
-run_command rm $CACHE_TEMP_SOURCE
-
-testname "apply bsdiff patch to new location"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/new.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-testname "reapply bsdiff patch to new location"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/new.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
-# put some junk in the old file
-run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
-
-testname "apply bsdiff patch to new location with corrupted source"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo || fail
-$ADB pull $WORK_DIR/new.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-# put some junk in the cache copy, too
-run_command dd if=/dev/urandom of=$CACHE_TEMP_SOURCE count=100 bs=1024 || fail
-
-run_command rm $WORK_DIR/new.file
-testname "apply bsdiff patch to new location with corrupted source and copy (no new file)"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail
-
-# put some junk in the new file
-run_command dd if=/dev/urandom of=$WORK_DIR/new.file count=100 bs=1024 || fail
-
-testname "apply bsdiff patch to new location with corrupted source and copy (bad new file)"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail
-
-# --------------- apply patch with low space on /system ----------------------
-
-$ADB push $DATA_DIR/old.file $WORK_DIR
-$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
-
-free_kb=$(free_space $WORK_FS)
-echo "${free_kb}kb free on /$WORK_FS; we'll soon fix that."
-echo run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail
-run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail
-free_kb=$(free_space $WORK_FS)
-echo "${free_kb}kb free on /$WORK_FS now."
-
-testname "apply bsdiff patch with low space"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/old.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-testname "reapply bsdiff patch with low space"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/old.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-# --------------- apply patch with low space on /system and /cache ----------------------
-
-$ADB push $DATA_DIR/old.file $WORK_DIR
-$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR
-
-free_kb=$(free_space $WORK_FS)
-echo "${free_kb}kb free on /$WORK_FS"
-
-run_command mkdir /cache/subdir
-run_command 'echo > /cache/subdir/a.file'
-run_command 'echo > /cache/a.file'
-run_command mkdir /cache/recovery /cache/recovery/otatest
-run_command 'echo > /cache/recovery/otatest/b.file'
-run_command "echo > $CACHE_TEMP_SOURCE"
-free_kb=$(free_space cache)
-echo "${free_kb}kb free on /cache; we'll soon fix that."
-run_command dd if=/dev/zero of=/cache/bloat_small.dat count=128 bs=1024 || fail
-run_command dd if=/dev/zero of=/cache/bloat_large.dat count=$((free_kb-640)) bs=1024 || fail
-free_kb=$(free_space cache)
-echo "${free_kb}kb free on /cache now."
-
-testname "apply bsdiff patch with low space, full cache, can't delete enough"
-$ADB shell 'cat >> /cache/bloat_large.dat' & open_pid=$!
-echo "open_pid is $open_pid"
-
-# size check should fail even though it deletes some stuff
-run_command $WORK_DIR/applypatch -s $NEW_SIZE && fail
-run_command ls /cache/bloat_small.dat && fail          # was deleted
-run_command ls /cache/a.file && fail                   # was deleted
-run_command ls /cache/recovery/otatest/b.file && fail  # was deleted
-run_command ls /cache/bloat_large.dat || fail          # wasn't deleted because it was open
-run_command ls /cache/subdir/a.file || fail            # wasn't deleted because it's in a subdir
-run_command ls $CACHE_TEMP_SOURCE || fail              # wasn't deleted because it's the source file copy
-
-# should fail; not enough files can be deleted
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff && fail
-run_command ls /cache/bloat_large.dat || fail   # wasn't deleted because it was open
-run_command ls /cache/subdir/a.file || fail     # wasn't deleted because it's in a subdir
-run_command ls $CACHE_TEMP_SOURCE || fail       # wasn't deleted because it's the source file copy
-
-kill $open_pid   # /cache/bloat_large.dat is no longer open
-
-testname "apply bsdiff patch with low space, full cache, can delete enough"
-
-# should succeed after deleting /cache/bloat_large.dat
-run_command $WORK_DIR/applypatch -s $NEW_SIZE || fail
-run_command ls /cache/bloat_large.dat && fail   # was deleted
-run_command ls /cache/subdir/a.file || fail     # still wasn't deleted because it's in a subdir
-run_command ls $CACHE_TEMP_SOURCE || fail       # wasn't deleted because it's the source file copy
-
-# should succeed
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/old.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-run_command ls /cache/subdir/a.file || fail     # still wasn't deleted because it's in a subdir
-run_command ls $CACHE_TEMP_SOURCE && fail       # was deleted because patching overwrote it, then deleted it
-
-# --------------- apply patch from cache ----------------------
-
-$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
-# put some junk in the old file
-run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail
-
-testname "apply bsdiff patch from cache (corrupted source) with low space"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/old.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE
-# remove the old file entirely
-run_command rm $WORK_DIR/old.file
-
-testname "apply bsdiff patch from cache (missing source) with low space"
-run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail
-$ADB pull $WORK_DIR/old.file $tmpdir/patched
-diff -q $DATA_DIR/new.file $tmpdir/patched || fail
-
-
-# --------------- cleanup ----------------------
-
-cleanup
-
-echo
-echo PASS
-echo
-
diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp
index 7b191a8..aa32d57 100644
--- a/applypatch/applypatch_modes.cpp
+++ b/applypatch/applypatch_modes.cpp
@@ -44,19 +44,6 @@
     return applypatch_check(argv[2], sha1);
 }
 
-static int SpaceMode(int argc, const char** argv) {
-    if (argc != 3) {
-        return 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, const char** argv, std::vector<std::string>* sha1s,
@@ -175,13 +162,12 @@
             "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> "
             "[<src-sha1>:<patch> ...]\n"
             "   or  %s -c <file> [<sha1> ...]\n"
-            "   or  %s -s <bytes>\n"
             "   or  %s -l\n"
             "\n"
             "Filenames may be of the form\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]);
+            argv[0], argv[0], argv[0]);
         return 2;
     }
 
@@ -191,8 +177,6 @@
         result = ShowLicenses();
     } else if (strncmp(argv[1], "-c", 3) == 0) {
         result = CheckMode(argc, argv);
-    } else if (strncmp(argv[1], "-s", 3) == 0) {
-        result = SpaceMode(argc, argv);
     } else {
         result = PatchMode(argc, argv);
     }
diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp
index 9920c2b..f75a2c6 100644
--- a/applypatch/bspatch.cpp
+++ b/applypatch/bspatch.cpp
@@ -24,9 +24,9 @@
 #include <sys/types.h>
 
 #include <bspatch.h>
+#include <openssl/sha.h>
 
 #include "applypatch/applypatch.h"
-#include "openssl/sha.h"
 
 void ShowBSDiffLicense() {
     puts("The bsdiff library used herein is:\n"
@@ -60,10 +60,10 @@
         );
 }
 
-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);
+int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch,
+                     size_t patch_offset, SinkFn sink, SHA_CTX* ctx) {
+  auto sha_sink = [&sink, &ctx](const uint8_t* data, size_t len) {
+    len = sink(data, len);
     if (ctx) SHA1_Update(ctx, data, len);
     return len;
   };
@@ -72,8 +72,8 @@
                          patch->data.size(), sha_sink);
 }
 
-int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, const Value* patch,
-                        ssize_t patch_offset, std::vector<unsigned char>* new_data) {
+int ApplyBSDiffPatchMem(const unsigned char* old_data, size_t old_size, const Value* patch,
+                        size_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;
diff --git a/applypatch/imgdiff_test.sh b/applypatch/imgdiff_test.sh
deleted file mode 100755
index dcdb922..0000000
--- a/applypatch/imgdiff_test.sh
+++ /dev/null
@@ -1,118 +0,0 @@
-#!/bin/bash
-#
-# A script for testing imgdiff/applypatch.  It takes two full OTA
-# packages as arguments.  It generates (on the host) patches for all
-# the zip/jar/apk files they have in common, as well as boot and
-# recovery images.  It then applies the patches on the device (or
-# emulator) and checks that the resulting file is correct.
-
-EMULATOR_PORT=5580
-
-# set to 0 to use a device instead
-USE_EMULATOR=0
-
-# where on the device to do all the patching.
-WORK_DIR=/data/local/tmp
-
-START_OTA_PACKAGE=$1
-END_OTA_PACKAGE=$2
-
-# ------------------------
-
-tmpdir=$(mktemp -d)
-
-if [ "$USE_EMULATOR" == 1 ]; then
-  emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT &
-  pid_emulator=$!
-  ADB="adb -s emulator-$EMULATOR_PORT "
-else
-  ADB="adb -d "
-fi
-
-echo "waiting to connect to device"
-$ADB wait-for-device
-
-# run a command on the device; exit with the exit status of the device
-# command.
-run_command() {
-  $ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}'
-}
-
-testname() {
-  echo
-  echo "$1"...
-  testname="$1"
-}
-
-fail() {
-  echo
-  echo FAIL: $testname
-  echo
-  [ "$open_pid" == "" ] || kill $open_pid
-  [ "$pid_emulator" == "" ] || kill $pid_emulator
-  exit 1
-}
-
-sha1() {
-  sha1sum $1 | awk '{print $1}'
-}
-
-size() {
-  stat -c %s $1 | tr -d '\n'
-}
-
-cleanup() {
-  # not necessary if we're about to kill the emulator, but nice for
-  # running on real devices or already-running emulators.
-  testname "removing test files"
-  run_command rm $WORK_DIR/applypatch
-  run_command rm $WORK_DIR/source
-  run_command rm $WORK_DIR/target
-  run_command rm $WORK_DIR/patch
-
-  [ "$pid_emulator" == "" ] || kill $pid_emulator
-
-  rm -rf $tmpdir
-}
-
-$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch
-
-patch_and_apply() {
-  local fn=$1
-  shift
-
-  unzip -p $START_OTA_PACKAGE $fn > $tmpdir/source
-  unzip -p $END_OTA_PACKAGE $fn > $tmpdir/target
-  imgdiff "$@" $tmpdir/source $tmpdir/target $tmpdir/patch
-  bsdiff $tmpdir/source $tmpdir/target $tmpdir/patch.bs
-  echo "patch for $fn is $(size $tmpdir/patch) [of $(size $tmpdir/target)] ($(size $tmpdir/patch.bs) with bsdiff)"
-  echo "$fn $(size $tmpdir/patch) of $(size $tmpdir/target) bsdiff $(size $tmpdir/patch.bs)" >> /tmp/stats.txt
-  $ADB push $tmpdir/source $WORK_DIR/source || fail "source push failed"
-  run_command rm /data/local/tmp/target
-  $ADB push $tmpdir/patch $WORK_DIR/patch || fail "patch push failed"
-  run_command /data/local/tmp/applypatch /data/local/tmp/source \
-    /data/local/tmp/target $(sha1 $tmpdir/target) $(size $tmpdir/target) \
-    $(sha1 $tmpdir/source):/data/local/tmp/patch \
-    || fail "applypatch of $fn failed"
-  $ADB pull /data/local/tmp/target $tmpdir/result
-  diff -q $tmpdir/target $tmpdir/result || fail "patch output not correct!"
-}
-
-# --------------- basic execution ----------------------
-
-for i in $((zipinfo -1 $START_OTA_PACKAGE; zipinfo -1 $END_OTA_PACKAGE) | \
-           sort | uniq -d | egrep -e '[.](apk|jar|zip)$'); do
-  patch_and_apply $i -z
-done
-patch_and_apply boot.img
-patch_and_apply system/recovery.img
-
-
-# --------------- cleanup ----------------------
-
-cleanup
-
-echo
-echo PASS
-echo
-
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index adcc61f..3d905f7 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -43,12 +43,11 @@
   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) {
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
+                    size_t patch_size, SinkFn sink) {
   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);
+  return ApplyImagePatch(old_data, old_size, &patch, sink, nullptr, nullptr);
 }
 
 /*
@@ -57,8 +56,8 @@
  * 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) {
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink,
+                    SHA_CTX* ctx, const Value* bonus_data) {
   if (patch->data.size() < 12) {
     printf("patch too short to contain header\n");
     return -1;
@@ -97,11 +96,14 @@
       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)) {
+      if (src_start + src_len > old_size) {
         printf("source data too short\n");
         return -1;
       }
-      ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, token, ctx);
+      if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) {
+        printf("Failed to apply bsdiff patch.\n");
+        return -1;
+      }
     } else if (type == CHUNK_RAW) {
       const char* raw_header = &patch->data[pos];
       pos += 4;
@@ -110,15 +112,14 @@
         return -1;
       }
 
-      ssize_t data_len = Read4(raw_header);
+      size_t data_len = static_cast<size_t>(Read4(raw_header));
 
       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) {
+      if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len) != data_len) {
         printf("failed to write chunk %d raw data\n", i);
         return -1;
       }
@@ -143,7 +144,7 @@
       int memLevel = Read4(deflate_header + 52);
       int strategy = Read4(deflate_header + 56);
 
-      if (src_start + src_len > static_cast<size_t>(old_size)) {
+      if (src_start + src_len > old_size) {
         printf("source data too short\n");
         return -1;
       }
@@ -240,9 +241,9 @@
           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;
+          size_t have = temp_data.size() - strm.avail_out;
 
-          if (sink(temp_data.data(), have, token) != have) {
+          if (sink(temp_data.data(), have) != have) {
             printf("failed to write %zd compressed bytes to output\n", have);
             return -1;
           }
diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index 4489dec..da55432 100644
--- a/applypatch/include/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 #include <sys/stat.h>
 
+#include <functional>
 #include <memory>
 #include <string>
 #include <vector>
@@ -41,7 +42,7 @@
 // and use it as the source instead.
 #define CACHE_TEMP_SOURCE "/cache/saved.file"
 
-typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*);
+using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
 
 // applypatch.cpp
 int ShowLicenses();
@@ -66,18 +67,14 @@
 
 // bspatch.cpp
 void ShowBSDiffLicense();
-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);
-int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size,
-                        const Value* patch, ssize_t patch_offset,
-                        std::vector<unsigned char>* new_data);
+int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch,
+                     size_t patch_offset, SinkFn sink, SHA_CTX* ctx);
+int ApplyBSDiffPatchMem(const unsigned char* old_data, size_t old_size, const Value* patch,
+                        size_t patch_offset, std::vector<unsigned char>* new_data);
 
 // imgpatch.cpp
-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);
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink,
+                    SHA_CTX* ctx, const Value* bonus_data);
 
 // freecache.cpp
 int MakeFreeSpaceOnCache(size_t bytes_needed);
diff --git a/applypatch/include/applypatch/imgpatch.h b/applypatch/include/applypatch/imgpatch.h
index 6549f79..07c6609 100644
--- a/applypatch/include/applypatch/imgpatch.h
+++ b/applypatch/include/applypatch/imgpatch.h
@@ -19,10 +19,11 @@
 
 #include <sys/types.h>
 
-using SinkFn = ssize_t (*)(const unsigned char*, ssize_t, void*);
+#include <functional>
 
-int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
-                    const unsigned char* patch_data, ssize_t patch_size,
-                    SinkFn sink, void* token);
+using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
+
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
+                    size_t patch_size, SinkFn sink);
 
 #endif  // _APPLYPATCH_IMGPATCH_H
diff --git a/applypatch/testdata/new.file b/applypatch/testdata/new.file
deleted file mode 100644
index cdeb8fd..0000000
--- a/applypatch/testdata/new.file
+++ /dev/null
Binary files differ
diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file
deleted file mode 100644
index 166c873..0000000
--- a/applypatch/testdata/old.file
+++ /dev/null
Binary files differ
diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff
deleted file mode 100644
index b78d385..0000000
--- a/applypatch/testdata/patch.bsdiff
+++ /dev/null
Binary files differ
diff --git a/boot_control/Android.mk b/boot_control/Android.mk
new file mode 100644
index 0000000..27e3d97
--- /dev/null
+++ b/boot_control/Android.mk
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := bootctrl.bcb
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_SRC_FILES := boot_control.cpp
+LOCAL_CFLAGS := \
+  -D_FILE_OFFSET_BITS=64 \
+  -Werror \
+  -Wall \
+  -Wextra \
+  -Wno-unused-parameter
+LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_STATIC_LIBRARIES := libbootloader_message libfs_mgr libbase
+LOCAL_POST_INSTALL_CMD := \
+  $(hide) mkdir -p $(TARGET_OUT_SHARED_LIBRARIES)/hw && \
+  ln -sf bootctrl.bcb.so $(TARGET_OUT_SHARED_LIBRARIES)/hw/bootctrl.default.so
+include $(BUILD_SHARED_LIBRARY)
diff --git a/boot_control/boot_control.cpp b/boot_control/boot_control.cpp
new file mode 100644
index 0000000..ec97b6c
--- /dev/null
+++ b/boot_control/boot_control.cpp
@@ -0,0 +1,401 @@
+/*
+ * 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.
+ */
+
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
+
+#include <bootloader_message/bootloader_message.h>
+
+struct boot_control_private_t {
+  // The base struct needs to be first in the list.
+  boot_control_module_t base;
+
+  // Whether this struct was initialized with data from the bootloader message
+  // that doesn't change until next reboot.
+  bool initialized;
+
+  // The path to the misc_device as reported in the fstab.
+  const char* misc_device;
+
+  // The number of slots present on the device.
+  unsigned int num_slots;
+
+  // The slot where we are running from.
+  unsigned int current_slot;
+};
+
+namespace {
+
+// The number of boot attempts that should be made from a new slot before
+// rolling back to the previous slot.
+constexpr unsigned int kDefaultBootAttempts = 7;
+static_assert(kDefaultBootAttempts < 8, "tries_remaining field only has 3 bits");
+
+constexpr unsigned int kMaxNumSlots =
+    sizeof(bootloader_control::slot_info) / sizeof(bootloader_control::slot_info[0]);
+constexpr const char* kSlotSuffixes[kMaxNumSlots] = { "_a", "_b", "_c", "_d" };
+constexpr off_t kBootloaderControlOffset = offsetof(bootloader_message_ab, slot_suffix);
+
+static uint32_t CRC32(const uint8_t* buf, size_t size) {
+  static uint32_t crc_table[256];
+
+  // Compute the CRC-32 table only once.
+  if (!crc_table[1]) {
+    for (uint32_t i = 0; i < 256; ++i) {
+      uint32_t crc = i;
+      for (uint32_t j = 0; j < 8; ++j) {
+        uint32_t mask = -(crc & 1);
+        crc = (crc >> 1) ^ (0xEDB88320 & mask);
+      }
+      crc_table[i] = crc;
+    }
+  }
+
+  uint32_t ret = -1;
+  for (size_t i = 0; i < size; ++i) {
+    ret = (ret >> 8) ^ crc_table[(ret ^ buf[i]) & 0xFF];
+  }
+
+  return ~ret;
+}
+
+// Return the little-endian representation of the CRC-32 of the first fields
+// in |boot_ctrl| up to the crc32_le field.
+uint32_t BootloaderControlLECRC(const bootloader_control* boot_ctrl) {
+  return htole32(
+      CRC32(reinterpret_cast<const uint8_t*>(boot_ctrl), offsetof(bootloader_control, crc32_le)));
+}
+
+bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer) {
+  android::base::unique_fd fd(open(misc_device, O_RDONLY));
+  if (fd.get() == -1) {
+    PLOG(ERROR) << "failed to open " << misc_device;
+    return false;
+  }
+  if (lseek(fd, kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) {
+    PLOG(ERROR) << "failed to lseek " << misc_device;
+    return false;
+  }
+  if (!android::base::ReadFully(fd.get(), buffer, sizeof(bootloader_control))) {
+    PLOG(ERROR) << "failed to read " << misc_device;
+    return false;
+  }
+  return true;
+}
+
+bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control* buffer) {
+  buffer->crc32_le = BootloaderControlLECRC(buffer);
+  android::base::unique_fd fd(open(misc_device, O_WRONLY | O_SYNC));
+  if (fd.get() == -1) {
+    PLOG(ERROR) << "failed to open " << misc_device;
+    return false;
+  }
+  if (lseek(fd.get(), kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) {
+    PLOG(ERROR) << "failed to lseek " << misc_device;
+    return false;
+  }
+  if (!android::base::WriteFully(fd.get(), buffer, sizeof(bootloader_control))) {
+    PLOG(ERROR) << "failed to write " << misc_device;
+    return false;
+  }
+  return true;
+}
+
+void InitDefaultBootloaderControl(const boot_control_private_t* module,
+                                  bootloader_control* boot_ctrl) {
+  memset(boot_ctrl, 0, sizeof(*boot_ctrl));
+
+  if (module->current_slot < kMaxNumSlots) {
+    strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[module->current_slot],
+            sizeof(boot_ctrl->slot_suffix));
+  }
+  boot_ctrl->magic = BOOT_CTRL_MAGIC;
+  boot_ctrl->version = BOOT_CTRL_VERSION;
+
+  // Figure out the number of slots by checking if the partitions exist,
+  // otherwise assume the maximum supported by the header.
+  boot_ctrl->nb_slot = kMaxNumSlots;
+  std::string base_path = module->misc_device;
+  size_t last_path_sep = base_path.rfind('/');
+  if (last_path_sep != std::string::npos) {
+    // We test the existence of the "boot" partition on each possible slot,
+    // which is a partition required by Android Bootloader Requirements.
+    base_path = base_path.substr(0, last_path_sep + 1) + "boot";
+    int last_existing_slot = -1;
+    int first_missing_slot = -1;
+    for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) {
+      std::string partition_path = base_path + kSlotSuffixes[slot];
+      struct stat part_stat;
+      int err = stat(partition_path.c_str(), &part_stat);
+      if (!err) {
+        last_existing_slot = slot;
+        LOG(INFO) << "Found slot: " << kSlotSuffixes[slot];
+      } else if (err < 0 && errno == ENOENT && first_missing_slot == -1) {
+        first_missing_slot = slot;
+      }
+    }
+    // We only declare that we found the actual number of slots if we found all
+    // the boot partitions up to the number of slots, and no boot partition
+    // after that. Not finding any of the boot partitions implies a problem so
+    // we just leave the number of slots in the maximum value.
+    if ((last_existing_slot != -1 && last_existing_slot + 1 == first_missing_slot) ||
+        (first_missing_slot == -1 && last_existing_slot + 1 == kMaxNumSlots)) {
+      boot_ctrl->nb_slot = last_existing_slot + 1;
+      LOG(INFO) << "Found a system with " << last_existing_slot + 1 << " slots.";
+    }
+  }
+
+  for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) {
+    slot_metadata entry = {};
+
+    if (slot < boot_ctrl->nb_slot) {
+      entry.priority = 7;
+      entry.tries_remaining = kDefaultBootAttempts;
+      entry.successful_boot = 0;
+    } else {
+      entry.priority = 0;  // Unbootable
+    }
+
+    // When the boot_control stored on disk is invalid, we assume that the
+    // current slot is successful. The bootloader should repair this situation
+    // before booting and write a valid boot_control slot, so if we reach this
+    // stage it means that the misc partition was corrupted since boot.
+    if (module->current_slot == slot) {
+      entry.successful_boot = 1;
+    }
+
+    boot_ctrl->slot_info[slot] = entry;
+  }
+  boot_ctrl->recovery_tries_remaining = 0;
+
+  boot_ctrl->crc32_le = BootloaderControlLECRC(boot_ctrl);
+}
+
+// Return the index of the slot suffix passed or -1 if not a valid slot suffix.
+int SlotSuffixToIndex(const char* suffix) {
+  for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) {
+    if (!strcmp(kSlotSuffixes[slot], suffix)) return slot;
+  }
+  return -1;
+}
+
+// Initialize the boot_control_private struct with the information from
+// the bootloader_message buffer stored in |boot_ctrl|. Returns whether the
+// initialization succeeded.
+bool BootControl_lazyInitialization(boot_control_private_t* module) {
+  if (module->initialized) return true;
+
+  // Initialize the current_slot from the read-only property. If the property
+  // was not set (from either the command line or the device tree), we can later
+  // initialize it from the bootloader_control struct.
+  std::string suffix_prop = android::base::GetProperty("ro.boot.slot_suffix", "");
+  module->current_slot = SlotSuffixToIndex(suffix_prop.c_str());
+
+  std::string err;
+  std::string device = get_bootloader_message_blk_device(&err);
+  if (device.empty()) return false;
+
+  bootloader_control boot_ctrl;
+  if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) return false;
+
+  // Note that since there isn't a module unload function this memory is leaked.
+  module->misc_device = strdup(device.c_str());
+  module->initialized = true;
+
+  // Validate the loaded data, otherwise we will destroy it and re-initialize it
+  // with the current information.
+  uint32_t computed_crc32 = BootloaderControlLECRC(&boot_ctrl);
+  if (boot_ctrl.crc32_le != computed_crc32) {
+    LOG(WARNING) << "Invalid boot control found, expected CRC-32 0x" << std::hex << computed_crc32
+                 << " but found 0x" << std::hex << boot_ctrl.crc32_le << ". Re-initializing.";
+    InitDefaultBootloaderControl(module, &boot_ctrl);
+    UpdateAndSaveBootloaderControl(device.c_str(), &boot_ctrl);
+  }
+
+  module->num_slots = boot_ctrl.nb_slot;
+  return true;
+}
+
+void BootControl_init(boot_control_module_t* module) {
+  BootControl_lazyInitialization(reinterpret_cast<boot_control_private_t*>(module));
+}
+
+unsigned int BootControl_getNumberSlots(boot_control_module_t* module) {
+  return reinterpret_cast<boot_control_private_t*>(module)->num_slots;
+}
+
+unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) {
+  return reinterpret_cast<boot_control_private_t*>(module)->current_slot;
+}
+
+int BootControl_markBootSuccessful(boot_control_module_t* module) {
+  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+
+  bootctrl.slot_info[bootctrl_module->current_slot].successful_boot = 1;
+  // tries_remaining == 0 means that the slot is not bootable anymore, make
+  // sure we mark the current slot as bootable if it succeeds in the last
+  // attempt.
+  bootctrl.slot_info[bootctrl_module->current_slot].tries_remaining = 1;
+  if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+  return 0;
+}
+
+int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) {
+  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
+
+  if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
+    // Invalid slot number.
+    return -1;
+  }
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+
+  // Set every other slot with a lower priority than the new "active" slot.
+  const unsigned int kActivePriority = 15;
+  const unsigned int kActiveTries = 6;
+  for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) {
+    if (i != slot) {
+      if (bootctrl.slot_info[i].priority >= kActivePriority)
+        bootctrl.slot_info[i].priority = kActivePriority - 1;
+    }
+  }
+
+  // Note that setting a slot as active doesn't change the successful bit.
+  // The successful bit will only be changed by setSlotAsUnbootable().
+  bootctrl.slot_info[slot].priority = kActivePriority;
+  bootctrl.slot_info[slot].tries_remaining = kActiveTries;
+
+  // Setting the current slot as active is a way to revert the operation that
+  // set *another* slot as active at the end of an updater. This is commonly
+  // used to cancel the pending update. We should only reset the verity_corrpted
+  // bit when attempting a new slot, otherwise the verity bit on the current
+  // slot would be flip.
+  if (slot != bootctrl_module->current_slot) bootctrl.slot_info[slot].verity_corrupted = 0;
+
+  if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+  return 0;
+}
+
+int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) {
+  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
+
+  if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
+    // Invalid slot number.
+    return -1;
+  }
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+
+  // The only way to mark a slot as unbootable, regardless of the priority is to
+  // set the tries_remaining to 0.
+  bootctrl.slot_info[slot].successful_boot = 0;
+  bootctrl.slot_info[slot].tries_remaining = 0;
+  if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+  return 0;
+}
+
+int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) {
+  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
+
+  if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
+    // Invalid slot number.
+    return -1;
+  }
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+
+  return bootctrl.slot_info[slot].tries_remaining;
+}
+
+int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) {
+  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
+
+  if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
+    // Invalid slot number.
+    return -1;
+  }
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
+
+  return bootctrl.slot_info[slot].successful_boot && bootctrl.slot_info[slot].tries_remaining;
+}
+
+const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) {
+  if (slot >= kMaxNumSlots || slot >= reinterpret_cast<boot_control_private_t*>(module)->num_slots) {
+    return NULL;
+  }
+  return kSlotSuffixes[slot];
+}
+
+static int BootControl_open(const hw_module_t* module __unused, const char* id __unused,
+                            hw_device_t** device __unused) {
+  /* Nothing to do currently. */
+  return 0;
+}
+
+struct hw_module_methods_t BootControl_methods = {
+  .open = BootControl_open,
+};
+
+}  // namespace
+
+boot_control_private_t HAL_MODULE_INFO_SYM = {
+  .base =
+      {
+          .common =
+              {
+                  .tag = HARDWARE_MODULE_TAG,
+                  .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
+                  .hal_api_version = HARDWARE_HAL_API_VERSION,
+                  .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
+                  .name = "AOSP reference bootctrl HAL",
+                  .author = "The Android Open Source Project",
+                  .methods = &BootControl_methods,
+              },
+          .init = BootControl_init,
+          .getNumberSlots = BootControl_getNumberSlots,
+          .getCurrentSlot = BootControl_getCurrentSlot,
+          .markBootSuccessful = BootControl_markBootSuccessful,
+          .setActiveBootSlot = BootControl_setActiveBootSlot,
+          .setSlotAsUnbootable = BootControl_setSlotAsUnbootable,
+          .isSlotBootable = BootControl_isSlotBootable,
+          .getSuffix = BootControl_getSuffix,
+          .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful,
+      },
+  .initialized = false,
+  .misc_device = nullptr,
+  .num_slots = 0,
+  .current_slot = 0,
+};
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index d8086be..f91446b 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -117,6 +117,13 @@
   return true;
 }
 
+std::string get_bootloader_message_blk_device(std::string* err) {
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) return "";
+  if (!wait_for_device(misc_blk_device, err)) return "";
+  return misc_blk_device;
+}
+
 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,
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index bc5104d..2ffbfc9 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -180,6 +180,11 @@
 #include <string>
 #include <vector>
 
+// Return the block device name for the bootloader message partition and waits
+// for the device for up to 10 seconds. In case of error returns the empty
+// string.
+std::string get_bootloader_message_blk_device(std::string* err);
+
 // Read bootloader message into boot. Error message will be set in err.
 bool read_bootloader_message(bootloader_message* boot, std::string* err);
 
diff --git a/edify/parser.yy b/edify/parser.yy
index 97205fe..b1685eb 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -23,6 +23,8 @@
 #include <string>
 #include <vector>
 
+#include <android-base/macros.h>
+
 #include "expr.h"
 #include "yydefs.h"
 #include "parser.h"
@@ -121,6 +123,7 @@
     $$->emplace_back($1);
 }
 | arglist ',' expr {
+    UNUSED($1);
     $$->push_back(std::unique_ptr<Expr>($3));
 }
 ;
diff --git a/error_code.h b/error_code.h
index cde4ee6..0e79c87 100644
--- a/error_code.h
+++ b/error_code.h
@@ -24,6 +24,7 @@
   kZipOpenFailure,
   kBootreasonInBlacklist,
   kPackageCompatibilityFailure,
+  kScriptExecutionFailure,
 };
 
 enum CauseCode {
diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp
index 1725e88..219374f 100644
--- a/fuse_sideload.cpp
+++ b/fuse_sideload.cpp
@@ -61,6 +61,9 @@
 #include <sys/uio.h>
 #include <unistd.h>
 
+#include <string>
+
+#include <android-base/stringprintf.h>
 #include <openssl/sha.h>
 
 #include "fuse_sideload.h"
@@ -226,11 +229,13 @@
     return NO_STATUS;
 }
 
-static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+static int handle_flush(void* /* data */, struct fuse_data* /* fd */,
+                        const struct fuse_in_header* /* hdr */) {
     return 0;
 }
 
-static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+static int handle_release(void* /* data */, struct fuse_data* /* fd */,
+                          const struct fuse_in_header* /* hdr */) {
     return 0;
 }
 
@@ -362,164 +367,163 @@
     return NO_STATUS;
 }
 
-int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
-                      uint64_t file_size, uint32_t block_size)
-{
-    int result;
+int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size,
+                      uint32_t block_size) {
+  // If something's already mounted on our mountpoint, try to remove it. (Mostly in case of a
+  // previous abnormal exit.)
+  umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE);
 
-    // If something's already mounted on our mountpoint, try to remove
-    // it.  (Mostly in case of a previous abnormal exit.)
-    umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE);
+  // fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read.
+  if (block_size < 4096) {
+    fprintf(stderr, "block size (%u) is too small\n", block_size);
+    return -1;
+  }
+  if (block_size > (1 << 22)) {  // 4 MiB
+    fprintf(stderr, "block size (%u) is too large\n", block_size);
+    return -1;
+  }
 
-    if (block_size < 1024) {
-        fprintf(stderr, "block size (%u) is too small\n", block_size);
-        return -1;
-    }
-    if (block_size > (1<<22)) {   // 4 MiB
-        fprintf(stderr, "block size (%u) is too large\n", block_size);
-        return -1;
-    }
+  struct fuse_data fd = {};
+  fd.vtab = vtab;
+  fd.cookie = cookie;
+  fd.file_size = file_size;
+  fd.block_size = block_size;
+  fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1);
 
-    struct fuse_data fd;
-    memset(&fd, 0, sizeof(fd));
-    fd.vtab = vtab;
-    fd.cookie = cookie;
-    fd.file_size = file_size;
-    fd.block_size = block_size;
-    fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1);
+  int result;
+  if (fd.file_blocks > (1 << 18)) {
+    fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks);
+    result = -1;
+    goto done;
+  }
 
-    if (fd.file_blocks > (1<<18)) {
-        fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks);
-        result = -1;
-        goto done;
-    }
+  fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH);
+  if (fd.hashes == NULL) {
+    fprintf(stderr, "failed to allocate %d bites for hashes\n",
+            fd.file_blocks * SHA256_DIGEST_LENGTH);
+    result = -1;
+    goto done;
+  }
 
-    fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH);
-    if (fd.hashes == NULL) {
-        fprintf(stderr, "failed to allocate %d bites for hashes\n",
-                fd.file_blocks * SHA256_DIGEST_LENGTH);
-        result = -1;
-        goto done;
-    }
+  fd.uid = getuid();
+  fd.gid = getgid();
 
-    fd.uid = getuid();
-    fd.gid = getgid();
+  fd.curr_block = -1;
+  fd.block_data = (uint8_t*)malloc(block_size);
+  if (fd.block_data == NULL) {
+    fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size);
+    result = -1;
+    goto done;
+  }
+  fd.extra_block = (uint8_t*)malloc(block_size);
+  if (fd.extra_block == NULL) {
+    fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size);
+    result = -1;
+    goto done;
+  }
 
-    fd.curr_block = -1;
-    fd.block_data = (uint8_t*)malloc(block_size);
-    if (fd.block_data == NULL) {
-        fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size);
-        result = -1;
-        goto done;
-    }
-    fd.extra_block = (uint8_t*)malloc(block_size);
-    if (fd.extra_block == NULL) {
-        fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size);
-        result = -1;
-        goto done;
-    }
+  fd.ffd = open("/dev/fuse", O_RDWR);
+  if (fd.ffd < 0) {
+    perror("open /dev/fuse");
+    result = -1;
+    goto done;
+  }
 
-    fd.ffd = open("/dev/fuse", O_RDWR);
-    if (fd.ffd < 0) {
-        perror("open /dev/fuse");
-        result = -1;
-        goto done;
-    }
+  {
+    std::string opts = android::base::StringPrintf(
+        "fd=%d,user_id=%d,group_id=%d,max_read=%u,allow_other,rootmode=040000", fd.ffd, fd.uid,
+        fd.gid, block_size);
 
-    char opts[256];
-    snprintf(opts, sizeof(opts),
-             ("fd=%d,user_id=%d,group_id=%d,max_read=%u,"
-              "allow_other,rootmode=040000"),
-             fd.ffd, fd.uid, fd.gid, block_size);
-
-    result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT,
-                   "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts);
+    result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse",
+                   MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts.c_str());
     if (result < 0) {
-        perror("mount");
-        goto done;
+      perror("mount");
+      goto done;
     }
-    uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8];
-    for (;;) {
-        ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer)));
-        if (len == -1) {
-            perror("read request");
-            if (errno == ENODEV) {
-                result = -1;
-                break;
-            }
-            continue;
-        }
+  }
 
-        if ((size_t)len < sizeof(struct fuse_in_header)) {
-            fprintf(stderr, "request too short: len=%zu\n", (size_t)len);
-            continue;
-        }
-
-        struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer;
-        void* data = request_buffer + sizeof(struct fuse_in_header);
-
-        result = -ENOSYS;
-
-        switch (hdr->opcode) {
-             case FUSE_INIT:
-                result = handle_init(data, &fd, hdr);
-                break;
-
-             case FUSE_LOOKUP:
-                result = handle_lookup(data, &fd, hdr);
-                break;
-
-            case FUSE_GETATTR:
-                result = handle_getattr(data, &fd, hdr);
-                break;
-
-            case FUSE_OPEN:
-                result = handle_open(data, &fd, hdr);
-                break;
-
-            case FUSE_READ:
-                result = handle_read(data, &fd, hdr);
-                break;
-
-            case FUSE_FLUSH:
-                result = handle_flush(data, &fd, hdr);
-                break;
-
-            case FUSE_RELEASE:
-                result = handle_release(data, &fd, hdr);
-                break;
-
-            default:
-                fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode);
-                break;
-        }
-
-        if (result == NO_STATUS_EXIT) {
-            result = 0;
-            break;
-        }
-
-        if (result != NO_STATUS) {
-            struct fuse_out_header outhdr;
-            outhdr.len = sizeof(outhdr);
-            outhdr.error = result;
-            outhdr.unique = hdr->unique;
-            TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr)));
-        }
+  uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX * 8];
+  for (;;) {
+    ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer)));
+    if (len == -1) {
+      perror("read request");
+      if (errno == ENODEV) {
+        result = -1;
+        break;
+      }
+      continue;
     }
 
-  done:
-    fd.vtab->close(fd.cookie);
-
-    result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH);
-    if (result < 0) {
-        printf("fuse_sideload umount failed: %s\n", strerror(errno));
+    if (static_cast<size_t>(len) < sizeof(struct fuse_in_header)) {
+      fprintf(stderr, "request too short: len=%zd\n", len);
+      continue;
     }
 
-    if (fd.ffd) close(fd.ffd);
-    free(fd.hashes);
-    free(fd.block_data);
-    free(fd.extra_block);
+    struct fuse_in_header* hdr = reinterpret_cast<struct fuse_in_header*>(request_buffer);
+    void* data = request_buffer + sizeof(struct fuse_in_header);
 
-    return result;
+    result = -ENOSYS;
+
+    switch (hdr->opcode) {
+      case FUSE_INIT:
+        result = handle_init(data, &fd, hdr);
+        break;
+
+      case FUSE_LOOKUP:
+        result = handle_lookup(data, &fd, hdr);
+        break;
+
+      case FUSE_GETATTR:
+        result = handle_getattr(data, &fd, hdr);
+        break;
+
+      case FUSE_OPEN:
+        result = handle_open(data, &fd, hdr);
+        break;
+
+      case FUSE_READ:
+        result = handle_read(data, &fd, hdr);
+        break;
+
+      case FUSE_FLUSH:
+        result = handle_flush(data, &fd, hdr);
+        break;
+
+      case FUSE_RELEASE:
+        result = handle_release(data, &fd, hdr);
+        break;
+
+      default:
+        fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode);
+        break;
+    }
+
+    if (result == NO_STATUS_EXIT) {
+      result = 0;
+      break;
+    }
+
+    if (result != NO_STATUS) {
+      struct fuse_out_header outhdr;
+      outhdr.len = sizeof(outhdr);
+      outhdr.error = result;
+      outhdr.unique = hdr->unique;
+      TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr)));
+    }
+  }
+
+done:
+  fd.vtab->close(fd.cookie);
+
+  result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH);
+  if (result < 0) {
+    printf("fuse_sideload umount failed: %s\n", strerror(errno));
+  }
+
+  if (fd.ffd) close(fd.ffd);
+  free(fd.hashes);
+  free(fd.block_data);
+  free(fd.extra_block);
+
+  return result;
 }
diff --git a/install.cpp b/install.cpp
index ffeba2e..a1f2e4f 100644
--- a/install.cpp
+++ b/install.cpp
@@ -49,24 +49,18 @@
 
 #include "common.h"
 #include "error_code.h"
-#include "minui/minui.h"
 #include "otautil/SysUtil.h"
 #include "otautil/ThermalUtil.h"
+#include "private/install.h"
 #include "roots.h"
 #include "ui.h"
 #include "verifier.h"
 
 using namespace std::chrono_literals;
 
-#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 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;
 
@@ -85,61 +79,53 @@
     return -1;
 }
 
-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;
-    }
+bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
+  CHECK(metadata != nullptr);
 
-    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;
+  static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
+  ZipString path(METADATA_PATH);
+  ZipEntry entry;
+  if (FindEntry(zip, path, &entry) != 0) {
+    LOG(ERROR) << "Failed to find " << METADATA_PATH;
+    return false;
+  }
+
+  uint32_t length = entry.uncompressed_length;
+  metadata->resize(length, '\0');
+  int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&(*metadata)[0]), length);
+  if (err != 0) {
+    LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
+    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(ZipArchiveHandle zip, std::vector<std::string>& log_buffer) {
-    std::string meta_data;
-    if (!read_metadata_from_package(zip, &meta_data)) {
-        return;
+static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>* log_buffer) {
+  std::string metadata;
+  if (!read_metadata_from_package(zip, &metadata)) {
+    return;
+  }
+  // Examples of the pre-build and post-build strings in metadata:
+  //   pre-build-incremental=2943039
+  //   post-build-incremental=2951741
+  std::vector<std::string> lines = android::base::Split(metadata, "\n");
+  for (const std::string& line : lines) {
+    std::string str = android::base::Trim(line);
+    if (android::base::StartsWith(str, "pre-build-incremental")) {
+      int source_build = parse_build_number(str);
+      if (source_build != -1) {
+        log_buffer->push_back(android::base::StringPrintf("source_build: %d", source_build));
+      }
+    } else if (android::base::StartsWith(str, "post-build-incremental")) {
+      int target_build = parse_build_number(str);
+      if (target_build != -1) {
+        log_buffer->push_back(android::base::StringPrintf("target_build: %d", target_build));
+      }
     }
-    // Examples of the pre-build and post-build strings in metadata:
-    // pre-build-incremental=2943039
-    // post-build-incremental=2951741
-    std::vector<std::string> lines = android::base::Split(meta_data, "\n");
-    for (const std::string& line : lines) {
-        std::string str = android::base::Trim(line);
-        if (android::base::StartsWith(str, "pre-build-incremental")){
-            int source_build = parse_build_number(str);
-            if (source_build != -1) {
-                log_buffer.push_back(android::base::StringPrintf("source_build: %d",
-                        source_build));
-            }
-        } else if (android::base::StartsWith(str, "post-build-incremental")) {
-            int target_build = parse_build_number(str);
-            if (target_build != -1) {
-                log_buffer.push_back(android::base::StringPrintf("target_build: %d",
-                        target_build));
-            }
-        }
-    }
+  }
 }
 
-// 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
@@ -220,8 +206,9 @@
   return 0;
 }
 
-int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
-                          int status_fd, std::vector<std::string>* cmd) {
+int update_binary_command(const std::string& package, ZipArchiveHandle zip,
+                          const std::string& binary_path, int /* retry_count */, int status_fd,
+                          std::vector<std::string>* cmd) {
   CHECK(cmd != nullptr);
   int ret = check_newer_ab_build(zip);
   if (ret != 0) {
@@ -255,8 +242,8 @@
   }
   long payload_offset = payload_entry.offset;
   *cmd = {
-    "/sbin/update_engine_sideload",
-    "--payload=file://" + path,
+    binary_path,
+    "--payload=file://" + package,
     android::base::StringPrintf("--offset=%ld", payload_offset),
     "--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
     android::base::StringPrintf("--status_fd=%d", status_fd),
@@ -266,8 +253,9 @@
 
 #else  // !AB_OTA_UPDATER
 
-int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
-                          int status_fd, std::vector<std::string>* cmd) {
+int update_binary_command(const std::string& package, ZipArchiveHandle zip,
+                          const std::string& binary_path, int retry_count, int status_fd,
+                          std::vector<std::string>* cmd) {
   CHECK(cmd != nullptr);
 
   // On traditional updates we extract the update binary from the package.
@@ -279,11 +267,10 @@
     return INSTALL_CORRUPT;
   }
 
-  const char* binary = "/tmp/update_binary";
-  unlink(binary);
-  int fd = creat(binary, 0755);
+  unlink(binary_path.c_str());
+  int fd = creat(binary_path.c_str(), 0755);
   if (fd == -1) {
-    PLOG(ERROR) << "Failed to create " << binary;
+    PLOG(ERROR) << "Failed to create " << binary_path;
     return INSTALL_ERROR;
   }
 
@@ -295,10 +282,10 @@
   }
 
   *cmd = {
-    binary,
+    binary_path,
     EXPAND(RECOVERY_API_VERSION),  // defined in Android.mk
     std::to_string(status_fd),
-    path,
+    package,
   };
   if (retry_count > 0) {
     cmd->push_back("retry");
@@ -317,8 +304,8 @@
 }
 
 // If the package contains an update binary, extract it and run it.
-static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
-                             std::vector<std::string>& log_buffer, int retry_count,
+static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
+                             std::vector<std::string>* log_buffer, int retry_count,
                              int* max_temperature) {
   read_source_target_build(zip, log_buffer);
 
@@ -326,7 +313,13 @@
   pipe(pipefd);
 
   std::vector<std::string> args;
-  int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
+#ifdef AB_OTA_UPDATER
+  int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count,
+                                  pipefd[1], &args);
+#else
+  int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1],
+                                  &args);
+#endif
   if (ret) {
     close(pipefd[0]);
     close(pipefd[1]);
@@ -461,7 +454,7 @@
     } else if (command == "log") {
       if (!args.empty()) {
         // Save the logging request from updater and write to last_install later.
-        log_buffer.push_back(args);
+        log_buffer->push_back(args);
       } else {
         LOG(ERROR) << "invalid \"log\" parameters: " << line;
       }
@@ -481,7 +474,7 @@
     return INSTALL_RETRY;
   }
   if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-    LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")";
+    LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")";
     return INSTALL_ERROR;
   }
 
@@ -556,150 +549,149 @@
   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, 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);
-    LOG(INFO) << "Update location: " << path;
+static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
+                                  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);
+  LOG(INFO) << "Update location: " << path;
 
-    // Map the update package into memory.
-    ui->Print("Opening update package...\n");
+  // Map the update package into memory.
+  ui->Print("Opening update package...\n");
 
-    if (path && needs_mount) {
-        if (path[0] == '@') {
-            ensure_path_mounted(path+1);
-        } else {
-            ensure_path_mounted(path);
-        }
+  if (needs_mount) {
+    if (path[0] == '@') {
+      ensure_path_mounted(path.substr(1).c_str());
+    } else {
+      ensure_path_mounted(path.c_str());
     }
+  }
 
-    MemMapping map;
-    if (sysMapFile(path, &map) != 0) {
-        LOG(ERROR) << "failed to map file";
-        return INSTALL_CORRUPT;
-    }
+  MemMapping map;
+  if (!map.MapFile(path)) {
+    LOG(ERROR) << "failed to map file";
+    return INSTALL_CORRUPT;
+  }
 
-    // Verify package.
-    if (!verify_package(map.addr, map.length)) {
-        log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
-        sysReleaseMap(&map);
-        return INSTALL_CORRUPT;
-    }
+  // Verify package.
+  if (!verify_package(map.addr, map.length)) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
+    return INSTALL_CORRUPT;
+  }
 
-    // Try to open the package.
-    ZipArchiveHandle zip;
-    int err = OpenArchiveFromMemory(map.addr, map.length, path, &zip);
-    if (err != 0) {
-        LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
-        log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
+  // Try to open the package.
+  ZipArchiveHandle zip;
+  int err = OpenArchiveFromMemory(map.addr, map.length, path.c_str(), &zip);
+  if (err != 0) {
+    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, max_temperature);
-    ui->SetEnableReboot(true);
-    ui->Print("\n");
-
-    sysReleaseMap(&map);
     CloseArchive(zip);
-    return result;
+    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));
+    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, max_temperature);
+  ui->SetEnableReboot(true);
+  ui->Print("\n");
+
+  CloseArchive(zip);
+  return result;
 }
 
-int
-install_package(const char* path, bool* wipe_cache, const char* install_file,
-                bool needs_mount, int retry_count)
-{
-    modified_flash = true;
-    auto start = std::chrono::system_clock::now();
+int install_package(const std::string& path, bool* wipe_cache, const std::string& install_file,
+                    bool needs_mount, int retry_count) {
+  CHECK(!path.empty());
+  CHECK(!install_file.empty());
+  CHECK(wipe_cache != nullptr);
 
-    int start_temperature = GetMaxValueFromThermalZone();
-    int max_temperature = start_temperature;
+  modified_flash = true;
+  auto start = std::chrono::system_clock::now();
 
-    int result;
-    std::vector<std::string> log_buffer;
-    if (setup_install_mounts() != 0) {
-        LOG(ERROR) << "failed to set up expected mounts for install; aborting";
-        result = INSTALL_ERROR;
+  int start_temperature = GetMaxValueFromThermalZone();
+  int max_temperature = start_temperature;
+
+  int result;
+  std::vector<std::string> log_buffer;
+  if (setup_install_mounts() != 0) {
+    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,
+                                    &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());
+
+  bool has_cache = volume_for_path("/cache") != nullptr;
+  // Skip logging the uncrypt_status on devices without /cache.
+  if (has_cache) {
+    static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
+    if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
+      LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS;
     } else {
-        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());
-
-    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;
+      std::string uncrypt_status;
+      if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) {
+        PLOG(WARNING) << "failed to read uncrypt status";
+      } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) {
+        LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status;
       } else {
-        std::string uncrypt_status;
-        if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) {
-          PLOG(WARNING) << "failed to read uncrypt status";
-        } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) {
-          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.
-    std::vector<std::string> log_header = {
-        path,
-        result == INSTALL_SUCCESS ? "1" : "0",
-        "time_total: " + std::to_string(time_total),
-        "retry: " + std::to_string(retry_count),
-    };
+  // The first two lines need to be the package name and install result.
+  std::vector<std::string> log_header = {
+    path,
+    result == INSTALL_SUCCESS ? "1" : "0",
+    "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));
-    }
+  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") + "\n";
-    if (!android::base::WriteStringToFile(log_content, install_file)) {
-        PLOG(ERROR) << "failed to write " << install_file;
-    }
+  std::string log_content =
+      android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n";
+  if (!android::base::WriteStringToFile(log_content, install_file)) {
+    PLOG(ERROR) << "failed to write " << install_file;
+  }
 
-    // Write a copy into last_log.
-    LOG(INFO) << log_content;
+  // Write a copy into last_log.
+  LOG(INFO) << log_content;
 
-    return result;
+  return result;
 }
 
 bool verify_package(const unsigned char* package_data, size_t package_size) {
+  static constexpr const char* PUBLIC_KEYS_FILE = "/res/keys";
   std::vector<Certificate> loadedKeys;
   if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
     LOG(ERROR) << "Failed to load keys";
diff --git a/install.h b/install.h
index 68f0a8d..f3fda30 100644
--- a/install.h
+++ b/install.h
@@ -23,10 +23,9 @@
 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.
-int install_package(const char* root_path, bool* wipe_cache, const char* install_file,
+// Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on
+// exit, caller should wipe the cache partition.
+int install_package(const std::string& package, bool* wipe_cache, const std::string& install_file,
                     bool needs_mount, int retry_count);
 
 // Verify the package by ota keys. Return true if the package is verified successfully,
@@ -35,9 +34,9 @@
 
 // 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(ZipArchiveHandle zip, std::string* meta_data);
+bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata);
 
-// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
+// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
 // entry doesn't exist.
 bool verify_package_compatibility(ZipArchiveHandle package_zip);
 
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
index 7eef13e..de0b0c8 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -29,6 +29,7 @@
 
 LOCAL_CLANG := true
 LOCAL_MODULE := minadbd_test
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_SRC_FILES := fuse_adb_provider_test.cpp
 LOCAL_CFLAGS := $(minadbd_cflags)
 LOCAL_C_INCLUDES := $(LOCAL_PATH) system/core/adb
diff --git a/minadbd/AndroidTest.xml b/minadbd/AndroidTest.xml
new file mode 100644
index 0000000..7ea235b
--- /dev/null
+++ b/minadbd/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for minadbd_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="minadbd_test->/data/local/tmp/minadbd_test" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="minadbd_test" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/minadbd/fuse_adb_provider_test.cpp b/minadbd/fuse_adb_provider_test.cpp
index 0f2e881..31be2a6 100644
--- a/minadbd/fuse_adb_provider_test.cpp
+++ b/minadbd/fuse_adb_provider_test.cpp
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-#include "fuse_adb_provider.h"
-
-#include <gtest/gtest.h>
-
 #include <errno.h>
 #include <fcntl.h>
+#include <signal.h>
 #include <sys/socket.h>
 
 #include <string>
 
+#include <gtest/gtest.h>
+
 #include "adb_io.h"
+#include "fuse_adb_provider.h"
 
 TEST(fuse_adb_provider, read_block_adb) {
   adb_data data = {};
@@ -46,9 +46,8 @@
 
   uint32_t block = 1234U;
   const char expected_block[] = "00001234";
-  ASSERT_EQ(0, read_block_adb(reinterpret_cast<void*>(&data), block,
-                              reinterpret_cast<uint8_t*>(block_data),
-                              sizeof(expected_data) - 1));
+  ASSERT_EQ(0, read_block_adb(static_cast<void*>(&data), block,
+                              reinterpret_cast<uint8_t*>(block_data), sizeof(expected_data) - 1));
 
   // Check that read_block_adb requested the right block.
   char block_req[sizeof(expected_block)] = {};
@@ -80,9 +79,12 @@
 
   ASSERT_EQ(0, close(sockets[1]));
 
+  // write(2) raises SIGPIPE since the reading end has been closed. Ignore the signal to avoid
+  // failing the test.
+  signal(SIGPIPE, SIG_IGN);
+
   char buf[1];
-  ASSERT_EQ(-EIO, read_block_adb(reinterpret_cast<void*>(&data), 0,
-                                 reinterpret_cast<uint8_t*>(buf), 1));
+  ASSERT_EQ(-EIO, read_block_adb(static_cast<void*>(&data), 0, reinterpret_cast<uint8_t*>(buf), 1));
 
   close(sockets[0]);
 }
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 426d982..61c06cc 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -21,64 +21,38 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <string>
+#include <thread>
+
 #include "adb.h"
 #include "fdevent.h"
 #include "fuse_adb_provider.h"
 #include "sysdeps.h"
 
-typedef struct stinfo stinfo;
-
-struct stinfo {
-    void (*func)(int fd, void *cookie);
-    int fd;
-    void *cookie;
-};
-
-void service_bootstrap_func(void* x) {
-    stinfo* sti = reinterpret_cast<stinfo*>(x);
-    sti->func(sti->fd, sti->cookie);
-    free(sti);
-}
-
-static void sideload_host_service(int sfd, void* data) {
-    char* args = reinterpret_cast<char*>(data);
+static void sideload_host_service(int sfd, const std::string& args) {
     int file_size;
     int block_size;
-    if (sscanf(args, "%d:%d", &file_size, &block_size) != 2) {
-        printf("bad sideload-host arguments: %s\n", args);
+    if (sscanf(args.c_str(), "%d:%d", &file_size, &block_size) != 2) {
+        printf("bad sideload-host arguments: %s\n", args.c_str());
         exit(1);
     }
-    free(args);
 
     printf("sideload-host file size %d block size %d\n", file_size, block_size);
 
     int result = run_adb_fuse(sfd, file_size, block_size);
 
     printf("sideload_host finished\n");
-    sleep(1);
     exit(result == 0 ? 0 : 1);
 }
 
-static int create_service_thread(void (*func)(int, void *), void *cookie) {
+static int create_service_thread(void (*func)(int, const std::string&), const std::string& args) {
     int s[2];
     if (adb_socketpair(s)) {
         printf("cannot create service socket pair\n");
         return -1;
     }
 
-    stinfo* sti = static_cast<stinfo*>(malloc(sizeof(stinfo)));
-    if(sti == 0) fatal("cannot allocate stinfo");
-    sti->func = func;
-    sti->cookie = cookie;
-    sti->fd = s[1];
-
-    if (!adb_thread_create(service_bootstrap_func, sti)) {
-        free(sti);
-        adb_close(s[0]);
-        adb_close(s[1]);
-        printf("cannot create service thread\n");
-        return -1;
-    }
+    std::thread([s, func, args]() { func(s[1], args); }).detach();
 
     VLOG(SERVICES) << "service thread started, " << s[0] << ":" << s[1];
     return s[0];
@@ -93,7 +67,7 @@
         // sideload-host).
         exit(3);
     } else if (!strncmp(name, "sideload-host:", 14)) {
-        char* arg = strdup(name + 14);
+        std::string arg(name + 14);
         ret = create_service_thread(sideload_host_service, arg);
     }
     if (ret >= 0) {
diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp
index 1b15a04..a59df00 100644
--- a/minui/graphics_adf.cpp
+++ b/minui/graphics_adf.cpp
@@ -28,7 +28,8 @@
 
 #include "minui/minui.h"
 
-MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), n_surfaces(0), surfaces() {}
+MinuiBackendAdf::MinuiBackendAdf()
+    : intf_fd(-1), dev(), current_surface(0), n_surfaces(0), surfaces() {}
 
 int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) {
   *surf = {};
diff --git a/mounts.cpp b/mounts.cpp
index f23376b..76fa657 100644
--- a/mounts.cpp
+++ b/mounts.cpp
@@ -27,6 +27,8 @@
 #include <string>
 #include <vector>
 
+#include <android-base/logging.h>
+
 struct MountedVolume {
     std::string device;
     std::string mount_point;
@@ -60,13 +62,6 @@
     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];
@@ -75,15 +70,13 @@
 }
 
 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);
+  // 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();
+  int result = umount(mount_point.c_str());
+  if (result == -1) {
+    PLOG(WARNING) << "Failed to umount " << mount_point;
+  }
+  return result;
 }
diff --git a/mounts.h b/mounts.h
index 1b76703..0de1ebd 100644
--- a/mounts.h
+++ b/mounts.h
@@ -21,12 +21,8 @@
 
 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
diff --git a/otafault/Android.mk b/otafault/Android.mk
index 71c2c62..ec4cdb3 100644
--- a/otafault/Android.mk
+++ b/otafault/Android.mk
@@ -23,7 +23,12 @@
     libbase \
     liblog
 
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := \
+    -Werror \
+    -Wthread-safety \
+    -Wthread-safety-negative \
+    -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS
+
 LOCAL_SRC_FILES := config.cpp ota_io.cpp
 LOCAL_MODULE_TAGS := eng
 LOCAL_MODULE := libotafault
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
index 3a89bb5..faae527 100644
--- a/otafault/ota_io.cpp
+++ b/otafault/ota_io.cpp
@@ -24,10 +24,13 @@
 
 #include <map>
 #include <memory>
+#include <mutex>
 
+#include <android-base/thread_annotations.h>
 #include "config.h"
 
-static std::map<intptr_t, const char*> filename_cache;
+static std::mutex filename_mutex;
+static std::map<intptr_t, const char*> filename_cache GUARDED_BY(filename_mutex);
 static std::string read_fault_file_name = "";
 static std::string write_fault_file_name = "";
 static std::string fsync_fault_file_name = "";
@@ -55,23 +58,28 @@
 int ota_open(const char* path, int oflags) {
     // Let the caller handle errors; we do not care if open succeeds or fails
     int fd = open(path, oflags);
+    std::lock_guard<std::mutex> lock(filename_mutex);
     filename_cache[fd] = path;
     return fd;
 }
 
 int ota_open(const char* path, int oflags, mode_t mode) {
     int fd = open(path, oflags, mode);
+    std::lock_guard<std::mutex> lock(filename_mutex);
     filename_cache[fd] = path;
-    return fd; }
+    return fd;
+}
 
 FILE* ota_fopen(const char* path, const char* mode) {
     FILE* fh = fopen(path, mode);
+    std::lock_guard<std::mutex> lock(filename_mutex);
     filename_cache[(intptr_t)fh] = path;
     return fh;
 }
 
 static int __ota_close(int fd) {
     // descriptors can be reused, so make sure not to leave them in the cache
+    std::lock_guard<std::mutex> lock(filename_mutex);
     filename_cache.erase(fd);
     return close(fd);
 }
@@ -85,6 +93,7 @@
 }
 
 static int __ota_fclose(FILE* fh) {
+    std::lock_guard<std::mutex> lock(filename_mutex);
     filename_cache.erase(reinterpret_cast<intptr_t>(fh));
     return fclose(fh);
 }
@@ -99,6 +108,7 @@
 
 size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) {
     if (should_fault_inject(OTAIO_READ)) {
+        std::lock_guard<std::mutex> lock(filename_mutex);
         auto cached = filename_cache.find((intptr_t)stream);
         const char* cached_path = cached->second;
         if (cached != filename_cache.end() &&
@@ -119,6 +129,7 @@
 
 ssize_t ota_read(int fd, void* buf, size_t nbyte) {
     if (should_fault_inject(OTAIO_READ)) {
+        std::lock_guard<std::mutex> lock(filename_mutex);
         auto cached = filename_cache.find(fd);
         const char* cached_path = cached->second;
         if (cached != filename_cache.end()
@@ -138,6 +149,7 @@
 
 size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) {
     if (should_fault_inject(OTAIO_WRITE)) {
+        std::lock_guard<std::mutex> lock(filename_mutex);
         auto cached = filename_cache.find((intptr_t)stream);
         const char* cached_path = cached->second;
         if (cached != filename_cache.end() &&
@@ -157,6 +169,7 @@
 
 ssize_t ota_write(int fd, const void* buf, size_t nbyte) {
     if (should_fault_inject(OTAIO_WRITE)) {
+        std::lock_guard<std::mutex> lock(filename_mutex);
         auto cached = filename_cache.find(fd);
         const char* cached_path = cached->second;
         if (cached != filename_cache.end() &&
@@ -176,6 +189,7 @@
 
 int ota_fsync(int fd) {
     if (should_fault_inject(OTAIO_FSYNC)) {
+        std::lock_guard<std::mutex> lock(filename_mutex);
         auto cached = filename_cache.find(fd);
         const char* cached_path = cached->second;
         if (cached != filename_cache.end() &&
diff --git a/otautil/Android.bp b/otautil/Android.bp
new file mode 100644
index 0000000..0b23143
--- /dev/null
+++ b/otautil/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+cc_library_static {
+    name: "libotautil",
+
+    srcs: [
+        "SysUtil.cpp",
+        "DirUtil.cpp",
+        "ZipUtil.cpp",
+        "ThermalUtil.cpp",
+    ],
+
+    static_libs: [
+        "libselinux",
+        "libbase",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
diff --git a/otautil/Android.mk b/otautil/Android.mk
deleted file mode 100644
index f7ca9a9..0000000
--- a/otautil/Android.mk
+++ /dev/null
@@ -1,33 +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.
-
-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/otautil/SysUtil.cpp b/otautil/SysUtil.cpp
index a2133b9..dfa2150 100644
--- a/otautil/SysUtil.cpp
+++ b/otautil/SysUtil.cpp
@@ -16,14 +16,12 @@
 
 #include "SysUtil.h"
 
-#include <errno.h>
 #include <fcntl.h>
-#include <stdint.h>
+#include <stdint.h>  // SIZE_MAX
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <algorithm>
 #include <string>
 #include <vector>
 
@@ -32,9 +30,7 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
-static bool sysMapFD(int fd, MemMapping* pMap) {
-  CHECK(pMap != nullptr);
-
+bool MemMapping::MapFD(int fd) {
   struct stat sb;
   if (fstat(fd, &sb) == -1) {
     PLOG(ERROR) << "fstat(" << fd << ") failed";
@@ -47,50 +43,49 @@
     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) });
+  addr = static_cast<unsigned char*>(memPtr);
+  length = sb.st_size;
+  ranges_.clear();
+  ranges_.emplace_back(MappedRange{ 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
+//   /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);
-
+// Each block range represents a half-open interval; the line "30 33" reprents the blocks
+// [30, 31, 32].
+bool MemMapping::MapBlockFile(const std::string& filename) {
   std::string content;
   if (!android::base::ReadFileToString(filename, &content)) {
     PLOG(ERROR) << "Failed to read " << filename;
-    return -1;
+    return false;
   }
 
   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;
+    return false;
   }
 
   size_t size;
-  unsigned int blksize;
-  if (sscanf(lines[1].c_str(), "%zu %u", &size, &blksize) != 2) {
+  size_t blksize;
+  if (sscanf(lines[1].c_str(), "%zu %zu", &size, &blksize) != 2) {
     LOG(ERROR) << "Failed to parse file size and block size: " << lines[1];
-    return -1;
+    return false;
   }
 
   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;
+    return false;
   }
 
   size_t blocks;
@@ -101,14 +96,14 @@
       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;
+    return false;
   }
 
   // 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;
+    return false;
   }
 
   const std::string& block_dev = lines[0];
@@ -116,10 +111,10 @@
   if (fd == -1) {
     PLOG(ERROR) << "failed to open block device " << block_dev;
     munmap(reserve, blocks * blksize);
-    return -1;
+    return false;
   }
 
-  pMap->ranges.resize(range_count);
+  ranges_.clear();
 
   unsigned char* next = static_cast<unsigned char*>(reserve);
   size_t remaining_size = blocks * blksize;
@@ -129,84 +124,79 @@
 
     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;
+      LOG(ERROR) << "failed to parse range " << i << ": " << 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;
+    size_t range_size = (end - start) * blksize;
+    if (end <= start || (end - start) > SIZE_MAX / blksize || range_size > remaining_size) {
+      LOG(ERROR) << "Invalid range: " << 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;
+    void* range_start = mmap64(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
+                               static_cast<off64_t>(start) * blksize);
+    if (range_start == MAP_FAILED) {
+      PLOG(ERROR) << "failed to map range " << i << ": " << line;
       success = false;
       break;
     }
-    pMap->ranges[i].addr = addr;
-    pMap->ranges[i].length = length;
+    ranges_.emplace_back(MappedRange{ range_start, range_size });
 
-    next += length;
-    remaining_size -= length;
+    next += range_size;
+    remaining_size -= range_size;
   }
   if (success && remaining_size != 0) {
-    LOG(ERROR) << "ranges in block map are invalid: remaining_size = " << remaining_size;
+    LOG(ERROR) << "Invalid ranges: remaining_size " << remaining_size;
     success = false;
   }
   if (!success) {
     munmap(reserve, blocks * blksize);
-    return -1;
+    return false;
   }
 
-  pMap->addr = static_cast<unsigned char*>(reserve);
-  pMap->length = size;
+  addr = static_cast<unsigned char*>(reserve);
+  length = size;
 
   LOG(INFO) << "mmapped " << range_count << " ranges";
 
-  return 0;
+  return true;
 }
 
-int sysMapFile(const char* fn, MemMapping* pMap) {
-  if (fn == nullptr || pMap == nullptr) {
-    LOG(ERROR) << "Invalid argument(s)";
-    return -1;
+bool MemMapping::MapFile(const std::string& fn) {
+  if (fn.empty()) {
+    LOG(ERROR) << "Empty filename";
+    return false;
   }
 
-  *pMap = {};
-
   if (fn[0] == '@') {
-    if (sysMapBlockFile(fn + 1, pMap) != 0) {
+    // Block map file "@/cache/recovery/block.map".
+    if (!MapBlockFile(fn.substr(1))) {
       LOG(ERROR) << "Map of '" << fn << "' failed";
-      return -1;
+      return false;
     }
   } else {
     // This is a regular file.
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn, O_RDONLY)));
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY)));
     if (fd == -1) {
       PLOG(ERROR) << "Unable to open '" << fn << "'";
-      return -1;
+      return false;
     }
 
-    if (!sysMapFD(fd, pMap)) {
+    if (!MapFD(fd)) {
       LOG(ERROR) << "Map of '" << fn << "' failed";
-      return -1;
+      return false;
     }
   }
-  return 0;
+  return true;
 }
 
-/*
- * Release a memory mapping.
- */
-void sysReleaseMap(MemMapping* pMap) {
-  std::for_each(pMap->ranges.cbegin(), pMap->ranges.cend(), [](const MappedRange& range) {
+MemMapping::~MemMapping() {
+  for (const auto& range : ranges_) {
     if (munmap(range.addr, range.length) == -1) {
-      PLOG(ERROR) << "munmap(" << range.addr << ", " << range.length << ") failed";
+      PLOG(ERROR) << "Failed to munmap(" << range.addr << ", " << range.length << ")";
     }
-  });
-  pMap->ranges.clear();
+  };
+  ranges_.clear();
 }
diff --git a/otautil/SysUtil.h b/otautil/SysUtil.h
index 6a79bf3..52f6d20 100644
--- a/otautil/SysUtil.h
+++ b/otautil/SysUtil.h
@@ -19,37 +19,35 @@
 
 #include <sys/types.h>
 
+#include <string>
 #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 */
+class MemMapping {
+ public:
+  ~MemMapping();
+  // Map a file into a private, read-only memory segment. If 'filename' begins with an '@'
+  // character, it is a map of blocks to be mapped, otherwise it is treated as an ordinary file.
+  bool MapFile(const std::string& filename);
+  size_t ranges() const {
+    return ranges_.size();
+  };
 
-  std::vector<MappedRange> ranges;
+  unsigned char* addr;  // start of data
+  size_t length;        // length of data
+
+ private:
+  struct MappedRange {
+    void* addr;
+    size_t length;
+  };
+
+  bool MapBlockFile(const std::string& filename);
+  bool MapFD(int fd);
+
+  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/ZipUtil.cpp b/otautil/ZipUtil.cpp
index 714c956..9cc97e4 100644
--- a/otautil/ZipUtil.cpp
+++ b/otautil/ZipUtil.cpp
@@ -74,7 +74,6 @@
         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;
diff --git a/private/install.h b/private/install.h
index 12d303b..ef64bd4 100644
--- a/private/install.h
+++ b/private/install.h
@@ -23,5 +23,9 @@
 
 #include <ziparchive/zip_archive.h>
 
-int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
-                          int status_fd, std::vector<std::string>* cmd);
+// Extract the update binary from the open zip archive |zip| located at |package| to |binary_path|.
+// Store the command line that should be called into |cmd|. 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& package, ZipArchiveHandle zip,
+                          const std::string& binary_path, int retry_count, int status_fd,
+                          std::vector<std::string>* cmd);
diff --git a/recovery.cpp b/recovery.cpp
index 30edaa5..dfae7f0 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -40,7 +40,6 @@
 #include <string>
 #include <vector>
 
-#include <adb.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
@@ -1156,7 +1155,7 @@
         {
           bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
           if (adb) {
-            status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
+            status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
           } else {
             status = apply_from_sdcard(device, &should_wipe_cache);
           }
@@ -1585,7 +1584,7 @@
         if (!sideload_auto_reboot) {
             ui->ShowText(true);
         }
-        status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
+        status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
         if (status == INSTALL_SUCCESS && should_wipe_cache) {
             if (!wipe_cache(false, device)) {
                 status = INSTALL_ERROR;
@@ -1607,14 +1606,22 @@
         }
     }
 
-    if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {
-        copy_logs();
+    if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
         ui->SetBackground(RecoveryUI::ERROR);
+        if (!ui->IsTextVisible()) {
+            sleep(5);
+        }
     }
 
     Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
-    if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) ||
-            ui->IsTextVisible()) {
+    // 1. If the recovery menu is visible, prompt and wait for commands.
+    // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into
+    //    recovery to sideload a package.)
+    // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device
+    //    without waiting.
+    // 4. In all other cases, reboot the device. Therefore, normal users will observe the device
+    //    reboot after it shows the "error" screen for 5s.
+    if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) {
         Device::BuiltinAction temp = prompt_and_wait(device, status);
         if (temp != Device::NO_ACTION) {
             after = temp;
diff --git a/roots.cpp b/roots.cpp
index 6e5ef98..9b42702 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -232,14 +232,14 @@
                            << ") not supported on " << v->fs_type;
                 return -1;
             }
-            char *num_sectors;
-            if (asprintf(&num_sectors, "%zd", length / 512) <= 0) {
+            char *num_sectors = nullptr;
+            if (length >= 512 && asprintf(&num_sectors, "%zd", length / 512) <= 0) {
                 LOG(ERROR) << "format_volume: failed to create " << v->fs_type
                            << " command for " << v->blk_device;
                 return -1;
             }
             const char *f2fs_path = "/sbin/mkfs.f2fs";
-            const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, NULL};
+            const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, nullptr};
 
             result = exec_cmd(f2fs_path, (char* const*)f2fs_argv);
             free(num_sectors);
@@ -260,26 +260,29 @@
 }
 
 int setup_install_mounts() {
-    if (fstab == NULL) {
-        LOG(ERROR) << "can't set up install mounts: no fstab loaded";
+  if (fstab == nullptr) {
+    LOG(ERROR) << "can't set up install mounts: no fstab loaded";
+    return -1;
+  }
+  for (int i = 0; i < fstab->num_entries; ++i) {
+    const Volume* v = fstab->recs + i;
+
+    // We don't want to do anything with "/".
+    if (strcmp(v->mount_point, "/") == 0) {
+      continue;
+    }
+
+    if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) {
+      if (ensure_path_mounted(v->mount_point) != 0) {
+        LOG(ERROR) << "failed to mount " << v->mount_point;
         return -1;
+      }
+    } else {
+      if (ensure_path_unmounted(v->mount_point) != 0) {
+        LOG(ERROR) << "failed to unmount " << v->mount_point;
+        return -1;
+      }
     }
-    for (int i = 0; i < fstab->num_entries; ++i) {
-        Volume* v = fstab->recs + i;
-
-        if (strcmp(v->mount_point, "/tmp") == 0 ||
-            strcmp(v->mount_point, "/cache") == 0) {
-            if (ensure_path_mounted(v->mount_point) != 0) {
-                LOG(ERROR) << "failed to mount " << v->mount_point;
-                return -1;
-            }
-
-        } else {
-            if (ensure_path_unmounted(v->mount_point) != 0) {
-                LOG(ERROR) << "failed to unmount " << v->mount_point;
-                return -1;
-            }
-        }
-    }
-    return 0;
+  }
+  return 0;
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index f59f486..02a2401 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,11 +20,12 @@
 include $(CLEAR_VARS)
 LOCAL_CFLAGS := -Werror
 LOCAL_MODULE := recovery_unit_test
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_STATIC_LIBRARIES := \
     libverifier \
     libminui \
     libotautil \
+    libupdater \
     libziparchive \
     libutils \
     libz \
@@ -35,6 +36,7 @@
     unit/asn1_decoder_test.cpp \
     unit/dirutil_test.cpp \
     unit/locale_test.cpp \
+    unit/rangeset_test.cpp \
     unit/sysutil_test.cpp \
     unit/zip_test.cpp \
     unit/ziputil_test.cpp
@@ -45,10 +47,8 @@
 
 # Manual tests
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_CFLAGS := -Werror
 LOCAL_MODULE := recovery_manual_test
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 LOCAL_STATIC_LIBRARIES := \
     libminui \
     libbase
@@ -85,13 +85,16 @@
     -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
 
+ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
+LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
+endif
+
 LOCAL_MODULE := recovery_component_test
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_C_INCLUDES := bootable/recovery
 LOCAL_SRC_FILES := \
     component/applypatch_test.cpp \
@@ -102,6 +105,7 @@
     component/sideload_test.cpp \
     component/uncrypt_test.cpp \
     component/updater_test.cpp \
+    component/update_verifier_test.cpp \
     component/verifier_test.cpp
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
@@ -122,6 +126,7 @@
     libimgpatch \
     libbsdiff \
     libbspatch \
+    libfusesideload \
     libotafault \
     librecovery \
     libupdater \
@@ -129,6 +134,7 @@
     libverifier \
     libotautil \
     libmounts \
+    libupdate_verifier \
     libdivsufsort \
     libdivsufsort64 \
     libfs_mgr \
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
new file mode 100644
index 0000000..3999aa5
--- /dev/null
+++ b/tests/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for recovery_component_test and recovery_unit_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test" />
+        <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="recovery_component_test" />
+    </test>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="recovery_unit_test" />
+    </test>
+</configuration>
diff --git a/tests/common/component_test_util.h b/tests/common/component_test_util.h
deleted file mode 100644
index 3fee32d..0000000
--- a/tests/common/component_test_util.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
index 5cba68f..016fed9 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -105,9 +105,6 @@
   static size_t new_size;
 };
 
-std::string ApplyPatchTest::old_file;
-std::string ApplyPatchTest::new_file;
-
 static void cp(const std::string& src, const std::string& tgt) {
   std::string cmd = "cp " + src + " " + tgt;
   system(cmd.c_str());
@@ -132,48 +129,8 @@
   }
 };
 
-class ApplyPatchFullTest : public ApplyPatchCacheTest {
- public:
-  static void SetUpTestCase() {
-    ApplyPatchTest::SetUpTestCase();
-
-    output_f = new TemporaryFile();
-    output_loc = std::string(output_f->path);
-
-    struct FileContents fc;
-
-    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())));
-
-    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");
-  }
-
-  virtual void TearDown() {
-    cp("/cache/reallysaved.file", cache_file);
-    ApplyPatchCacheTest::TearDown();
-  }
-};
-
+std::string ApplyPatchTest::old_file;
+std::string ApplyPatchTest::new_file;
 std::string ApplyPatchTest::rand_file;
 std::string ApplyPatchTest::patch_file;
 std::string ApplyPatchTest::cache_file;
@@ -184,10 +141,6 @@
 size_t ApplyPatchTest::old_size;
 size_t ApplyPatchTest::new_size;
 
-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));
@@ -424,20 +377,6 @@
   ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
 }
 
-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
index 0357acc..b38bc71 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/component/bootloader_message_test.cpp
@@ -21,14 +21,13 @@
 #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();
+    std::string err;
+    has_misc = !get_bootloader_message_blk_device(&err).empty();
   }
 
   virtual void TearDown() override {
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index 2f64850..7d00a3d 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <stdio.h>
+
 #include <string>
 #include <vector>
 
@@ -27,12 +29,6 @@
 
 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) {
@@ -79,6 +75,18 @@
   if (num_deflate != nullptr) *num_deflate = deflate;
 }
 
+static void verify_patched_image(const std::string& src, const std::string& patch,
+                                 const std::string& tgt) {
+  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(),
+                               [&patched](const unsigned char* data, size_t len) {
+                                 patched.append(reinterpret_cast<const char*>(data), len);
+                                 return len;
+                               }));
+  ASSERT_EQ(tgt, patched);
+}
+
 TEST(ImgdiffTest, invalid_args) {
   // Insufficient inputs.
   ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" }));
@@ -124,11 +132,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, zip_mode_smoke_store) {
@@ -177,11 +181,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, zip_mode_smoke_compressed) {
@@ -230,11 +230,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
@@ -286,11 +282,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, image_mode_simple) {
@@ -333,11 +325,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, image_mode_different_num_chunks) {
@@ -413,11 +401,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, image_mode_spurious_magic) {
@@ -454,11 +438,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, image_mode_short_input1) {
@@ -494,11 +474,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, image_mode_short_input2) {
@@ -534,11 +510,7 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
 
 TEST(ImgdiffTest, image_mode_single_entry_long) {
@@ -577,9 +549,5 @@
   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);
+  verify_patched_image(src, patch, tgt);
 }
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index a5c0c10..968196f 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -15,6 +15,8 @@
  */
 
 #include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #include <string>
@@ -65,6 +67,56 @@
   CloseArchive(zip);
 }
 
+TEST(InstallTest, read_metadata_from_package_smoke) {
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+  const std::string content("abcdefg");
+  ASSERT_EQ(0, writer.WriteBytes(content.data(), 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::string metadata;
+  ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
+  ASSERT_EQ(content, metadata);
+  CloseArchive(zip);
+
+  TemporaryFile temp_file2;
+  FILE* zip_file2 = fdopen(temp_file2.fd, "w");
+  ZipWriter writer2(zip_file2);
+  ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
+  ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
+  ASSERT_EQ(0, writer2.FinishEntry());
+  ASSERT_EQ(0, writer2.Finish());
+  ASSERT_EQ(0, fclose(zip_file2));
+
+  ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip));
+  metadata.clear();
+  ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
+  ASSERT_EQ(content, metadata);
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, read_metadata_from_package_no_entry) {
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
+  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::string metadata;
+  ASSERT_FALSE(read_metadata_from_package(zip, &metadata));
+  CloseArchive(zip);
+}
+
 TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
   TemporaryFile compatibility_zip_file;
   FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
@@ -175,18 +227,62 @@
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  ZipString payload_name("payload.bin");
+  ZipEntry payload_entry;
+  ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
   int status_fd = 10;
-  std::string path = "/path/to/update.zip";
+  std::string package = "/path/to/update.zip";
+  std::string binary_path = "/sbin/update_engine_sideload";
   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(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+  ASSERT_EQ(5U, cmd.size());
+  ASSERT_EQ(binary_path, cmd[0]);
+  ASSERT_EQ("--payload=file://" + package, cmd[1]);
+  ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
   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.";
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
+  ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
+  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 package = "/path/to/update.zip";
+  TemporaryDir td;
+  std::string binary_path = std::string(td.path) + "/update_binary";
+  std::vector<std::string> cmd;
+  ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+  ASSERT_EQ(4U, cmd.size());
+  ASSERT_EQ(binary_path, cmd[0]);
+  ASSERT_EQ("3", cmd[1]);  // RECOVERY_API_VERSION
+  ASSERT_EQ(std::to_string(status_fd), cmd[2]);
+  ASSERT_EQ(package, cmd[3]);
+  struct stat sb;
+  ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
+  ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+
+  // With non-zero retry count. update_binary will be removed automatically.
+  cmd.clear();
+  ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 2, status_fd, &cmd));
+  ASSERT_EQ(5U, cmd.size());
+  ASSERT_EQ(binary_path, cmd[0]);
+  ASSERT_EQ("3", cmd[1]);  // RECOVERY_API_VERSION
+  ASSERT_EQ(std::to_string(status_fd), cmd[2]);
+  ASSERT_EQ(package, cmd[3]);
+  ASSERT_EQ("retry", cmd[4]);
+  sb = {};
+  ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
+  ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+
+  CloseArchive(zip);
 #endif  // AB_OTA_UPDATER
 }
 
@@ -217,12 +313,30 @@
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
   int status_fd = 10;
-  std::string path = "/path/to/update.zip";
+  std::string package = "/path/to/update.zip";
+  std::string binary_path = "/sbin/update_engine_sideload";
   std::vector<std::string> cmd;
-  ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(path, zip, 0, status_fd, &cmd));
+  ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 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.";
+  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));
+
+  // Missing update binary.
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  int status_fd = 10;
+  std::string package = "/path/to/update.zip";
+  TemporaryDir td;
+  std::string binary_path = std::string(td.path) + "/update_binary";
+  std::vector<std::string> cmd;
+  ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+  CloseArchive(zip);
 #endif  // AB_OTA_UPDATER
 }
diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp
index ea93e9b..40cfc69 100644
--- a/tests/component/sideload_test.cpp
+++ b/tests/component/sideload_test.cpp
@@ -13,9 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #include <unistd.h>
+
 #include <gtest/gtest.h>
 
-TEST(SideloadTest, fusedevice) {
-  ASSERT_NE(-1, access("/dev/fuse", R_OK | W_OK));
+#include "fuse_sideload.h"
+
+TEST(SideloadTest, fuse_device) {
+  ASSERT_EQ(0, access("/dev/fuse", R_OK | W_OK));
+}
+
+TEST(SideloadTest, run_fuse_sideload_wrong_parameters) {
+  provider_vtab vtab;
+  vtab.close = [](void*) {};
+
+  ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, 4095));
+  ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, (1 << 22) + 1));
+
+  // Too many blocks.
+  ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, ((1 << 18) + 1) * 4096, 4096));
 }
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
index 4f2b816..3925236 100644
--- a/tests/component/uncrypt_test.cpp
+++ b/tests/component/uncrypt_test.cpp
@@ -25,11 +25,12 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <android-base/test_utils.h>
 #include <android-base/unique_fd.h>
 #include <bootloader_message/bootloader_message.h>
 #include <gtest/gtest.h>
 
-#include "common/component_test_util.h"
+using namespace std::string_literals;
 
 static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt";
 static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb";
@@ -62,131 +63,108 @@
 
     ASSERT_TRUE(success) << "uncrypt service is not available.";
 
-    has_misc = parse_misc();
+    std::string err;
+    has_misc = !get_bootloader_message_blk_device(&err).empty();
+  }
+
+  void SetupOrClearBcb(bool isSetup, const std::string& message,
+                       const std::string& message_in_bcb) const {
+    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", isSetup ? "setup-bcb" : "clear-bcb"));
+
+    // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+    sleep(1);
+
+    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<sockaddr*>(&un), sizeof(sockaddr_un)) != 0) {
+        success = true;
+        break;
+      }
+      sleep(1);
+    }
+    ASSERT_TRUE(success);
+
+    if (isSetup) {
+      // Send out the BCB message.
+      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", isSetup ? "setup-bcb" : "clear-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;
+
+    if (isSetup) {
+      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;
+    } else {
+      // All the bytes should be cleared.
+      ASSERT_EQ(std::string(sizeof(boot), '\0'),
+                std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+    }
   }
 
   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;
+  SetupOrClearBcb(true, message, message_in_bcb);
 }
 
 TEST_F(UncryptTest, clear_bcb) {
-  if (!has_misc) {
-    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
-    return;
-  }
+  SetupOrClearBcb(false, "", "");
+}
 
-  // Trigger the clear-bcb service.
-  ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb"));
+TEST_F(UncryptTest, setup_bcb_wipe_ab) {
+  TemporaryFile wipe_package;
+  ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path));
 
-  // 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)));
+  // It's expected to store a wipe package in /misc, with the package size passed to recovery.
+  std::string message =
+      "--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s;
+  std::string message_in_bcb =
+      "recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n";
+  SetupOrClearBcb(true, message, message_in_bcb);
 }
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
new file mode 100644
index 0000000..73b4478
--- /dev/null
+++ b/tests/component/update_verifier_test.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <update_verifier/update_verifier.h>
+
+class UpdateVerifierTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+#ifdef PRODUCT_SUPPORTS_VERITY
+    verity_supported = true;
+#else
+    verity_supported = false;
+#endif
+  }
+
+  bool verity_supported;
+};
+
+TEST_F(UpdateVerifierTest, verify_image_no_care_map) {
+  // Non-existing care_map is allowed.
+  ASSERT_TRUE(verify_image("/doesntexist"));
+}
+
+TEST_F(UpdateVerifierTest, verify_image_smoke) {
+  // This test relies on dm-verity support.
+  if (!verity_supported) {
+    GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+    return;
+  }
+
+  // The care map file can have only two or four lines.
+  TemporaryFile temp_file;
+  std::string content = "system\n2,0,1";
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+  ASSERT_TRUE(verify_image(temp_file.path));
+
+  // Leading and trailing newlines should be accepted.
+  ASSERT_TRUE(android::base::WriteStringToFile("\n" + content + "\n\n", temp_file.path));
+  ASSERT_TRUE(verify_image(temp_file.path));
+}
+
+TEST_F(UpdateVerifierTest, verify_image_wrong_lines) {
+  // The care map file can have only two or four lines.
+  TemporaryFile temp_file;
+  ASSERT_FALSE(verify_image(temp_file.path));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("line1", temp_file.path));
+  ASSERT_FALSE(verify_image(temp_file.path));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2\nline3", temp_file.path));
+  ASSERT_FALSE(verify_image(temp_file.path));
+}
+
+TEST_F(UpdateVerifierTest, verify_image_malformed_care_map) {
+  // This test relies on dm-verity support.
+  if (!verity_supported) {
+    GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+    return;
+  }
+
+  TemporaryFile temp_file;
+  std::string content = "system\n2,1,0";
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+  ASSERT_FALSE(verify_image(temp_file.path));
+}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 5652ddf..35e87fd 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -570,7 +570,7 @@
   ASSERT_EQ(0, fclose(zip_file_ptr));
 
   MemMapping map;
-  ASSERT_EQ(0, sysMapFile(zip_file.path, &map));
+  ASSERT_TRUE(map.MapFile(zip_file.path));
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
 
@@ -607,3 +607,69 @@
   ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
   CloseArchive(handle);
 }
+
+TEST_F(UpdaterTest, new_data_short_write) {
+  // Create a zip file with new_data.
+  TemporaryFile zip_file;
+  FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
+  ZipWriter zip_writer(zip_file_ptr);
+
+  // Add the empty new data.
+  ASSERT_EQ(0, zip_writer.StartEntry("empty_new_data", 0));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+  // Add the short written new data.
+  ASSERT_EQ(0, zip_writer.StartEntry("short_new_data", 0));
+  std::string new_data_short = std::string(10, 'a');
+  ASSERT_EQ(0, zip_writer.WriteBytes(new_data_short.data(), new_data_short.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+  // Add the data of exactly one block.
+  ASSERT_EQ(0, zip_writer.StartEntry("exact_new_data", 0));
+  std::string new_data_exact = std::string(4096, 'a');
+  ASSERT_EQ(0, zip_writer.WriteBytes(new_data_exact.data(), new_data_exact.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+  // Add a dummy patch data.
+  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+
+  std::vector<std::string> transfer_list = {
+    "4",
+    "1",
+    "0",
+    "0",
+    "new 2,0,1",
+  };
+  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());
+  ASSERT_EQ(0, zip_writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  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;
+
+  // Updater should report the failure gracefully rather than stuck in deadlock.
+  TemporaryFile update_file;
+  std::string script_empty_data = "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list"), "empty_new_data", "patch_data"))";
+  expect("", script_empty_data.c_str(), kNoCause, &updater_info);
+
+  std::string script_short_data = "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list"), "short_new_data", "patch_data"))";
+  expect("", script_short_data.c_str(), kNoCause, &updater_info);
+
+  // Expect to write 1 block of new data successfully.
+  std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))";
+  expect("t", script_exact_data.c_str(), kNoCause, &updater_info);
+}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index 4c06487..e520f50 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -40,7 +40,7 @@
   void SetUp() override {
     std::vector<std::string> args = GetParam();
     std::string package = from_testdata_base(args[0]);
-    if (sysMapFile(package.c_str(), &memmap) != 0) {
+    if (!memmap.MapFile(package)) {
       FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
     }
 
@@ -132,6 +132,51 @@
                                         package.size(), certs));
 }
 
+TEST(VerifierTest, BadPackage_AlteredFooter) {
+  std::string testkey_v3;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+  TemporaryFile key_file1;
+  ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path));
+  std::vector<Certificate> certs;
+  ASSERT_TRUE(load_keys(key_file1.path, certs));
+
+  std::string package;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
+  ASSERT_EQ(std::string("\xc0\x06\xff\xff\xd2\x06", 6), package.substr(package.size() - 6, 6));
+
+  // Alter the footer.
+  package[package.size() - 5] = '\x05';
+  ASSERT_EQ(VERIFY_FAILURE,
+            verify_file(reinterpret_cast<const unsigned char*>(package.data()), package.size(),
+                        certs));
+}
+
+TEST(VerifierTest, BadPackage_AlteredContent) {
+  std::string testkey_v3;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+  TemporaryFile key_file1;
+  ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path));
+  std::vector<Certificate> certs;
+  ASSERT_TRUE(load_keys(key_file1.path, certs));
+
+  std::string package;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
+  ASSERT_GT(package.size(), static_cast<size_t>(100));
+
+  // Alter the content.
+  std::string altered1(package);
+  altered1[50] += 1;
+  ASSERT_EQ(VERIFY_FAILURE,
+            verify_file(reinterpret_cast<const unsigned char*>(altered1.data()), altered1.size(),
+                        certs));
+
+  std::string altered2(package);
+  altered2[10] += 1;
+  ASSERT_EQ(VERIFY_FAILURE,
+            verify_file(reinterpret_cast<const unsigned char*>(altered2.data()), altered2.size(),
+                        certs));
+}
+
 TEST_P(VerifierSuccessTest, VerifySucceed) {
   ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS);
 }
@@ -174,6 +219,4 @@
 INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest,
     ::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"})));
+      std::vector<std::string>({"fake-eocd.zip", "v1"})));
diff --git a/tests/testdata/alter-footer.zip b/tests/testdata/alter-footer.zip
deleted file mode 100644
index f497ec0..0000000
--- a/tests/testdata/alter-footer.zip
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/alter-metadata.zip b/tests/testdata/alter-metadata.zip
deleted file mode 100644
index 1c71fbc..0000000
--- a/tests/testdata/alter-metadata.zip
+++ /dev/null
Binary files differ
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
new file mode 100644
index 0000000..3c6d77e
--- /dev/null
+++ b/tests/unit/rangeset_test.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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 <signal.h>
+#include <sys/types.h>
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "updater/rangeset.h"
+
+TEST(RangeSetTest, Parse_smoke) {
+  RangeSet rs = RangeSet::Parse("2,1,10");
+  ASSERT_EQ(static_cast<size_t>(1), rs.size());
+  ASSERT_EQ((Range{ 1, 10 }), rs[0]);
+  ASSERT_EQ(static_cast<size_t>(9), rs.blocks());
+
+  RangeSet rs2 = RangeSet::Parse("4,15,20,1,10");
+  ASSERT_EQ(static_cast<size_t>(2), rs2.size());
+  ASSERT_EQ((Range{ 15, 20 }), rs2[0]);
+  ASSERT_EQ((Range{ 1, 10 }), rs2[1]);
+  ASSERT_EQ(static_cast<size_t>(14), rs2.blocks());
+
+  // Leading zeros are fine. But android::base::ParseUint() doesn't like trailing zeros like "10 ".
+  ASSERT_EQ(rs, RangeSet::Parse(" 2, 1,   10"));
+  ASSERT_EXIT(RangeSet::Parse("2,1,10 "), ::testing::KilledBySignal(SIGABRT), "");
+}
+
+TEST(RangeSetTest, Parse_InvalidCases) {
+  // Insufficient number of tokens.
+  ASSERT_EXIT(RangeSet::Parse(""), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_EXIT(RangeSet::Parse("2,1"), ::testing::KilledBySignal(SIGABRT), "");
+
+  // The first token (i.e. the number of following tokens) is invalid.
+  ASSERT_EXIT(RangeSet::Parse("a,1,1"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_EXIT(RangeSet::Parse("3,1,1"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_EXIT(RangeSet::Parse("-3,1,1"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_EXIT(RangeSet::Parse("2,1,2,3"), ::testing::KilledBySignal(SIGABRT), "");
+
+  // Invalid tokens.
+  ASSERT_EXIT(RangeSet::Parse("2,1,10a"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_EXIT(RangeSet::Parse("2,,10"), ::testing::KilledBySignal(SIGABRT), "");
+
+  // Empty or negative range.
+  ASSERT_EXIT(RangeSet::Parse("2,2,2"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_EXIT(RangeSet::Parse("2,2,1"), ::testing::KilledBySignal(SIGABRT), "");
+}
+
+TEST(RangeSetTest, Overlaps) {
+  RangeSet r1 = RangeSet::Parse("2,1,6");
+  RangeSet r2 = RangeSet::Parse("2,5,10");
+  ASSERT_TRUE(r1.Overlaps(r2));
+  ASSERT_TRUE(r2.Overlaps(r1));
+
+  r2 = RangeSet::Parse("2,6,10");
+  ASSERT_FALSE(r1.Overlaps(r2));
+  ASSERT_FALSE(r2.Overlaps(r1));
+
+  ASSERT_FALSE(RangeSet::Parse("2,3,5").Overlaps(RangeSet::Parse("2,5,7")));
+  ASSERT_FALSE(RangeSet::Parse("2,5,7").Overlaps(RangeSet::Parse("2,3,5")));
+}
+
+TEST(RangeSetTest, GetBlockNumber) {
+  RangeSet rs = RangeSet::Parse("2,1,10");
+  ASSERT_EQ(static_cast<size_t>(1), rs.GetBlockNumber(0));
+  ASSERT_EQ(static_cast<size_t>(6), rs.GetBlockNumber(5));
+  ASSERT_EQ(static_cast<size_t>(9), rs.GetBlockNumber(8));
+
+  // Out of bound.
+  ASSERT_EXIT(rs.GetBlockNumber(9), ::testing::KilledBySignal(SIGABRT), "");
+}
+
+TEST(RangeSetTest, equality) {
+  ASSERT_EQ(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,1,6"));
+
+  ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,1,7"));
+  ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,2,7"));
+
+  // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5".
+  ASSERT_NE(RangeSet::Parse("4,1,5,8,10"), RangeSet::Parse("4,8,10,1,5"));
+}
+
+TEST(RangeSetTest, iterators) {
+  RangeSet rs = RangeSet::Parse("4,1,5,8,10");
+  std::vector<Range> ranges;
+  for (const auto& range : rs) {
+    ranges.push_back(range);
+  }
+  ASSERT_EQ((std::vector<Range>{ Range{ 1, 5 }, Range{ 8, 10 } }), ranges);
+
+  ranges.clear();
+
+  // Reverse iterators.
+  for (auto it = rs.crbegin(); it != rs.crend(); it++) {
+    ranges.push_back(*it);
+  }
+  ASSERT_EQ((std::vector<Range>{ Range{ 8, 10 }, Range{ 1, 5 } }), ranges);
+}
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
index f469966..434ee25 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -27,27 +27,23 @@
   MemMapping mapping;
 
   // Invalid argument.
-  ASSERT_EQ(-1, sysMapFile(nullptr, &mapping));
-  ASSERT_EQ(-1, sysMapFile("/somefile", nullptr));
+  ASSERT_FALSE(mapping.MapFile(""));
 }
 
-TEST(SysUtilTest, sysMapFileRegularFile) {
+TEST(SysUtilTest, MapFileRegularFile) {
   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::MapFile() should map the file to one range.
   MemMapping mapping;
-  ASSERT_EQ(0, sysMapFile(temp_file1.path, &mapping));
+  ASSERT_TRUE(mapping.MapFile(temp_file1.path));
   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());
+  ASSERT_EQ(1U, mapping.ranges());
 }
 
-TEST(SysUtilTest, sysMapFileBlockMap) {
+TEST(SysUtilTest, MapFileBlockMap) {
   // Create a file that has 10 blocks.
   TemporaryFile package;
   std::string content;
@@ -63,78 +59,72 @@
   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_TRUE(mapping.MapFile(filename));
   ASSERT_EQ(file_size, mapping.length);
-  ASSERT_EQ(1U, mapping.ranges.size());
+  ASSERT_EQ(1U, mapping.ranges());
 
   // 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_TRUE(mapping.MapFile(filename));
   ASSERT_EQ(file_size, mapping.length);
-  ASSERT_EQ(1U, mapping.ranges.size());
+  ASSERT_EQ(1U, mapping.ranges());
 
   // 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_TRUE(mapping.MapFile(filename));
   ASSERT_EQ(file_size, mapping.length);
-  ASSERT_EQ(1U, mapping.ranges.size());
+  ASSERT_EQ(1U, mapping.ranges());
 
   // 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_TRUE(mapping.MapFile(filename));
   ASSERT_EQ(file_size, mapping.length);
-  ASSERT_EQ(3U, mapping.ranges.size());
-
-  sysReleaseMap(&mapping);
-  ASSERT_EQ(0U, mapping.ranges.size());
+  ASSERT_EQ(3U, mapping.ranges());
 }
 
-TEST(SysUtilTest, sysMapFileBlockMapInvalidBlockMap) {
+TEST(SysUtilTest, MapFileBlockMapInvalidBlockMap) {
   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_FALSE(mapping.MapFile(filename));
 
   ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n", temp_file.path));
-  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_FALSE(mapping.MapFile(filename));
 
   // 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_FALSE(mapping.MapFile(filename));
 
   ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n2\n0 1\n", temp_file.path));
-  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_FALSE(mapping.MapFile(filename));
 
   // 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_FALSE(mapping.MapFile(filename));
 
   ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n\n0 1\n", temp_file.path));
-  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_FALSE(mapping.MapFile(filename));
 
   // 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_FALSE(mapping.MapFile(filename));
 
   ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 0\n1\n0 1\n", temp_file.path));
-  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_FALSE(mapping.MapFile(filename));
 
   ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n0 1\n", temp_file.path));
-  ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+  ASSERT_FALSE(mapping.MapFile(filename));
 
   // 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());
+  ASSERT_FALSE(mapping.MapFile(filename));
 }
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index 4a1a49b..df4e38c 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -66,9 +66,9 @@
 }
 
 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));
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_path));
 
   // Map an update package into memory and open the archive from there.
   ZipArchiveHandle handle;
@@ -85,6 +85,5 @@
   ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd));
 
   CloseArchive(handle);
-  sysReleaseMap(&map);
 }
 
diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk
index 1acd5ec..37d9bfe 100644
--- a/update_verifier/Android.mk
+++ b/update_verifier/Android.mk
@@ -14,12 +14,43 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# libupdate_verifier (static library)
+# ===============================
 include $(CLEAR_VARS)
 
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := update_verifier.cpp
+LOCAL_SRC_FILES := \
+    update_verifier.cpp
+
+LOCAL_MODULE := libupdate_verifier
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libcutils \
+    android.hardware.boot@1.0
+
+LOCAL_CFLAGS := -Wall -Werror
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+    $(LOCAL_PATH)/include
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+
+ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
+LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
+endif
+
+include $(BUILD_STATIC_LIBRARY)
+
+# update_verifier (executable)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    update_verifier_main.cpp
 
 LOCAL_MODULE := update_verifier
+LOCAL_STATIC_LIBRARIES := \
+    libupdate_verifier
 LOCAL_SHARED_LIBRARIES := \
     libbase \
     libcutils \
@@ -29,13 +60,8 @@
     libhidlbase \
     android.hardware.boot@1.0
 
-LOCAL_CFLAGS := -Werror
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+LOCAL_CFLAGS := -Wall -Werror
 
 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/include/update_verifier/update_verifier.h b/update_verifier/include/update_verifier/update_verifier.h
new file mode 100644
index 0000000..16b394e
--- /dev/null
+++ b/update_verifier/include/update_verifier/update_verifier.h
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+int update_verifier(int argc, char** argv);
+
+// Exposed for testing purpose.
+bool verify_image(const std::string& care_map_name);
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index 350020f..fdbcfde 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -35,6 +35,8 @@
  * verifier reaches the end after the verification.
  */
 
+#include "update_verifier/update_verifier.h"
+
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -42,6 +44,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <string>
 #include <vector>
 
@@ -59,12 +62,6 @@
 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;
-
 // 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-")) {
@@ -82,6 +79,7 @@
   // (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.
+  static constexpr auto DM_PATH_PREFIX = "/sys/block/";
   dirent** namelist;
   int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort);
   if (n == -1) {
@@ -93,6 +91,8 @@
     return false;
   }
 
+  static constexpr auto DM_PATH_SUFFIX = "/dm/name";
+  static constexpr auto DEV_PATH = "/dev/block/";
   std::string dm_block_device;
   while (n--) {
     std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX;
@@ -143,16 +143,21 @@
       return false;
     }
 
+    static constexpr size_t BLOCKSIZE = 4096;
     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 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;
+    size_t remain = (range_end - range_start) * BLOCKSIZE;
+    while (remain > 0) {
+      size_t to_read = std::min(remain, 1024 * BLOCKSIZE);
+      std::vector<uint8_t> buf(to_read);
+      if (!android::base::ReadFully(fd.get(), buf.data(), to_read)) {
+        PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
+        return false;
+      }
+      remain -= to_read;
     }
     blk_count += (range_end - range_start);
   }
@@ -161,7 +166,7 @@
   return true;
 }
 
-static bool verify_image(const std::string& care_map_name) {
+bool verify_image(const std::string& care_map_name) {
     android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY)));
     // If the device is flashed before the current boot, it may not have care_map.txt
     // in /data/ota_package. To allow the device to continue booting in this situation,
@@ -205,7 +210,7 @@
   while (true) pause();
 }
 
-int main(int argc, char** argv) {
+int update_verifier(int argc, char** argv) {
   for (int i = 1; i < argc; i++) {
     LOG(INFO) << "Started with arg " << i << ": " << argv[i];
   }
@@ -238,6 +243,7 @@
       return reboot_device();
     }
 
+    static constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
     if (!verify_image(CARE_MAP_FILE)) {
       LOG(ERROR) << "Failed to verify all blocks in care map file.";
       return reboot_device();
diff --git a/update_verifier/update_verifier_main.cpp b/update_verifier/update_verifier_main.cpp
new file mode 100644
index 0000000..46e8bbb
--- /dev/null
+++ b/update_verifier/update_verifier_main.cpp
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+// See the comments in update_verifier.cpp.
+
+#include "update_verifier/update_verifier.h"
+
+int main(int argc, char** argv) {
+  return update_verifier(argc, argv);
+}
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index c614ccc..d5c1704 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -18,6 +18,7 @@
 #include <errno.h>
 #include <dirent.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <linux/fs.h>
 #include <pthread.h>
 #include <stdarg.h>
@@ -49,9 +50,10 @@
 
 #include "edify/expr.h"
 #include "error_code.h"
-#include "updater/install.h"
 #include "ota_io.h"
 #include "print_sha1.h"
+#include "updater/install.h"
+#include "updater/rangeset.h"
 #include "updater/updater.h"
 
 // Set this to 0 to interpret 'erase' transfers to mean do a
@@ -64,100 +66,10 @@
 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.
-
-  // 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::unordered_map<std::string, RangeSet> stash_map;
 
-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;
-  }
-
-  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;
-    }
-
-    if (!android::base::ParseUint(pieces[i + 2], &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;
-  }
-
-  return rs;
-
-err:
-  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 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;
-      }
-    }
-  }
-
-  return false;
-}
-
 static int read_all(int fd, uint8_t* data, size_t size) {
     size_t so_far = 0;
     while (so_far < size) {
@@ -200,18 +112,17 @@
 }
 
 static bool discard_blocks(int fd, off64_t offset, uint64_t size) {
-    // Don't discard blocks unless the update is a retry run.
-    if (!is_retry) {
-        return true;
-    }
-
-    uint64_t args[2] = {static_cast<uint64_t>(offset), size};
-    int status = ioctl(fd, BLKDISCARD, &args);
-    if (status == -1) {
-        PLOG(ERROR) << "BLKDISCARD ioctl failed";
-        return false;
-    }
+  // Don't discard blocks unless the update is a retry run.
+  if (!is_retry) {
     return true;
+  }
+
+  uint64_t args[2] = { static_cast<uint64_t>(offset), size };
+  if (ioctl(fd, BLKDISCARD, &args) == -1) {
+    PLOG(ERROR) << "BLKDISCARD ioctl failed";
+    return false;
+  }
+  return true;
 }
 
 static bool check_lseek(int fd, off64_t offset, int whence) {
@@ -231,180 +142,197 @@
     buffer.resize(size);
 }
 
-struct RangeSinkState {
-    explicit RangeSinkState(RangeSet& rs) : tgt(rs) { };
+/**
+ * RangeSinkWriter reads data from the given FD, and writes them to the destination specified by the
+ * given RangeSet.
+ */
+class RangeSinkWriter {
+ public:
+  RangeSinkWriter(int fd, const RangeSet& tgt)
+      : fd_(fd), tgt_(tgt), next_range_(0), current_range_left_(0), bytes_written_(0) {
+    CHECK_NE(tgt.size(), static_cast<size_t>(0));
+  };
 
-    int fd;
-    const RangeSet& tgt;
-    size_t p_block;
-    size_t p_remain;
+  bool Finished() const {
+    return next_range_ == tgt_.size() && current_range_left_ == 0;
+  }
+
+  size_t Write(const uint8_t* data, size_t size) {
+    if (Finished()) {
+      LOG(ERROR) << "range sink write overrun; can't write " << size << " bytes";
+      return 0;
+    }
+
+    size_t written = 0;
+    while (size > 0) {
+      // Move to the next range as needed.
+      if (current_range_left_ == 0) {
+        if (next_range_ < tgt_.size()) {
+          const Range& range = tgt_[next_range_];
+          off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE;
+          current_range_left_ = (range.second - range.first) * BLOCKSIZE;
+          next_range_++;
+          if (!discard_blocks(fd_, offset, current_range_left_)) {
+            break;
+          }
+
+          if (!check_lseek(fd_, offset, SEEK_SET)) {
+            break;
+          }
+        } else {
+          // We can't write any more; return how many bytes have been written so far.
+          break;
+        }
+      }
+
+      size_t write_now = size;
+      if (current_range_left_ < write_now) {
+        write_now = current_range_left_;
+      }
+
+      if (write_all(fd_, data, write_now) == -1) {
+        break;
+      }
+
+      data += write_now;
+      size -= write_now;
+
+      current_range_left_ -= write_now;
+      written += write_now;
+    }
+
+    bytes_written_ += written;
+    return written;
+  }
+
+  size_t BytesWritten() const {
+    return bytes_written_;
+  }
+
+ private:
+  // The input data.
+  int fd_;
+  // The destination for the data.
+  const RangeSet& tgt_;
+  // The next range that we should write to.
+  size_t next_range_;
+  // The number of bytes to write before moving to the next range.
+  size_t current_range_left_;
+  // Total bytes written by the writer.
+  size_t bytes_written_;
 };
 
-static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) {
-    RangeSinkState* rss = reinterpret_cast<RangeSinkState*>(token);
-
-    if (rss->p_remain == 0) {
-        LOG(ERROR) << "range sink write overrun";
-        return 0;
-    }
-
-    ssize_t written = 0;
-    while (size > 0) {
-        size_t write_now = size;
-
-        if (rss->p_remain < write_now) {
-            write_now = rss->p_remain;
-        }
-
-        if (write_all(rss->fd, data, write_now) == -1) {
-            break;
-        }
-
-        data += write_now;
-        size -= write_now;
-
-        rss->p_remain -= write_now;
-        written += write_now;
-
-        if (rss->p_remain == 0) {
-            // move to the next block
-            ++rss->p_block;
-            if (rss->p_block < rss->tgt.count) {
-                rss->p_remain = (rss->tgt.pos[rss->p_block * 2 + 1] -
-                                 rss->tgt.pos[rss->p_block * 2]) * BLOCKSIZE;
-
-                off64_t offset = static_cast<off64_t>(rss->tgt.pos[rss->p_block*2]) * BLOCKSIZE;
-                if (!discard_blocks(rss->fd, offset, rss->p_remain)) {
-                    break;
-                }
-
-                if (!check_lseek(rss->fd, offset, SEEK_SET)) {
-                    break;
-                }
-
-            } else {
-                // we can't write any more; return how many bytes have
-                // been written so far.
-                break;
-            }
-        }
-    }
-
-    return written;
-}
-
-// All of the data for all the 'new' transfers is contained in one
-// file in the update package, concatenated together in the order in
-// which transfers.list will need it.  We want to stream it out of the
-// archive (it's compressed) without writing it to a temp file, but we
-// can't write each section until it's that transfer's turn to go.
-//
-// To achieve this, we expand the new data from the archive in a
-// background thread, and block that threads 'receive uncompressed
-// data' function until the main thread has reached a point where we
-// want some new data to be written.  We signal the background thread
-// with the destination for the data and block the main thread,
-// waiting for the background thread to complete writing that section.
-// Then it signals the main thread to wake up and goes back to
-// blocking waiting for a transfer.
-//
-// NewThreadInfo is the struct used to pass information back and forth
-// between the two threads.  When the main thread wants some data
-// written, it sets rss to the destination location and signals the
-// condition.  When the background thread is done writing, it clears
-// rss and signals the condition again.
-
+/**
+ * All of the data for all the 'new' transfers is contained in one file in the update package,
+ * concatenated together in the order in which transfers.list will need it. We want to stream it out
+ * of the archive (it's compressed) without writing it to a temp file, but we can't write each
+ * section until it's that transfer's turn to go.
+ *
+ * To achieve this, we expand the new data from the archive in a background thread, and block that
+ * threads 'receive uncompressed data' function until the main thread has reached a point where we
+ * want some new data to be written. We signal the background thread with the destination for the
+ * data and block the main thread, waiting for the background thread to complete writing that
+ * section. Then it signals the main thread to wake up and goes back to blocking waiting for a
+ * transfer.
+ *
+ * NewThreadInfo is the struct used to pass information back and forth between the two threads. When
+ * the main thread wants some data written, it sets writer to the destination location and signals
+ * the condition. When the background thread is done writing, it clears writer and signals the
+ * condition again.
+ */
 struct NewThreadInfo {
-    ZipArchiveHandle za;
-    ZipEntry entry;
+  ZipArchiveHandle za;
+  ZipEntry entry;
 
-    RangeSinkState* rss;
+  RangeSinkWriter* writer;
+  bool receiver_available;
 
-    pthread_mutex_t mu;
-    pthread_cond_t cv;
+  pthread_mutex_t mu;
+  pthread_cond_t cv;
 };
 
 static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) {
-    NewThreadInfo* nti = reinterpret_cast<NewThreadInfo*>(cookie);
+  NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
 
-    while (size > 0) {
-        // Wait for nti->rss to be non-null, indicating some of this
-        // data is wanted.
-        pthread_mutex_lock(&nti->mu);
-        while (nti->rss == nullptr) {
-            pthread_cond_wait(&nti->cv, &nti->mu);
-        }
-        pthread_mutex_unlock(&nti->mu);
-
-        // At this point nti->rss is set, and we own it.  The main
-        // thread is waiting for it to disappear from nti.
-        ssize_t written = RangeSinkWrite(data, size, nti->rss);
-        data += written;
-        size -= written;
-
-        if (nti->rss->p_block == nti->rss->tgt.count) {
-            // we have written all the bytes desired by this rss.
-
-            pthread_mutex_lock(&nti->mu);
-            nti->rss = nullptr;
-            pthread_cond_broadcast(&nti->cv);
-            pthread_mutex_unlock(&nti->mu);
-        }
+  while (size > 0) {
+    // Wait for nti->writer to be non-null, indicating some of this data is wanted.
+    pthread_mutex_lock(&nti->mu);
+    while (nti->writer == nullptr) {
+      pthread_cond_wait(&nti->cv, &nti->mu);
     }
+    pthread_mutex_unlock(&nti->mu);
 
-    return true;
+    // At this point nti->writer is set, and we own it. The main thread is waiting for it to
+    // disappear from nti.
+    size_t written = nti->writer->Write(data, size);
+    data += written;
+    size -= written;
+
+    if (nti->writer->Finished()) {
+      // We have written all the bytes desired by this writer.
+
+      pthread_mutex_lock(&nti->mu);
+      nti->writer = nullptr;
+      pthread_cond_broadcast(&nti->cv);
+      pthread_mutex_unlock(&nti->mu);
+    }
+  }
+
+  return true;
 }
 
 static void* unzip_new_data(void* cookie) {
-    NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
-    ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti);
-    return nullptr;
+  NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
+  ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti);
+
+  pthread_mutex_lock(&nti->mu);
+  nti->receiver_available = false;
+  if (nti->writer != nullptr) {
+    pthread_cond_broadcast(&nti->cv);
+  }
+  pthread_mutex_unlock(&nti->mu);
+  return nullptr;
 }
 
 static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>& buffer, int fd) {
-    size_t p = 0;
-    uint8_t* data = buffer.data();
-
-    for (size_t i = 0; i < src.count; ++i) {
-        if (!check_lseek(fd, (off64_t) src.pos[i * 2] * BLOCKSIZE, SEEK_SET)) {
-            return -1;
-        }
-
-        size_t size = (src.pos[i * 2 + 1] - src.pos[i * 2]) * BLOCKSIZE;
-
-        if (read_all(fd, data + p, size) == -1) {
-            return -1;
-        }
-
-        p += size;
+  size_t p = 0;
+  for (const auto& range : src) {
+    if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) {
+      return -1;
     }
 
-    return 0;
+    size_t size = (range.second - range.first) * BLOCKSIZE;
+    if (read_all(fd, buffer.data() + p, size) == -1) {
+      return -1;
+    }
+
+    p += size;
+  }
+
+  return 0;
 }
 
 static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, int fd) {
-    const uint8_t* data = buffer.data();
-
-    size_t p = 0;
-    for (size_t i = 0; i < tgt.count; ++i) {
-        off64_t offset = static_cast<off64_t>(tgt.pos[i * 2]) * BLOCKSIZE;
-        size_t size = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * BLOCKSIZE;
-        if (!discard_blocks(fd, offset, size)) {
-            return -1;
-        }
-
-        if (!check_lseek(fd, offset, SEEK_SET)) {
-            return -1;
-        }
-
-        if (write_all(fd, data + p, size) == -1) {
-            return -1;
-        }
-
-        p += size;
+  size_t written = 0;
+  for (const auto& range : tgt) {
+    off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE;
+    size_t size = (range.second - range.first) * BLOCKSIZE;
+    if (!discard_blocks(fd, offset, size)) {
+      return -1;
     }
 
-    return 0;
+    if (!check_lseek(fd, offset, SEEK_SET)) {
+      return -1;
+    }
+
+    if (write_all(fd, buffer.data() + written, size) == -1) {
+      return -1;
+    }
+
+    written += size;
+  }
+
+  return 0;
 }
 
 // Parameters for transfer list command functions
@@ -463,31 +391,26 @@
     return;
   }
 
-  RangeSet src = parse_range(params.tokens[pos++]);
+  RangeSet src = RangeSet::Parse(params.tokens[pos++]);
 
   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 };
+    locs = RangeSet(std::vector<Range>{ Range{ 0, src.blocks() } });
   } 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));
+    locs = RangeSet::Parse(params.tokens[pos++]);
+    CHECK_EQ(src.blocks(), locs.blocks());
   }
 
-  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);
+  LOG(INFO) << "printing hash in hex for " << src.blocks() << " source blocks";
+  for (size_t i = 0; i < src.blocks(); i++) {
+    size_t block_num = src.GetBlockNumber(i);
+    size_t buffer_index = locs.GetBlockNumber(i);
     CHECK_LE((buffer_index + 1) * BLOCKSIZE, buffer.size());
 
     uint8_t digest[SHA_DIGEST_LENGTH];
@@ -503,11 +426,10 @@
                                                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());
+  CHECK_EQ(src.blocks() * BLOCKSIZE, buffer.size());
 
-  for (size_t i = 0; i < src.size; i++) {
-    int block_num = src.get_block(i);
-    CHECK_NE(block_num, -1);
+  for (size_t i = 0; i < src.blocks(); i++) {
+    size_t block_num = src.GetBlockNumber(i);
 
     uint8_t digest[SHA_DIGEST_LENGTH];
     SHA1(buffer.data() + i * BLOCKSIZE, BLOCKSIZE, digest);
@@ -526,7 +448,7 @@
 
   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);
+  std::vector<uint8_t> buffer(src.blocks() * BLOCKSIZE);
   if (ReadBlocks(src, buffer, fd) == -1) {
       LOG(ERROR) << "failed to read source blocks for stash: " << id;
       return;
@@ -618,85 +540,81 @@
 
 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.
-    if (!params.canwrite) {
-        if (stash_map.find(id) != stash_map.end()) {
-            const RangeSet& src = stash_map[id];
-            allocate(src.size * BLOCKSIZE, buffer);
+  // 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.
+  if (!params.canwrite) {
+    if (stash_map.find(id) != stash_map.end()) {
+      const RangeSet& src = stash_map[id];
+      allocate(src.blocks() * BLOCKSIZE, buffer);
 
-            if (ReadBlocks(src, buffer, params.fd) == -1) {
-                LOG(ERROR) << "failed to read source blocks in stash map.";
-                return -1;
-            }
-            if (VerifyBlocks(id, buffer, src.size, true) != 0) {
-                LOG(ERROR) << "failed to verify loaded source blocks in stash map.";
-                PrintHashForCorruptedStashedBlocks(id, buffer, src);
-                return -1;
-            }
-            return 0;
-        }
-    }
-
-    size_t blockcount = 0;
-
-    if (!blocks) {
-        blocks = &blockcount;
-    }
-
-    std::string fn = GetStashFileName(params.stashbase, id, "");
-
-    struct stat sb;
-    int res = stat(fn.c_str(), &sb);
-
-    if (res == -1) {
-        if (errno != ENOENT || printnoent) {
-            PLOG(ERROR) << "stat \"" << fn << "\" failed";
-            PrintHashForMissingStashedBlocks(id, params.fd);
-        }
+      if (ReadBlocks(src, buffer, params.fd) == -1) {
+        LOG(ERROR) << "failed to read source blocks in stash map.";
         return -1;
-    }
-
-    LOG(INFO) << " loading " << fn;
-
-    if ((sb.st_size % BLOCKSIZE) != 0) {
-        LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE;
+      }
+      if (VerifyBlocks(id, buffer, src.blocks(), true) != 0) {
+        LOG(ERROR) << "failed to verify loaded source blocks in stash map.";
+        PrintHashForCorruptedStashedBlocks(id, buffer, src);
         return -1;
+      }
+      return 0;
     }
+  }
 
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY)));
-    if (fd == -1) {
-        PLOG(ERROR) << "open \"" << fn << "\" failed";
-        return -1;
+  size_t blockcount = 0;
+  if (!blocks) {
+    blocks = &blockcount;
+  }
+
+  std::string fn = GetStashFileName(params.stashbase, id, "");
+
+  struct stat sb;
+  if (stat(fn.c_str(), &sb) == -1) {
+    if (errno != ENOENT || printnoent) {
+      PLOG(ERROR) << "stat \"" << fn << "\" failed";
+      PrintHashForMissingStashedBlocks(id, params.fd);
     }
+    return -1;
+  }
 
-    allocate(sb.st_size, buffer);
+  LOG(INFO) << " loading " << fn;
 
-    if (read_all(fd, buffer, sb.st_size) == -1) {
-        return -1;
+  if ((sb.st_size % BLOCKSIZE) != 0) {
+    LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE;
+    return -1;
+  }
+
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "open \"" << fn << "\" failed";
+    return -1;
+  }
+
+  allocate(sb.st_size, buffer);
+
+  if (read_all(fd, buffer, sb.st_size) == -1) {
+    return -1;
+  }
+
+  *blocks = sb.st_size / BLOCKSIZE;
+
+  if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) {
+    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;
+  }
 
-    *blocks = sb.st_size / BLOCKSIZE;
-
-    if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) {
-        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;
-    }
-
-    return 0;
+  return 0;
 }
 
 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;
     }
@@ -866,113 +784,96 @@
   return 0;
 }
 
+// Source contains packed data, which we want to move to the locations given in locs in the dest
+// buffer. source and dest may be the same buffer.
 static void MoveRange(std::vector<uint8_t>& dest, const RangeSet& locs,
-        const std::vector<uint8_t>& source) {
-    // source contains packed data, which we want to move to the
-    // locations given in locs in the dest buffer.  source and dest
-    // may be the same buffer.
-
-    const uint8_t* from = source.data();
-    uint8_t* to = dest.data();
-    size_t start = locs.size;
-    for (int i = locs.count-1; i >= 0; --i) {
-        size_t blocks = locs.pos[i*2+1] - locs.pos[i*2];
-        start -= blocks;
-        memmove(to + (locs.pos[i*2] * BLOCKSIZE), from + (start * BLOCKSIZE),
-                blocks * BLOCKSIZE);
-    }
+                      const std::vector<uint8_t>& source) {
+  const uint8_t* from = source.data();
+  uint8_t* to = dest.data();
+  size_t start = locs.blocks();
+  // Must do the movement backward.
+  for (auto it = locs.crbegin(); it != locs.crend(); it++) {
+    size_t blocks = it->second - it->first;
+    start -= blocks;
+    memmove(to + (it->first * BLOCKSIZE), from + (start * BLOCKSIZE), blocks * BLOCKSIZE);
+  }
 }
 
-// Do a source/target load for move/bsdiff/imgdiff in version 2.
-// 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)
-//
-// 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.
+/**
+ * We expect to parse the remainder of the parameter tokens as one of:
+ *
+ *    <src_block_count> <src_range>
+ *        (loads data from source image only)
+ *
+ *    <src_block_count> - <[stash_id:stash_range] ...>
+ *        (loads data from stashes only)
+ *
+ *    <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...>
+ *        (loads data from both source image and stashes)
+ *
+ * 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 for detecting overlaps. Any stashes required are loaded using
+ * LoadStash.
+ */
+static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size_t* src_blocks,
+                            bool* overlap) {
+  CHECK(src_blocks != nullptr);
+  CHECK(overlap != nullptr);
 
-static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
-                              bool* overlap) {
+  // <src_block_count>
+  const std::string& token = params.tokens[params.cpos++];
+  if (!android::base::ParseUint(token, src_blocks)) {
+    LOG(ERROR) << "invalid src_block_count \"" << token << "\"";
+    return -1;
+  }
 
-    // At least it needs to provide three parameters: <tgt_range>,
-    // <src_block_count> and "-"/<src_range>.
-    if (params.cpos + 2 >= params.tokens.size()) {
-        LOG(ERROR) << "invalid parameters";
-        return -1;
+  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 = RangeSet::Parse(params.tokens[params.cpos++]);
+    *overlap = src.Overlaps(tgt);
+
+    if (ReadBlocks(src, params.buffer, params.fd) == -1) {
+      return -1;
     }
 
-    // <tgt_range>
-    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)) {
-        LOG(ERROR) << "invalid src_block_count \"" << token << "\"";
-        return -1;
+    if (params.cpos >= params.tokens.size()) {
+      // no stashes, only source range
+      return 0;
     }
 
-    allocate(src_blocks * BLOCKSIZE, params.buffer);
+    RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]);
+    MoveRange(params.buffer, locs, 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++]);
-        int res = ReadBlocks(src, params.buffer, params.fd);
-
-        if (overlap) {
-            *overlap = range_overlaps(src, tgt);
-        }
-
-        if (res == -1) {
-            return -1;
-        }
-
-        if (params.cpos >= params.tokens.size()) {
-            // no stashes, only source range
-            return 0;
-        }
-
-        RangeSet locs = parse_range(params.tokens[params.cpos++]);
-        MoveRange(params.buffer, locs, params.buffer);
+  // <[stash_id:stash_range]>
+  while (params.cpos < params.tokens.size()) {
+    // Each word is a an index into the stash table, a colon, and then a RangeSet describing where
+    // in the source block that stashed data should go.
+    std::vector<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":");
+    if (tokens.size() != 2) {
+      LOG(ERROR) << "invalid parameter";
+      return -1;
     }
 
-    // <[stash_id:stash_range]>
-    while (params.cpos < params.tokens.size()) {
-        // Each word is a an index into the stash table, a colon, and
-        // then a rangeset describing where in the source block that
-        // stashed data should go.
-        std::vector<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":");
-        if (tokens.size() != 2) {
-            LOG(ERROR) << "invalid parameter";
-            return -1;
-        }
-
-        std::vector<uint8_t> stash;
-        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
-            LOG(ERROR) << "failed to load stash " << tokens[0];
-            continue;
-        }
-
-        RangeSet locs = parse_range(tokens[1]);
-
-        MoveRange(params.buffer, locs, stash);
+    std::vector<uint8_t> stash;
+    if (LoadStash(params, tokens[0], false, nullptr, stash, true) == -1) {
+      // These source blocks will fail verification if used later, but we
+      // will let the caller decide if this is a fatal failure
+      LOG(ERROR) << "failed to load stash " << tokens[0];
+      continue;
     }
 
-    return 0;
+    RangeSet locs = RangeSet::Parse(tokens[1]);
+    MoveRange(params.buffer, locs, stash);
+  }
+
+  return 0;
 }
 
 /**
@@ -989,9 +890,8 @@
  *    <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
+ * 'onehash' tells whether to expect separate source and targe block hashes, or if they are both the
+ * same and only one hash should be expected. params.isunresumable will be set to true 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,
@@ -1002,87 +902,100 @@
  *
  * 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) {
-    if (params.cpos >= params.tokens.size()) {
-        LOG(ERROR) << "missing source hash";
-        return -1;
-    }
+static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* src_blocks,
+                              bool onehash, bool* overlap) {
+  CHECK(src_blocks != nullptr);
+  CHECK(overlap != nullptr);
 
-    std::string srchash = params.tokens[params.cpos++];
-    std::string tgthash;
-
-    if (onehash) {
-        tgthash = srchash;
-    } else {
-        if (params.cpos >= params.tokens.size()) {
-            LOG(ERROR) << "missing target hash";
-            return -1;
-        }
-        tgthash = params.tokens[params.cpos++];
-    }
-
-    if (LoadSrcTgtVersion2(params, tgt, src_blocks, &overlap) == -1) {
-        return -1;
-    }
-
-    std::vector<uint8_t> tgtbuffer(tgt.size * BLOCKSIZE);
-
-    if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
-        return -1;
-    }
-
-    if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) {
-        // Target blocks already have expected content, command should be skipped.
-        return 1;
-    }
-
-    if (VerifyBlocks(srchash, params.buffer, src_blocks, true) == 0) {
-        // If source and target blocks overlap, stash the source blocks so we can
-        // resume from possible write errors. In verify mode, we can skip stashing
-        // because the source blocks won't be overwritten.
-        if (overlap && params.canwrite) {
-            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) {
-                LOG(ERROR) << "failed to stash overlapping source blocks";
-                return -1;
-            }
-
-            params.stashed += src_blocks;
-            // Can be deleted when the write has completed.
-            if (!stash_exists) {
-                params.freestash = srchash;
-            }
-        }
-
-        // Source blocks have expected content, command can proceed.
-        return 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.
-    LOG(ERROR) << "partition has unexpected contents";
-    PrintHashForCorruptedSourceBlocks(params, params.buffer);
-
-    params.isunresumable = true;
-
+  if (params.cpos >= params.tokens.size()) {
+    LOG(ERROR) << "missing source hash";
     return -1;
+  }
+
+  std::string srchash = params.tokens[params.cpos++];
+  std::string tgthash;
+
+  if (onehash) {
+    tgthash = srchash;
+  } else {
+    if (params.cpos >= params.tokens.size()) {
+      LOG(ERROR) << "missing target hash";
+      return -1;
+    }
+    tgthash = params.tokens[params.cpos++];
+  }
+
+  // At least it needs to provide three parameters: <tgt_range>, <src_block_count> and
+  // "-"/<src_range>.
+  if (params.cpos + 2 >= params.tokens.size()) {
+    LOG(ERROR) << "invalid parameters";
+    return -1;
+  }
+
+  // <tgt_range>
+  tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+
+  std::vector<uint8_t> tgtbuffer(tgt.blocks() * BLOCKSIZE);
+  if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
+    return -1;
+  }
+
+  // Return now if target blocks already have expected content.
+  if (VerifyBlocks(tgthash, tgtbuffer, tgt.blocks(), false) == 0) {
+    return 1;
+  }
+
+  // Load source blocks.
+  if (LoadSourceBlocks(params, tgt, src_blocks, overlap) == -1) {
+    return -1;
+  }
+
+  if (VerifyBlocks(srchash, params.buffer, *src_blocks, true) == 0) {
+    // If source and target blocks overlap, stash the source blocks so we can
+    // resume from possible write errors. In verify mode, we can skip stashing
+    // because the source blocks won't be overwritten.
+    if (*overlap && params.canwrite) {
+      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) {
+        LOG(ERROR) << "failed to stash overlapping source blocks";
+        return -1;
+      }
+
+      params.stashed += *src_blocks;
+      // Can be deleted when the write has completed.
+      if (!stash_exists) {
+        params.freestash = srchash;
+      }
+    }
+
+    // Source blocks have expected content, command can proceed.
+    return 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.
+  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;
   RangeSet tgt;
-  int status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap);
+  int status = LoadSrcTgtVersion3(params, tgt, &blocks, true, &overlap);
 
   if (status == -1) {
     LOG(ERROR) << "failed to read blocks for move";
@@ -1112,7 +1025,7 @@
     params.freestash.clear();
   }
 
-  params.written += tgt.size;
+  params.written += tgt.blocks();
 
   return 0;
 }
@@ -1132,13 +1045,13 @@
     return 0;
   }
 
-  RangeSet src = parse_range(params.tokens[params.cpos++]);
+  RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]);
 
-  allocate(src.size * BLOCKSIZE, params.buffer);
+  allocate(src.blocks() * BLOCKSIZE, params.buffer);
   if (ReadBlocks(src, params.buffer, params.fd) == -1) {
     return -1;
   }
-  blocks = src.size;
+  blocks = src.blocks();
   stash_map[id] = src;
 
   if (VerifyBlocks(id, params.buffer, blocks, true) != 0) {
@@ -1177,220 +1090,202 @@
 }
 
 static int PerformCommandZero(CommandParameters& params) {
+  if (params.cpos >= params.tokens.size()) {
+    LOG(ERROR) << "missing target blocks for zero";
+    return -1;
+  }
 
-    if (params.cpos >= params.tokens.size()) {
-        LOG(ERROR) << "missing target blocks for zero";
+  RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+
+  LOG(INFO) << "  zeroing " << tgt.blocks() << " blocks";
+
+  allocate(BLOCKSIZE, params.buffer);
+  memset(params.buffer.data(), 0, BLOCKSIZE);
+
+  if (params.canwrite) {
+    for (const auto& range : tgt) {
+      off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE;
+      size_t size = (range.second - range.first) * BLOCKSIZE;
+      if (!discard_blocks(params.fd, offset, size)) {
         return -1;
-    }
+      }
 
-    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
+      if (!check_lseek(params.fd, offset, SEEK_SET)) {
+        return -1;
+      }
 
-    LOG(INFO) << "  zeroing " << tgt.size << " blocks";
-
-    allocate(BLOCKSIZE, params.buffer);
-    memset(params.buffer.data(), 0, BLOCKSIZE);
-
-    if (params.canwrite) {
-        for (size_t i = 0; i < tgt.count; ++i) {
-            off64_t offset = static_cast<off64_t>(tgt.pos[i * 2]) * BLOCKSIZE;
-            size_t size = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * BLOCKSIZE;
-            if (!discard_blocks(params.fd, offset, size)) {
-                return -1;
-            }
-
-            if (!check_lseek(params.fd, offset, SEEK_SET)) {
-                return -1;
-            }
-
-            for (size_t j = tgt.pos[i * 2]; j < tgt.pos[i * 2 + 1]; ++j) {
-                if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) {
-                    return -1;
-                }
-            }
+      for (size_t j = range.first; j < range.second; ++j) {
+        if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) {
+          return -1;
         }
+      }
     }
+  }
 
-    if (params.cmdname[0] == 'z') {
-        // Update only for the zero command, as the erase command will call
-        // this if DEBUG_ERASE is defined.
-        params.written += tgt.size;
-    }
+  if (params.cmdname[0] == 'z') {
+    // Update only for the zero command, as the erase command will call
+    // this if DEBUG_ERASE is defined.
+    params.written += tgt.blocks();
+  }
 
-    return 0;
+  return 0;
 }
 
 static int PerformCommandNew(CommandParameters& params) {
+  if (params.cpos >= params.tokens.size()) {
+    LOG(ERROR) << "missing target blocks for new";
+    return -1;
+  }
 
-    if (params.cpos >= params.tokens.size()) {
-        LOG(ERROR) << "missing target blocks for new";
-        return -1;
-    }
+  RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
 
-    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
+  if (params.canwrite) {
+    LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data";
 
-    if (params.canwrite) {
-        LOG(INFO) << " writing " << tgt.size << " blocks of new data";
+    RangeSinkWriter writer(params.fd, tgt);
+    pthread_mutex_lock(&params.nti.mu);
+    params.nti.writer = &writer;
+    pthread_cond_broadcast(&params.nti.cv);
 
-        RangeSinkState rss(tgt);
-        rss.fd = params.fd;
-        rss.p_block = 0;
-        rss.p_remain = (tgt.pos[1] - tgt.pos[0]) * BLOCKSIZE;
-
-        off64_t offset = static_cast<off64_t>(tgt.pos[0]) * BLOCKSIZE;
-        if (!discard_blocks(params.fd, offset, tgt.size * BLOCKSIZE)) {
-            return -1;
-        }
-
-        if (!check_lseek(params.fd, offset, SEEK_SET)) {
-            return -1;
-        }
-
-        pthread_mutex_lock(&params.nti.mu);
-        params.nti.rss = &rss;
-        pthread_cond_broadcast(&params.nti.cv);
-
-        while (params.nti.rss) {
-            pthread_cond_wait(&params.nti.cv, &params.nti.mu);
-        }
-
+    while (params.nti.writer != nullptr) {
+      if (!params.nti.receiver_available) {
+        LOG(ERROR) << "missing " << (tgt.blocks() * BLOCKSIZE - params.nti.writer->BytesWritten())
+                   << " bytes of new data";
         pthread_mutex_unlock(&params.nti.mu);
+        return -1;
+      }
+      pthread_cond_wait(&params.nti.cv, &params.nti.mu);
     }
 
-    params.written += tgt.size;
+    pthread_mutex_unlock(&params.nti.mu);
+  }
 
-    return 0;
+  params.written += tgt.blocks();
+
+  return 0;
 }
 
 static int PerformCommandDiff(CommandParameters& params) {
+  // <offset> <length>
+  if (params.cpos + 1 >= params.tokens.size()) {
+    LOG(ERROR) << "missing patch offset or length for " << params.cmdname;
+    return -1;
+  }
 
-    // <offset> <length>
-    if (params.cpos + 1 >= params.tokens.size()) {
-        LOG(ERROR) << "missing patch offset or length for " << params.cmdname;
-        return -1;
-    }
+  size_t offset;
+  if (!android::base::ParseUint(params.tokens[params.cpos++], &offset)) {
+    LOG(ERROR) << "invalid patch offset";
+    return -1;
+  }
 
-    size_t offset;
-    if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &offset)) {
-        LOG(ERROR) << "invalid patch offset";
-        return -1;
-    }
+  size_t len;
+  if (!android::base::ParseUint(params.tokens[params.cpos++], &len)) {
+    LOG(ERROR) << "invalid patch len";
+    return -1;
+  }
 
-    size_t len;
-    if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &len)) {
-        LOG(ERROR) << "invalid patch len";
-        return -1;
-    }
+  RangeSet tgt;
+  size_t blocks = 0;
+  bool overlap = false;
+  int status = LoadSrcTgtVersion3(params, tgt, &blocks, false, &overlap);
 
-    RangeSet tgt;
-    size_t blocks = 0;
-    bool overlap = false;
-    int status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap);
+  if (status == -1) {
+    LOG(ERROR) << "failed to read blocks for diff";
+    return -1;
+  }
 
-    if (status == -1) {
-        LOG(ERROR) << "failed to read blocks for diff";
-        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) {
-        LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
-    }
+      LOG(INFO) << "patching " << blocks << " blocks to " << tgt.blocks();
+      Value patch_value(
+          VAL_BLOB, std::string(reinterpret_cast<const char*>(params.patch_start + offset), len));
 
-    if (params.canwrite) {
-        if (status == 0) {
-            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;
-            rss.p_remain = (tgt.pos[1] - tgt.pos[0]) * BLOCKSIZE;
-
-            off64_t offset = static_cast<off64_t>(tgt.pos[0]) * BLOCKSIZE;
-            if (!discard_blocks(params.fd, offset, rss.p_remain)) {
-                return -1;
-            }
-
-            if (!check_lseek(params.fd, offset, SEEK_SET)) {
-                return -1;
-            }
-
-            if (params.cmdname[0] == 'i') {      // imgdiff
-                if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value,
-                        &RangeSinkWrite, &rss, nullptr, nullptr) != 0) {
-                    LOG(ERROR) << "Failed to apply image patch.";
-                    return -1;
-                }
-            } else {
-                if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value,
-                        0, &RangeSinkWrite, &rss, nullptr) != 0) {
-                    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) {
-                LOG(ERROR) << "range sink underrun?";
-            }
-        } else {
-            LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.size
-                      << " [" << params.cmdline << "]";
+      RangeSinkWriter writer(params.fd, tgt);
+      if (params.cmdname[0] == 'i') {  // imgdiff
+        if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value,
+                            std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1,
+                                      std::placeholders::_2),
+                            nullptr, nullptr) != 0) {
+          LOG(ERROR) << "Failed to apply image patch.";
+          return -1;
         }
+      } else {
+        if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, 0,
+                             std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1,
+                                       std::placeholders::_2),
+                             nullptr) != 0) {
+          LOG(ERROR) << "Failed to apply bsdiff patch.";
+          return -1;
+        }
+      }
+
+      // We expect the output of the patcher to fill the tgt ranges exactly.
+      if (!writer.Finished()) {
+        LOG(ERROR) << "range sink underrun?";
+      }
+    } else {
+      LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " ["
+                << params.cmdline << "]";
     }
+  }
 
-    if (!params.freestash.empty()) {
-        FreeStash(params.stashbase, params.freestash);
-        params.freestash.clear();
-    }
+  if (!params.freestash.empty()) {
+    FreeStash(params.stashbase, params.freestash);
+    params.freestash.clear();
+  }
 
-    params.written += tgt.size;
+  params.written += tgt.blocks();
 
-    return 0;
+  return 0;
 }
 
 static int PerformCommandErase(CommandParameters& params) {
-    if (DEBUG_ERASE) {
-        return PerformCommandZero(params);
-    }
+  if (DEBUG_ERASE) {
+    return PerformCommandZero(params);
+  }
 
-    struct stat sb;
-    if (fstat(params.fd, &sb) == -1) {
-        PLOG(ERROR) << "failed to fstat device to erase";
+  struct stat sb;
+  if (fstat(params.fd, &sb) == -1) {
+    PLOG(ERROR) << "failed to fstat device to erase";
+    return -1;
+  }
+
+  if (!S_ISBLK(sb.st_mode)) {
+    LOG(ERROR) << "not a block device; skipping erase";
+    return -1;
+  }
+
+  if (params.cpos >= params.tokens.size()) {
+    LOG(ERROR) << "missing target blocks for erase";
+    return -1;
+  }
+
+  RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+
+  if (params.canwrite) {
+    LOG(INFO) << " erasing " << tgt.blocks() << " blocks";
+
+    for (const auto& range : tgt) {
+      uint64_t blocks[2];
+      // offset in bytes
+      blocks[0] = range.first * static_cast<uint64_t>(BLOCKSIZE);
+      // length in bytes
+      blocks[1] = (range.second - range.first) * static_cast<uint64_t>(BLOCKSIZE);
+
+      if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) {
+        PLOG(ERROR) << "BLKDISCARD ioctl failed";
         return -1;
+      }
     }
+  }
 
-    if (!S_ISBLK(sb.st_mode)) {
-        LOG(ERROR) << "not a block device; skipping erase";
-        return -1;
-    }
-
-    if (params.cpos >= params.tokens.size()) {
-        LOG(ERROR) << "missing target blocks for erase";
-        return -1;
-    }
-
-    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
-
-    if (params.canwrite) {
-        LOG(INFO) << " erasing " << tgt.size << " blocks";
-
-        for (size_t i = 0; i < tgt.count; ++i) {
-            uint64_t blocks[2];
-            // offset in bytes
-            blocks[0] = tgt.pos[i * 2] * (uint64_t) BLOCKSIZE;
-            // length in bytes
-            blocks[1] = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * (uint64_t) BLOCKSIZE;
-
-            if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) {
-                PLOG(ERROR) << "BLKDISCARD ioctl failed";
-                return -1;
-            }
-        }
-    }
-
-    return 0;
+  return 0;
 }
 
 // Definitions for transfer list command functions
@@ -1429,10 +1324,10 @@
     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();
+  const std::unique_ptr<Value>& blockdev_filename = args[0];
+  const std::unique_ptr<Value>& transfer_list_value = args[1];
+  const std::unique_ptr<Value>& new_data_fn = args[2];
+  const std::unique_ptr<Value>& patch_data_fn = args[3];
 
   if (blockdev_filename->type != VAL_STRING) {
     ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name);
@@ -1487,6 +1382,7 @@
   if (params.canwrite) {
     params.nti.za = za;
     params.nti.entry = new_entry;
+    params.nti.receiver_available = true;
 
     pthread_mutex_init(&params.nti.mu, nullptr);
     pthread_cond_init(&params.nti.cv, nullptr);
@@ -1722,64 +1618,62 @@
 }
 
 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());
+  if (argv.size() != 2) {
+    ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu", argv.size());
+    return StringValue("");
+  }
+
+  std::vector<std::unique_ptr<Value>> args;
+  if (!ReadValueArgs(state, argv, &args)) {
+    return nullptr;
+  }
+
+  const std::unique_ptr<Value>& blockdev_filename = args[0];
+  const std::unique_ptr<Value>& ranges = args[1];
+
+  if (blockdev_filename->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name);
+    return StringValue("");
+  }
+  if (ranges->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
+    return StringValue("");
+  }
+
+  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 = RangeSet::Parse(ranges->data);
+
+  SHA_CTX ctx;
+  SHA1_Init(&ctx);
+
+  std::vector<uint8_t> buffer(BLOCKSIZE);
+  for (const auto& range : rs) {
+    if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) {
+      ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(),
+                 strerror(errno));
+      return StringValue("");
+    }
+
+    for (size_t j = range.first; j < range.second; ++j) {
+      if (read_all(fd, buffer, BLOCKSIZE) == -1) {
+        ErrorAbort(state, kFreadFailure, "failed to read %s: %s", blockdev_filename->data.c_str(),
+                   strerror(errno));
         return StringValue("");
+      }
+
+      SHA1_Update(&ctx, buffer.data(), BLOCKSIZE);
     }
+  }
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1_Final(digest, &ctx);
 
-    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("");
-    }
-    if (ranges->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
-        return StringValue("");
-    }
-
-    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);
-
-    SHA_CTX ctx;
-    SHA1_Init(&ctx);
-
-    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.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.c_str(), strerror(errno));
-                return StringValue("");
-            }
-
-            SHA1_Update(&ctx, buffer.data(), BLOCKSIZE);
-        }
-    }
-    uint8_t digest[SHA_DIGEST_LENGTH];
-    SHA1_Final(digest, &ctx);
-
-    return StringValue(print_sha1(digest));
+  return StringValue(print_sha1(digest));
 }
 
 // This function checks if a device has been remounted R/W prior to an incremental
@@ -1789,145 +1683,140 @@
 
 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 (argv.size() != 1) {
+    ErrorAbort(state, kArgsParsingFailure, "check_first_block expects 1 argument, got %zu",
+               argv.size());
+    return StringValue("");
+  }
 
-    std::vector<std::unique_ptr<Value>> args;
-    if (!ReadValueArgs(state, argv, &args)) {
-        return nullptr;
-    }
+  std::vector<std::unique_ptr<Value>> args;
+  if (!ReadValueArgs(state, argv, &args)) {
+    return nullptr;
+  }
 
-    const Value* arg_filename = args[0].get();
+  const std::unique_ptr<Value>& arg_filename = args[0];
 
-    if (arg_filename->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name);
-        return StringValue("");
-    }
+  if (arg_filename->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name);
+    return StringValue("");
+  }
 
-    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.c_str(),
-                   strerror(errno));
-        return StringValue("");
-    }
+  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.c_str(),
+               strerror(errno));
+    return StringValue("");
+  }
 
-    RangeSet blk0 {1 /*count*/, 1/*size*/, std::vector<size_t> {0, 1}/*position*/};
-    std::vector<uint8_t> block0_buffer(BLOCKSIZE);
+  RangeSet blk0(std::vector<Range>{ Range{ 0, 1 } });
+  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.c_str(),
-                strerror(errno));
-        return StringValue("");
-    }
+  if (ReadBlocks(blk0, block0_buffer, fd) == -1) {
+    ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(),
+               strerror(errno));
+    return StringValue("");
+  }
 
-    // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
-    // Super block starts from block 0, offset 0x400
-    //   0x2C: len32 Mount time
-    //   0x30: len32 Write time
-    //   0x34: len16 Number of mounts since the last fsck
-    //   0x38: len16 Magic signature 0xEF53
+  // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
+  // Super block starts from block 0, offset 0x400
+  //   0x2C: len32 Mount time
+  //   0x30: len32 Write time
+  //   0x34: len16 Number of mounts since the last fsck
+  //   0x38: len16 Magic signature 0xEF53
 
-    time_t mount_time = *reinterpret_cast<uint32_t*>(&block0_buffer[0x400+0x2C]);
-    uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400+0x34]);
+  time_t mount_time = *reinterpret_cast<uint32_t*>(&block0_buffer[0x400 + 0x2C]);
+  uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]);
 
-    if (mount_count > 0) {
-        uiPrintf(state, "Device was remounted R/W %d times\n", mount_count);
-        uiPrintf(state, "Last remount happened on %s", ctime(&mount_time));
-    }
+  if (mount_count > 0) {
+    uiPrintf(state, "Device was remounted R/W %" PRIu16 " times", mount_count);
+    uiPrintf(state, "Last remount happened on %s", ctime(&mount_time));
+  }
 
-    return StringValue("t");
+  return StringValue("t");
 }
 
-
 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());
+  if (argv.size() != 2) {
+    ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu",
+               argv.size());
+    return StringValue("");
+  }
+
+  std::vector<std::unique_ptr<Value>> args;
+  if (!ReadValueArgs(state, argv, &args)) {
+    return nullptr;
+  }
+
+  const std::unique_ptr<Value>& filename = args[0];
+  const std::unique_ptr<Value>& ranges = args[1];
+
+  if (filename->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name);
+    return StringValue("");
+  }
+  if (ranges->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
+    return StringValue("");
+  }
+
+  // Output notice to log when recover is attempted
+  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);
+
+  if (!fh) {
+    ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(),
+               strerror(errno));
+    return StringValue("");
+  }
+
+  if (!fh.has_ecc() || !fh.has_verity()) {
+    ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors");
+    return StringValue("");
+  }
+
+  fec_status status;
+  if (!fh.get_status(status)) {
+    ErrorAbort(state, kLibfecFailure, "failed to read FEC status");
+    return StringValue("");
+  }
+
+  uint8_t buffer[BLOCKSIZE];
+  for (const auto& range : RangeSet::Parse(ranges->data)) {
+    for (size_t j = range.first; j < range.second; ++j) {
+      // Stay within the data area, libfec validates and corrects metadata
+      if (status.data_size <= static_cast<uint64_t>(j) * BLOCKSIZE) {
+        continue;
+      }
+
+      if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) {
+        ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s",
+                   filename->data.c_str(), j, strerror(errno));
         return StringValue("");
+      }
+
+      // If we want to be able to recover from a situation where rewriting a corrected
+      // block doesn't guarantee the same data will be returned when re-read later, we
+      // can save a copy of corrected blocks to /cache. Note:
+      //
+      //  1. Maximum space required from /cache is the same as the maximum number of
+      //     corrupted blocks we can correct. For RS(255, 253) and a 2 GiB partition,
+      //     this would be ~16 MiB, for example.
+      //
+      //  2. To find out if this block was corrupted, call fec_get_status after each
+      //     read and check if the errors field value has increased.
     }
-
-    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("");
-    }
-    if (ranges->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
-        return StringValue("");
-    }
-
-    // Output notice to log when recover is attempted
-    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.c_str(), O_RDWR);
-
-    if (!fh) {
-        ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(),
-                   strerror(errno));
-        return StringValue("");
-    }
-
-    if (!fh.has_ecc() || !fh.has_verity()) {
-        ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors");
-        return StringValue("");
-    }
-
-    fec_status status;
-
-    if (!fh.get_status(status)) {
-        ErrorAbort(state, kLibfecFailure, "failed to read FEC status");
-        return StringValue("");
-    }
-
-    RangeSet rs = parse_range(ranges->data);
-
-    uint8_t buffer[BLOCKSIZE];
-
-    for (size_t i = 0; i < rs.count; ++i) {
-        for (size_t j = rs.pos[i * 2]; j < rs.pos[i * 2 + 1]; ++j) {
-            // Stay within the data area, libfec validates and corrects metadata
-            if (status.data_size <= (uint64_t)j * BLOCKSIZE) {
-                continue;
-            }
-
-            if (fh.pread(buffer, BLOCKSIZE, (off64_t)j * BLOCKSIZE) != BLOCKSIZE) {
-                ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s",
-                           filename->data.c_str(), j, strerror(errno));
-                return StringValue("");
-            }
-
-            // If we want to be able to recover from a situation where rewriting a corrected
-            // block doesn't guarantee the same data will be returned when re-read later, we
-            // can save a copy of corrected blocks to /cache. Note:
-            //
-            //  1. Maximum space required from /cache is the same as the maximum number of
-            //     corrupted blocks we can correct. For RS(255, 253) and a 2 GiB partition,
-            //     this would be ~16 MiB, for example.
-            //
-            //  2. To find out if this block was corrupted, call fec_get_status after each
-            //     read and check if the errors field value has increased.
-        }
-    }
-    LOG(INFO) << "..." << filename->data << " image recovered successfully.";
-    return StringValue("t");
+  }
+  LOG(INFO) << "..." << filename->data << " image recovered successfully.";
+  return StringValue("t");
 }
 
 void RegisterBlockImageFunctions() {
-    RegisterFunction("block_image_verify", BlockImageVerifyFn);
-    RegisterFunction("block_image_update", BlockImageUpdateFn);
-    RegisterFunction("block_image_recover", BlockImageRecoverFn);
-    RegisterFunction("check_first_block", CheckFirstBlockFn);
-    RegisterFunction("range_sha1", RangeSha1Fn);
+  RegisterFunction("block_image_verify", BlockImageVerifyFn);
+  RegisterFunction("block_image_update", BlockImageUpdateFn);
+  RegisterFunction("block_image_recover", BlockImageRecoverFn);
+  RegisterFunction("check_first_block", CheckFirstBlockFn);
+  RegisterFunction("range_sha1", RangeSha1Fn);
 }
diff --git a/updater/include/updater/rangeset.h b/updater/include/updater/rangeset.h
new file mode 100644
index 0000000..fad0380
--- /dev/null
+++ b/updater/include/updater/rangeset.h
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
+using Range = std::pair<size_t, size_t>;
+
+class RangeSet {
+ public:
+  RangeSet() : blocks_(0) {}
+
+  explicit RangeSet(std::vector<Range>&& pairs) {
+    CHECK_NE(pairs.size(), static_cast<size_t>(0)) << "Invalid number of tokens";
+
+    // Sanity check the input.
+    size_t result = 0;
+    for (const auto& range : pairs) {
+      CHECK_LT(range.first, range.second)
+          << "Empty or negative range: " << range.first << ", " << range.second;
+      size_t sz = range.second - range.first;
+      CHECK_LE(result, SIZE_MAX - sz) << "RangeSet size overflow";
+      result += sz;
+    }
+
+    ranges_ = pairs;
+    blocks_ = result;
+  }
+
+  static RangeSet Parse(const std::string& range_text) {
+    std::vector<std::string> pieces = android::base::Split(range_text, ",");
+    CHECK_GE(pieces.size(), static_cast<size_t>(3)) << "Invalid range text: " << range_text;
+
+    size_t num;
+    CHECK(android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX)))
+        << "Failed to parse the number of tokens: " << range_text;
+
+    CHECK_NE(num, static_cast<size_t>(0)) << "Invalid number of tokens: " << range_text;
+    CHECK_EQ(num % 2, static_cast<size_t>(0)) << "Number of tokens must be even: " << range_text;
+    CHECK_EQ(num, pieces.size() - 1) << "Mismatching number of tokens: " << range_text;
+
+    std::vector<Range> pairs;
+    for (size_t i = 0; i < num; i += 2) {
+      size_t first;
+      CHECK(android::base::ParseUint(pieces[i + 1], &first, static_cast<size_t>(INT_MAX)));
+      size_t second;
+      CHECK(android::base::ParseUint(pieces[i + 2], &second, static_cast<size_t>(INT_MAX)));
+
+      pairs.emplace_back(first, second);
+    }
+
+    return RangeSet(std::move(pairs));
+  }
+
+  // Get the block number for the i-th (starting from 0) block in the RangeSet.
+  size_t GetBlockNumber(size_t idx) const {
+    CHECK_LT(idx, blocks_) << "Out of bound index " << idx << " (total blocks: " << blocks_ << ")";
+
+    for (const auto& range : ranges_) {
+      if (idx < range.second - range.first) {
+        return range.first + idx;
+      }
+      idx -= (range.second - range.first);
+    }
+
+    CHECK(false) << "Failed to find block number for index " << idx;
+    return 0;  // Unreachable, but to make compiler happy.
+  }
+
+  // RangeSet has half-closed half-open bounds. For example, "3,5" contains blocks 3 and 4. So "3,5"
+  // and "5,7" are not overlapped.
+  bool Overlaps(const RangeSet& other) const {
+    for (const auto& range : ranges_) {
+      size_t start = range.first;
+      size_t end = range.second;
+      for (const auto& other_range : other.ranges_) {
+        size_t other_start = other_range.first;
+        size_t other_end = other_range.second;
+        // [start, end) vs [other_start, other_end)
+        if (!(other_start >= end || start >= other_end)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  // size() gives the number of Range's in this RangeSet.
+  size_t size() const {
+    return ranges_.size();
+  }
+
+  // blocks() gives the number of all blocks in this RangeSet.
+  size_t blocks() const {
+    return blocks_;
+  }
+
+  // We provide const iterators only.
+  std::vector<Range>::const_iterator cbegin() const {
+    return ranges_.cbegin();
+  }
+
+  std::vector<Range>::const_iterator cend() const {
+    return ranges_.cend();
+  }
+
+  // Need to provide begin()/end() since range-based loop expects begin()/end().
+  std::vector<Range>::const_iterator begin() const {
+    return ranges_.cbegin();
+  }
+
+  std::vector<Range>::const_iterator end() const {
+    return ranges_.cend();
+  }
+
+  // Reverse const iterators for MoveRange().
+  std::vector<Range>::const_reverse_iterator crbegin() const {
+    return ranges_.crbegin();
+  }
+
+  std::vector<Range>::const_reverse_iterator crend() const {
+    return ranges_.crend();
+  }
+
+  const Range& operator[](size_t i) const {
+    return ranges_[i];
+  }
+
+  bool operator==(const RangeSet& other) const {
+    // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5".
+    return (ranges_ == other.ranges_);
+  }
+
+  bool operator!=(const RangeSet& other) const {
+    return ranges_ != other.ranges_;
+  }
+
+ private:
+  // Actual limit for each value and the total number are both INT_MAX.
+  std::vector<Range> ranges_;
+  size_t blocks_;
+};
diff --git a/updater/install.cpp b/updater/install.cpp
index 84cf5d6..c5f9a89 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -181,8 +181,8 @@
 
   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));
+    uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(),
+             strerror(errno));
     return StringValue("");
   }
 
@@ -231,12 +231,12 @@
   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());
+    uiPrintf(state, "Failed to unmount %s: No such volume", 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));
+      uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno));
     }
   }
 
@@ -317,9 +317,11 @@
     std::string num_sectors = std::to_string(size / 512);
 
     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));
+    const char* f2fs_argv[] = {
+      "mkfs.f2fs", "-t", "-d1", location.c_str(), (size < 512) ? nullptr : num_sectors.c_str(),
+      nullptr
+    };
+    int status = exec_cmd(f2fs_path, const_cast<char**>(f2fs_argv));
     if (status != 0) {
       LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location;
       return StringValue("");
@@ -699,15 +701,15 @@
   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.
+// 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, 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);
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
   }
 
-  std::string buffer = android::base::Join(args, "") + "\n";
+  std::string buffer = android::base::Join(args, "");
   uiPrint(state, buffer);
   return StringValue(buffer);
 }
diff --git a/updater/updater.cpp b/updater/updater.cpp
index c09e267..1be8b60 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -89,7 +89,7 @@
 
   const char* package_filename = argv[3];
   MemMapping map;
-  if (sysMapFile(package_filename, &map) != 0) {
+  if (!map.MapFile(package_filename)) {
     LOG(ERROR) << "failed to map package " << argv[3];
     return 3;
   }
@@ -193,13 +193,15 @@
       }
     }
 
-    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);
-      }
+    // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
+    // a more specific code has been set in errmsg.
+    if (state.error_code == kNoError) {
+      state.error_code = kScriptExecutionFailure;
+    }
+    fprintf(cmd_pipe, "log error: %d\n", state.error_code);
+    // Cause code should provide additional information about the abort.
+    if (state.cause_code != kNoCause) {
+      fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
     }
 
     if (updater_info.package_zip) {
@@ -213,7 +215,6 @@
   if (updater_info.package_zip) {
     CloseArchive(updater_info.package_zip);
   }
-  sysReleaseMap(&map);
 
   return 0;
 }