Merge "applypatch: Drop the support for patching non-EMMC targets."
diff --git a/Android.mk b/Android.mk
index f8e5ac2..58b8a22 100644
--- a/Android.mk
+++ b/Android.mk
@@ -144,15 +144,12 @@
 # libverifier (static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_MODULE := libverifier
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := \
     asn1_decoder.cpp \
-    verifier.cpp \
-    ui.cpp
+    verifier.cpp
 LOCAL_STATIC_LIBRARIES := \
-    libminui \
     libcrypto_utils \
     libcrypto \
     libbase
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index 8be5c36..a7412d2 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -21,8 +21,7 @@
     applypatch.cpp \
     bspatch.cpp \
     freecache.cpp \
-    imgpatch.cpp \
-    utils.cpp
+    imgpatch.cpp
 LOCAL_MODULE := libapplypatch
 LOCAL_MODULE_TAGS := eng
 LOCAL_C_INCLUDES := \
@@ -46,8 +45,7 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := \
     bspatch.cpp \
-    imgpatch.cpp \
-    utils.cpp
+    imgpatch.cpp
 LOCAL_MODULE := libimgpatch
 LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include \
@@ -56,6 +54,7 @@
 LOCAL_STATIC_LIBRARIES := \
     libcrypto \
     libbspatch \
+    libbase \
     libbz \
     libz
 LOCAL_CFLAGS := \
@@ -68,8 +67,7 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := \
     bspatch.cpp \
-    imgpatch.cpp \
-    utils.cpp
+    imgpatch.cpp
 LOCAL_MODULE := libimgpatch
 LOCAL_MODULE_HOST_OS := linux
 LOCAL_C_INCLUDES := \
@@ -79,6 +77,7 @@
 LOCAL_STATIC_LIBRARIES := \
     libcrypto \
     libbspatch \
+    libbase \
     libbz \
     libz
 LOCAL_CFLAGS := \
@@ -123,9 +122,7 @@
 LOCAL_CFLAGS := -Werror
 include $(BUILD_EXECUTABLE)
 
-libimgdiff_src_files := \
-    imgdiff.cpp \
-    utils.cpp
+libimgdiff_src_files := imgdiff.cpp
 
 # libbsdiff is compiled with -D_FILE_OFFSET_BITS=64.
 libimgdiff_cflags := \
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index fba74e8..41d73ab 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -145,12 +145,22 @@
 #include <bsdiff.h>
 #include <zlib.h>
 
-#include "utils.h"
-
 using android::base::get_unaligned;
 
 static constexpr auto BUFFER_SIZE = 0x8000;
 
+// If we use this function to write the offset and length (type size_t), their values should not
+// exceed 2^63; because the signed bit will be casted away.
+static inline bool Write8(int fd, int64_t value) {
+  return android::base::WriteFully(fd, &value, sizeof(int64_t));
+}
+
+// Similarly, the value should not exceed 2^31 if we are casting from size_t (e.g. target chunk
+// size).
+static inline bool Write4(int fd, int32_t value) {
+  return android::base::WriteFully(fd, &value, sizeof(int32_t));
+}
+
 class ImageChunk {
  public:
   static constexpr auto WINDOWBITS = -15;  // 32kb window; negative to indicate a raw stream.
@@ -163,11 +173,12 @@
         start_(start),
         input_file_ptr_(file_content),
         raw_data_len_(raw_data_len),
-        entry_name_(""),
         compress_level_(6),
         source_start_(0),
         source_len_(0),
-        source_uncompressed_len_(0) {}
+        source_uncompressed_len_(0) {
+    CHECK(file_content != nullptr) << "input file container can't be nullptr";
+  }
 
   int GetType() const {
     return type_;
@@ -199,7 +210,8 @@
   }
 
   size_t GetHeaderSize(size_t patch_size) const;
-  size_t WriteHeaderToFile(FILE* f, const std::vector<uint8_t> patch, size_t offset);
+  // Return the offset of the next patch into the patch data.
+  size_t WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset);
 
   /*
    * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob
@@ -222,9 +234,9 @@
   void MergeAdjacentNormal(const ImageChunk& other);
 
  private:
-  int type_;                                   // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
-  size_t start_;                               // offset of chunk in the original input file
-  const std::vector<uint8_t>* input_file_ptr_; // pointer to the full content of original input file
+  int type_;                                    // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
+  size_t start_;                                // offset of chunk in the original input file
+  const std::vector<uint8_t>* input_file_ptr_;  // ptr to the full content of original input file
   size_t raw_data_len_;
 
   // --- for CHUNK_DEFLATE chunks only: ---
@@ -280,11 +292,11 @@
 }
 
 void ImageChunk::SetEntryName(std::string entryname) {
-  entry_name_ = entryname;
+  entry_name_ = std::move(entryname);
 }
 
 void ImageChunk::SetUncompressedData(std::vector<uint8_t> data) {
-  uncompressed_data_ = data;
+  uncompressed_data_ = std::move(data);
 }
 
 bool ImageChunk::SetBonusData(const std::vector<uint8_t>& bonus_data) {
@@ -295,7 +307,7 @@
   return true;
 }
 
-// Convert CHUNK_NORMAL & CHUNK_DEFLATE to CHUNK_RAW if the terget size is
+// Convert CHUNK_NORMAL & CHUNK_DEFLATE to CHUNK_RAW if the target size is
 // smaller. Also take the header size into account during size comparison.
 bool ImageChunk::ChangeChunkToRaw(size_t patch_size) {
   if (type_ == CHUNK_RAW) {
@@ -310,6 +322,7 @@
 void ImageChunk::ChangeDeflateChunkToNormal() {
   if (type_ != CHUNK_DEFLATE) return;
   type_ = CHUNK_NORMAL;
+  entry_name_.clear();
   uncompressed_data_.clear();
 }
 
@@ -317,7 +330,7 @@
 // header_type    4 bytes
 // CHUNK_NORMAL   8*3 = 24 bytes
 // CHUNK_DEFLATE  8*5 + 4*5 = 60 bytes
-// CHUNK_RAW      4 bytes
+// CHUNK_RAW      4 bytes + patch_size
 size_t ImageChunk::GetHeaderSize(size_t patch_size) const {
   switch (type_) {
     case CHUNK_NORMAL:
@@ -327,43 +340,43 @@
     case CHUNK_RAW:
       return 4 + 4 + patch_size;
     default:
-      printf("unexpected chunk type: %d\n", type_);  // should not reach here.
-      CHECK(false);
+      CHECK(false) << "unexpected chunk type: " << type_;  // Should not reach here.
       return 0;
   }
 }
 
-size_t ImageChunk::WriteHeaderToFile(FILE* f, const std::vector<uint8_t> patch, size_t offset) {
-  Write4(type_, f);
+size_t ImageChunk::WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) {
+  Write4(fd, type_);
   switch (type_) {
     case CHUNK_NORMAL:
       printf("normal   (%10zu, %10zu)  %10zu\n", start_, raw_data_len_, patch.size());
-      Write8(source_start_, f);
-      Write8(source_len_, f);
-      Write8(offset, f);
+      Write8(fd, static_cast<int64_t>(source_start_));
+      Write8(fd, static_cast<int64_t>(source_len_));
+      Write8(fd, static_cast<int64_t>(offset));
       return offset + patch.size();
     case CHUNK_DEFLATE:
       printf("deflate  (%10zu, %10zu)  %10zu  %s\n", start_, raw_data_len_, patch.size(),
              entry_name_.c_str());
-      Write8(source_start_, f);
-      Write8(source_len_, f);
-      Write8(offset, f);
-      Write8(source_uncompressed_len_, f);
-      Write8(uncompressed_data_.size(), f);
-      Write4(compress_level_, f);
-      Write4(METHOD, f);
-      Write4(WINDOWBITS, f);
-      Write4(MEMLEVEL, f);
-      Write4(STRATEGY, f);
+      Write8(fd, static_cast<int64_t>(source_start_));
+      Write8(fd, static_cast<int64_t>(source_len_));
+      Write8(fd, static_cast<int64_t>(offset));
+      Write8(fd, static_cast<int64_t>(source_uncompressed_len_));
+      Write8(fd, static_cast<int64_t>(uncompressed_data_.size()));
+      Write4(fd, compress_level_);
+      Write4(fd, METHOD);
+      Write4(fd, WINDOWBITS);
+      Write4(fd, MEMLEVEL);
+      Write4(fd, STRATEGY);
       return offset + patch.size();
     case CHUNK_RAW:
       printf("raw      (%10zu, %10zu)\n", start_, raw_data_len_);
-      Write4(patch.size(), f);
-      fwrite(patch.data(), 1, patch.size(), f);
+      Write4(fd, static_cast<int32_t>(patch.size()));
+      if (!android::base::WriteFully(fd, patch.data(), patch.size())) {
+        CHECK(false) << "failed to write " << patch.size() <<" bytes patch";
+      }
       return offset;
     default:
-      printf("unexpected chunk type: %d\n", type_);
-      CHECK(false);
+      CHECK(false) << "unexpected chunk type: " << type_;
       return offset;
   }
 }
@@ -480,20 +493,21 @@
 
 static bool ReadZip(const char* filename, std::vector<ImageChunk>* chunks,
                     std::vector<uint8_t>* zip_file, bool include_pseudo_chunk) {
-  CHECK(zip_file != nullptr);
+  CHECK(chunks != nullptr && zip_file != nullptr);
+
+  android::base::unique_fd fd(open(filename, O_RDONLY));
+  if (fd == -1) {
+    printf("failed to open \"%s\" %s\n", filename, strerror(errno));
+    return false;
+  }
   struct stat st;
-  if (stat(filename, &st) != 0) {
+  if (fstat(fd, &st) != 0) {
     printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
     return false;
   }
 
   size_t sz = static_cast<size_t>(st.st_size);
   zip_file->resize(sz);
-  android::base::unique_fd fd(open(filename, O_RDONLY));
-  if (fd == -1) {
-    printf("failed to open \"%s\" %s\n", filename, strerror(errno));
-    return false;
-  }
   if (!android::base::ReadFully(fd, zip_file->data(), sz)) {
     printf("failed to read \"%s\" %s\n", filename, strerror(errno));
     return false;
@@ -596,20 +610,21 @@
 // Read the given file and break it up into chunks, and putting the data in to a vector.
 static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
                       std::vector<uint8_t>* img) {
-  CHECK(img != nullptr);
+  CHECK(chunks != nullptr && img != nullptr);
+
+  android::base::unique_fd fd(open(filename, O_RDONLY));
+  if (fd == -1) {
+    printf("failed to open \"%s\" %s\n", filename, strerror(errno));
+    return false;
+  }
   struct stat st;
-  if (stat(filename, &st) != 0) {
+  if (fstat(fd, &st) != 0) {
     printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
     return false;
   }
 
   size_t sz = static_cast<size_t>(st.st_size);
   img->resize(sz);
-  android::base::unique_fd fd(open(filename, O_RDONLY));
-  if (fd == -1) {
-    printf("failed to open \"%s\" %s\n", filename, strerror(errno));
-    return false;
-  }
   if (!android::base::ReadFully(fd, img->data(), sz)) {
     printf("failed to read \"%s\" %s\n", filename, strerror(errno));
     return false;
@@ -618,9 +633,8 @@
   size_t pos = 0;
 
   while (pos < sz) {
-    if (sz - pos >= 4 && img->at(pos) == 0x1f && img->at(pos + 1) == 0x8b &&
-        img->at(pos + 2) == 0x08 &&  // deflate compression
-        img->at(pos + 3) == 0x00) {  // no header flags
+    // 0x00 no header flags, 0x08 deflate compression, 0x1f8b gzip magic number
+    if (sz - pos >= 4 && get_unaligned<uint32_t>(img->data() + pos) == 0x00088b1f) {
       // 'pos' is the offset of the start of a gzip chunk.
       size_t chunk_offset = pos;
 
@@ -695,7 +709,7 @@
       // the uncompressed data.  Double-check to make sure that it
       // matches the size of the data we got when we actually did
       // the decompression.
-      size_t footer_size = Read4(img->data() + pos - 4);
+      size_t footer_size = get_unaligned<uint32_t>(img->data() + pos - 4);
       if (footer_size != body.DataLengthForPatch()) {
         printf("Error: footer size %zu != decompressed size %zu\n", footer_size,
                body.GetRawDataLength());
@@ -708,9 +722,8 @@
       // Scan forward until we find a gzip header.
       size_t data_len = 0;
       while (data_len + pos < sz) {
-        if (data_len + pos + 4 <= sz && img->at(pos + data_len) == 0x1f &&
-            img->at(pos + data_len + 1) == 0x8b && img->at(pos + data_len + 2) == 0x08 &&
-            img->at(pos + data_len + 3) == 0x00) {
+        if (data_len + pos + 4 <= sz &&
+            get_unaligned<uint32_t>(img->data() + pos + data_len) == 0x00088b1f) {
           break;
         }
         data_len++;
@@ -759,13 +772,19 @@
     return false;
   }
 
+  android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
+  if (patch_fd == -1) {
+    printf("failed to open %s: %s\n", ptemp, strerror(errno));
+    return false;
+  }
   struct stat st;
-  if (stat(ptemp, &st) != 0) {
+  if (fstat(patch_fd, &st) != 0) {
     printf("failed to stat patch file %s: %s\n", ptemp, strerror(errno));
     return false;
   }
 
   size_t sz = static_cast<size_t>(st.st_size);
+  // Change the chunk type to raw if the patch takes less space that way.
   if (tgt->ChangeChunkToRaw(sz)) {
     unlink(ptemp);
     size_t patch_size = tgt->DataLengthForPatch();
@@ -773,12 +792,6 @@
     std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
     return true;
   }
-
-  android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
-  if (patch_fd == -1) {
-    printf("failed to open %s: %s\n", ptemp, strerror(errno));
-    return false;
-  }
   patch_data->resize(sz);
   if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
     printf("failed to read \"%s\" %s\n", ptemp, strerror(errno));
@@ -845,18 +858,19 @@
 
   std::vector<uint8_t> bonus_data;
   if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
-    struct stat st;
-    if (stat(argv[2], &st) != 0) {
-      printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno));
-      return 1;
-    }
-    size_t bonus_size = st.st_size;
-    bonus_data.resize(bonus_size);
     android::base::unique_fd fd(open(argv[2], O_RDONLY));
     if (fd == -1) {
       printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno));
       return 1;
     }
+    struct stat st;
+    if (fstat(fd, &st) != 0) {
+      printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno));
+      return 1;
+    }
+
+    size_t bonus_size = st.st_size;
+    bonus_data.resize(bonus_size);
     if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) {
       printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno));
       return 1;
@@ -999,9 +1013,15 @@
       ImageChunk* src;
       if (tgt_chunks[i].GetType() == CHUNK_DEFLATE &&
           (src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks))) {
-        MakePatch(src, &tgt_chunks[i], &patch_data[i], nullptr);
+        if (!MakePatch(src, &tgt_chunks[i], &patch_data[i], nullptr)) {
+          printf("Failed to generate patch for target chunk %zu: ", i);
+          return 1;
+        }
       } else {
-        MakePatch(&src_chunks[0], &tgt_chunks[i], &patch_data[i], &bsdiff_cache);
+        if (!MakePatch(&src_chunks[0], &tgt_chunks[i], &patch_data[i], &bsdiff_cache)) {
+          printf("Failed to generate patch for target chunk %zu: ", i);
+          return 1;
+        }
       }
     } else {
       if (i == 1 && !bonus_data.empty()) {
@@ -1009,7 +1029,10 @@
         src_chunks[i].SetBonusData(bonus_data);
       }
 
-      MakePatch(&src_chunks[i], &tgt_chunks[i], &patch_data[i], nullptr);
+      if (!MakePatch(&src_chunks[i], &tgt_chunks[i], &patch_data[i], nullptr)) {
+        printf("Failed to generate patch for target chunk %zu: ", i);
+        return 1;
+      }
     }
     printf("patch %3zu is %zu bytes (of %zu)\n", i, patch_data[i].size(),
            src_chunks[i].GetRawDataLength());
@@ -1030,28 +1053,32 @@
 
   size_t offset = total_header_size;
 
-  FILE* f = fopen(argv[3], "wb");
-  if (f == nullptr) {
+  android::base::unique_fd patch_fd(open(argv[3], O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+  if (patch_fd == -1) {
     printf("failed to open \"%s\": %s\n", argv[3], strerror(errno));
+    return 1;
   }
 
   // Write out the headers.
-
-  fwrite("IMGDIFF2", 1, 8, f);
-  Write4(static_cast<int32_t>(tgt_chunks.size()), f);
+  if (!android::base::WriteStringToFd("IMGDIFF2", patch_fd)) {
+    printf("failed to write \"IMGDIFF2\" to \"%s\": %s\n", argv[3], strerror(errno));
+    return 1;
+  }
+  Write4(patch_fd, static_cast<int32_t>(tgt_chunks.size()));
   for (size_t i = 0; i < tgt_chunks.size(); ++i) {
     printf("chunk %zu: ", i);
-    offset = tgt_chunks[i].WriteHeaderToFile(f, patch_data[i], offset);
+    offset = tgt_chunks[i].WriteHeaderToFd(patch_fd, patch_data[i], offset);
   }
 
   // Append each chunk's bsdiff patch, in order.
   for (size_t i = 0; i < tgt_chunks.size(); ++i) {
     if (tgt_chunks[i].GetType() != CHUNK_RAW) {
-      fwrite(patch_data[i].data(), 1, patch_data[i].size(), f);
+      if (!android::base::WriteFully(patch_fd, patch_data[i].data(), patch_data[i].size())) {
+        CHECK(false) << "failed to write " << patch_data[i].size() << " bytes patch for chunk "
+                     << i;
+      }
     }
   }
 
-  fclose(f);
-
   return 0;
 }
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index 8f4a2a4..adcc61f 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -31,10 +31,17 @@
 
 #include <applypatch/applypatch.h>
 #include <applypatch/imgdiff.h>
+#include <android-base/memory.h>
 #include <openssl/sha.h>
 #include <zlib.h>
 
-#include "utils.h"
+static inline int64_t Read8(const void *address) {
+  return android::base::get_unaligned<int64_t>(address);
+}
+
+static inline int32_t Read4(const void *address) {
+  return android::base::get_unaligned<int32_t>(address);
+}
 
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const unsigned char* patch_data, ssize_t patch_size,
@@ -86,9 +93,9 @@
         return -1;
       }
 
-      size_t src_start = Read8(normal_header);
-      size_t src_len = Read8(normal_header + 8);
-      size_t patch_offset = Read8(normal_header + 16);
+      size_t src_start = static_cast<size_t>(Read8(normal_header));
+      size_t src_len = static_cast<size_t>(Read8(normal_header + 8));
+      size_t patch_offset = static_cast<size_t>(Read8(normal_header + 16));
 
       if (src_start + src_len > static_cast<size_t>(old_size)) {
         printf("source data too short\n");
@@ -125,11 +132,11 @@
         return -1;
       }
 
-      size_t src_start = Read8(deflate_header);
-      size_t src_len = Read8(deflate_header + 8);
-      size_t patch_offset = Read8(deflate_header + 16);
-      size_t expanded_len = Read8(deflate_header + 24);
-      size_t target_len = Read8(deflate_header + 32);
+      size_t src_start = static_cast<size_t>(Read8(deflate_header));
+      size_t src_len = static_cast<size_t>(Read8(deflate_header + 8));
+      size_t patch_offset = static_cast<size_t>(Read8(deflate_header + 16));
+      size_t expanded_len = static_cast<size_t>(Read8(deflate_header + 24));
+      size_t target_len = static_cast<size_t>(Read8(deflate_header + 32));
       int level = Read4(deflate_header + 40);
       int method = Read4(deflate_header + 44);
       int windowBits = Read4(deflate_header + 48);
diff --git a/applypatch/utils.cpp b/applypatch/utils.cpp
deleted file mode 100644
index 450dc8d..0000000
--- a/applypatch/utils.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-
-#include "utils.h"
-
-/** Write a 4-byte value to f in little-endian order. */
-void Write4(int value, FILE* f) {
-  fputc(value & 0xff, f);
-  fputc((value >> 8) & 0xff, f);
-  fputc((value >> 16) & 0xff, f);
-  fputc((value >> 24) & 0xff, f);
-}
-
-/** Write an 8-byte value to f in little-endian order. */
-void Write8(int64_t value, FILE* f) {
-  fputc(value & 0xff, f);
-  fputc((value >> 8) & 0xff, f);
-  fputc((value >> 16) & 0xff, f);
-  fputc((value >> 24) & 0xff, f);
-  fputc((value >> 32) & 0xff, f);
-  fputc((value >> 40) & 0xff, f);
-  fputc((value >> 48) & 0xff, f);
-  fputc((value >> 56) & 0xff, f);
-}
-
-int Read2(const void* pv) {
-    const unsigned char* p = reinterpret_cast<const unsigned char*>(pv);
-    return (int)(((unsigned int)p[1] << 8) |
-                 (unsigned int)p[0]);
-}
-
-int Read4(const void* pv) {
-    const unsigned char* p = reinterpret_cast<const unsigned char*>(pv);
-    return (int)(((unsigned int)p[3] << 24) |
-                 ((unsigned int)p[2] << 16) |
-                 ((unsigned int)p[1] << 8) |
-                 (unsigned int)p[0]);
-}
-
-int64_t Read8(const void* pv) {
-    const unsigned char* p = reinterpret_cast<const unsigned char*>(pv);
-    return (int64_t)(((uint64_t)p[7] << 56) |
-                       ((uint64_t)p[6] << 48) |
-                       ((uint64_t)p[5] << 40) |
-                       ((uint64_t)p[4] << 32) |
-                       ((uint64_t)p[3] << 24) |
-                       ((uint64_t)p[2] << 16) |
-                       ((uint64_t)p[1] << 8) |
-                       (uint64_t)p[0]);
-}
diff --git a/applypatch/utils.h b/applypatch/utils.h
deleted file mode 100644
index c7c8e90..0000000
--- a/applypatch/utils.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _BUILD_TOOLS_APPLYPATCH_UTILS_H
-#define _BUILD_TOOLS_APPLYPATCH_UTILS_H
-
-#include <inttypes.h>
-#include <stdio.h>
-
-// Read and write little-endian values of various sizes.
-
-void Write4(int value, FILE* f);
-void Write8(int64_t value, FILE* f);
-int Read2(const void* p);
-int Read4(const void* p);
-int64_t Read8(const void* p);
-
-#endif //  _BUILD_TOOLS_APPLYPATCH_UTILS_H
diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp
index e7aef78..285214f 100644
--- a/asn1_decoder.cpp
+++ b/asn1_decoder.cpp
@@ -14,178 +14,145 @@
  * limitations under the License.
  */
 
-#include <malloc.h>
-#include <stdint.h>
-#include <string.h>
-
 #include "asn1_decoder.h"
 
+#include <stdint.h>
 
-typedef struct asn1_context {
-    size_t length;
-    uint8_t* p;
-    int app_type;
-} asn1_context_t;
-
-
-static const int kMaskConstructed = 0xE0;
-static const int kMaskTag = 0x7F;
-static const int kMaskAppType = 0x1F;
-
-static const int kTagOctetString = 0x04;
-static const int kTagOid = 0x06;
-static const int kTagSequence = 0x30;
-static const int kTagSet = 0x31;
-static const int kTagConstructed = 0xA0;
-
-asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) {
-    asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t));
-    if (ctx == NULL) {
-        return NULL;
-    }
-    ctx->p = buffer;
-    ctx->length = length;
-    return ctx;
+int asn1_context::peek_byte() const {
+  if (length_ == 0) {
+    return -1;
+  }
+  return *p_;
 }
 
-void asn1_context_free(asn1_context_t* ctx) {
-    free(ctx);
+int asn1_context::get_byte() {
+  if (length_ == 0) {
+    return -1;
+  }
+
+  int byte = *p_;
+  p_++;
+  length_--;
+  return byte;
 }
 
-static inline int peek_byte(asn1_context_t* ctx) {
-    if (ctx->length <= 0) {
-        return -1;
-    }
-    return *ctx->p;
+bool asn1_context::skip_bytes(size_t num_skip) {
+  if (length_ < num_skip) {
+    return false;
+  }
+  p_ += num_skip;
+  length_ -= num_skip;
+  return true;
 }
 
-static inline int get_byte(asn1_context_t* ctx) {
-    if (ctx->length <= 0) {
-        return -1;
-    }
-    int byte = *ctx->p;
-    ctx->p++;
-    ctx->length--;
-    return byte;
-}
-
-static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) {
-    if (ctx->length < num_skip) {
-        return false;
-    }
-    ctx->p += num_skip;
-    ctx->length -= num_skip;
+bool asn1_context::decode_length(size_t* out_len) {
+  int num_octets = get_byte();
+  if (num_octets == -1) {
+    return false;
+  }
+  if ((num_octets & 0x80) == 0x00) {
+    *out_len = num_octets;
     return true;
-}
-
-static bool decode_length(asn1_context_t* ctx, size_t* out_len) {
-    int num_octets = get_byte(ctx);
-    if (num_octets == -1) {
-        return false;
+  }
+  num_octets &= kMaskTag;
+  if (static_cast<size_t>(num_octets) >= sizeof(size_t)) {
+    return false;
+  }
+  size_t length = 0;
+  for (int i = 0; i < num_octets; ++i) {
+    int byte = get_byte();
+    if (byte == -1) {
+      return false;
     }
-    if ((num_octets & 0x80) == 0x00) {
-        *out_len = num_octets;
-        return 1;
-    }
-    num_octets &= kMaskTag;
-    if ((size_t)num_octets >= sizeof(size_t)) {
-        return false;
-    }
-    size_t length = 0;
-    for (int i = 0; i < num_octets; ++i) {
-        int byte = get_byte(ctx);
-        if (byte == -1) {
-            return false;
-        }
-        length <<= 8;
-        length += byte;
-    }
-    *out_len = length;
-    return true;
+    length <<= 8;
+    length += byte;
+  }
+  *out_len = length;
+  return true;
 }
 
 /**
  * Returns the constructed type and advances the pointer. E.g. A0 -> 0
  */
-asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) {
-    int type = get_byte(ctx);
-    if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
-        return NULL;
-    }
+asn1_context* asn1_context::asn1_constructed_get() {
+  int type = get_byte();
+  if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  asn1_context* app_ctx = new asn1_context(p_, length);
+  app_ctx->app_type_ = type & kMaskAppType;
+  return app_ctx;
+}
+
+bool asn1_context::asn1_constructed_skip_all() {
+  int byte = peek_byte();
+  while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
+    skip_bytes(1);
     size_t length;
-    if (!decode_length(ctx, &length) || length > ctx->length) {
-        return NULL;
+    if (!decode_length(&length) || !skip_bytes(length)) {
+      return false;
     }
-    asn1_context_t* app_ctx = asn1_context_new(ctx->p, length);
-    app_ctx->app_type = type & kMaskAppType;
-    return app_ctx;
+    byte = peek_byte();
+  }
+  return byte != -1;
 }
 
-bool asn1_constructed_skip_all(asn1_context_t* ctx) {
-    int byte = peek_byte(ctx);
-    while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
-        skip_bytes(ctx, 1);
-        size_t length;
-        if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
-            return false;
-        }
-        byte = peek_byte(ctx);
-    }
-    return byte != -1;
+int asn1_context::asn1_constructed_type() const {
+  return app_type_;
 }
 
-int asn1_constructed_type(asn1_context_t* ctx) {
-    return ctx->app_type;
+asn1_context* asn1_context::asn1_sequence_get() {
+  if ((get_byte() & kMaskTag) != kTagSequence) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  return new asn1_context(p_, length);
 }
 
-asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) {
-    if ((get_byte(ctx) & kMaskTag) != kTagSequence) {
-        return NULL;
-    }
-    size_t length;
-    if (!decode_length(ctx, &length) || length > ctx->length) {
-        return NULL;
-    }
-    return asn1_context_new(ctx->p, length);
+asn1_context* asn1_context::asn1_set_get() {
+  if ((get_byte() & kMaskTag) != kTagSet) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  return new asn1_context(p_, length);
 }
 
-asn1_context_t* asn1_set_get(asn1_context_t* ctx) {
-    if ((get_byte(ctx) & kMaskTag) != kTagSet) {
-        return NULL;
-    }
-    size_t length;
-    if (!decode_length(ctx, &length) || length > ctx->length) {
-        return NULL;
-    }
-    return asn1_context_new(ctx->p, length);
+bool asn1_context::asn1_sequence_next() {
+  size_t length;
+  if (get_byte() == -1 || !decode_length(&length) || !skip_bytes(length)) {
+    return false;
+  }
+  return true;
 }
 
-bool asn1_sequence_next(asn1_context_t* ctx) {
-    size_t length;
-    if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
-        return false;
-    }
-    return true;
+bool asn1_context::asn1_oid_get(const uint8_t** oid, size_t* length) {
+  if (get_byte() != kTagOid) {
+    return false;
+  }
+  if (!decode_length(length) || *length == 0 || *length > length_) {
+    return false;
+  }
+  *oid = p_;
+  return true;
 }
 
-bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) {
-    if (get_byte(ctx) != kTagOid) {
-        return false;
-    }
-    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
-        return false;
-    }
-    *oid = ctx->p;
-    return true;
-}
-
-bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) {
-    if (get_byte(ctx) != kTagOctetString) {
-        return false;
-    }
-    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
-        return false;
-    }
-    *octet_string = ctx->p;
-    return true;
+bool asn1_context::asn1_octet_string_get(const uint8_t** octet_string, size_t* length) {
+  if (get_byte() != kTagOctetString) {
+    return false;
+  }
+  if (!decode_length(length) || *length == 0 || *length > length_) {
+    return false;
+  }
+  *octet_string = p_;
+  return true;
 }
diff --git a/asn1_decoder.h b/asn1_decoder.h
index b17141c..3e99211 100644
--- a/asn1_decoder.h
+++ b/asn1_decoder.h
@@ -14,23 +14,42 @@
  * limitations under the License.
  */
 
-
 #ifndef ASN1_DECODER_H_
 #define ASN1_DECODER_H_
 
 #include <stdint.h>
 
-typedef struct asn1_context asn1_context_t;
+class asn1_context {
+ public:
+  asn1_context(const uint8_t* buffer, size_t length) : p_(buffer), length_(length), app_type_(0) {}
+  int asn1_constructed_type() const;
+  asn1_context* asn1_constructed_get();
+  bool asn1_constructed_skip_all();
+  asn1_context* asn1_sequence_get();
+  asn1_context* asn1_set_get();
+  bool asn1_sequence_next();
+  bool asn1_oid_get(const uint8_t** oid, size_t* length);
+  bool asn1_octet_string_get(const uint8_t** octet_string, size_t* length);
 
-asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length);
-void asn1_context_free(asn1_context_t* ctx);
-asn1_context_t* asn1_constructed_get(asn1_context_t* ctx);
-bool asn1_constructed_skip_all(asn1_context_t* ctx);
-int asn1_constructed_type(asn1_context_t* ctx);
-asn1_context_t* asn1_sequence_get(asn1_context_t* ctx);
-asn1_context_t* asn1_set_get(asn1_context_t* ctx);
-bool asn1_sequence_next(asn1_context_t* seq);
-bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length);
-bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length);
+ private:
+  static constexpr int kMaskConstructed = 0xE0;
+  static constexpr int kMaskTag = 0x7F;
+  static constexpr int kMaskAppType = 0x1F;
+
+  static constexpr int kTagOctetString = 0x04;
+  static constexpr int kTagOid = 0x06;
+  static constexpr int kTagSequence = 0x30;
+  static constexpr int kTagSet = 0x31;
+  static constexpr int kTagConstructed = 0xA0;
+
+  int peek_byte() const;
+  int get_byte();
+  bool skip_bytes(size_t num_skip);
+  bool decode_length(size_t* out_len);
+
+  const uint8_t* p_;
+  size_t length_;
+  int app_type_;
+};
 
 #endif /* ASN1_DECODER_H_ */
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index d8086be..d17e055 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -19,6 +19,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
+#include <unistd.h>
 
 #include <string>
 #include <vector>
@@ -30,8 +31,13 @@
 #include <fs_mgr.h>
 
 static std::string get_misc_blk_device(std::string* err) {
-  std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
-                                                             fs_mgr_free_fstab);
+  std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(nullptr, fs_mgr_free_fstab);
+  // Use different fstab paths for normal boot and recovery boot, respectively
+  if (access("/sbin/recovery", F_OK) == 0) {
+    fstab.reset(fs_mgr_read_fstab_with_dt("/etc/recovery.fstab"));
+  } else {
+    fstab.reset(fs_mgr_read_fstab_default());
+  }
   if (!fstab) {
     *err = "failed to read default fstab";
     return "";
diff --git a/edify/edify_parser.cpp b/edify/edify_parser.cpp
index 908fcf1..f1b5628 100644
--- a/edify/edify_parser.cpp
+++ b/edify/edify_parser.cpp
@@ -27,18 +27,19 @@
 #include <errno.h>
 #include <stdio.h>
 
+#include <memory>
 #include <string>
 
 #include <android-base/file.h>
 
 #include "expr.h"
 
-static void ExprDump(int depth, const Expr* n, const std::string& script) {
+static void ExprDump(int depth, const std::unique_ptr<Expr>& n, const std::string& script) {
     printf("%*s", depth*2, "");
     printf("%s %p (%d-%d) \"%s\"\n",
-           n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end,
+           n->name.c_str(), n->fn, n->start, n->end,
            script.substr(n->start, n->end - n->start).c_str());
-    for (int i = 0; i < n->argc; ++i) {
+    for (size_t i = 0; i < n->argv.size(); ++i) {
         ExprDump(depth+1, n->argv[i], script);
     }
 }
@@ -57,7 +58,7 @@
         return 1;
     }
 
-    Expr* root;
+    std::unique_ptr<Expr> root;
     int error_count = 0;
     int error = parse_string(buffer.data(), &root, &error_count);
     printf("parse returned %d; %d errors encountered\n", error, error_count);
diff --git a/edify/expr.cpp b/edify/expr.cpp
index 329cf3a..54ab332 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -40,12 +40,12 @@
     return !s.empty();
 }
 
-bool Evaluate(State* state, Expr* expr, std::string* result) {
+bool Evaluate(State* state, const std::unique_ptr<Expr>& expr, std::string* result) {
     if (result == nullptr) {
         return false;
     }
 
-    std::unique_ptr<Value> v(expr->fn(expr->name, state, expr->argc, expr->argv));
+    std::unique_ptr<Value> v(expr->fn(expr->name.c_str(), state, expr->argv));
     if (!v) {
         return false;
     }
@@ -58,8 +58,8 @@
     return true;
 }
 
-Value* EvaluateValue(State* state, Expr* expr) {
-    return expr->fn(expr->name, state, expr->argc, expr->argv);
+Value* EvaluateValue(State* state, const std::unique_ptr<Expr>& expr) {
+    return expr->fn(expr->name.c_str(), state, expr->argv);
 }
 
 Value* StringValue(const char* str) {
@@ -73,12 +73,12 @@
     return StringValue(str.c_str());
 }
 
-Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc == 0) {
+Value* ConcatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.empty()) {
         return StringValue("");
     }
     std::string result;
-    for (int i = 0; i < argc; ++i) {
+    for (size_t i = 0; i < argv.size(); ++i) {
         std::string str;
         if (!Evaluate(state, argv[i], &str)) {
             return nullptr;
@@ -89,8 +89,8 @@
     return StringValue(result);
 }
 
-Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2 && argc != 3) {
+Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2 && argv.size() != 3) {
         state->errmsg = "ifelse expects 2 or 3 arguments";
         return nullptr;
     }
@@ -102,16 +102,16 @@
 
     if (!cond.empty()) {
         return EvaluateValue(state, argv[1]);
-    } else if (argc == 3) {
+    } else if (argv.size() == 3) {
         return EvaluateValue(state, argv[2]);
     }
 
     return StringValue("");
 }
 
-Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string msg;
-    if (argc > 0 && Evaluate(state, argv[0], &msg)) {
+    if (!argv.empty() && Evaluate(state, argv[0], &msg)) {
         state->errmsg = msg;
     } else {
         state->errmsg = "called abort()";
@@ -119,8 +119,8 @@
     return nullptr;
 }
 
-Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]) {
-    for (int i = 0; i < argc; ++i) {
+Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    for (size_t i = 0; i < argv.size(); ++i) {
         std::string result;
         if (!Evaluate(state, argv[i], &result)) {
             return nullptr;
@@ -134,7 +134,7 @@
     return StringValue("");
 }
 
-Value* SleepFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* SleepFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string val;
     if (!Evaluate(state, argv[0], &val)) {
         return nullptr;
@@ -149,8 +149,8 @@
     return StringValue(val);
 }
 
-Value* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) {
-    for (int i = 0; i < argc; ++i) {
+Value* StdoutFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    for (size_t i = 0; i < argv.size(); ++i) {
         std::string v;
         if (!Evaluate(state, argv[i], &v)) {
             return nullptr;
@@ -161,7 +161,7 @@
 }
 
 Value* LogicalAndFn(const char* name, State* state,
-                   int argc, Expr* argv[]) {
+                    const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string left;
     if (!Evaluate(state, argv[0], &left)) {
         return nullptr;
@@ -174,7 +174,7 @@
 }
 
 Value* LogicalOrFn(const char* name, State* state,
-                   int argc, Expr* argv[]) {
+                   const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string left;
     if (!Evaluate(state, argv[0], &left)) {
         return nullptr;
@@ -187,7 +187,7 @@
 }
 
 Value* LogicalNotFn(const char* name, State* state,
-                    int argc, Expr* argv[]) {
+                    const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string val;
     if (!Evaluate(state, argv[0], &val)) {
         return nullptr;
@@ -197,7 +197,7 @@
 }
 
 Value* SubstringFn(const char* name, State* state,
-                   int argc, Expr* argv[]) {
+                   const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string needle;
     if (!Evaluate(state, argv[0], &needle)) {
         return nullptr;
@@ -212,7 +212,7 @@
     return StringValue(result);
 }
 
-Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string left;
     if (!Evaluate(state, argv[0], &left)) {
         return nullptr;
@@ -226,7 +226,8 @@
     return StringValue(result);
 }
 
-Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* InequalityFn(const char* name, State* state,
+                    const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string left;
     if (!Evaluate(state, argv[0], &left)) {
         return nullptr;
@@ -240,7 +241,7 @@
     return StringValue(result);
 }
 
-Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
     std::unique_ptr<Value> left(EvaluateValue(state, argv[0]));
     if (!left) {
         return nullptr;
@@ -248,14 +249,15 @@
     return EvaluateValue(state, argv[1]);
 }
 
-Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2) {
+Value* LessThanIntFn(const char* name, State* state,
+                     const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2) {
         state->errmsg = "less_than_int expects 2 arguments";
         return nullptr;
     }
 
     std::vector<std::string> args;
-    if (!ReadArgs(state, 2, argv, &args)) {
+    if (!ReadArgs(state, argv, &args)) {
         return nullptr;
     }
 
@@ -276,20 +278,34 @@
 }
 
 Value* GreaterThanIntFn(const char* name, State* state,
-                        int argc, Expr* argv[]) {
-    if (argc != 2) {
+                        const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2) {
         state->errmsg = "greater_than_int expects 2 arguments";
         return nullptr;
     }
 
-    Expr* temp[2];
-    temp[0] = argv[1];
-    temp[1] = argv[0];
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+        return nullptr;
+    }
 
-    return LessThanIntFn(name, state, 2, temp);
+    // Parse up to at least long long or 64-bit integers.
+    int64_t l_int;
+    if (!android::base::ParseInt(args[0].c_str(), &l_int)) {
+        state->errmsg = "failed to parse int in " + args[0];
+        return nullptr;
+    }
+
+    int64_t r_int;
+    if (!android::base::ParseInt(args[1].c_str(), &r_int)) {
+        state->errmsg = "failed to parse int in " + args[1];
+        return nullptr;
+    }
+
+    return StringValue(l_int > r_int ? "t" : "");
 }
 
-Value* Literal(const char* name, State* state, int argc, Expr* argv[]) {
+Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
     return StringValue(name);
 }
 
@@ -329,14 +345,22 @@
 //   convenience methods for functions
 // -----------------------------------------------------------------
 
-// Evaluate the expressions in argv, and put the results of strings in
-// args. If any expression evaluates to nullptr, free the rest and return
-// false. Return true on success.
-bool ReadArgs(State* state, int argc, Expr* argv[], std::vector<std::string>* args) {
+// Evaluate the expressions in argv, and put the results of strings in args. If any expression
+// evaluates to nullptr, return false. Return true on success.
+bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+              std::vector<std::string>* args) {
+    return ReadArgs(state, argv, args, 0, argv.size());
+}
+
+bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+              std::vector<std::string>* args, size_t start, size_t len) {
     if (args == nullptr) {
         return false;
     }
-    for (int i = 0; i < argc; ++i) {
+    if (start + len > argv.size()) {
+        return false;
+    }
+    for (size_t i = start; i < start + len; ++i) {
         std::string var;
         if (!Evaluate(state, argv[i], &var)) {
             args->clear();
@@ -347,15 +371,22 @@
     return true;
 }
 
-// Evaluate the expressions in argv, and put the results of Value* in
-// args. If any expression evaluate to nullptr, free the rest and return
-// false. Return true on success.
-bool ReadValueArgs(State* state, int argc, Expr* argv[],
+// Evaluate the expressions in argv, and put the results of Value* in args. If any expression
+// evaluate to nullptr, return false. Return true on success.
+bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
                    std::vector<std::unique_ptr<Value>>* args) {
+    return ReadValueArgs(state, argv, args, 0, argv.size());
+}
+
+bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+                   std::vector<std::unique_ptr<Value>>* args, size_t start, size_t len) {
     if (args == nullptr) {
         return false;
     }
-    for (int i = 0; i < argc; ++i) {
+    if (len == 0 || start + len > argv.size()) {
+        return false;
+    }
+    for (size_t i = start; i < start + len; ++i) {
         std::unique_ptr<Value> v(EvaluateValue(state, argv[i]));
         if (!v) {
             args->clear();
diff --git a/edify/expr.h b/edify/expr.h
index 911adbc..4838d20 100644
--- a/edify/expr.h
+++ b/edify/expr.h
@@ -18,7 +18,10 @@
 #define _EXPRESSION_H
 
 #include <unistd.h>
+
+#include <memory>
 #include <string>
+#include <vector>
 
 #include "error_code.h"
 
@@ -65,47 +68,49 @@
 
 struct Expr;
 
-using Function = Value* (*)(const char* name, State* state, int argc, Expr* argv[]);
+using Function = Value* (*)(const char* name, State* state,
+                            const std::vector<std::unique_ptr<Expr>>& argv);
 
 struct Expr {
-    Function fn;
-    const char* name;
-    int argc;
-    Expr** argv;
-    int start, end;
+  Function fn;
+  std::string name;
+  std::vector<std::unique_ptr<Expr>> argv;
+  int start, end;
+
+  Expr(Function fn, const std::string& name, int start, int end) :
+    fn(fn),
+    name(name),
+    start(start),
+    end(end) {}
 };
 
-// Take one of the Expr*s passed to the function as an argument,
-// evaluate it, return the resulting Value.  The caller takes
-// ownership of the returned Value.
-Value* EvaluateValue(State* state, Expr* expr);
+// Evaluate the input expr, return the resulting Value.
+Value* EvaluateValue(State* state, const std::unique_ptr<Expr>& expr);
 
-// Take one of the Expr*s passed to the function as an argument,
-// evaluate it, assert that it is a string, and update the result
-// parameter. This function returns true if the evaluation succeeds.
-// This is a convenience function for older functions that want to
-// deal only with strings.
-bool Evaluate(State* state, Expr* expr, std::string* result);
+// Evaluate the input expr, assert that it is a string, and update the result parameter. This
+// function returns true if the evaluation succeeds. This is a convenience function for older
+// functions that want to deal only with strings.
+bool Evaluate(State* state, const std::unique_ptr<Expr>& expr, std::string* result);
 
 // Glue to make an Expr out of a literal.
-Value* Literal(const char* name, State* state, int argc, Expr* argv[]);
+Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
 
 // Functions corresponding to various syntactic sugar operators.
 // ("concat" is also available as a builtin function, to concatenate
 // more than two strings.)
-Value* ConcatFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* SubstringFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]);
+Value* ConcatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* LogicalAndFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* LogicalOrFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* LogicalNotFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* SubstringFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* InequalityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
 
 // Global builtins, registered by RegisterBuiltins().
-Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]);
-Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]);
+Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
+Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
 
 // Register a new function.  The same Function may be registered under
 // multiple names, but a given name should only be used once.
@@ -120,15 +125,19 @@
 
 // --- convenience functions for use in functions ---
 
-// Evaluate the expressions in argv, and put the results of strings in
-// args. If any expression evaluates to nullptr, free the rest and return
-// false. Return true on success.
-bool ReadArgs(State* state, int argc, Expr* argv[], std::vector<std::string>* args);
+// Evaluate the expressions in argv, and put the results of strings in args. If any expression
+// evaluates to nullptr, return false. Return true on success.
+bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+              std::vector<std::string>* args);
+bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+              std::vector<std::string>* args, size_t start, size_t len);
 
-// Evaluate the expressions in argv, and put the results of Value* in
-// args. If any expression evaluate to nullptr, free the rest and return
-// false. Return true on success.
-bool ReadValueArgs(State* state, int argc, Expr* argv[], std::vector<std::unique_ptr<Value>>* args);
+// Evaluate the expressions in argv, and put the results of Value* in args. If any
+// expression evaluate to nullptr, return false. Return true on success.
+bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+                   std::vector<std::unique_ptr<Value>>* args);
+bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
+                   std::vector<std::unique_ptr<Value>>* args, size_t start, size_t len);
 
 // Use printf-style arguments to compose an error message to put into
 // *state.  Returns NULL.
@@ -145,6 +154,6 @@
 
 Value* StringValue(const std::string& str);
 
-int parse_string(const char* str, Expr** root, int* error_count);
+int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count);
 
 #endif  // _EXPRESSION_H
diff --git a/edify/parser.yy b/edify/parser.yy
index 58a8dec..97205fe 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -19,6 +19,10 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <memory>
+#include <string>
+#include <vector>
+
 #include "expr.h"
 #include "yydefs.h"
 #include "parser.h"
@@ -26,8 +30,8 @@
 extern int gLine;
 extern int gColumn;
 
-void yyerror(Expr** root, int* error_count, const char* s);
-int yyparse(Expr** root, int* error_count);
+void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s);
+int yyparse(std::unique_ptr<Expr>* root, int* error_count);
 
 struct yy_buffer_state;
 void yy_switch_to_buffer(struct yy_buffer_state* new_buffer);
@@ -38,17 +42,11 @@
 static Expr* Build(Function fn, YYLTYPE loc, size_t count, ...) {
     va_list v;
     va_start(v, count);
-    Expr* e = static_cast<Expr*>(malloc(sizeof(Expr)));
-    e->fn = fn;
-    e->name = "(operator)";
-    e->argc = count;
-    e->argv = static_cast<Expr**>(malloc(count * sizeof(Expr*)));
+    Expr* e = new Expr(fn, "(operator)", loc.start, loc.end);
     for (size_t i = 0; i < count; ++i) {
-        e->argv[i] = va_arg(v, Expr*);
+        e->argv.emplace_back(va_arg(v, Expr*));
     }
     va_end(v);
-    e->start = loc.start;
-    e->end = loc.end;
     return e;
 }
 
@@ -59,10 +57,7 @@
 %union {
     char* str;
     Expr* expr;
-    struct {
-        int argc;
-        Expr** argv;
-    } args;
+    std::vector<std::unique_ptr<Expr>>* args;
 }
 
 %token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF
@@ -70,7 +65,10 @@
 %type <expr> expr
 %type <args> arglist
 
-%parse-param {Expr** root}
+%destructor { delete $$; } expr
+%destructor { delete $$; } arglist
+
+%parse-param {std::unique_ptr<Expr>* root}
 %parse-param {int* error_count}
 %error-verbose
 
@@ -85,17 +83,11 @@
 
 %%
 
-input:  expr           { *root = $1; }
+input:  expr           { root->reset($1); }
 ;
 
 expr:  STRING {
-    $$ = static_cast<Expr*>(malloc(sizeof(Expr)));
-    $$->fn = Literal;
-    $$->name = $1;
-    $$->argc = 0;
-    $$->argv = NULL;
-    $$->start = @$.start;
-    $$->end = @$.end;
+    $$ = new Expr(Literal, $1, @$.start, @$.end);
 }
 |  '(' expr ')'                      { $$ = $2; $$->start=@$.start; $$->end=@$.end; }
 |  expr ';'                          { $$ = $1; $$->start=@1.start; $$->end=@1.end; }
@@ -110,41 +102,32 @@
 |  IF expr THEN expr ENDIF           { $$ = Build(IfElseFn, @$, 2, $2, $4); }
 |  IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); }
 | STRING '(' arglist ')' {
-    $$ = static_cast<Expr*>(malloc(sizeof(Expr)));
-    $$->fn = FindFunction($1);
-    if ($$->fn == nullptr) {
-        char buffer[256];
-        snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1);
-        yyerror(root, error_count, buffer);
+    Function fn = FindFunction($1);
+    if (fn == nullptr) {
+        std::string msg = "unknown function \"" + std::string($1) + "\"";
+        yyerror(root, error_count, msg.c_str());
         YYERROR;
     }
-    $$->name = $1;
-    $$->argc = $3.argc;
-    $$->argv = $3.argv;
-    $$->start = @$.start;
-    $$->end = @$.end;
+    $$ = new Expr(fn, $1, @$.start, @$.end);
+    $$->argv = std::move(*$3);
 }
 ;
 
 arglist:    /* empty */ {
-    $$.argc = 0;
-    $$.argv = NULL;
+    $$ = new std::vector<std::unique_ptr<Expr>>;
 }
 | expr {
-    $$.argc = 1;
-    $$.argv = static_cast<Expr**>(malloc(sizeof(Expr*)));
-    $$.argv[0] = $1;
+    $$ = new std::vector<std::unique_ptr<Expr>>;
+    $$->emplace_back($1);
 }
 | arglist ',' expr {
-    $$.argc = $1.argc + 1;
-    $$.argv = static_cast<Expr**>(realloc($$.argv, $$.argc * sizeof(Expr*)));
-    $$.argv[$$.argc-1] = $3;
+    $$->push_back(std::unique_ptr<Expr>($3));
 }
 ;
 
 %%
 
-void yyerror(Expr** root, int* error_count, const char* s) {
+void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s) {
   if (strlen(s) == 0) {
     s = "syntax error";
   }
@@ -152,7 +135,7 @@
   ++*error_count;
 }
 
-int parse_string(const char* str, Expr** root, int* error_count) {
+int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count) {
     yy_switch_to_buffer(yy_scan_string(str));
     return yyparse(root, error_count);
 }
diff --git a/install.cpp b/install.cpp
index ce89e0d..db8fb97 100644
--- a/install.cpp
+++ b/install.cpp
@@ -27,6 +27,7 @@
 #include <unistd.h>
 
 #include <chrono>
+#include <functional>
 #include <limits>
 #include <map>
 #include <string>
@@ -578,23 +579,24 @@
 }
 
 bool verify_package(const unsigned char* package_data, size_t package_size) {
-    std::vector<Certificate> loadedKeys;
-    if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
-        LOG(ERROR) << "Failed to load keys";
-        return false;
-    }
-    LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE;
+  std::vector<Certificate> loadedKeys;
+  if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
+    LOG(ERROR) << "Failed to load keys";
+    return false;
+  }
+  LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE;
 
-    // Verify package.
-    ui->Print("Verifying update package...\n");
-    auto t0 = std::chrono::system_clock::now();
-    int err = verify_file(const_cast<unsigned char*>(package_data), package_size, loadedKeys);
-    std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
-    ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
-    if (err != VERIFY_SUCCESS) {
-        LOG(ERROR) << "Signature verification failed";
-        LOG(ERROR) << "error: " << kZipVerificationFailure;
-        return false;
-    }
-    return true;
+  // Verify package.
+  ui->Print("Verifying update package...\n");
+  auto t0 = std::chrono::system_clock::now();
+  int err = verify_file(package_data, package_size, loadedKeys,
+                        std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
+  std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
+  ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
+  if (err != VERIFY_SUCCESS) {
+    LOG(ERROR) << "Signature verification failed";
+    LOG(ERROR) << "error: " << kZipVerificationFailure;
+    return false;
+  }
+  return true;
 }
diff --git a/minui/events.cpp b/minui/events.cpp
index fa44033..0e1fd44 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -24,6 +24,8 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 
+#include <functional>
+
 #include "minui/minui.h"
 
 #define MAX_DEVICES 16
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
index f5b0113..3a89bb5 100644
--- a/otafault/ota_io.cpp
+++ b/otafault/ota_io.cpp
@@ -89,7 +89,7 @@
     return fclose(fh);
 }
 
-void OtaFcloser::operator()(FILE* f) {
+void OtaFcloser::operator()(FILE* f) const {
     __ota_fclose(f);
 };
 
diff --git a/otafault/ota_io.h b/otafault/ota_io.h
index 395b423..9428f1b 100644
--- a/otafault/ota_io.h
+++ b/otafault/ota_io.h
@@ -59,7 +59,7 @@
 int ota_close(unique_fd& fd);
 
 struct OtaFcloser {
-  void operator()(FILE*);
+  void operator()(FILE*) const;
 };
 
 using unique_file = std::unique_ptr<FILE, OtaFcloser>;
diff --git a/recovery.cpp b/recovery.cpp
index 91c511a..c226216 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -53,6 +53,7 @@
 #include <cutils/properties.h> /* for property_list */
 #include <healthd/BatteryMonitor.h>
 #include <private/android_logger.h> /* private pmsg functions */
+#include <private/android_filesystem_config.h>  /* for AID_SYSTEM */
 #include <selinux/label.h>
 #include <selinux/selinux.h>
 #include <ziparchive/zip_archive.h>
@@ -460,9 +461,9 @@
     copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
     save_kernel_log(LAST_KMSG_FILE);
     chmod(LOG_FILE, 0600);
-    chown(LOG_FILE, 1000, 1000);   // system user
+    chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
     chmod(LAST_KMSG_FILE, 0600);
-    chown(LAST_KMSG_FILE, 1000, 1000);   // system user
+    chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM);
     chmod(LAST_LOG_FILE, 0640);
     chmod(LAST_INSTALL_FILE, 0644);
     sync();
@@ -751,13 +752,15 @@
 
 static bool prompt_and_wipe_data(Device* device) {
   const char* const headers[] = {
-    "Boot halted, user data is corrupt",
-    "Wipe all user data to recover",
+    "Can't load Android system. Your data may be corrupt.",
+    "If you continue to get this message, you may need to",
+    "perform a factory data reset and erase all user data",
+    "stored on this device.",
     NULL
   };
   const char* const items[] = {
-    "Retry boot",
-    "Wipe user data",
+    "Try again",
+    "Factory data reset",
     NULL
   };
   for (;;) {
@@ -790,47 +793,45 @@
     return success;
 }
 
-// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported.
-// Otherwise, it goes with BLKDISCARD (if device supports BLKDISCARDZEROES) or
-// BLKZEROOUT.
+// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
+// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
 static bool secure_wipe_partition(const std::string& partition) {
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
-    if (fd == -1) {
-        PLOG(ERROR) << "failed to open \"" << partition << "\"";
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open \"" << partition << "\"";
+    return false;
+  }
+
+  uint64_t range[2] = { 0, 0 };
+  if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
+    PLOG(ERROR) << "Failed to get partition size";
+    return false;
+  }
+  LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
+
+  LOG(INFO) << "  Trying BLKSECDISCARD...";
+  if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
+    PLOG(WARNING) << "  Failed";
+
+    // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
+    unsigned int zeroes;
+    if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
+      LOG(INFO) << "  Trying BLKDISCARD...";
+      if (ioctl(fd, BLKDISCARD, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
         return false;
-    }
-
-    uint64_t range[2] = {0, 0};
-    if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
-        PLOG(ERROR) << "failed to get partition size";
+      }
+    } else {
+      LOG(INFO) << "  Trying BLKZEROOUT...";
+      if (ioctl(fd, BLKZEROOUT, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
         return false;
+      }
     }
-    printf("Secure-wiping \"%s\" from %" PRIu64 " to %" PRIu64 ".\n",
-           partition.c_str(), range[0], range[1]);
+  }
 
-    printf("Trying BLKSECDISCARD...\t");
-    if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
-        printf("failed: %s\n", strerror(errno));
-
-        // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
-        unsigned int zeroes;
-        if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
-            printf("Trying BLKDISCARD...\t");
-            if (ioctl(fd, BLKDISCARD, &range) == -1) {
-                printf("failed: %s\n", strerror(errno));
-                return false;
-            }
-        } else {
-            printf("Trying BLKZEROOUT...\t");
-            if (ioctl(fd, BLKZEROOUT, &range) == -1) {
-                printf("failed: %s\n", strerror(errno));
-                return false;
-            }
-        }
-    }
-
-    printf("done\n");
-    return true;
+  LOG(INFO) << "  Done";
+  return true;
 }
 
 // Check if the wipe package matches expectation:
@@ -862,7 +863,7 @@
         return false;
     }
     std::string metadata;
-    if (!read_metadata_from_package(&zip, &metadata)) {
+    if (!read_metadata_from_package(zip, &metadata)) {
         CloseArchive(zip);
         return false;
     }
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 706877b..bb2772d 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -98,7 +98,7 @@
     }
 }
 
-int ScreenRecoveryUI::PixelsFromDp(int dp) {
+int ScreenRecoveryUI::PixelsFromDp(int dp) const {
     return dp * density_;
 }
 
@@ -256,12 +256,12 @@
     *y += 4;
 }
 
-void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) {
+void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const {
     gr_text(gr_sys_font(), x, *y, line, bold);
     *y += char_height_ + 4;
 }
 
-void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) {
+void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) const {
     for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
         DrawTextLine(x, y, lines[i], false);
     }
diff --git a/screen_ui.h b/screen_ui.h
index b2dcf4a..a2322c3 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -160,14 +160,14 @@
     void LoadBitmap(const char* filename, GRSurface** surface);
     void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
 
-    int PixelsFromDp(int dp);
+    int PixelsFromDp(int dp) const;
     virtual int GetAnimationBaseline();
     virtual int GetProgressBaseline();
     virtual int GetTextBaseline();
 
     void DrawHorizontalRule(int* y);
-    void DrawTextLine(int x, int* y, const char* line, bool bold);
-    void DrawTextLines(int x, int* y, const char* const* lines);
+    void DrawTextLine(int x, int* y, const char* line, bool bold) const;
+    void DrawTextLines(int x, int* y, const char* const* lines) const;
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/tests/Android.mk b/tests/Android.mk
index ec971b3..ff6e14c 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -81,7 +81,10 @@
 
 # Component tests
 include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := \
+    -Werror \
+    -D_FILE_OFFSET_BITS=64
+
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 LOCAL_MODULE := recovery_component_test
 LOCAL_C_INCLUDES := bootable/recovery
@@ -117,7 +120,6 @@
     libupdater \
     libbootloader_message \
     libverifier \
-    libminui \
     libotautil \
     libmounts \
     libdivsufsort \
@@ -136,6 +138,10 @@
     libz \
     libbase \
     libtune2fs \
+    libfec \
+    libfec_rs \
+    libsquashfs_utils \
+    libcutils \
     $(tune2fs_static_libraries)
 
 testdata_files := $(call find-subdir-files, testdata/*)
diff --git a/tests/component/edify_test.cpp b/tests/component/edify_test.cpp
index 287e40c..61a1e6b 100644
--- a/tests/component/edify_test.cpp
+++ b/tests/component/edify_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <memory>
 #include <string>
 
 #include <gtest/gtest.h>
@@ -21,7 +22,7 @@
 #include "edify/expr.h"
 
 static void expect(const char* expr_str, const char* expected) {
-    Expr* e;
+    std::unique_ptr<Expr> e;
     int error_count = 0;
     EXPECT_EQ(0, parse_string(expr_str, &e, &error_count));
     EXPECT_EQ(0, error_count);
@@ -152,7 +153,7 @@
 TEST_F(EdifyTest, unknown_function) {
     // unknown function
     const char* script1 = "unknown_function()";
-    Expr* expr;
+    std::unique_ptr<Expr> expr;
     int error_count = 0;
     EXPECT_EQ(1, parse_string(script1, &expr, &error_count));
     EXPECT_EQ(1, error_count);
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index be2dd38..2f64850 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -18,13 +18,14 @@
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/memory.h>
 #include <android-base/test_utils.h>
 #include <applypatch/imgdiff.h>
 #include <applypatch/imgpatch.h>
 #include <gtest/gtest.h>
 #include <ziparchive/zip_writer.h>
 
-#include "applypatch/utils.h"
+using android::base::get_unaligned;
 
 static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
   std::string* s = static_cast<std::string*>(token);
@@ -41,7 +42,7 @@
   ASSERT_GE(size, 12U);
   ASSERT_EQ("IMGDIFF2", std::string(data, 8));
 
-  const int num_chunks = Read4(data + 8);
+  const int num_chunks = get_unaligned<int32_t>(data + 8);
   ASSERT_GE(num_chunks, 0);
 
   size_t normal = 0;
@@ -51,7 +52,7 @@
   size_t pos = 12;
   for (int i = 0; i < num_chunks; ++i) {
     ASSERT_LE(pos + 4, size);
-    int type = Read4(data + pos);
+    int type = get_unaligned<int32_t>(data + pos);
     pos += 4;
     if (type == CHUNK_NORMAL) {
       pos += 24;
@@ -59,7 +60,7 @@
       normal++;
     } else if (type == CHUNK_RAW) {
       ASSERT_LE(pos + 4, size);
-      ssize_t data_len = Read4(data + pos);
+      ssize_t data_len = get_unaligned<int32_t>(data + pos);
       ASSERT_GT(data_len, 0);
       pos += 4 + data_len;
       ASSERT_LE(pos, size);
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 8c4bdba..5652ddf 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -19,7 +19,9 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <memory>
 #include <string>
+#include <vector>
 
 #include <android-base/file.h>
 #include <android-base/properties.h>
@@ -27,12 +29,17 @@
 #include <android-base/strings.h>
 #include <android-base/test_utils.h>
 #include <bootloader_message/bootloader_message.h>
+#include <bsdiff.h>
 #include <gtest/gtest.h>
 #include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
 
 #include "common/test_constants.h"
 #include "edify/expr.h"
 #include "error_code.h"
+#include "otautil/SysUtil.h"
+#include "print_sha1.h"
+#include "updater/blockimg.h"
 #include "updater/install.h"
 #include "updater/updater.h"
 
@@ -40,7 +47,7 @@
 
 static void expect(const char* expected, const char* expr_str, CauseCode cause_code,
                    UpdaterInfo* info = nullptr) {
-  Expr* e;
+  std::unique_ptr<Expr> e;
   int error_count = 0;
   ASSERT_EQ(0, parse_string(expr_str, &e, &error_count));
   ASSERT_EQ(0, error_count);
@@ -64,12 +71,19 @@
   ASSERT_EQ(cause_code, state.cause_code);
 }
 
+static std::string get_sha1(const std::string& content) {
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
+  return print_sha1(digest);
+}
+
 class UpdaterTest : public ::testing::Test {
-  protected:
-    virtual void SetUp() {
-        RegisterBuiltins();
-        RegisterInstallFunctions();
-    }
+ protected:
+  virtual void SetUp() override {
+    RegisterBuiltins();
+    RegisterInstallFunctions();
+    RegisterBlockImageFunctions();
+  }
 };
 
 TEST_F(UpdaterTest, getprop) {
@@ -113,6 +127,55 @@
     expect(nullptr, "sha1_check()", kArgsParsingFailure);
 }
 
+TEST_F(UpdaterTest, apply_patch_check) {
+  // Zero-argument is not valid.
+  expect(nullptr, "apply_patch_check()", kArgsParsingFailure);
+
+  // File not found.
+  expect("", "apply_patch_check(\"/doesntexist\")", kNoCause);
+
+  std::string src_file = from_testdata_base("old.file");
+  std::string src_content;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file, &src_content));
+  size_t src_size = src_content.size();
+  std::string src_hash = get_sha1(src_content);
+
+  // One-argument with EMMC:file:size:sha1 should pass the check.
+  std::string filename = android::base::Join(
+      std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size), src_hash }, ":");
+  std::string cmd = "apply_patch_check(\"" + filename + "\")";
+  expect("t", cmd.c_str(), kNoCause);
+
+  // EMMC:file:(size-1):sha1:(size+1):sha1 should fail the check.
+  std::string filename_bad = android::base::Join(
+      std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1), src_hash,
+                                std::to_string(src_size + 1), src_hash },
+      ":");
+  cmd = "apply_patch_check(\"" + filename_bad + "\")";
+  expect("", cmd.c_str(), kNoCause);
+
+  // EMMC:file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
+  filename_bad =
+      android::base::Join(std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1),
+                                                    src_hash, std::to_string(src_size), src_hash,
+                                                    std::to_string(src_size + 1), src_hash },
+                          ":");
+  cmd = "apply_patch_check(\"" + filename_bad + "\")";
+  expect("t", cmd.c_str(), kNoCause);
+
+  // Multiple arguments.
+  cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"wrong_sha2\")";
+  expect("", cmd.c_str(), kNoCause);
+
+  cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"" + src_hash +
+        "\", \"wrong_sha2\")";
+  expect("t", cmd.c_str(), kNoCause);
+
+  cmd = "apply_patch_check(\"" + filename_bad + "\", \"wrong_sha1\", \"" + src_hash +
+        "\", \"wrong_sha2\")";
+  expect("t", cmd.c_str(), kNoCause);
+}
+
 TEST_F(UpdaterTest, file_getprop) {
     // file_getprop() expects two arguments.
     expect(nullptr, "file_getprop()", kArgsParsingFailure);
@@ -447,3 +510,100 @@
   // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
   ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
 }
+
+TEST_F(UpdaterTest, block_image_update) {
+  // Create a zip file with new_data and patch_data.
+  TemporaryFile zip_file;
+  FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
+  ZipWriter zip_writer(zip_file_ptr);
+
+  // Add a dummy new data.
+  ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+
+  // Generate and add the patch data.
+  std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
+  std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
+  TemporaryFile patch_file;
+  ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()),
+      src_content.size(), reinterpret_cast<const uint8_t*>(tgt_content.data()),
+      tgt_content.size(), patch_file.path, nullptr));
+  std::string patch_content;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
+  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
+  ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+
+  // Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed.
+  std::string src_hash = get_sha1(src_content);
+  std::string tgt_hash = get_sha1(tgt_content);
+  std::vector<std::string> transfer_list = {
+    "4",
+    "2",
+    "0",
+    "2",
+    "stash " + src_hash + " 2,0,2",
+    android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(),
+                                src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
+    "free " + src_hash,
+  };
+  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
+  std::string commands = android::base::Join(transfer_list, '\n');
+  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+
+  // Stash and free some blocks, then fail the 2nd update intentionally.
+  std::vector<std::string> fail_transfer_list = {
+    "4",
+    "2",
+    "0",
+    "2",
+    "stash " + tgt_hash + " 2,0,2",
+    "free " + tgt_hash,
+    "fail",
+  };
+  ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0));
+  std::string fail_commands = android::base::Join(fail_transfer_list, '\n');
+  ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size()));
+  ASSERT_EQ(0, zip_writer.FinishEntry());
+  ASSERT_EQ(0, zip_writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  MemMapping map;
+  ASSERT_EQ(0, sysMapFile(zip_file.path, &map));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  // Execute the commands in the 1st transfer list.
+  TemporaryFile update_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+  std::string script = "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+  // The update_file should be patched correctly.
+  std::string updated_content;
+  ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
+  ASSERT_EQ(tgt_hash, get_sha1(updated_content));
+
+  // Expect the 2nd update to fail, but expect the stashed blocks to be freed.
+  script = "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))";
+  expect("", script.c_str(), kNoCause, &updater_info);
+  // Updater generates the stash name based on the input file name.
+  std::string name_digest = get_sha1(update_file.path);
+  std::string stash_base = "/cache/recovery/" + name_digest;
+  ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
+  ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK));
+  ASSERT_EQ(0, rmdir(stash_base.c_str()));
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index b740af9..07a8c96 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -22,93 +22,34 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <memory>
 #include <string>
 #include <vector>
 
-#include <openssl/sha.h>
-
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
-#include <ziparchive/zip_archive.h>
+#include <android-base/test_utils.h>
 
-#include "common.h"
 #include "common/test_constants.h"
 #include "otautil/SysUtil.h"
-#include "ui.h"
 #include "verifier.h"
 
-RecoveryUI* ui = NULL;
-
-class MockUI : public RecoveryUI {
-  bool Init(const std::string&) override {
-    return true;
-  }
-  void SetStage(int, int) override {}
-  void SetBackground(Icon /*icon*/) override {}
-  void SetSystemUpdateText(bool /*security_update*/) override {}
-
-  void SetProgressType(ProgressType /*determinate*/) override {}
-  void ShowProgress(float /*portion*/, float /*seconds*/) override {}
-  void SetProgress(float /*fraction*/) override {}
-
-  void ShowText(bool /*visible*/) override {}
-  bool IsTextVisible() override {
-    return false;
-  }
-  bool WasTextEverVisible() override {
-    return false;
-  }
-  void Print(const char* fmt, ...) override {
-    va_list ap;
-    va_start(ap, fmt);
-    vfprintf(stderr, fmt, ap);
-    va_end(ap);
-  }
-  void PrintOnScreenOnly(const char* fmt, ...) override {
-    va_list ap;
-    va_start(ap, fmt);
-    vfprintf(stderr, fmt, ap);
-    va_end(ap);
-  }
-  void ShowFile(const char*) override {}
-
-  void StartMenu(const char* const* /*headers*/, const char* const* /*items*/,
-                 int /*initial_selection*/) override {}
-  int SelectMenu(int /*sel*/) override {
-    return 0;
-  }
-  void EndMenu() override {}
-};
-
-void
-ui_print(const char* format, ...) {
-    va_list ap;
-    va_start(ap, format);
-    vfprintf(stdout, format, ap);
-    va_end(ap);
-}
-
 class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
-  public:
-    MemMapping memmap;
-    std::vector<Certificate> certs;
-
-    virtual void SetUp() {
-        std::vector<std::string> args = GetParam();
-        std::string package = from_testdata_base(args[0]);
-        if (sysMapFile(package.c_str(), &memmap) != 0) {
-            FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
-        }
-
-        for (auto it = ++(args.cbegin()); it != args.cend(); ++it) {
-            std::string public_key_file = from_testdata_base("testkey_" + *it + ".txt");
-            ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
-        }
+ protected:
+  void SetUp() override {
+    std::vector<std::string> args = GetParam();
+    std::string package = from_testdata_base(args[0]);
+    if (sysMapFile(package.c_str(), &memmap) != 0) {
+      FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
     }
 
-    static void SetUpTestCase() {
-        ui = new MockUI();
+    for (auto it = ++args.cbegin(); it != args.cend(); ++it) {
+      std::string public_key_file = from_testdata_base("testkey_" + *it + ".txt");
+      ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
     }
+  }
+
+  MemMapping memmap;
+  std::vector<Certificate> certs;
 };
 
 class VerifierSuccessTest : public VerifierTest {
@@ -117,48 +58,105 @@
 class VerifierFailureTest : public VerifierTest {
 };
 
+TEST(VerifierTest, load_keys_multiple_keys) {
+  std::string testkey_v4;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
+
+  std::string testkey_v3;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+
+  std::string keys = testkey_v4 + "," + testkey_v3 + "," + testkey_v4;
+  TemporaryFile key_file1;
+  ASSERT_TRUE(android::base::WriteStringToFile(keys, key_file1.path));
+  std::vector<Certificate> certs;
+  ASSERT_TRUE(load_keys(key_file1.path, certs));
+  ASSERT_EQ(3U, certs.size());
+}
+
+TEST(VerifierTest, load_keys_invalid_keys) {
+  std::vector<Certificate> certs;
+  ASSERT_FALSE(load_keys("/doesntexist", certs));
+
+  // Empty file.
+  TemporaryFile key_file1;
+  ASSERT_FALSE(load_keys(key_file1.path, certs));
+
+  // Invalid contents.
+  ASSERT_TRUE(android::base::WriteStringToFile("invalid", key_file1.path));
+  ASSERT_FALSE(load_keys(key_file1.path, certs));
+
+  std::string testkey_v4;
+  ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
+
+  // Invalid key version: "v4 ..." => "v6 ...".
+  std::string invalid_key2(testkey_v4);
+  invalid_key2[1] = '6';
+  TemporaryFile key_file2;
+  ASSERT_TRUE(android::base::WriteStringToFile(invalid_key2, key_file2.path));
+  ASSERT_FALSE(load_keys(key_file2.path, certs));
+
+  // Invalid key content: inserted extra bytes ",2209831334".
+  std::string invalid_key3(testkey_v4);
+  invalid_key3.insert(invalid_key2.size() - 2, ",2209831334");
+  TemporaryFile key_file3;
+  ASSERT_TRUE(android::base::WriteStringToFile(invalid_key3, key_file3.path));
+  ASSERT_FALSE(load_keys(key_file3.path, certs));
+
+  // Invalid key: the last key must not end with an extra ','.
+  std::string invalid_key4 = testkey_v4 + ",";
+  TemporaryFile key_file4;
+  ASSERT_TRUE(android::base::WriteStringToFile(invalid_key4, key_file4.path));
+  ASSERT_FALSE(load_keys(key_file4.path, certs));
+
+  // Invalid key separator.
+  std::string invalid_key5 = testkey_v4 + ";" + testkey_v4;
+  TemporaryFile key_file5;
+  ASSERT_TRUE(android::base::WriteStringToFile(invalid_key5, key_file5.path));
+  ASSERT_FALSE(load_keys(key_file5.path, certs));
+}
+
 TEST_P(VerifierSuccessTest, VerifySucceed) {
-    ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_SUCCESS);
+  ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS);
 }
 
 TEST_P(VerifierFailureTest, VerifyFailure) {
-    ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_FAILURE);
+  ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_FAILURE);
 }
 
 INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest,
-        ::testing::Values(
-            std::vector<std::string>({"otasigned_v1.zip", "v1"}),
-            std::vector<std::string>({"otasigned_v2.zip", "v2"}),
-            std::vector<std::string>({"otasigned_v3.zip", "v3"}),
-            std::vector<std::string>({"otasigned_v4.zip", "v4"}),
-            std::vector<std::string>({"otasigned_v5.zip", "v5"})));
+    ::testing::Values(
+      std::vector<std::string>({"otasigned_v1.zip", "v1"}),
+      std::vector<std::string>({"otasigned_v2.zip", "v2"}),
+      std::vector<std::string>({"otasigned_v3.zip", "v3"}),
+      std::vector<std::string>({"otasigned_v4.zip", "v4"}),
+      std::vector<std::string>({"otasigned_v5.zip", "v5"})));
 
 INSTANTIATE_TEST_CASE_P(MultiKeySuccess, VerifierSuccessTest,
-        ::testing::Values(
-            std::vector<std::string>({"otasigned_v1.zip", "v1", "v2"}),
-            std::vector<std::string>({"otasigned_v2.zip", "v5", "v2"}),
-            std::vector<std::string>({"otasigned_v3.zip", "v5", "v1", "v3"}),
-            std::vector<std::string>({"otasigned_v4.zip", "v5", "v1", "v4"}),
-            std::vector<std::string>({"otasigned_v5.zip", "v4", "v1", "v5"})));
+    ::testing::Values(
+      std::vector<std::string>({"otasigned_v1.zip", "v1", "v2"}),
+      std::vector<std::string>({"otasigned_v2.zip", "v5", "v2"}),
+      std::vector<std::string>({"otasigned_v3.zip", "v5", "v1", "v3"}),
+      std::vector<std::string>({"otasigned_v4.zip", "v5", "v1", "v4"}),
+      std::vector<std::string>({"otasigned_v5.zip", "v4", "v1", "v5"})));
 
 INSTANTIATE_TEST_CASE_P(WrongKey, VerifierFailureTest,
-        ::testing::Values(
-            std::vector<std::string>({"otasigned_v1.zip", "v2"}),
-            std::vector<std::string>({"otasigned_v2.zip", "v1"}),
-            std::vector<std::string>({"otasigned_v3.zip", "v5"}),
-            std::vector<std::string>({"otasigned_v4.zip", "v5"}),
-            std::vector<std::string>({"otasigned_v5.zip", "v3"})));
+    ::testing::Values(
+      std::vector<std::string>({"otasigned_v1.zip", "v2"}),
+      std::vector<std::string>({"otasigned_v2.zip", "v1"}),
+      std::vector<std::string>({"otasigned_v3.zip", "v5"}),
+      std::vector<std::string>({"otasigned_v4.zip", "v5"}),
+      std::vector<std::string>({"otasigned_v5.zip", "v3"})));
 
 INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest,
-        ::testing::Values(
-            std::vector<std::string>({"otasigned_v1.zip", "v3"}),
-            std::vector<std::string>({"otasigned_v2.zip", "v4"}),
-            std::vector<std::string>({"otasigned_v3.zip", "v1"}),
-            std::vector<std::string>({"otasigned_v4.zip", "v2"})));
+    ::testing::Values(
+      std::vector<std::string>({"otasigned_v1.zip", "v3"}),
+      std::vector<std::string>({"otasigned_v2.zip", "v4"}),
+      std::vector<std::string>({"otasigned_v3.zip", "v1"}),
+      std::vector<std::string>({"otasigned_v4.zip", "v2"})));
 
 INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest,
-        ::testing::Values(
-            std::vector<std::string>({"random.zip", "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"})));
+    ::testing::Values(
+      std::vector<std::string>({"random.zip", "v1"}),
+      std::vector<std::string>({"fake-eocd.zip", "v1"}),
+      std::vector<std::string>({"alter-metadata.zip", "v1"}),
+      std::vector<std::string>({"alter-footer.zip", "v1"})));
diff --git a/tests/unit/asn1_decoder_test.cpp b/tests/unit/asn1_decoder_test.cpp
index af96d87..b334a65 100644
--- a/tests/unit/asn1_decoder_test.cpp
+++ b/tests/unit/asn1_decoder_test.cpp
@@ -14,225 +14,188 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "asn1_decoder_test"
-
-#include <cutils/log.h>
-#include <gtest/gtest.h>
 #include <stdint.h>
-#include <unistd.h>
+
+#include <memory>
+
+#include <gtest/gtest.h>
 
 #include "asn1_decoder.h"
 
-namespace android {
+TEST(Asn1DecoderTest, Empty_Failure) {
+  uint8_t empty[] = {};
+  asn1_context ctx(empty, sizeof(empty));
 
-class Asn1DecoderTest : public testing::Test {
-};
+  ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
+  ASSERT_FALSE(ctx.asn1_constructed_skip_all());
+  ASSERT_EQ(0, ctx.asn1_constructed_type());
+  ASSERT_EQ(nullptr, ctx.asn1_sequence_get());
+  ASSERT_EQ(nullptr, ctx.asn1_set_get());
+  ASSERT_FALSE(ctx.asn1_sequence_next());
 
-TEST_F(Asn1DecoderTest, Empty_Failure) {
-    uint8_t empty[] = { };
-    asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty));
-
-    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
-    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
-    EXPECT_EQ(0, asn1_constructed_type(ctx));
-    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
-    EXPECT_EQ(NULL, asn1_set_get(ctx));
-    EXPECT_FALSE(asn1_sequence_next(ctx));
-
-    uint8_t* junk;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length));
-    EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length));
-
-    asn1_context_free(ctx);
+  const uint8_t* junk;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_oid_get(&junk, &length));
+  ASSERT_FALSE(ctx.asn1_octet_string_get(&junk, &length));
 }
 
-TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
-    uint8_t truncated[] = { 0xA0, 0x82, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
+  uint8_t truncated[] = { 0xA0, 0x82 };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
 }
 
-TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
-    uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A,
-                            0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
+  uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
 }
 
-TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
-    uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_constructed_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    EXPECT_EQ(5, asn1_constructed_type(ptr));
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
+  uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_constructed_get());
+  ASSERT_NE(nullptr, ptr);
+  ASSERT_EQ(5, ptr->asn1_constructed_type());
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, ConstructedGet_Success) {
-    uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_constructed_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    EXPECT_EQ(5, asn1_constructed_type(ptr));
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0x01U, *oid);
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedGet_Success) {
+  uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_constructed_get());
+  ASSERT_NE(nullptr, ptr);
+  ASSERT_EQ(5, ptr->asn1_constructed_type());
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0x01U, *oid);
 }
 
-TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
-    uint8_t truncated[] = { 0xA2, 0x82, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
+  uint8_t truncated[] = { 0xA2, 0x82 };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_FALSE(ctx.asn1_constructed_skip_all());
 }
 
-TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) {
-    uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01,
-                            0xA1, 0x03, 0x02, 0x01, 0x01,
-                            0x06, 0x01, 0xA5, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    ASSERT_TRUE(asn1_constructed_skip_all(ctx));
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0xA5U, *oid);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, ConstructedSkipAll_Success) {
+  uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01, 0xA1, 0x03, 0x02, 0x01, 0x01, 0x06, 0x01, 0xA5 };
+  asn1_context ctx(data, sizeof(data));
+  ASSERT_TRUE(ctx.asn1_constructed_skip_all());
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ctx.asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0xA5U, *oid);
 }
 
-TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
-    uint8_t truncated[] = { 0x30, 0x82, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
+  uint8_t truncated[] = { 0x30, 0x82 };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_EQ(nullptr, ctx.asn1_sequence_get());
 }
 
-TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
-    uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_sequence_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
+  uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_sequence_get());
+  ASSERT_NE(nullptr, ptr);
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, SequenceGet_Success) {
-    uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_sequence_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0x01U, *oid);
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SequenceGet_Success) {
+  uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_sequence_get());
+  ASSERT_NE(nullptr, ptr);
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0x01U, *oid);
 }
 
-TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
-    uint8_t truncated[] = { 0x31, 0x82, };
-    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
-    EXPECT_EQ(NULL, asn1_set_get(ctx));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
+  uint8_t truncated[] = { 0x31, 0x82 };
+  asn1_context ctx(truncated, sizeof(truncated));
+  ASSERT_EQ(nullptr, ctx.asn1_set_get());
 }
 
-TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
-    uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_set_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
+  uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_set_get());
+  ASSERT_NE(nullptr, ptr);
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, SetGet_Success) {
-    uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    asn1_context_t* ptr = asn1_set_get(ctx);
-    ASSERT_NE((asn1_context_t*)NULL, ptr);
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0xBAU, *oid);
-    asn1_context_free(ptr);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, SetGet_Success) {
+  uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA };
+  asn1_context ctx(data, sizeof(data));
+  std::unique_ptr<asn1_context> ptr(ctx.asn1_set_get());
+  ASSERT_NE(nullptr, ptr);
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0xBAU, *oid);
 }
 
-TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) {
-    uint8_t data[] = { 0x06, 0x00, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OidGet_LengthZero_Failure) {
+  uint8_t data[] = { 0x06, 0x00, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) {
-    uint8_t data[] = { 0x06, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* oid;
-    size_t length;
-    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OidGet_TooSmall_Failure) {
+  uint8_t data[] = { 0x06, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_oid_get(&oid, &length));
 }
 
-TEST_F(Asn1DecoderTest, OidGet_Success) {
-    uint8_t data[] = { 0x06, 0x01, 0x99, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* oid;
-    size_t length;
-    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0x99U, *oid);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OidGet_Success) {
+  uint8_t data[] = { 0x06, 0x01, 0x99 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* oid;
+  size_t length;
+  ASSERT_TRUE(ctx.asn1_oid_get(&oid, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0x99U, *oid);
 }
 
-TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
-    uint8_t data[] = { 0x04, 0x00, 0x55, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* string;
-    size_t length;
-    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
+  uint8_t data[] = { 0x04, 0x00, 0x55 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* string;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_octet_string_get(&string, &length));
 }
 
-TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
-    uint8_t data[] = { 0x04, 0x01, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* string;
-    size_t length;
-    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
+  uint8_t data[] = { 0x04, 0x01 };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* string;
+  size_t length;
+  ASSERT_FALSE(ctx.asn1_octet_string_get(&string, &length));
 }
 
-TEST_F(Asn1DecoderTest, OctetStringGet_Success) {
-    uint8_t data[] = { 0x04, 0x01, 0xAA, };
-    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
-    uint8_t* string;
-    size_t length;
-    ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length));
-    EXPECT_EQ(1U, length);
-    EXPECT_EQ(0xAAU, *string);
-    asn1_context_free(ctx);
+TEST(Asn1DecoderTest, OctetStringGet_Success) {
+  uint8_t data[] = { 0x04, 0x01, 0xAA };
+  asn1_context ctx(data, sizeof(data));
+  const uint8_t* string;
+  size_t length;
+  ASSERT_TRUE(ctx.asn1_octet_string_get(&string, &length));
+  ASSERT_EQ(1U, length);
+  ASSERT_EQ(0xAAU, *string);
 }
-
-} // namespace android
diff --git a/ui.cpp b/ui.cpp
index 3ecd6d1..9194ae3 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -30,6 +30,7 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <functional>
 #include <string>
 
 #include <android-base/file.h>
@@ -239,7 +240,7 @@
 }
 
 void* RecoveryUI::time_key_helper(void* cookie) {
-    key_timer_t* info = (key_timer_t*) cookie;
+    key_timer_t* info = static_cast<key_timer_t*>(cookie);
     info->ui->time_key(info->key_code, info->count);
     delete info;
     return nullptr;
diff --git a/updater/Android.mk b/updater/Android.mk
index 3a47dac..a113fe8 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -110,21 +110,11 @@
 # any subsidiary static libraries required for your registered
 # extension libs.
 
-inc := $(call intermediates-dir-for,PACKAGING,updater_extensions)/register.inc
-
-# Encode the value of TARGET_RECOVERY_UPDATER_LIBS into the filename of the dependency.
-# So if TARGET_RECOVERY_UPDATER_LIBS is changed, a new dependency file will be generated.
-# Note that we have to remove any existing depency files before creating new one,
-# so no obsolete dependecy file gets used if you switch back to an old value.
-inc_dep_file := $(inc).dep.$(subst $(space),-,$(sort $(TARGET_RECOVERY_UPDATER_LIBS)))
-$(inc_dep_file): stem := $(inc).dep
-$(inc_dep_file) :
-	$(hide) mkdir -p $(dir $@)
-	$(hide) rm -f $(stem).*
-	$(hide) touch $@
+LOCAL_MODULE_CLASS := EXECUTABLES
+inc := $(call local-generated-sources-dir)/register.inc
 
 $(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
-$(inc) : $(inc_dep_file)
+$(inc) :
 	$(hide) mkdir -p $(dir $@)
 	$(hide) echo "" > $@
 	$(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;)
@@ -132,11 +122,9 @@
 	$(hide) $(foreach lib,$(libs),echo "  Register_$(lib)();" >> $@;)
 	$(hide) echo "}" >> $@
 
-$(call intermediates-dir-for,EXECUTABLES,updater,,,$(TARGET_PREFER_32_BIT))/updater.o : $(inc)
-LOCAL_C_INCLUDES += $(dir $(inc))
+LOCAL_GENERATED_SOURCES := $(inc)
 
 inc :=
-inc_dep_file :=
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 03ce413..c614ccc 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -32,6 +32,7 @@
 #include <unistd.h>
 #include <fec/io.h>
 
+#include <functional>
 #include <memory>
 #include <string>
 #include <unordered_map>
@@ -43,6 +44,7 @@
 #include <android-base/unique_fd.h>
 #include <applypatch/applypatch.h>
 #include <openssl/sha.h>
+#include <private/android_filesystem_config.h>
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
@@ -66,6 +68,21 @@
   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;
@@ -339,7 +356,7 @@
 }
 
 static void* unzip_new_data(void* cookie) {
-    NewThreadInfo* nti = (NewThreadInfo*) cookie;
+    NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
     ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti);
     return nullptr;
 }
@@ -412,33 +429,109 @@
     uint8_t* patch_start;
 };
 
-// Do a source/target load for move/bsdiff/imgdiff in version 1.
-// We expect to parse the remainder of the parameter tokens as:
-//
-//    <src_range> <tgt_range>
-//
-// The source range is loaded into the provided buffer, reallocating
-// it to make it larger if necessary.
+// Print the hash in hex for corrupted source blocks (excluding the stashed blocks which is
+// handled separately).
+static void PrintHashForCorruptedSourceBlocks(const CommandParameters& params,
+                                              const std::vector<uint8_t>& buffer) {
+  LOG(INFO) << "unexpected contents of source blocks in cmd:\n" << params.cmdline;
+  CHECK(params.tokens[0] == "move" || params.tokens[0] == "bsdiff" ||
+        params.tokens[0] == "imgdiff");
 
-static int LoadSrcTgtVersion1(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
-        std::vector<uint8_t>& buffer, int fd) {
-
-    if (params.cpos + 1 >= params.tokens.size()) {
-        LOG(ERROR) << "invalid parameters";
-        return -1;
+  size_t pos = 0;
+  // Command example:
+  // move <onehash> <tgt_range> <src_blk_count> <src_range> [<loc_range> <stashed_blocks>]
+  // bsdiff <offset> <len> <src_hash> <tgt_hash> <tgt_range> <src_blk_count> <src_range>
+  //        [<loc_range> <stashed_blocks>]
+  if (params.tokens[0] == "move") {
+    // src_range for move starts at the 4th position.
+    if (params.tokens.size() < 5) {
+      LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline;
+      return;
     }
+    pos = 4;
+  } else {
+    // src_range for diff starts at the 7th position.
+    if (params.tokens.size() < 8) {
+      LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline;
+      return;
+    }
+    pos = 7;
+  }
 
-    // <src_range>
-    RangeSet src = parse_range(params.tokens[params.cpos++]);
+  // Source blocks in stash only, no work to do.
+  if (params.tokens[pos] == "-") {
+    return;
+  }
 
-    // <tgt_range>
-    tgt = parse_range(params.tokens[params.cpos++]);
+  RangeSet src = parse_range(params.tokens[pos++]);
 
-    allocate(src.size * BLOCKSIZE, buffer);
-    int rc = ReadBlocks(src, buffer, fd);
-    src_blocks = src.size;
+  RangeSet locs;
+  // If there's no stashed blocks, content in the buffer is consecutive and has the same
+  // order as the source blocks.
+  if (pos == params.tokens.size()) {
+    locs.count = 1;
+    locs.size = src.size;
+    locs.pos = { 0, src.size };
+  } else {
+    // Otherwise, the next token is the offset of the source blocks in the target range.
+    // Example: for the tokens <4,63946,63947,63948,63979> <4,6,7,8,39> <stashed_blocks>;
+    // We want to print SHA-1 for the data in buffer[6], buffer[8], buffer[9] ... buffer[38];
+    // this corresponds to the 32 src blocks #63946, #63948, #63949 ... #63978.
+    locs = parse_range(params.tokens[pos++]);
+    CHECK_EQ(src.size, locs.size);
+    CHECK_EQ(locs.pos.size() % 2, static_cast<size_t>(0));
+  }
 
-    return rc;
+  LOG(INFO) << "printing hash in hex for " << src.size << " source blocks";
+  for (size_t i = 0; i < src.size; i++) {
+    int block_num = src.get_block(i);
+    CHECK_NE(block_num, -1);
+    int buffer_index = locs.get_block(i);
+    CHECK_NE(buffer_index, -1);
+    CHECK_LE((buffer_index + 1) * BLOCKSIZE, buffer.size());
+
+    uint8_t digest[SHA_DIGEST_LENGTH];
+    SHA1(buffer.data() + buffer_index * BLOCKSIZE, BLOCKSIZE, digest);
+    std::string hexdigest = print_sha1(digest);
+    LOG(INFO) << "  block number: " << block_num << ", SHA-1: " << hexdigest;
+  }
+}
+
+// If the calculated hash for the whole stash doesn't match the stash id, print the SHA-1
+// in hex for each block.
+static void PrintHashForCorruptedStashedBlocks(const std::string& id,
+                                               const std::vector<uint8_t>& buffer,
+                                               const RangeSet& src) {
+  LOG(INFO) << "printing hash in hex for stash_id: " << id;
+  CHECK_EQ(src.size * BLOCKSIZE, buffer.size());
+
+  for (size_t i = 0; i < src.size; i++) {
+    int block_num = src.get_block(i);
+    CHECK_NE(block_num, -1);
+
+    uint8_t digest[SHA_DIGEST_LENGTH];
+    SHA1(buffer.data() + i * BLOCKSIZE, BLOCKSIZE, digest);
+    std::string hexdigest = print_sha1(digest);
+    LOG(INFO) << "  block number: " << block_num << ", SHA-1: " << hexdigest;
+  }
+}
+
+// If the stash file doesn't exist, read the source blocks this stash contains and print the
+// SHA-1 for these blocks.
+static void PrintHashForMissingStashedBlocks(const std::string& id, int fd) {
+  if (stash_map.find(id) == stash_map.end()) {
+    LOG(ERROR) << "No stash saved for id: " << id;
+    return;
+  }
+
+  LOG(INFO) << "print hash in hex for source blocks in missing stash: " << id;
+  const RangeSet& src = stash_map[id];
+  std::vector<uint8_t> buffer(src.size * BLOCKSIZE);
+  if (ReadBlocks(src, buffer, fd) == -1) {
+      LOG(ERROR) << "failed to read source blocks for stash: " << id;
+      return;
+  }
+  PrintHashForCorruptedStashedBlocks(id, buffer, src);
 }
 
 static int VerifyBlocks(const std::string& expected, const std::vector<uint8_t>& buffer,
@@ -473,92 +566,58 @@
     return fn;
 }
 
-typedef void (*StashCallback)(const std::string&, void*);
+// Does a best effort enumeration of stash files. Ignores possible non-file items in the stash
+// directory and continues despite of errors. Calls the 'callback' function for each file.
+static void EnumerateStash(const std::string& dirname,
+                           const std::function<void(const std::string&)>& callback) {
+  if (dirname.empty()) return;
 
-// Does a best effort enumeration of stash files. Ignores possible non-file
-// items in the stash directory and continues despite of errors. Calls the
-// 'callback' function for each file and passes 'data' to the function as a
-// parameter.
+  std::unique_ptr<DIR, decltype(&closedir)> directory(opendir(dirname.c_str()), closedir);
 
-static void EnumerateStash(const std::string& dirname, StashCallback callback, void* data) {
-    if (dirname.empty() || callback == nullptr) {
-        return;
+  if (directory == nullptr) {
+    if (errno != ENOENT) {
+      PLOG(ERROR) << "opendir \"" << dirname << "\" failed";
     }
-
-    std::unique_ptr<DIR, int(*)(DIR*)> directory(opendir(dirname.c_str()), closedir);
-
-    if (directory == nullptr) {
-        if (errno != ENOENT) {
-            PLOG(ERROR) << "opendir \"" << dirname << "\" failed";
-        }
-        return;
-    }
-
-    struct dirent* item;
-    while ((item = readdir(directory.get())) != nullptr) {
-        if (item->d_type != DT_REG) {
-            continue;
-        }
-
-        std::string fn = dirname + "/" + std::string(item->d_name);
-        callback(fn, data);
-    }
-}
-
-static void UpdateFileSize(const std::string& fn, void* data) {
-  if (fn.empty() || !data) {
     return;
   }
 
-  struct stat sb;
-  if (stat(fn.c_str(), &sb) == -1) {
-    PLOG(ERROR) << "stat \"" << fn << "\" failed";
-    return;
+  dirent* item;
+  while ((item = readdir(directory.get())) != nullptr) {
+    if (item->d_type != DT_REG) continue;
+    callback(dirname + "/" + item->d_name);
   }
-
-  size_t* size = static_cast<size_t*>(data);
-  *size += sb.st_size;
 }
 
 // Deletes the stash directory and all files in it. Assumes that it only
 // contains files. There is nothing we can do about unlikely, but possible
 // errors, so they are merely logged.
+static void DeleteFile(const std::string& fn) {
+  if (fn.empty()) return;
 
-static void DeleteFile(const std::string& fn, void* /* data */) {
-    if (!fn.empty()) {
-        LOG(INFO) << "deleting " << fn;
+  LOG(INFO) << "deleting " << fn;
 
-        if (unlink(fn.c_str()) == -1 && errno != ENOENT) {
-            PLOG(ERROR) << "unlink \"" << fn << "\" failed";
-        }
-    }
-}
-
-static void DeletePartial(const std::string& fn, void* data) {
-    if (android::base::EndsWith(fn, ".partial")) {
-        DeleteFile(fn, data);
-    }
+  if (unlink(fn.c_str()) == -1 && errno != ENOENT) {
+    PLOG(ERROR) << "unlink \"" << fn << "\" failed";
+  }
 }
 
 static void DeleteStash(const std::string& base) {
-    if (base.empty()) {
-        return;
+  if (base.empty()) return;
+
+  LOG(INFO) << "deleting stash " << base;
+
+  std::string dirname = GetStashFileName(base, "", "");
+  EnumerateStash(dirname, DeleteFile);
+
+  if (rmdir(dirname.c_str()) == -1) {
+    if (errno != ENOENT && errno != ENOTDIR) {
+      PLOG(ERROR) << "rmdir \"" << dirname << "\" failed";
     }
-
-    LOG(INFO) << "deleting stash " << base;
-
-    std::string dirname = GetStashFileName(base, "", "");
-    EnumerateStash(dirname, DeleteFile, nullptr);
-
-    if (rmdir(dirname.c_str()) == -1) {
-        if (errno != ENOENT && errno != ENOTDIR) {
-            PLOG(ERROR) << "rmdir \"" << dirname << "\" failed";
-        }
-    }
+  }
 }
 
-static int LoadStash(CommandParameters& params, const std::string& base, const std::string& id,
-        bool verify, size_t* blocks, std::vector<uint8_t>& buffer, bool printnoent) {
+static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks,
+                     std::vector<uint8_t>& buffer, bool printnoent) {
     // In verify mode, if source range_set was saved for the given hash,
     // check contents in the source blocks first. If the check fails,
     // search for the stashed files on /cache as usual.
@@ -573,23 +632,20 @@
             }
             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;
         }
     }
 
-    if (base.empty()) {
-        return -1;
-    }
-
     size_t blockcount = 0;
 
     if (!blocks) {
         blocks = &blockcount;
     }
 
-    std::string fn = GetStashFileName(base, id, "");
+    std::string fn = GetStashFileName(params.stashbase, id, "");
 
     struct stat sb;
     int res = stat(fn.c_str(), &sb);
@@ -597,6 +653,7 @@
     if (res == -1) {
         if (errno != ENOENT || printnoent) {
             PLOG(ERROR) << "stat \"" << fn << "\" failed";
+            PrintHashForMissingStashedBlocks(id, params.fd);
         }
         return -1;
     }
@@ -624,7 +681,14 @@
 
     if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) {
         LOG(ERROR) << "unexpected contents in " << fn;
-        DeleteFile(fn, nullptr);
+        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;
     }
 
@@ -632,7 +696,7 @@
 }
 
 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;
     }
@@ -670,6 +734,11 @@
         return -1;
     }
 
+    if (fchown(fd, AID_SYSTEM, AID_SYSTEM) != 0) {  // system user
+        PLOG(ERROR) << "failed to chown \"" << fn << "\"";
+        return -1;
+    }
+
     if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) {
         return -1;
     }
@@ -739,6 +808,12 @@
       return -1;
     }
 
+    if (chown(dirname.c_str(), AID_SYSTEM, AID_SYSTEM) != 0) {  // system user
+      ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s\n", dirname.c_str(),
+                 strerror(errno));
+      return -1;
+    }
+
     if (CacheSizeCheck(max_stash_size) != 0) {
       ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)\n",
                  max_stash_size);
@@ -750,13 +825,24 @@
 
   LOG(INFO) << "using existing stash " << dirname;
 
-  // If the directory already exists, calculate the space already allocated to
-  // stash files and check if there's enough for all required blocks. Delete any
-  // partially completed stash files first.
+  // If the directory already exists, calculate the space already allocated to stash files and check
+  // if there's enough for all required blocks. Delete any partially completed stash files first.
+  EnumerateStash(dirname, [](const std::string& fn) {
+    if (android::base::EndsWith(fn, ".partial")) {
+      DeleteFile(fn);
+    }
+  });
 
-  EnumerateStash(dirname, DeletePartial, nullptr);
   size_t existing = 0;
-  EnumerateStash(dirname, UpdateFileSize, &existing);
+  EnumerateStash(dirname, [&existing](const std::string& fn) {
+    if (fn.empty()) return;
+    struct stat sb;
+    if (stat(fn.c_str(), &sb) == -1) {
+      PLOG(ERROR) << "stat \"" << fn << "\" failed";
+      return;
+    }
+    existing += static_cast<size_t>(sb.st_size);
+  });
 
   if (max_stash_size > existing) {
     size_t needed = max_stash_size - existing;
@@ -770,61 +856,14 @@
   return 0;  // Using existing directory
 }
 
-static int SaveStash(CommandParameters& params, const std::string& base,
-        std::vector<uint8_t>& buffer, int fd, bool usehash) {
-
-    // <stash_id> <src_range>
-    if (params.cpos + 1 >= params.tokens.size()) {
-        LOG(ERROR) << "missing id and/or src range fields in stash command";
-        return -1;
-    }
-    const std::string& id = params.tokens[params.cpos++];
-
-    size_t blocks = 0;
-    if (usehash && LoadStash(params, base, id, true, &blocks, buffer, false) == 0) {
-        // Stash file already exists and has expected contents. Do not
-        // read from source again, as the source may have been already
-        // overwritten during a previous attempt.
-        return 0;
-    }
-
-    RangeSet src = parse_range(params.tokens[params.cpos++]);
-
-    allocate(src.size * BLOCKSIZE, buffer);
-    if (ReadBlocks(src, buffer, fd) == -1) {
-        return -1;
-    }
-    blocks = src.size;
-
-    if (usehash && VerifyBlocks(id, buffer, blocks, true) != 0) {
-        // Source blocks have unexpected contents. If we actually need this
-        // data later, this is an unrecoverable error. However, the command
-        // that uses the data may have already completed previously, so the
-        // possible failure will occur during source block verification.
-        LOG(ERROR) << "failed to load source blocks for stash " << id;
-        return 0;
-    }
-
-    // In verify mode, save source range_set instead of stashing blocks.
-    if (!params.canwrite && usehash) {
-        stash_map[id] = src;
-        return 0;
-    }
-
-    LOG(INFO) << "stashing " << blocks << " blocks to " << id;
-    params.stashed += blocks;
-    return WriteStash(base, id, blocks, buffer, false, nullptr);
-}
-
 static int FreeStash(const std::string& base, const std::string& id) {
-    if (base.empty() || id.empty()) {
-        return -1;
-    }
+  if (base.empty() || id.empty()) {
+    return -1;
+  }
 
-    std::string fn = GetStashFileName(base, id, "");
-    DeleteFile(fn, nullptr);
+  DeleteFile(GetStashFileName(base, id, ""));
 
-    return 0;
+  return 0;
 }
 
 static void MoveRange(std::vector<uint8_t>& dest, const RangeSet& locs,
@@ -856,13 +895,12 @@
 //    <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...>
 //        (loads data from both source image and stashes)
 //
-// On return, buffer is filled with the loaded source data (rearranged
-// and combined with stashed data as necessary).  buffer may be
-// reallocated if needed to accommodate the source data.  *tgt is the
-// target RangeSet.  Any stashes required are loaded using LoadStash.
+// On return, params.buffer is filled with the loaded source data (rearranged and combined with
+// stashed data as necessary). buffer may be reallocated if needed to accommodate the source data.
+// *tgt is the target RangeSet. Any stashes required are loaded using LoadStash.
 
 static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
-        std::vector<uint8_t>& buffer, int fd, const std::string& stashbase, bool* overlap) {
+                              bool* overlap) {
 
     // At least it needs to provide three parameters: <tgt_range>,
     // <src_block_count> and "-"/<src_range>.
@@ -881,7 +919,7 @@
         return -1;
     }
 
-    allocate(src_blocks * BLOCKSIZE, buffer);
+    allocate(src_blocks * BLOCKSIZE, params.buffer);
 
     // "-" or <src_range> [<src_loc>]
     if (params.tokens[params.cpos] == "-") {
@@ -889,7 +927,7 @@
         params.cpos++;
     } else {
         RangeSet src = parse_range(params.tokens[params.cpos++]);
-        int res = ReadBlocks(src, buffer, fd);
+        int res = ReadBlocks(src, params.buffer, params.fd);
 
         if (overlap) {
             *overlap = range_overlaps(src, tgt);
@@ -905,7 +943,7 @@
         }
 
         RangeSet locs = parse_range(params.tokens[params.cpos++]);
-        MoveRange(buffer, locs, buffer);
+        MoveRange(params.buffer, locs, params.buffer);
     }
 
     // <[stash_id:stash_range]>
@@ -920,7 +958,7 @@
         }
 
         std::vector<uint8_t> stash;
-        int res = LoadStash(params, stashbase, tokens[0], false, nullptr, stash, true);
+        int res = LoadStash(params, tokens[0], false, nullptr, stash, true);
 
         if (res == -1) {
             // These source blocks will fail verification if used later, but we
@@ -931,32 +969,41 @@
 
         RangeSet locs = parse_range(tokens[1]);
 
-        MoveRange(buffer, locs, stash);
+        MoveRange(params.buffer, locs, stash);
     }
 
     return 0;
 }
 
-// Do a source/target load for move/bsdiff/imgdiff in version 3.
-//
-// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which
-// tells the function whether to expect separate source and targe block hashes, or
-// if they are both the same and only one hash should be expected, and
-// 'isunresumable', which receives a non-zero value if block verification fails in
-// a way that the update cannot be resumed anymore.
-//
-// If the function is unable to load the necessary blocks or their contents don't
-// match the hashes, the return value is -1 and the command should be aborted.
-//
-// If the return value is 1, the command has already been completed according to
-// the contents of the target blocks, and should not be performed again.
-//
-// If the return value is 0, source blocks have expected content and the command
-// can be performed.
-
+/**
+ * Do a source/target load for move/bsdiff/imgdiff in version 3.
+ *
+ * We expect to parse the remainder of the parameter tokens as one of:
+ *
+ *    <tgt_range> <src_block_count> <src_range>
+ *        (loads data from source image only)
+ *
+ *    <tgt_range> <src_block_count> - <[stash_id:stash_range] ...>
+ *        (loads data from stashes only)
+ *
+ *    <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...>
+ *        (loads data from both source image and stashes)
+ *
+ * Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which tells the function
+ * whether to expect separate source and targe block hashes, or if they are both the same and only
+ * one hash should be expected, and 'isunresumable', which receives a non-zero value if block
+ * verification fails in a way that the update cannot be resumed anymore.
+ *
+ * If the function is unable to load the necessary blocks or their contents don't match the hashes,
+ * the return value is -1 and the command should be aborted.
+ *
+ * If the return value is 1, the command has already been completed according to the contents of the
+ * target blocks, and should not be performed again.
+ *
+ * If the return value is 0, source blocks have expected content and the command can be performed.
+ */
 static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
-        bool onehash, bool& overlap) {
-
+                              bool onehash, bool& overlap) {
     if (params.cpos >= params.tokens.size()) {
         LOG(ERROR) << "missing source hash";
         return -1;
@@ -975,8 +1022,7 @@
         tgthash = params.tokens[params.cpos++];
     }
 
-    if (LoadSrcTgtVersion2(params, tgt, src_blocks, params.buffer, params.fd,
-                           params.stashbase, &overlap) == -1) {
+    if (LoadSrcTgtVersion2(params, tgt, src_blocks, &overlap) == -1) {
         return -1;
     }
 
@@ -987,7 +1033,7 @@
     }
 
     if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) {
-        // Target blocks already have expected content, command should be skipped
+        // Target blocks already have expected content, command should be skipped.
         return 1;
     }
 
@@ -1006,104 +1052,128 @@
             }
 
             params.stashed += src_blocks;
-            // Can be deleted when the write has completed
+            // Can be deleted when the write has completed.
             if (!stash_exists) {
                 params.freestash = srchash;
             }
         }
 
-        // Source blocks have expected content, command can proceed
+        // Source blocks have expected content, command can proceed.
         return 0;
     }
 
-    if (overlap && LoadStash(params, params.stashbase, srchash, true, nullptr, params.buffer,
-                             true) == 0) {
+    if (overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) {
         // Overlapping source blocks were previously stashed, command can proceed.
         // We are recovering from an interrupted command, so we don't know if the
         // stash can safely be deleted after this command.
         return 0;
     }
 
-    // Valid source data not available, update cannot be resumed
+    // Valid source data not available, update cannot be resumed.
     LOG(ERROR) << "partition has unexpected contents";
+    PrintHashForCorruptedSourceBlocks(params, params.buffer);
+
     params.isunresumable = true;
 
     return -1;
 }
 
 static int PerformCommandMove(CommandParameters& params) {
-    size_t blocks = 0;
-    bool overlap = false;
-    int status = 0;
-    RangeSet tgt;
+  size_t blocks = 0;
+  bool overlap = false;
+  RangeSet tgt;
+  int status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap);
 
-    if (params.version == 1) {
-        status = LoadSrcTgtVersion1(params, tgt, blocks, params.buffer, params.fd);
-    } else if (params.version == 2) {
-        status = LoadSrcTgtVersion2(params, tgt, blocks, params.buffer, params.fd,
-                params.stashbase, nullptr);
-    } else if (params.version >= 3) {
-        status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap);
-    }
+  if (status == -1) {
+    LOG(ERROR) << "failed to read blocks for move";
+    return -1;
+  }
 
-    if (status == -1) {
-        LOG(ERROR) << "failed to read blocks for move";
-        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) << "  moving " << blocks << " blocks";
+
+      if (WriteBlocks(tgt, params.buffer, params.fd) == -1) {
+        return -1;
+      }
+    } else {
+      LOG(INFO) << "skipping " << blocks << " already moved blocks";
     }
+  }
 
-    if (params.canwrite) {
-        if (status == 0) {
-            LOG(INFO) << "  moving " << blocks << " blocks";
+  if (!params.freestash.empty()) {
+    FreeStash(params.stashbase, params.freestash);
+    params.freestash.clear();
+  }
 
-            if (WriteBlocks(tgt, params.buffer, params.fd) == -1) {
-                return -1;
-            }
-        } else {
-            LOG(INFO) << "skipping " << blocks << " already moved blocks";
-        }
+  params.written += tgt.size;
 
-    }
-
-    if (!params.freestash.empty()) {
-        FreeStash(params.stashbase, params.freestash);
-        params.freestash.clear();
-    }
-
-    params.written += tgt.size;
-
-    return 0;
+  return 0;
 }
 
 static int PerformCommandStash(CommandParameters& params) {
-    return SaveStash(params, params.stashbase, params.buffer, params.fd,
-            (params.version >= 3));
+  // <stash_id> <src_range>
+  if (params.cpos + 1 >= params.tokens.size()) {
+    LOG(ERROR) << "missing id and/or src range fields in stash command";
+    return -1;
+  }
+
+  const std::string& id = params.tokens[params.cpos++];
+  size_t blocks = 0;
+  if (LoadStash(params, id, true, &blocks, params.buffer, false) == 0) {
+    // Stash file already exists and has expected contents. Do not read from source again, as the
+    // source may have been already overwritten during a previous attempt.
+    return 0;
+  }
+
+  RangeSet src = parse_range(params.tokens[params.cpos++]);
+
+  allocate(src.size * BLOCKSIZE, params.buffer);
+  if (ReadBlocks(src, params.buffer, params.fd) == -1) {
+    return -1;
+  }
+  blocks = src.size;
+  stash_map[id] = src;
+
+  if (VerifyBlocks(id, params.buffer, blocks, true) != 0) {
+    // Source blocks have unexpected contents. If we actually need this data later, this is an
+    // unrecoverable error. However, the command that uses the data may have already completed
+    // previously, so the possible failure will occur during source block verification.
+    LOG(ERROR) << "failed to load source blocks for stash " << id;
+    return 0;
+  }
+
+  // In verify mode, we don't need to stash any blocks.
+  if (!params.canwrite) {
+    return 0;
+  }
+
+  LOG(INFO) << "stashing " << blocks << " blocks to " << id;
+  params.stashed += blocks;
+  return WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr);
 }
 
 static int PerformCommandFree(CommandParameters& params) {
-    // <stash_id>
-    if (params.cpos >= params.tokens.size()) {
-        LOG(ERROR) << "missing stash id in free command";
-        return -1;
-    }
+  // <stash_id>
+  if (params.cpos >= params.tokens.size()) {
+    LOG(ERROR) << "missing stash id in free command";
+    return -1;
+  }
 
-    const std::string& id = params.tokens[params.cpos++];
+  const std::string& id = params.tokens[params.cpos++];
+  stash_map.erase(id);
 
-    if (!params.canwrite && stash_map.find(id) != stash_map.end()) {
-        stash_map.erase(id);
-        return 0;
-    }
+  if (params.createdstash || params.canwrite) {
+    return FreeStash(params.stashbase, id);
+  }
 
-    if (params.createdstash || params.canwrite) {
-        return FreeStash(params.stashbase, id);
-    }
-
-    return 0;
+  return 0;
 }
 
 static int PerformCommandZero(CommandParameters& params) {
@@ -1214,15 +1284,7 @@
     RangeSet tgt;
     size_t blocks = 0;
     bool overlap = false;
-    int status = 0;
-    if (params.version == 1) {
-        status = LoadSrcTgtVersion1(params, tgt, blocks, params.buffer, params.fd);
-    } else if (params.version == 2) {
-        status = LoadSrcTgtVersion2(params, tgt, blocks, params.buffer, params.fd,
-                params.stashbase, nullptr);
-    } else if (params.version >= 3) {
-        status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap);
-    }
+    int status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap);
 
     if (status == -1) {
         LOG(ERROR) << "failed to read blocks for diff";
@@ -1238,10 +1300,8 @@
     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;
@@ -1347,299 +1407,286 @@
 //    - new data stream (filename within package.zip)
 //    - patch stream (filename within package.zip, must be uncompressed)
 
-static Value* PerformBlockImageUpdate(const char* name, State* state, int /* argc */, Expr* argv[],
-        const Command* commands, size_t cmdcount, bool dryrun) {
-    CommandParameters params = {};
-    params.canwrite = !dryrun;
+static Value* PerformBlockImageUpdate(const char* name, State* state,
+                                      const std::vector<std::unique_ptr<Expr>>& argv,
+                                      const Command* commands, size_t cmdcount, bool dryrun) {
+  CommandParameters params = {};
+  params.canwrite = !dryrun;
 
-    LOG(INFO) << "performing " << (dryrun ? "verification" : "update");
-    if (state->is_retry) {
-        is_retry = true;
-        LOG(INFO) << "This update is a retry.";
+  LOG(INFO) << "performing " << (dryrun ? "verification" : "update");
+  if (state->is_retry) {
+    is_retry = true;
+    LOG(INFO) << "This update is a retry.";
+  }
+  if (argv.size() != 4) {
+    ErrorAbort(state, kArgsParsingFailure, "block_image_update expects 4 arguments, got %zu",
+               argv.size());
+    return StringValue("");
+  }
+
+  std::vector<std::unique_ptr<Value>> args;
+  if (!ReadValueArgs(state, argv, &args)) {
+    return nullptr;
+  }
+
+  const Value* blockdev_filename = args[0].get();
+  const Value* transfer_list_value = args[1].get();
+  const Value* new_data_fn = args[2].get();
+  const Value* patch_data_fn = args[3].get();
+
+  if (blockdev_filename->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name);
+    return StringValue("");
+  }
+  if (transfer_list_value->type != VAL_BLOB) {
+    ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name);
+    return StringValue("");
+  }
+  if (new_data_fn->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name);
+    return StringValue("");
+  }
+  if (patch_data_fn->type != VAL_STRING) {
+    ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string", name);
+    return StringValue("");
+  }
+
+  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
+  if (ui == nullptr) {
+    return StringValue("");
+  }
+
+  FILE* cmd_pipe = ui->cmd_pipe;
+  ZipArchiveHandle za = ui->package_zip;
+
+  if (cmd_pipe == nullptr || za == nullptr) {
+    return StringValue("");
+  }
+
+  ZipString path_data(patch_data_fn->data.c_str());
+  ZipEntry patch_entry;
+  if (FindEntry(za, path_data, &patch_entry) != 0) {
+    LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package";
+    return StringValue("");
+  }
+
+  params.patch_start = ui->package_zip_addr + patch_entry.offset;
+  ZipString new_data(new_data_fn->data.c_str());
+  ZipEntry new_entry;
+  if (FindEntry(za, new_data, &new_entry) != 0) {
+    LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package";
+    return StringValue("");
+  }
+
+  params.fd.reset(TEMP_FAILURE_RETRY(ota_open(blockdev_filename->data.c_str(), O_RDWR)));
+  if (params.fd == -1) {
+    PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
+    return StringValue("");
+  }
+
+  if (params.canwrite) {
+    params.nti.za = za;
+    params.nti.entry = new_entry;
+
+    pthread_mutex_init(&params.nti.mu, nullptr);
+    pthread_cond_init(&params.nti.cv, nullptr);
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+    int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
+    if (error != 0) {
+      PLOG(ERROR) << "pthread_create failed";
+      return StringValue("");
+    }
+  }
+
+  std::vector<std::string> lines = android::base::Split(transfer_list_value->data, "\n");
+  if (lines.size() < 2) {
+    ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n",
+               lines.size());
+    return StringValue("");
+  }
+
+  // First line in transfer list is the version number.
+  if (!android::base::ParseInt(lines[0], &params.version, 3, 4)) {
+    LOG(ERROR) << "unexpected transfer list version [" << lines[0] << "]";
+    return StringValue("");
+  }
+
+  LOG(INFO) << "blockimg version is " << params.version;
+
+  // Second line in transfer list is the total number of blocks we expect to write.
+  size_t total_blocks;
+  if (!android::base::ParseUint(lines[1], &total_blocks)) {
+    ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str());
+    return StringValue("");
+  }
+
+  if (total_blocks == 0) {
+    return StringValue("t");
+  }
+
+  size_t start = 2;
+  if (lines.size() < 4) {
+    ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
+               lines.size());
+    return StringValue("");
+  }
+
+  // Third line is how many stash entries are needed simultaneously.
+  LOG(INFO) << "maximum stash entries " << lines[2];
+
+  // Fourth line is the maximum number of blocks that will be stashed simultaneously
+  size_t stash_max_blocks;
+  if (!android::base::ParseUint(lines[3], &stash_max_blocks)) {
+    ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n",
+               lines[3].c_str());
+    return StringValue("");
+  }
+
+  int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase);
+  if (res == -1) {
+    return StringValue("");
+  }
+
+  params.createdstash = res;
+
+  start += 2;
+
+  // Build a map of the available commands
+  std::unordered_map<std::string, const Command*> cmd_map;
+  for (size_t i = 0; i < cmdcount; ++i) {
+    if (cmd_map.find(commands[i].name) != cmd_map.end()) {
+      LOG(ERROR) << "Error: command [" << commands[i].name << "] already exists in the cmd map.";
+      return StringValue(strdup(""));
+    }
+    cmd_map[commands[i].name] = &commands[i];
+  }
+
+  int rc = -1;
+
+  // Subsequent lines are all individual transfer commands
+  for (auto it = lines.cbegin() + start; it != lines.cend(); it++) {
+    const std::string& line(*it);
+    if (line.empty()) continue;
+
+    params.tokens = android::base::Split(line, " ");
+    params.cpos = 0;
+    params.cmdname = params.tokens[params.cpos++].c_str();
+    params.cmdline = line.c_str();
+
+    if (cmd_map.find(params.cmdname) == cmd_map.end()) {
+      LOG(ERROR) << "unexpected command [" << params.cmdname << "]";
+      goto pbiudone;
     }
 
-    std::vector<std::unique_ptr<Value>> args;
-    if (!ReadValueArgs(state, 4, argv, &args)) {
-        return nullptr;
-    }
+    const Command* cmd = cmd_map[params.cmdname];
 
-    const Value* blockdev_filename = args[0].get();
-    const Value* transfer_list_value = args[1].get();
-    const Value* new_data_fn = args[2].get();
-    const Value* patch_data_fn = args[3].get();
-
-    if (blockdev_filename->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string",
-                   name);
-        return StringValue("");
-    }
-    if (transfer_list_value->type != VAL_BLOB) {
-        ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name);
-        return StringValue("");
-    }
-    if (new_data_fn->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name);
-        return StringValue("");
-    }
-    if (patch_data_fn->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string",
-                   name);
-        return StringValue("");
-    }
-
-    UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-    if (ui == nullptr) {
-        return StringValue("");
-    }
-
-    FILE* cmd_pipe = ui->cmd_pipe;
-    ZipArchiveHandle za = ui->package_zip;
-
-    if (cmd_pipe == nullptr || za == nullptr) {
-        return StringValue("");
-    }
-
-    ZipString path_data(patch_data_fn->data.c_str());
-    ZipEntry patch_entry;
-    if (FindEntry(za, path_data, &patch_entry) != 0) {
-        LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package";
-        return StringValue("");
-    }
-
-    params.patch_start = ui->package_zip_addr + patch_entry.offset;
-    ZipString new_data(new_data_fn->data.c_str());
-    ZipEntry new_entry;
-    if (FindEntry(za, new_data, &new_entry) != 0) {
-        LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package";
-        return StringValue("");
-    }
-
-    params.fd.reset(TEMP_FAILURE_RETRY(ota_open(blockdev_filename->data.c_str(), O_RDWR)));
-    if (params.fd == -1) {
-        PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
-        return StringValue("");
+    if (cmd->f != nullptr && cmd->f(params) == -1) {
+      LOG(ERROR) << "failed to execute command [" << line << "]";
+      goto pbiudone;
     }
 
     if (params.canwrite) {
-        params.nti.za = za;
-        params.nti.entry = new_entry;
-
-        pthread_mutex_init(&params.nti.mu, nullptr);
-        pthread_cond_init(&params.nti.cv, nullptr);
-        pthread_attr_t attr;
-        pthread_attr_init(&attr);
-        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
-
-        int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
-        if (error != 0) {
-            PLOG(ERROR) << "pthread_create failed";
-            return StringValue("");
-        }
-    }
-
-    std::vector<std::string> lines = android::base::Split(transfer_list_value->data, "\n");
-    if (lines.size() < 2) {
-        ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n",
-                   lines.size());
-        return StringValue("");
-    }
-
-    // First line in transfer list is the version number
-    if (!android::base::ParseInt(lines[0], &params.version, 1, 4)) {
-        LOG(ERROR) << "unexpected transfer list version [" << lines[0] << "]";
-        return StringValue("");
-    }
-
-    LOG(INFO) << "blockimg version is " << params.version;
-
-    // Second line in transfer list is the total number of blocks we expect to write
-    size_t total_blocks;
-    if (!android::base::ParseUint(lines[1], &total_blocks)) {
-        ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str());
-        return StringValue("");
-    }
-
-    if (total_blocks == 0) {
-        return StringValue("t");
-    }
-
-    size_t start = 2;
-    if (params.version >= 2) {
-        if (lines.size() < 4) {
-          ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
-                     lines.size());
-          return StringValue("");
-        }
-
-        // Third line is how many stash entries are needed simultaneously
-        LOG(INFO) << "maximum stash entries " << lines[2];
-
-        // Fourth line is the maximum number of blocks that will be stashed simultaneously
-        size_t stash_max_blocks;
-        if (!android::base::ParseUint(lines[3], &stash_max_blocks)) {
-            ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n",
-                       lines[3].c_str());
-            return StringValue("");
-        }
-
-        int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase);
-        if (res == -1) {
-            return StringValue("");
-        }
-
-        params.createdstash = res;
-
-        start += 2;
-    }
-
-    // Build a map of the available commands
-    std::unordered_map<std::string, const Command*> cmd_map;
-    for (size_t i = 0; i < cmdcount; ++i) {
-        if (cmd_map.find(commands[i].name) != cmd_map.end()) {
-            LOG(ERROR) << "Error: command [" << commands[i].name
-                       << "] already exists in the cmd map.";
-            return StringValue(strdup(""));
-        }
-        cmd_map[commands[i].name] = &commands[i];
-    }
-
-    int rc = -1;
-
-    // Subsequent lines are all individual transfer commands
-    for (auto it = lines.cbegin() + start; it != lines.cend(); it++) {
-        const std::string& line(*it);
-        if (line.empty()) continue;
-
-        params.tokens = android::base::Split(line, " ");
-        params.cpos = 0;
-        params.cmdname = params.tokens[params.cpos++].c_str();
-        params.cmdline = line.c_str();
-
-        if (cmd_map.find(params.cmdname) == cmd_map.end()) {
-            LOG(ERROR) << "unexpected command [" << params.cmdname << "]";
-            goto pbiudone;
-        }
-
-        const Command* cmd = cmd_map[params.cmdname];
-
-        if (cmd->f != nullptr && cmd->f(params) == -1) {
-            LOG(ERROR) << "failed to execute command [" << line << "]";
-            goto pbiudone;
-        }
-
-        if (params.canwrite) {
-            if (ota_fsync(params.fd) == -1) {
-                failure_type = kFsyncFailure;
-                PLOG(ERROR) << "fsync failed";
-                goto pbiudone;
-            }
-            fprintf(cmd_pipe, "set_progress %.4f\n",
-                    static_cast<double>(params.written) / total_blocks);
-            fflush(cmd_pipe);
-        }
-    }
-
-    if (params.canwrite) {
-        pthread_join(params.thread, nullptr);
-
-        LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
-        LOG(INFO) << "stashed " << params.stashed << " blocks";
-        LOG(INFO) << "max alloc needed was " << params.buffer.size();
-
-        const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
-        if (partition != nullptr && *(partition + 1) != 0) {
-            fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1,
-                    params.written * BLOCKSIZE);
-            fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1,
-                    params.stashed * BLOCKSIZE);
-            fflush(cmd_pipe);
-        }
-        // Delete stash only after successfully completing the update, as it
-        // may contain blocks needed to complete the update later.
-        DeleteStash(params.stashbase);
-    } else {
-        LOG(INFO) << "verified partition contents; update may be resumed";
-    }
-
-    rc = 0;
-
-pbiudone:
-    if (ota_fsync(params.fd) == -1) {
+      if (ota_fsync(params.fd) == -1) {
         failure_type = kFsyncFailure;
         PLOG(ERROR) << "fsync failed";
+        goto pbiudone;
+      }
+      fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks);
+      fflush(cmd_pipe);
     }
-    // params.fd will be automatically closed because it's a unique_fd.
+  }
 
-    // Only delete the stash if the update cannot be resumed, or it's
-    // a verification run and we created the stash.
-    if (params.isunresumable || (!params.canwrite && params.createdstash)) {
-        DeleteStash(params.stashbase);
+  if (params.canwrite) {
+    pthread_join(params.thread, nullptr);
+
+    LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
+    LOG(INFO) << "stashed " << params.stashed << " blocks";
+    LOG(INFO) << "max alloc needed was " << params.buffer.size();
+
+    const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+    if (partition != nullptr && *(partition + 1) != 0) {
+      fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
+      fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
+      fflush(cmd_pipe);
     }
+    // Delete stash only after successfully completing the update, as it may contain blocks needed
+    // to complete the update later.
+    DeleteStash(params.stashbase);
+  } else {
+    LOG(INFO) << "verified partition contents; update may be resumed";
+  }
 
-    if (failure_type != kNoCause && state->cause_code == kNoCause) {
-        state->cause_code = failure_type;
-    }
+  rc = 0;
 
-    return StringValue(rc == 0 ? "t" : "");
+pbiudone:
+  if (ota_fsync(params.fd) == -1) {
+    failure_type = kFsyncFailure;
+    PLOG(ERROR) << "fsync failed";
+  }
+  // params.fd will be automatically closed because it's a unique_fd.
+
+  // Only delete the stash if the update cannot be resumed, or it's a verification run and we
+  // created the stash.
+  if (params.isunresumable || (!params.canwrite && params.createdstash)) {
+    DeleteStash(params.stashbase);
+  }
+
+  if (failure_type != kNoCause && state->cause_code == kNoCause) {
+    state->cause_code = failure_type;
+  }
+
+  return StringValue(rc == 0 ? "t" : "");
 }
 
-// The transfer list is a text file containing commands to
-// transfer data from one place to another on the target
-// partition.  We parse it and execute the commands in order:
-//
-//    zero [rangeset]
-//      - fill the indicated blocks with zeros
-//
-//    new [rangeset]
-//      - fill the blocks with data read from the new_data file
-//
-//    erase [rangeset]
-//      - mark the given blocks as empty
-//
-//    move <...>
-//    bsdiff <patchstart> <patchlen> <...>
-//    imgdiff <patchstart> <patchlen> <...>
-//      - read the source blocks, apply a patch (or not in the
-//        case of move), write result to target blocks.  bsdiff or
-//        imgdiff specifies the type of patch; move means no patch
-//        at all.
-//
-//        The format of <...> differs between versions 1 and 2;
-//        see the LoadSrcTgtVersion{1,2}() functions for a
-//        description of what's expected.
-//
-//    stash <stash_id> <src_range>
-//      - (version 2+ only) load the given source range and stash
-//        the data in the given slot of the stash table.
-//
-//    free <stash_id>
-//      - (version 3+ only) free the given stash data.
-//
-// The creator of the transfer list will guarantee that no block
-// is read (ie, used as the source for a patch or move) after it
-// has been written.
-//
-// In version 2, the creator will guarantee that a given stash is
-// loaded (with a stash command) before it's used in a
-// move/bsdiff/imgdiff command.
-//
-// Within one command the source and target ranges may overlap so
-// in general we need to read the entire source into memory before
-// writing anything to the target blocks.
-//
-// All the patch data is concatenated into one patch_data file in
-// the update package.  It must be stored uncompressed because we
-// memory-map it in directly from the archive.  (Since patches are
-// already compressed, we lose very little by not compressing
-// their concatenation.)
-//
-// In version 3, commands that read data from the partition (i.e.
-// move/bsdiff/imgdiff/stash) have one or more additional hashes
-// before the range parameters, which are used to check if the
-// command has already been completed and verify the integrity of
-// the source data.
-
-Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) {
+/**
+ * The transfer list is a text file containing commands to transfer data from one place to another
+ * on the target partition. We parse it and execute the commands in order:
+ *
+ *    zero [rangeset]
+ *      - Fill the indicated blocks with zeros.
+ *
+ *    new [rangeset]
+ *      - Fill the blocks with data read from the new_data file.
+ *
+ *    erase [rangeset]
+ *      - Mark the given blocks as empty.
+ *
+ *    move <...>
+ *    bsdiff <patchstart> <patchlen> <...>
+ *    imgdiff <patchstart> <patchlen> <...>
+ *      - Read the source blocks, apply a patch (or not in the case of move), write result to target
+ *      blocks.  bsdiff or imgdiff specifies the type of patch; move means no patch at all.
+ *
+ *        See the comments in LoadSrcTgtVersion3() for a description of the <...> format.
+ *
+ *    stash <stash_id> <src_range>
+ *      - Load the given source range and stash the data in the given slot of the stash table.
+ *
+ *    free <stash_id>
+ *      - Free the given stash data.
+ *
+ * The creator of the transfer list will guarantee that no block is read (ie, used as the source for
+ * a patch or move) after it has been written.
+ *
+ * The creator will guarantee that a given stash is loaded (with a stash command) before it's used
+ * in a move/bsdiff/imgdiff command.
+ *
+ * Within one command the source and target ranges may overlap so in general we need to read the
+ * entire source into memory before writing anything to the target blocks.
+ *
+ * All the patch data is concatenated into one patch_data file in the update package. It must be
+ * stored uncompressed because we memory-map it in directly from the archive. (Since patches are
+ * already compressed, we lose very little by not compressing their concatenation.)
+ *
+ * Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more
+ * additional hashes before the range parameters, which are used to check if the command has already
+ * been completed and verify the integrity of the source data.
+ */
+Value* BlockImageVerifyFn(const char* name, State* state,
+                          const std::vector<std::unique_ptr<Expr>>& argv) {
     // Commands which are not tested are set to nullptr to skip them completely
     const Command commands[] = {
         { "bsdiff",     PerformCommandDiff  },
@@ -1653,11 +1700,12 @@
     };
 
     // Perform a dry run without writing to test if an update can proceed
-    return PerformBlockImageUpdate(name, state, argc, argv, commands,
+    return PerformBlockImageUpdate(name, state, argv, commands,
                 sizeof(commands) / sizeof(commands[0]), true);
 }
 
-Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* BlockImageUpdateFn(const char* name, State* state,
+                          const std::vector<std::unique_ptr<Expr>>& argv) {
     const Command commands[] = {
         { "bsdiff",     PerformCommandDiff  },
         { "erase",      PerformCommandErase },
@@ -1669,13 +1717,19 @@
         { "zero",       PerformCommandZero  }
     };
 
-    return PerformBlockImageUpdate(name, state, argc, argv, commands,
+    return PerformBlockImageUpdate(name, state, argv, commands,
                 sizeof(commands) / sizeof(commands[0]), false);
 }
 
-Value* RangeSha1Fn(const char* name, State* state, int /* argc */, Expr* argv[]) {
+Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2) {
+        ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu",
+                   argv.size());
+        return StringValue("");
+    }
+
     std::vector<std::unique_ptr<Value>> args;
-    if (!ReadValueArgs(state, 2, argv, &args)) {
+    if (!ReadValueArgs(state, argv, &args)) {
         return nullptr;
     }
 
@@ -1733,9 +1787,16 @@
 // 1st block of each partition and check for mounting time/count. It return string "t"
 // if executes successfully and an empty string otherwise.
 
-Value* CheckFirstBlockFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* 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("");
+    }
+
     std::vector<std::unique_ptr<Value>> args;
-    if (!ReadValueArgs(state, 1, argv, &args)) {
+    if (!ReadValueArgs(state, argv, &args)) {
         return nullptr;
     }
 
@@ -1781,9 +1842,16 @@
 }
 
 
-Value* BlockImageRecoverFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* BlockImageRecoverFn(const char* name, State* state,
+                           const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() != 2) {
+        ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu",
+                   argv.size());
+        return StringValue("");
+    }
+
     std::vector<std::unique_ptr<Value>> args;
-    if (!ReadValueArgs(state, 2, argv, &args)) {
+    if (!ReadValueArgs(state, argv, &args)) {
         return nullptr;
     }
 
diff --git a/updater/install.cpp b/updater/install.cpp
index 0963333..f91f3fc 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -126,15 +126,16 @@
 
 // mount(fs_type, partition_type, location, mount_point)
 // mount(fs_type, partition_type, location, mount_point, mount_options)
-//
+
 //    fs_type="ext4"   partition_type="EMMC"    location=device
-Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 4 && argc != 5) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %d", name, argc);
+Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 4 && argv.size() != 5) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, argc, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& fs_type = args[0];
@@ -143,7 +144,7 @@
   const std::string& mount_point = args[3];
   std::string mount_options;
 
-  if (argc == 5) {
+  if (argv.size() == 5) {
     mount_options = args[4];
   }
 
@@ -188,15 +189,14 @@
   return StringValue(mount_point);
 }
 
-
 // is_mounted(mount_point)
-Value* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
+Value* IsMountedFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, argc, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& mount_point = args[0];
@@ -214,12 +214,12 @@
   return StringValue(mount_point);
 }
 
-Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
+Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
   }
   std::vector<std::string> args;
-  if (!ReadArgs(state, argc, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& mount_point = args[0];
@@ -265,13 +265,14 @@
 //    if fs_size == 0, then make fs uses the entire partition.
 //    if fs_size > 0, that is the size to use
 //    if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs")
-Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 5) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %d", name, argc);
+Value* FormatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 5) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, argc, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& fs_type = args[0];
@@ -332,13 +333,15 @@
   return nullptr;
 }
 
-Value* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 2) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+Value* ShowProgressFn(const char* name, State* state,
+                      const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, argc, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& frac_str = args[0];
@@ -361,13 +364,13 @@
   return StringValue(frac_str);
 }
 
-Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
+Value* SetProgressFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 1, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& frac_str = args[0];
@@ -390,13 +393,15 @@
 //   Example: package_extract_dir("system", "/system")
 //
 //   Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path.
-Value* PackageExtractDirFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 2) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+Value* PackageExtractDirFn(const char* name, State* state,
+                           const std::vector<std::unique_ptr<Expr>>&argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 2, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& zip_path = args[0];
@@ -416,17 +421,20 @@
 //   Extracts a single package_file from the update package and writes it to dest_file,
 //   overwriting existing files if necessary. Without the dest_file argument, returns the
 //   contents of the package file as a binary blob.
-Value* PackageExtractFileFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc < 1 || argc > 2) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %d", name, argc);
+Value* PackageExtractFileFn(const char* name, State* state,
+                            const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1 || argv.size() > 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name,
+                      argv.size());
   }
 
-  if (argc == 2) {
+  if (argv.size() == 2) {
     // The two-argument version extracts to a file.
 
     std::vector<std::string> args;
-    if (!ReadArgs(state, 2, argv, &args)) {
-      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %d args", name, argc);
+    if (!ReadArgs(state, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+                        argv.size());
     }
     const std::string& zip_path = args[0];
     const std::string& dest_path = args[1];
@@ -468,8 +476,9 @@
     // The one-argument version returns the contents of the file as the result.
 
     std::vector<std::string> args;
-    if (!ReadArgs(state, 1, argv, &args)) {
-      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %d args", name, argc);
+    if (!ReadArgs(state, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+                        argv.size());
     }
     const std::string& zip_path = args[0];
 
@@ -495,9 +504,9 @@
   }
 }
 
-Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
+Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
   }
   std::string key;
   if (!Evaluate(state, argv[0], &key)) {
@@ -513,13 +522,14 @@
 //   interprets 'file' as a getprop-style file (key=value pairs, one
 //   per line. # comment lines, blank lines, lines without '=' ignored),
 //   and returns the value for 'key' (or "" if it isn't defined).
-Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 2) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+Value* FileGetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 2, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& filename = args[0];
@@ -578,9 +588,13 @@
 }
 
 // apply_patch_space(bytes)
-Value* ApplyPatchSpaceFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name,
+                      argv.size());
+  }
   std::vector<std::string> args;
-  if (!ReadArgs(state, 1, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& bytes_str = args[0];
@@ -606,14 +620,14 @@
 //   state. If the process is interrupted during patching, the target file may be in an intermediate
 //   state; a copy exists in the cache partition so restarting the update can successfully update
 //   the file.
-Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc < 6 || (argc % 2) == 1) {
+Value* ApplyPatchFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+    if (argv.size() < 6 || (argv.size() % 2) == 1) {
         return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 6 args and an "
-                          "even number, got %d", name, argc);
+                          "even number, got %zu", name, argv.size());
     }
 
     std::vector<std::string> args;
-    if (!ReadArgs(state, 4, argv, &args)) {
+    if (!ReadArgs(state, argv, &args, 0, 4)) {
         return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
     }
     const std::string& source_filename = args[0];
@@ -627,9 +641,9 @@
                           name, target_size_str.c_str());
     }
 
-    int patchcount = (argc-4) / 2;
+    int patchcount = (argv.size()-4) / 2;
     std::vector<std::unique_ptr<Value>> arg_values;
-    if (!ReadValueArgs(state, argc-4, argv+4, &arg_values)) {
+    if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) {
         return nullptr;
     }
 
@@ -664,20 +678,20 @@
 //   specified as 40 hex digits. This function differs from sha1_check(read_file(filename),
 //   sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will
 //   succeed even if the file was corrupted by an interrupted apply_patch() update.
-Value* ApplyPatchCheckFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc < 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %d", name,
-                      argc);
+Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 1, argv, &args)) {
+  if (!ReadArgs(state, argv, &args, 0, 1)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& filename = args[0];
 
   std::vector<std::string> sha1s;
-  if (!ReadArgs(state, argc - 1, argv + 1, &sha1s)) {
+  if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   int result = applypatch_check(filename.c_str(), sha1s);
@@ -687,9 +701,9 @@
 
 // This is the updater side handler for ui_print() in edify script. Contents
 // will be sent over to the recovery side for on-screen display.
-Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
+Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
   std::vector<std::string> args;
-  if (!ReadArgs(state, argc, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
 
@@ -698,31 +712,32 @@
   return StringValue(buffer);
 }
 
-Value* WipeCacheFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 0) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %d", name, argc);
+Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (!argv.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
+                      argv.size());
   }
   fprintf(static_cast<UpdaterInfo*>(state->cookie)->cmd_pipe, "wipe_cache\n");
   return StringValue("t");
 }
 
-Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc < 1) {
+Value* RunProgramFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, argc, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
 
-  char* args2[argc + 1];
-  for (int i = 0; i < argc; i++) {
+  char* args2[argv.size() + 1];
+  for (size_t i = 0; i < argv.size(); i++) {
     args2[i] = &args[i][0];
   }
-  args2[argc] = nullptr;
+  args2[argv.size()] = nullptr;
 
-  LOG(INFO) << "about to run program [" << args2[0] << "] with " << argc << " args";
+  LOG(INFO) << "about to run program [" << args2[0] << "] with " << argv.size() << " args";
 
   pid_t child = fork();
   if (child == 0) {
@@ -752,13 +767,13 @@
 //    returns the sha1 of the file if it matches any of the hex
 //    strings passed, or "" if it does not equal any of them.
 //
-Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc < 1) {
+Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
   }
 
   std::vector<std::unique_ptr<Value>> args;
-  if (!ReadValueArgs(state, argc, argv, &args)) {
+  if (!ReadValueArgs(state, argv, &args)) {
     return nullptr;
   }
 
@@ -768,11 +783,11 @@
   uint8_t digest[SHA_DIGEST_LENGTH];
   SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest);
 
-  if (argc == 1) {
+  if (argv.size() == 1) {
     return StringValue(print_sha1(digest));
   }
 
-  for (int i = 1; i < argc; ++i) {
+  for (size_t i = 1; i < argv.size(); ++i) {
     uint8_t arg_digest[SHA_DIGEST_LENGTH];
     if (args[i]->type != VAL_STRING) {
       LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping";
@@ -791,13 +806,13 @@
 
 // Read a local file and return its contents (the Value* returned
 // is actually a FileContents*).
-Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
+Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 1, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& filename = args[0];
@@ -815,13 +830,14 @@
 // write_value(value, filename)
 //   Writes 'value' to 'filename'.
 //   Example: write_value("960000", "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq")
-Value* WriteValueFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 2) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+Value* WriteValueFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 2, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
   }
 
@@ -848,13 +864,14 @@
 // property.  It can be "recovery" to boot from the recovery
 // partition, or "" (empty string) to boot from the regular boot
 // partition.
-Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 2) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+Value* RebootNowFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 2, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
   }
   const std::string& filename = args[0];
@@ -890,13 +907,14 @@
 // ("/misc" in the fstab), which is where this value is stored.  The
 // second argument is the string to store; it should not exceed 31
 // bytes.
-Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 2) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+Value* SetStageFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 2, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& filename = args[0];
@@ -923,13 +941,13 @@
 
 // Return the value most recently saved with SetStageFn.  The argument
 // is the block device for the misc partition.
-Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
+Value* GetStageFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 1, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& filename = args[0];
@@ -944,13 +962,14 @@
   return StringValue(boot.stage);
 }
 
-Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 2) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, 2, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
   const std::string& filename = args[0];
@@ -967,38 +986,39 @@
   return StringValue((status == 0) ? "t" : "");
 }
 
-Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc != 0) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %d", name, argc);
+Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (!argv.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
+                      argv.size());
   }
   UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
   fprintf(ui->cmd_pipe, "enable_reboot\n");
   return StringValue("t");
 }
 
-Value* Tune2FsFn(const char* name, State* state, int argc, Expr* argv[]) {
-  if (argc == 0) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %d", name, argc);
+Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %zu", name, argv.size());
   }
 
   std::vector<std::string> args;
-  if (!ReadArgs(state, argc, argv, &args)) {
+  if (!ReadArgs(state, argv, &args)) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name);
   }
 
-  char* args2[argc + 1];
+  char* args2[argv.size() + 1];
   // Tune2fs expects the program name as its args[0]
   args2[0] = const_cast<char*>(name);
   if (args2[0] == nullptr) {
     return nullptr;
   }
-  for (int i = 0; i < argc; ++i) {
+  for (size_t i = 0; i < argv.size(); ++i) {
     args2[i + 1] = &args[i][0];
   }
 
   // tune2fs changes the file system parameters on an ext2 file system; it
   // returns 0 on success.
-  int result = tune2fs_main(argc + 1, args2);
+  int result = tune2fs_main(argv.size() + 1, args2);
   if (result != 0) {
     return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
   }
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 22c060f..c09e267 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -130,7 +130,7 @@
 
   // Parse the script.
 
-  Expr* root;
+  std::unique_ptr<Expr> root;
   int error_count = 0;
   int error = parse_string(script.c_str(), &root, &error_count);
   if (error != 0 || error_count > 0) {
@@ -185,7 +185,7 @@
         // Parse the error code in abort message.
         // Example: "E30: This package is for bullhead devices."
         if (!line.empty() && line[0] == 'E') {
-          if (sscanf(line.c_str(), "E%u: ", &state.error_code) != 1) {
+          if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
             LOG(ERROR) << "Failed to parse error code: [" << line << "]";
           }
         }
diff --git a/verifier.cpp b/verifier.cpp
index 44098f7..23142c1 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -22,7 +22,9 @@
 #include <string.h>
 
 #include <algorithm>
+#include <functional>
 #include <memory>
+#include <vector>
 
 #include <android-base/logging.h>
 #include <openssl/bn.h>
@@ -30,9 +32,7 @@
 #include <openssl/obj_mac.h>
 
 #include "asn1_decoder.h"
-#include "common.h"
 #include "print_sha1.h"
-#include "ui.h"
 
 static constexpr size_t MiB = 1024 * 1024;
 
@@ -61,248 +61,240 @@
  *             SEQUENCE (SignatureAlgorithmIdentifier)
  *             OCTET STRING (SignatureValue)
  */
-static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der,
-        size_t* sig_der_length) {
-    asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len);
-    if (ctx == NULL) {
-        return false;
-    }
+static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len,
+                       std::vector<uint8_t>* sig_der) {
+  CHECK(sig_der != nullptr);
+  sig_der->clear();
 
-    asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx);
-    if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) {
-        asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq);
-        if (signed_data_app != NULL) {
-            asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app);
-            if (signed_data_seq != NULL
-                    && asn1_sequence_next(signed_data_seq)
-                    && asn1_sequence_next(signed_data_seq)
-                    && asn1_sequence_next(signed_data_seq)
-                    && asn1_constructed_skip_all(signed_data_seq)) {
-                asn1_context_t *sig_set = asn1_set_get(signed_data_seq);
-                if (sig_set != NULL) {
-                    asn1_context_t* sig_seq = asn1_sequence_get(sig_set);
-                    if (sig_seq != NULL
-                            && asn1_sequence_next(sig_seq)
-                            && asn1_sequence_next(sig_seq)
-                            && asn1_sequence_next(sig_seq)
-                            && asn1_sequence_next(sig_seq)) {
-                        uint8_t* sig_der_ptr;
-                        if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) {
-                            *sig_der = (uint8_t*) malloc(*sig_der_length);
-                            if (*sig_der != NULL) {
-                                memcpy(*sig_der, sig_der_ptr, *sig_der_length);
-                            }
-                        }
-                        asn1_context_free(sig_seq);
-                    }
-                    asn1_context_free(sig_set);
-                }
-                asn1_context_free(signed_data_seq);
-            }
-            asn1_context_free(signed_data_app);
-        }
-        asn1_context_free(pkcs7_seq);
-    }
-    asn1_context_free(ctx);
+  asn1_context ctx(pkcs7_der, pkcs7_der_len);
 
-    return *sig_der != NULL;
+  std::unique_ptr<asn1_context> pkcs7_seq(ctx.asn1_sequence_get());
+  if (pkcs7_seq == nullptr || !pkcs7_seq->asn1_sequence_next()) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> signed_data_app(pkcs7_seq->asn1_constructed_get());
+  if (signed_data_app == nullptr) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> signed_data_seq(signed_data_app->asn1_sequence_get());
+  if (signed_data_seq == nullptr ||
+      !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_constructed_skip_all()) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> sig_set(signed_data_seq->asn1_set_get());
+  if (sig_set == nullptr) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> sig_seq(sig_set->asn1_sequence_get());
+  if (sig_seq == nullptr ||
+      !sig_seq->asn1_sequence_next() ||
+      !sig_seq->asn1_sequence_next() ||
+      !sig_seq->asn1_sequence_next() ||
+      !sig_seq->asn1_sequence_next()) {
+    return false;
+  }
+
+  const uint8_t* sig_der_ptr;
+  size_t sig_der_length;
+  if (!sig_seq->asn1_octet_string_get(&sig_der_ptr, &sig_der_length)) {
+    return false;
+  }
+
+  sig_der->resize(sig_der_length);
+  std::copy(sig_der_ptr, sig_der_ptr + sig_der_length, sig_der->begin());
+  return true;
 }
 
-// Look for an RSA signature embedded in the .ZIP file comment given
-// the path to the zip.  Verify it matches one of the given public
-// keys.
-//
-// Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered
-// or no key matches the signature).
+/*
+ * Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip. Verifies
+ * that it matches one of the given public keys. A callback function can be optionally provided for
+ * posting the progress.
+ *
+ * Returns VERIFY_SUCCESS or VERIFY_FAILURE (if any error is encountered or no key matches the
+ * signature).
+ */
+int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys,
+                const std::function<void(float)>& set_progress) {
+  if (set_progress) {
+    set_progress(0.0);
+  }
 
-int verify_file(unsigned char* addr, size_t length,
-                const std::vector<Certificate>& keys) {
-    ui->SetProgress(0.0);
-
-    // An archive with a whole-file signature will end in six bytes:
-    //
-    //   (2-byte signature start) $ff $ff (2-byte comment size)
-    //
-    // (As far as the ZIP format is concerned, these are part of the
-    // archive comment.)  We start by reading this footer, this tells
-    // us how far back from the end we have to start reading to find
-    // the whole comment.
+  // An archive with a whole-file signature will end in six bytes:
+  //
+  //   (2-byte signature start) $ff $ff (2-byte comment size)
+  //
+  // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by
+  // reading this footer, this tells us how far back from the end we have to start reading to find
+  // the whole comment.
 
 #define FOOTER_SIZE 6
 
-    if (length < FOOTER_SIZE) {
-        LOG(ERROR) << "not big enough to contain footer";
-        return VERIFY_FAILURE;
-    }
+  if (length < FOOTER_SIZE) {
+    LOG(ERROR) << "not big enough to contain footer";
+    return VERIFY_FAILURE;
+  }
 
-    unsigned char* footer = addr + length - FOOTER_SIZE;
+  const unsigned char* footer = addr + length - FOOTER_SIZE;
 
-    if (footer[2] != 0xff || footer[3] != 0xff) {
-        LOG(ERROR) << "footer is wrong";
-        return VERIFY_FAILURE;
-    }
+  if (footer[2] != 0xff || footer[3] != 0xff) {
+    LOG(ERROR) << "footer is wrong";
+    return VERIFY_FAILURE;
+  }
 
-    size_t comment_size = footer[4] + (footer[5] << 8);
-    size_t signature_start = footer[0] + (footer[1] << 8);
-    LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start
-              << " bytes from end";
+  size_t comment_size = footer[4] + (footer[5] << 8);
+  size_t signature_start = footer[0] + (footer[1] << 8);
+  LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start
+            << " bytes from end";
 
-    if (signature_start <= FOOTER_SIZE) {
-        LOG(ERROR) << "Signature start is in the footer";
-        return VERIFY_FAILURE;
-    }
+  if (signature_start <= FOOTER_SIZE) {
+    LOG(ERROR) << "Signature start is in the footer";
+    return VERIFY_FAILURE;
+  }
 
 #define EOCD_HEADER_SIZE 22
 
-    // The end-of-central-directory record is 22 bytes plus any
-    // comment length.
-    size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
+  // The end-of-central-directory record is 22 bytes plus any comment length.
+  size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
 
-    if (length < eocd_size) {
-        LOG(ERROR) << "not big enough to contain EOCD";
-        return VERIFY_FAILURE;
-    }
-
-    // Determine how much of the file is covered by the signature.
-    // This is everything except the signature data and length, which
-    // includes all of the EOCD except for the comment length field (2
-    // bytes) and the comment data.
-    size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;
-
-    unsigned char* eocd = addr + length - eocd_size;
-
-    // If this is really is the EOCD record, it will begin with the
-    // magic number $50 $4b $05 $06.
-    if (eocd[0] != 0x50 || eocd[1] != 0x4b ||
-        eocd[2] != 0x05 || eocd[3] != 0x06) {
-        LOG(ERROR) << "signature length doesn't match EOCD marker";
-        return VERIFY_FAILURE;
-    }
-
-    for (size_t i = 4; i < eocd_size-3; ++i) {
-        if (eocd[i  ] == 0x50 && eocd[i+1] == 0x4b &&
-            eocd[i+2] == 0x05 && eocd[i+3] == 0x06) {
-            // if the sequence $50 $4b $05 $06 appears anywhere after
-            // the real one, libziparchive will find the later (wrong) one,
-            // which could be exploitable.  Fail verification if
-            // this sequence occurs anywhere after the real one.
-            LOG(ERROR) << "EOCD marker occurs after start of EOCD";
-            return VERIFY_FAILURE;
-        }
-    }
-
-    bool need_sha1 = false;
-    bool need_sha256 = false;
-    for (const auto& key : keys) {
-        switch (key.hash_len) {
-            case SHA_DIGEST_LENGTH: need_sha1 = true; break;
-            case SHA256_DIGEST_LENGTH: need_sha256 = true; break;
-        }
-    }
-
-    SHA_CTX sha1_ctx;
-    SHA256_CTX sha256_ctx;
-    SHA1_Init(&sha1_ctx);
-    SHA256_Init(&sha256_ctx);
-
-    double frac = -1.0;
-    size_t so_far = 0;
-    while (so_far < signed_len) {
-        // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a
-        // 1196MiB full OTA and 60% for an 89MiB incremental OTA.
-        // http://b/28135231.
-        size_t size = std::min(signed_len - so_far, 16 * MiB);
-
-        if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size);
-        if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size);
-        so_far += size;
-
-        double f = so_far / (double)signed_len;
-        if (f > frac + 0.02 || size == so_far) {
-            ui->SetProgress(f);
-            frac = f;
-        }
-    }
-
-    uint8_t sha1[SHA_DIGEST_LENGTH];
-    SHA1_Final(sha1, &sha1_ctx);
-    uint8_t sha256[SHA256_DIGEST_LENGTH];
-    SHA256_Final(sha256, &sha256_ctx);
-
-    uint8_t* sig_der = nullptr;
-    size_t sig_der_length = 0;
-
-    uint8_t* signature = eocd + eocd_size - signature_start;
-    size_t signature_size = signature_start - FOOTER_SIZE;
-
-    LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start) << ", length: "
-              << signature_size << "): " << print_hex(signature, signature_size);
-
-    if (!read_pkcs7(signature, signature_size, &sig_der, &sig_der_length)) {
-        LOG(ERROR) << "Could not find signature DER block";
-        return VERIFY_FAILURE;
-    }
-
-    /*
-     * Check to make sure at least one of the keys matches the signature. Since
-     * any key can match, we need to try each before determining a verification
-     * failure has happened.
-     */
-    size_t i = 0;
-    for (const auto& key : keys) {
-        const uint8_t* hash;
-        int hash_nid;
-        switch (key.hash_len) {
-            case SHA_DIGEST_LENGTH:
-                hash = sha1;
-                hash_nid = NID_sha1;
-                break;
-            case SHA256_DIGEST_LENGTH:
-                hash = sha256;
-                hash_nid = NID_sha256;
-                break;
-            default:
-                continue;
-        }
-
-        // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
-        // the signing tool appends after the signature itself.
-        if (key.key_type == Certificate::KEY_TYPE_RSA) {
-            if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der,
-                            sig_der_length, key.rsa.get())) {
-                LOG(INFO) << "failed to verify against RSA key " << i;
-                continue;
-            }
-
-            LOG(INFO) << "whole-file signature verified against RSA key " << i;
-            free(sig_der);
-            return VERIFY_SUCCESS;
-        } else if (key.key_type == Certificate::KEY_TYPE_EC
-                && key.hash_len == SHA256_DIGEST_LENGTH) {
-            if (!ECDSA_verify(0, hash, key.hash_len, sig_der,
-                              sig_der_length, key.ec.get())) {
-                LOG(INFO) << "failed to verify against EC key " << i;
-                continue;
-            }
-
-            LOG(INFO) << "whole-file signature verified against EC key " << i;
-            free(sig_der);
-            return VERIFY_SUCCESS;
-        } else {
-            LOG(INFO) << "Unknown key type " << key.key_type;
-        }
-        i++;
-    }
-
-    if (need_sha1) {
-        LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH);
-    }
-    if (need_sha256) {
-        LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH);
-    }
-    free(sig_der);
-    LOG(ERROR) << "failed to verify whole-file signature";
+  if (length < eocd_size) {
+    LOG(ERROR) << "not big enough to contain EOCD";
     return VERIFY_FAILURE;
+  }
+
+  // Determine how much of the file is covered by the signature. This is everything except the
+  // signature data and length, which includes all of the EOCD except for the comment length field
+  // (2 bytes) and the comment data.
+  size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;
+
+  const unsigned char* eocd = addr + length - eocd_size;
+
+  // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06.
+  if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) {
+    LOG(ERROR) << "signature length doesn't match EOCD marker";
+    return VERIFY_FAILURE;
+  }
+
+  for (size_t i = 4; i < eocd_size-3; ++i) {
+    if (eocd[i] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) {
+      // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will
+      // find the later (wrong) one, which could be exploitable. Fail the verification if this
+      // sequence occurs anywhere after the real one.
+      LOG(ERROR) << "EOCD marker occurs after start of EOCD";
+      return VERIFY_FAILURE;
+    }
+  }
+
+  bool need_sha1 = false;
+  bool need_sha256 = false;
+  for (const auto& key : keys) {
+    switch (key.hash_len) {
+      case SHA_DIGEST_LENGTH: need_sha1 = true; break;
+      case SHA256_DIGEST_LENGTH: need_sha256 = true; break;
+    }
+  }
+
+  SHA_CTX sha1_ctx;
+  SHA256_CTX sha256_ctx;
+  SHA1_Init(&sha1_ctx);
+  SHA256_Init(&sha256_ctx);
+
+  double frac = -1.0;
+  size_t so_far = 0;
+  while (so_far < signed_len) {
+    // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a
+    // 1196MiB full OTA and 60% for an 89MiB incremental OTA.
+    // http://b/28135231.
+    size_t size = std::min(signed_len - so_far, 16 * MiB);
+
+    if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size);
+    if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size);
+    so_far += size;
+
+    if (set_progress) {
+      double f = so_far / (double)signed_len;
+      if (f > frac + 0.02 || size == so_far) {
+        set_progress(f);
+        frac = f;
+      }
+    }
+  }
+
+  uint8_t sha1[SHA_DIGEST_LENGTH];
+  SHA1_Final(sha1, &sha1_ctx);
+  uint8_t sha256[SHA256_DIGEST_LENGTH];
+  SHA256_Final(sha256, &sha256_ctx);
+
+  const uint8_t* signature = eocd + eocd_size - signature_start;
+  size_t signature_size = signature_start - FOOTER_SIZE;
+
+  LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start) << ", length: "
+            << signature_size << "): " << print_hex(signature, signature_size);
+
+  std::vector<uint8_t> sig_der;
+  if (!read_pkcs7(signature, signature_size, &sig_der)) {
+    LOG(ERROR) << "Could not find signature DER block";
+    return VERIFY_FAILURE;
+  }
+
+  // Check to make sure at least one of the keys matches the signature. Since any key can match,
+  // we need to try each before determining a verification failure has happened.
+  size_t i = 0;
+  for (const auto& key : keys) {
+    const uint8_t* hash;
+    int hash_nid;
+    switch (key.hash_len) {
+      case SHA_DIGEST_LENGTH:
+        hash = sha1;
+        hash_nid = NID_sha1;
+        break;
+      case SHA256_DIGEST_LENGTH:
+        hash = sha256;
+        hash_nid = NID_sha256;
+        break;
+      default:
+        continue;
+    }
+
+    // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends
+    // after the signature itself.
+    if (key.key_type == Certificate::KEY_TYPE_RSA) {
+      if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der.data(), sig_der.size(),
+                      key.rsa.get())) {
+        LOG(INFO) << "failed to verify against RSA key " << i;
+        continue;
+      }
+
+      LOG(INFO) << "whole-file signature verified against RSA key " << i;
+      return VERIFY_SUCCESS;
+    } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) {
+      if (!ECDSA_verify(0, hash, key.hash_len, sig_der.data(), sig_der.size(), key.ec.get())) {
+        LOG(INFO) << "failed to verify against EC key " << i;
+        continue;
+      }
+
+      LOG(INFO) << "whole-file signature verified against EC key " << i;
+      return VERIFY_SUCCESS;
+    } else {
+      LOG(INFO) << "Unknown key type " << key.key_type;
+    }
+    i++;
+  }
+
+  if (need_sha1) {
+    LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH);
+  }
+  if (need_sha256) {
+    LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH);
+  }
+  LOG(ERROR) << "failed to verify whole-file signature";
+  return VERIFY_FAILURE;
 }
 
 std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) {
@@ -378,7 +370,7 @@
 }
 
 struct BNDeleter {
-  void operator()(BIGNUM* bn) {
+  void operator()(BIGNUM* bn) const {
     BN_free(bn);
   }
 };
diff --git a/verifier.h b/verifier.h
index 58083fe..6fa8f2b 100644
--- a/verifier.h
+++ b/verifier.h
@@ -17,6 +17,7 @@
 #ifndef _RECOVERY_VERIFIER_H
 #define _RECOVERY_VERIFIER_H
 
+#include <functional>
 #include <memory>
 #include <vector>
 
@@ -25,13 +26,13 @@
 #include <openssl/sha.h>
 
 struct RSADeleter {
-  void operator()(RSA* rsa) {
+  void operator()(RSA* rsa) const {
     RSA_free(rsa);
   }
 };
 
 struct ECKEYDeleter {
-  void operator()(EC_KEY* ec_key) {
+  void operator()(EC_KEY* ec_key) const {
     EC_KEY_free(ec_key);
   }
 };
@@ -58,13 +59,14 @@
     std::unique_ptr<EC_KEY, ECKEYDeleter> ec;
 };
 
-/* addr and length define a an update package file that has been
- * loaded (or mmap'ed, or whatever) into memory.  Verify that the file
- * is signed and the signature matches one of the given keys.  Return
- * one of the constants below.
+/*
+ * 'addr' and 'length' define an update package file that has been loaded (or mmap'ed, or
+ * whatever) into memory. Verifies that the file is signed and the signature matches one of the
+ * given keys. It optionally accepts a callback function for posting the progress to. Returns one
+ * of the constants of VERIFY_SUCCESS and VERIFY_FAILURE.
  */
-int verify_file(unsigned char* addr, size_t length,
-                const std::vector<Certificate>& keys);
+int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys,
+                const std::function<void(float)>& set_progress = nullptr);
 
 bool load_keys(const char* filename, std::vector<Certificate>& certs);
 
diff --git a/wear_touch.cpp b/wear_touch.cpp
index cf33daa..e2ab44d 100644
--- a/wear_touch.cpp
+++ b/wear_touch.cpp
@@ -118,7 +118,7 @@
 }
 
 void* WearSwipeDetector::touch_thread(void* cookie) {
-    ((WearSwipeDetector*)cookie)->run();
+    (static_cast<WearSwipeDetector*>(cookie))->run();
     return NULL;
 }