Merge "bootable/recovery: cleanup compiler warnings (potential leak of memory)"
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 9d505f9..95389da 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "applypatch/applypatch.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
@@ -27,16 +29,18 @@
 
 #include <memory>
 #include <string>
+#include <utility>
+#include <vector>
 
+#include <android-base/parseint.h>
 #include <android-base/strings.h>
+#include <openssl/sha.h>
 
-#include "openssl/sha.h"
-#include "applypatch/applypatch.h"
 #include "edify/expr.h"
 #include "ota_io.h"
 #include "print_sha1.h"
 
-static int LoadPartitionContents(const char* filename, FileContents* file);
+static int LoadPartitionContents(const std::string& filename, FileContents* file);
 static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token);
 static int GenerateTarget(FileContents* source_file,
                           const Value* source_patch_value,
@@ -48,39 +52,34 @@
                           size_t target_size,
                           const Value* bonus_data);
 
-// Read a file into memory; store the file contents and associated
-// metadata in *file.
-//
+// Read a file into memory; store the file contents and associated metadata in *file.
 // Return 0 on success.
 int LoadFileContents(const char* filename, FileContents* file) {
-    // A special 'filename' beginning with "EMMC:" means to
-    // load the contents of a partition.
-    if (strncmp(filename, "EMMC:", 5) == 0) {
-        return LoadPartitionContents(filename, file);
-    }
+  // A special 'filename' beginning with "EMMC:" means to load the contents of a partition.
+  if (strncmp(filename, "EMMC:", 5) == 0) {
+    return LoadPartitionContents(filename, file);
+  }
 
-    if (stat(filename, &file->st) != 0) {
-        printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
-        return -1;
-    }
+  if (stat(filename, &file->st) == -1) {
+    printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    std::vector<unsigned char> data(file->st.st_size);
-    FILE* f = ota_fopen(filename, "rb");
-    if (f == NULL) {
-        printf("failed to open \"%s\": %s\n", filename, strerror(errno));
-        return -1;
-    }
+  std::vector<unsigned char> data(file->st.st_size);
+  unique_file f(ota_fopen(filename, "rb"));
+  if (!f) {
+    printf("failed to open \"%s\": %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    size_t bytes_read = ota_fread(data.data(), 1, data.size(), f);
-    if (bytes_read != data.size()) {
-        printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size());
-        ota_fclose(f);
-        return -1;
-    }
-    ota_fclose(f);
-    file->data = std::move(data);
-    SHA1(file->data.data(), file->data.size(), file->sha1);
-    return 0;
+  size_t bytes_read = ota_fread(data.data(), 1, data.size(), f.get());
+  if (bytes_read != data.size()) {
+    printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size());
+    return -1;
+  }
+  file->data = std::move(data);
+  SHA1(file->data.data(), file->data.size(), file->sha1);
+  return 0;
 }
 
 // Load the contents of an EMMC partition into the provided
@@ -97,285 +96,258 @@
 // "end-of-file" marker), so the caller must specify the possible
 // lengths and the hash of the data, and we'll do the load expecting
 // to find one of those hashes.
-static int LoadPartitionContents(const char* filename, FileContents* file) {
-    std::string copy(filename);
-    std::vector<std::string> pieces = android::base::Split(copy, ":");
-    if (pieces.size() < 4 || pieces.size() % 2 != 0) {
-        printf("LoadPartitionContents called with bad filename (%s)\n", filename);
+static int LoadPartitionContents(const std::string& filename, FileContents* file) {
+  std::vector<std::string> pieces = android::base::Split(filename, ":");
+  if (pieces.size() < 4 || pieces.size() % 2 != 0 || pieces[0] != "EMMC") {
+    printf("LoadPartitionContents called with bad filename \"%s\"\n", filename.c_str());
+    return -1;
+  }
+
+  size_t pair_count = (pieces.size() - 2) / 2;  // # of (size, sha1) pairs in filename
+  std::vector<std::pair<size_t, std::string>> pairs;
+  for (size_t i = 0; i < pair_count; ++i) {
+    size_t size;
+    if (!android::base::ParseUint(pieces[i * 2 + 2], &size) || size == 0) {
+      printf("LoadPartitionContents called with bad size \"%s\"\n", pieces[i * 2 + 2].c_str());
+      return -1;
+    }
+    pairs.push_back({ size, pieces[i * 2 + 3] });
+  }
+
+  // Sort the pairs array so that they are in order of increasing size.
+  std::sort(pairs.begin(), pairs.end());
+
+  const char* partition = pieces[1].c_str();
+  unique_file dev(ota_fopen(partition, "rb"));
+  if (!dev) {
+    printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno));
+    return -1;
+  }
+
+  SHA_CTX sha_ctx;
+  SHA1_Init(&sha_ctx);
+
+  // Allocate enough memory to hold the largest size.
+  std::vector<unsigned char> buffer(pairs[pair_count - 1].first);
+  unsigned char* buffer_ptr = buffer.data();
+  size_t buffer_size = 0;  // # bytes read so far
+  bool found = false;
+
+  for (const auto& pair : pairs) {
+    size_t current_size = pair.first;
+    const std::string& current_sha1 = pair.second;
+
+    // Read enough additional bytes to get us up to the next size. (Again,
+    // we're trying the possibilities in order of increasing size).
+    size_t next = current_size - buffer_size;
+    if (next > 0) {
+      size_t read = ota_fread(buffer_ptr, 1, next, dev.get());
+      if (next != read) {
+        printf("short read (%zu bytes of %zu) for partition \"%s\"\n", read, next, partition);
         return -1;
+      }
+      SHA1_Update(&sha_ctx, buffer_ptr, read);
+      buffer_size += read;
+      buffer_ptr += read;
     }
 
-    if (pieces[0] != "EMMC") {
-        printf("LoadPartitionContents called with bad filename (%s)\n", filename);
-        return -1;
-    }
-    const char* partition = pieces[1].c_str();
+    // Duplicate the SHA context and finalize the duplicate so we can
+    // check it against this pair's expected hash.
+    SHA_CTX temp_ctx;
+    memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX));
+    uint8_t sha_so_far[SHA_DIGEST_LENGTH];
+    SHA1_Final(sha_so_far, &temp_ctx);
 
-    size_t pairs = (pieces.size() - 2) / 2;    // # of (size, sha1) pairs in filename
-    std::vector<size_t> index(pairs);
-    std::vector<size_t> size(pairs);
-    std::vector<std::string> sha1sum(pairs);
-
-    for (size_t i = 0; i < pairs; ++i) {
-        size[i] = strtol(pieces[i*2+2].c_str(), NULL, 10);
-        if (size[i] == 0) {
-            printf("LoadPartitionContents called with bad size (%s)\n", filename);
-            return -1;
-        }
-        sha1sum[i] = pieces[i*2+3].c_str();
-        index[i] = i;
-    }
-
-    // Sort the index[] array so it indexes the pairs in order of increasing size.
-    sort(index.begin(), index.end(),
-        [&](const size_t& i, const size_t& j) {
-            return (size[i] < size[j]);
-        }
-    );
-
-    FILE* dev = ota_fopen(partition, "rb");
-    if (dev == NULL) {
-        printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno));
-        return -1;
-    }
-
-    SHA_CTX sha_ctx;
-    SHA1_Init(&sha_ctx);
     uint8_t parsed_sha[SHA_DIGEST_LENGTH];
-
-    // Allocate enough memory to hold the largest size.
-    std::vector<unsigned char> data(size[index[pairs-1]]);
-    char* p = reinterpret_cast<char*>(data.data());
-    size_t data_size = 0;                // # bytes read so far
-    bool found = false;
-
-    for (size_t i = 0; i < pairs; ++i) {
-        // Read enough additional bytes to get us up to the next size. (Again,
-        // we're trying the possibilities in order of increasing size).
-        size_t next = size[index[i]] - data_size;
-        if (next > 0) {
-            size_t read = ota_fread(p, 1, next, dev);
-            if (next != read) {
-                printf("short read (%zu bytes of %zu) for partition \"%s\"\n",
-                       read, next, partition);
-                return -1;
-            }
-            SHA1_Update(&sha_ctx, p, read);
-            data_size += read;
-            p += read;
-        }
-
-        // Duplicate the SHA context and finalize the duplicate so we can
-        // check it against this pair's expected hash.
-        SHA_CTX temp_ctx;
-        memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX));
-        uint8_t sha_so_far[SHA_DIGEST_LENGTH];
-        SHA1_Final(sha_so_far, &temp_ctx);
-
-        if (ParseSha1(sha1sum[index[i]].c_str(), parsed_sha) != 0) {
-            printf("failed to parse sha1 %s in %s\n", sha1sum[index[i]].c_str(), filename);
-            return -1;
-        }
-
-        if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) {
-            // we have a match.  stop reading the partition; we'll return
-            // the data we've read so far.
-            printf("partition read matched size %zu sha %s\n",
-                   size[index[i]], sha1sum[index[i]].c_str());
-            found = true;
-            break;
-        }
+    if (ParseSha1(current_sha1.c_str(), parsed_sha) != 0) {
+      printf("failed to parse SHA-1 %s in %s\n", current_sha1.c_str(), filename.c_str());
+      return -1;
     }
 
-    ota_fclose(dev);
-
-    if (!found) {
-        // Ran off the end of the list of (size,sha1) pairs without finding a match.
-        printf("contents of partition \"%s\" didn't match %s\n", partition, filename);
-        return -1;
+    if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) {
+      // We have a match. Stop reading the partition; we'll return the data we've read so far.
+      printf("partition read matched size %zu SHA-1 %s\n", current_size, current_sha1.c_str());
+      found = true;
+      break;
     }
+  }
 
-    SHA1_Final(file->sha1, &sha_ctx);
+  if (!found) {
+    // Ran off the end of the list of (size, sha1) pairs without finding a match.
+    printf("contents of partition \"%s\" didn't match %s\n", partition, filename.c_str());
+    return -1;
+  }
 
-    data.resize(data_size);
-    file->data = std::move(data);
-    // Fake some stat() info.
-    file->st.st_mode = 0644;
-    file->st.st_uid = 0;
-    file->st.st_gid = 0;
+  SHA1_Final(file->sha1, &sha_ctx);
 
-    return 0;
+  buffer.resize(buffer_size);
+  file->data = std::move(buffer);
+  // Fake some stat() info.
+  file->st.st_mode = 0644;
+  file->st.st_uid = 0;
+  file->st.st_gid = 0;
+
+  return 0;
 }
 
 
 // Save the contents of the given FileContents object under the given
 // filename.  Return 0 on success.
 int SaveFileContents(const char* filename, const FileContents* file) {
-    int fd = ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR);
-    if (fd < 0) {
-        printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno));
-        return -1;
-    }
+  unique_fd fd(ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR));
+  if (fd == -1) {
+    printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd);
-    if (bytes_written != static_cast<ssize_t>(file->data.size())) {
-        printf("short write of \"%s\" (%zd bytes of %zu) (%s)\n",
-               filename, bytes_written, file->data.size(), strerror(errno));
-        ota_close(fd);
-        return -1;
-    }
-    if (ota_fsync(fd) != 0) {
-        printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno));
-        return -1;
-    }
-    if (ota_close(fd) != 0) {
-        printf("close of \"%s\" failed: %s\n", filename, strerror(errno));
-        return -1;
-    }
+  ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd);
+  if (bytes_written != static_cast<ssize_t>(file->data.size())) {
+    printf("short write of \"%s\" (%zd bytes of %zu): %s\n", filename, bytes_written,
+           file->data.size(), strerror(errno));
+    return -1;
+  }
+  if (ota_fsync(fd) != 0) {
+    printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno));
+    return -1;
+  }
+  if (ota_close(fd) != 0) {
+    printf("close of \"%s\" failed: %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    if (chmod(filename, file->st.st_mode) != 0) {
-        printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
-        return -1;
-    }
-    if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) {
-        printf("chown of \"%s\" failed: %s\n", filename, strerror(errno));
-        return -1;
-    }
+  if (chmod(filename, file->st.st_mode) != 0) {
+    printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
+    return -1;
+  }
+  if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) {
+    printf("chown of \"%s\" failed: %s\n", filename, strerror(errno));
+    return -1;
+  }
 
-    return 0;
+  return 0;
 }
 
 // Write a memory buffer to 'target' partition, a string of the form
 // "EMMC:<partition_device>[:...]". The target name
 // might contain multiple colons, but WriteToPartition() only uses the first
 // two and ignores the rest. Return 0 on success.
-int WriteToPartition(const unsigned char* data, size_t len, const char* target) {
-    std::string copy(target);
-    std::vector<std::string> pieces = android::base::Split(copy, ":");
+int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) {
+  std::vector<std::string> pieces = android::base::Split(target, ":");
+  if (pieces.size() < 2 || pieces[0] != "EMMC") {
+    printf("WriteToPartition called with bad target (%s)\n", target.c_str());
+    return -1;
+  }
 
-    if (pieces.size() < 2) {
-        printf("WriteToPartition called with bad target (%s)\n", target);
+  const char* partition = pieces[1].c_str();
+  unique_fd fd(ota_open(partition, O_RDWR));
+  if (fd == -1) {
+    printf("failed to open %s: %s\n", partition, strerror(errno));
+    return -1;
+  }
+
+  size_t start = 0;
+  bool success = false;
+  for (size_t attempt = 0; attempt < 2; ++attempt) {
+    if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
+      printf("failed seek on %s: %s\n", partition, strerror(errno));
+      return -1;
+    }
+    while (start < len) {
+      size_t to_write = len - start;
+      if (to_write > 1 << 20) to_write = 1 << 20;
+
+      ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data + start, to_write));
+      if (written == -1) {
+        printf("failed write writing to %s: %s\n", partition, strerror(errno));
         return -1;
+      }
+      start += written;
     }
 
-    if (pieces[0] != "EMMC") {
-        printf("WriteToPartition called with bad target (%s)\n", target);
-        return -1;
+    if (ota_fsync(fd) != 0) {
+      printf("failed to sync to %s: %s\n", partition, strerror(errno));
+      return -1;
     }
-    const char* partition = pieces[1].c_str();
-
-    size_t start = 0;
-    bool success = false;
-    int fd = ota_open(partition, O_RDWR | O_SYNC);
-    if (fd < 0) {
-        printf("failed to open %s: %s\n", partition, strerror(errno));
-        return -1;
-    }
-
-    for (size_t attempt = 0; attempt < 2; ++attempt) {
-        if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
-            printf("failed seek on %s: %s\n", partition, strerror(errno));
-            return -1;
-        }
-        while (start < len) {
-            size_t to_write = len - start;
-            if (to_write > 1<<20) to_write = 1<<20;
-
-            ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data+start, to_write));
-            if (written == -1) {
-                printf("failed write writing to %s: %s\n", partition, strerror(errno));
-                return -1;
-            }
-            start += written;
-        }
-        if (ota_fsync(fd) != 0) {
-            printf("failed to sync to %s (%s)\n", partition, strerror(errno));
-            return -1;
-        }
-        if (ota_close(fd) != 0) {
-            printf("failed to close %s (%s)\n", partition, strerror(errno));
-            return -1;
-        }
-        fd = ota_open(partition, O_RDONLY);
-        if (fd < 0) {
-            printf("failed to reopen %s for verify (%s)\n", partition, strerror(errno));
-            return -1;
-        }
-
-        // Drop caches so our subsequent verification read
-        // won't just be reading the cache.
-        sync();
-        int dc = ota_open("/proc/sys/vm/drop_caches", O_WRONLY);
-        if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) {
-            printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno));
-        } else {
-            printf("  caches dropped\n");
-        }
-        ota_close(dc);
-        sleep(1);
-
-        // verify
-        if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
-            printf("failed to seek back to beginning of %s: %s\n",
-                   partition, strerror(errno));
-            return -1;
-        }
-        unsigned char buffer[4096];
-        start = len;
-        for (size_t p = 0; p < len; p += sizeof(buffer)) {
-            size_t to_read = len - p;
-            if (to_read > sizeof(buffer)) {
-                to_read = sizeof(buffer);
-            }
-
-            size_t so_far = 0;
-            while (so_far < to_read) {
-                ssize_t read_count =
-                    TEMP_FAILURE_RETRY(ota_read(fd, buffer+so_far, to_read-so_far));
-                if (read_count == -1) {
-                    printf("verify read error %s at %zu: %s\n",
-                           partition, p, strerror(errno));
-                    return -1;
-                } else if (read_count == 0) {
-                    printf("verify read reached unexpected EOF, %s at %zu\n", partition, p);
-                    return -1;
-                }
-                if (static_cast<size_t>(read_count) < to_read) {
-                    printf("short verify read %s at %zu: %zd %zu %s\n",
-                           partition, p, read_count, to_read, strerror(errno));
-                }
-                so_far += read_count;
-            }
-
-            if (memcmp(buffer, data+p, to_read) != 0) {
-                printf("verification failed starting at %zu\n", p);
-                start = p;
-                break;
-            }
-        }
-
-        if (start == len) {
-            printf("verification read succeeded (attempt %zu)\n", attempt+1);
-            success = true;
-            break;
-        }
-    }
-
-    if (!success) {
-        printf("failed to verify after all attempts\n");
-        return -1;
-    }
-
     if (ota_close(fd) != 0) {
-        printf("error closing %s (%s)\n", partition, strerror(errno));
-        return -1;
+      printf("failed to close %s: %s\n", partition, strerror(errno));
+      return -1;
     }
+
+    fd.reset(ota_open(partition, O_RDONLY));
+    if (fd == -1) {
+      printf("failed to reopen %s for verify: %s\n", partition, strerror(errno));
+      return -1;
+    }
+
+    // Drop caches so our subsequent verification read won't just be reading the cache.
     sync();
+    unique_fd dc(ota_open("/proc/sys/vm/drop_caches", O_WRONLY));
+    if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) {
+      printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno));
+    } else {
+      printf("  caches dropped\n");
+    }
+    ota_close(dc);
+    sleep(1);
 
-    return 0;
+    // Verify.
+    if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
+      printf("failed to seek back to beginning of %s: %s\n", partition, strerror(errno));
+      return -1;
+    }
+
+    unsigned char buffer[4096];
+    start = len;
+    for (size_t p = 0; p < len; p += sizeof(buffer)) {
+      size_t to_read = len - p;
+      if (to_read > sizeof(buffer)) {
+        to_read = sizeof(buffer);
+      }
+
+      size_t so_far = 0;
+      while (so_far < to_read) {
+        ssize_t read_count = TEMP_FAILURE_RETRY(ota_read(fd, buffer + so_far, to_read - so_far));
+        if (read_count == -1) {
+          printf("verify read error %s at %zu: %s\n", partition, p, strerror(errno));
+          return -1;
+        } else if (read_count == 0) {
+          printf("verify read reached unexpected EOF, %s at %zu\n", partition, p);
+          return -1;
+        }
+        if (static_cast<size_t>(read_count) < to_read) {
+          printf("short verify read %s at %zu: %zd %zu\n", partition, p, read_count, to_read);
+        }
+        so_far += read_count;
+      }
+
+      if (memcmp(buffer, data + p, to_read) != 0) {
+        printf("verification failed starting at %zu\n", p);
+        start = p;
+        break;
+      }
+    }
+
+    if (start == len) {
+      printf("verification read succeeded (attempt %zu)\n", attempt + 1);
+      success = true;
+      break;
+    }
+  }
+
+  if (!success) {
+    printf("failed to verify after all attempts\n");
+    return -1;
+  }
+
+  if (ota_close(fd) == -1) {
+    printf("error closing %s: %s\n", partition, strerror(errno));
+    return -1;
+  }
+  sync();
+
+  return 0;
 }
 
-
 // Take a string 'str' of 40 hex digits and parse it into the 20
 // byte array 'digest'.  'str' may contain only the digest or be of
 // the form "<digest>:<anything>".  Return 0 on success, -1 on any
@@ -409,48 +381,46 @@
 // Return the index of the match on success, or -1 if no match is
 // found.
 int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str) {
-    for (size_t i = 0; i < patch_sha1_str.size(); ++i) {
-        uint8_t patch_sha1[SHA_DIGEST_LENGTH];
-        if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 &&
-            memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) {
-            return i;
-        }
+  for (size_t i = 0; i < patch_sha1_str.size(); ++i) {
+    uint8_t patch_sha1[SHA_DIGEST_LENGTH];
+    if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 &&
+        memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) {
+      return i;
     }
-    return -1;
+  }
+  return -1;
 }
 
 // Returns 0 if the contents of the file (argv[2]) or the cached file
 // match any of the sha1's on the command line (argv[3:]).  Returns
 // nonzero otherwise.
 int applypatch_check(const char* filename, const std::vector<std::string>& patch_sha1_str) {
-    FileContents file;
+  FileContents file;
 
-    // It's okay to specify no sha1s; the check will pass if the
-    // LoadFileContents is successful.  (Useful for reading
-    // partitions, where the filename encodes the sha1s; no need to
-    // check them twice.)
-    if (LoadFileContents(filename, &file) != 0 ||
-        (patch_sha1_str.size() > 0 && FindMatchingPatch(file.sha1, patch_sha1_str) < 0)) {
-        printf("file \"%s\" doesn't have any of expected "
-               "sha1 sums; checking cache\n", filename);
+  // It's okay to specify no sha1s; the check will pass if the
+  // LoadFileContents is successful.  (Useful for reading
+  // partitions, where the filename encodes the sha1s; no need to
+  // check them twice.)
+  if (LoadFileContents(filename, &file) != 0 ||
+      (!patch_sha1_str.empty() && FindMatchingPatch(file.sha1, patch_sha1_str) < 0)) {
+    printf("file \"%s\" doesn't have any of expected sha1 sums; checking cache\n", filename);
 
-        // If the source file is missing or corrupted, it might be because
-        // we were killed in the middle of patching it.  A copy of it
-        // should have been made in CACHE_TEMP_SOURCE.  If that file
-        // exists and matches the sha1 we're looking for, the check still
-        // passes.
-
-        if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) {
-            printf("failed to load cache file\n");
-            return 1;
-        }
-
-        if (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) {
-            printf("cache bits don't match any sha1 for \"%s\"\n", filename);
-            return 1;
-        }
+    // If the source file is missing or corrupted, it might be because
+    // we were killed in the middle of patching it.  A copy of it
+    // should have been made in CACHE_TEMP_SOURCE.  If that file
+    // exists and matches the sha1 we're looking for, the check still
+    // passes.
+    if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) {
+      printf("failed to load cache file\n");
+      return 1;
     }
-    return 0;
+
+    if (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) {
+      printf("cache bits don't match any sha1 for \"%s\"\n", filename);
+      return 1;
+    }
+  }
+  return 0;
 }
 
 int ShowLicenses() {
@@ -544,10 +514,8 @@
         return 1;
     }
 
-    FileContents copy_file;
     FileContents source_file;
     const Value* source_patch_value = nullptr;
-    const Value* copy_patch_value = nullptr;
 
     // We try to load the target file into the source_file object.
     if (LoadFileContents(target_filename, &source_file) == 0) {
@@ -575,6 +543,8 @@
         }
     }
 
+    FileContents copy_file;
+    const Value* copy_patch_value = nullptr;
     if (source_patch_value == nullptr) {
         source_file.data.clear();
         printf("source file is bad; trying copy\n");
@@ -612,50 +582,49 @@
  */
 int applypatch_flash(const char* source_filename, const char* target_filename,
                      const char* target_sha1_str, size_t target_size) {
-    printf("flash %s: ", target_filename);
+  printf("flash %s: ", target_filename);
 
-    uint8_t target_sha1[SHA_DIGEST_LENGTH];
-    if (ParseSha1(target_sha1_str, target_sha1) != 0) {
-        printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
-        return 1;
-    }
+  uint8_t target_sha1[SHA_DIGEST_LENGTH];
+  if (ParseSha1(target_sha1_str, target_sha1) != 0) {
+    printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
+    return 1;
+  }
 
-    FileContents source_file;
-    std::string target_str(target_filename);
+  std::string target_str(target_filename);
+  std::vector<std::string> pieces = android::base::Split(target_str, ":");
+  if (pieces.size() != 2 || pieces[0] != "EMMC") {
+    printf("invalid target name \"%s\"", target_filename);
+    return 1;
+  }
 
-    std::vector<std::string> pieces = android::base::Split(target_str, ":");
-    if (pieces.size() != 2 || pieces[0] != "EMMC") {
-        printf("invalid target name \"%s\"", target_filename);
-        return 1;
-    }
-
-    // Load the target into the source_file object to see if already applied.
-    pieces.push_back(std::to_string(target_size));
-    pieces.push_back(target_sha1_str);
-    std::string fullname = android::base::Join(pieces, ':');
-    if (LoadPartitionContents(fullname.c_str(), &source_file) == 0 &&
-        memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
-        // The early-exit case: the image was already applied, this partition
-        // has the desired hash, nothing for us to do.
-        printf("already %s\n", short_sha1(target_sha1).c_str());
-        return 0;
-    }
-
-    if (LoadFileContents(source_filename, &source_file) == 0) {
-        if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
-            // The source doesn't have desired checksum.
-            printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename);
-            printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(),
-                    short_sha1(source_file.sha1).c_str());
-            return 1;
-        }
-    }
-
-    if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
-        printf("write of copied data to %s failed\n", target_filename);
-        return 1;
-    }
+  // Load the target into the source_file object to see if already applied.
+  pieces.push_back(std::to_string(target_size));
+  pieces.push_back(target_sha1_str);
+  std::string fullname = android::base::Join(pieces, ':');
+  FileContents source_file;
+  if (LoadPartitionContents(fullname, &source_file) == 0 &&
+      memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
+    // The early-exit case: the image was already applied, this partition
+    // has the desired hash, nothing for us to do.
+    printf("already %s\n", short_sha1(target_sha1).c_str());
     return 0;
+  }
+
+  if (LoadFileContents(source_filename, &source_file) == 0) {
+    if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
+      // The source doesn't have desired checksum.
+      printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename);
+      printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(),
+             short_sha1(source_file.sha1).c_str());
+      return 1;
+    }
+  }
+
+  if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
+    printf("write of copied data to %s failed\n", target_filename);
+    return 1;
+  }
+  return 0;
 }
 
 static int GenerateTarget(FileContents* source_file,
@@ -667,221 +636,214 @@
                           const uint8_t target_sha1[SHA_DIGEST_LENGTH],
                           size_t target_size,
                           const Value* bonus_data) {
-    int retry = 1;
-    SHA_CTX ctx;
-    std::string memory_sink_str;
-    FileContents* source_to_use;
-    int made_copy = 0;
+  // assume that target_filename (eg "/system/app/Foo.apk") is located
+  // on the same filesystem as its top-level directory ("/system").
+  // We need something that exists for calling statfs().
+  std::string target_fs = target_filename;
+  auto slash_pos = target_fs.find('/', 1);
+  if (slash_pos != std::string::npos) {
+    target_fs.resize(slash_pos);
+  }
 
-    bool target_is_partition = (strncmp(target_filename, "EMMC:", 5) == 0);
-    const std::string tmp_target_filename = std::string(target_filename) + ".patch";
+  FileContents* source_to_use;
+  const Value* patch;
+  if (source_patch_value != nullptr) {
+    source_to_use = source_file;
+    patch = source_patch_value;
+  } else {
+    source_to_use = copy_file;
+    patch = copy_patch_value;
+  }
 
-    // assume that target_filename (eg "/system/app/Foo.apk") is located
-    // on the same filesystem as its top-level directory ("/system").
-    // We need something that exists for calling statfs().
-    std::string target_fs = target_filename;
-    auto slash_pos = target_fs.find('/', 1);
-    if (slash_pos != std::string::npos) {
-        target_fs.resize(slash_pos);
-    }
+  if (patch->type != VAL_BLOB) {
+    printf("patch is not a blob\n");
+    return 1;
+  }
 
-    const Value* patch;
-    if (source_patch_value != NULL) {
-        source_to_use = source_file;
-        patch = source_patch_value;
-    } else {
-        source_to_use = copy_file;
-        patch = copy_patch_value;
-    }
-    if (patch->type != VAL_BLOB) {
-        printf("patch is not a blob\n");
-        return 1;
-    }
-    const char* header = &patch->data[0];
-    size_t header_bytes_read = patch->data.size();
-    bool use_bsdiff = false;
-    if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
-        use_bsdiff = true;
-    } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) {
-        use_bsdiff = false;
-    } else {
-        printf("Unknown patch file format\n");
-        return 1;
-    }
+  const char* header = &patch->data[0];
+  size_t header_bytes_read = patch->data.size();
+  bool use_bsdiff = false;
+  if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
+    use_bsdiff = true;
+  } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) {
+    use_bsdiff = false;
+  } else {
+    printf("Unknown patch file format\n");
+    return 1;
+  }
 
-    do {
-        // Is there enough room in the target filesystem to hold the patched
-        // file?
+  bool target_is_partition = (strncmp(target_filename, "EMMC:", 5) == 0);
+  const std::string tmp_target_filename = std::string(target_filename) + ".patch";
 
-        if (target_is_partition) {
-            // If the target is a partition, we're actually going to
-            // write the output to /tmp and then copy it to the
-            // partition.  statfs() always returns 0 blocks free for
-            // /tmp, so instead we'll just assume that /tmp has enough
-            // space to hold the file.
-
-            // We still write the original source to cache, in case
-            // the partition write is interrupted.
-            if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
-                printf("not enough free space on /cache\n");
-                return 1;
-            }
-            if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
-                printf("failed to back up source file\n");
-                return 1;
-            }
-            made_copy = 1;
-            retry = 0;
-        } else {
-            int enough_space = 0;
-            if (retry > 0) {
-                size_t free_space = FreeSpaceForFile(target_fs.c_str());
-                enough_space =
-                    (free_space > (256 << 10)) &&          // 256k (two-block) minimum
-                    (free_space > (target_size * 3 / 2));  // 50% margin of error
-                if (!enough_space) {
-                    printf("target %zu bytes; free space %zu bytes; retry %d; enough %d\n",
-                           target_size, free_space, retry, enough_space);
-                }
-            }
-
-            if (!enough_space) {
-                retry = 0;
-            }
-
-            if (!enough_space && source_patch_value != NULL) {
-                // Using the original source, but not enough free space.  First
-                // copy the source file to cache, then delete it from the original
-                // location.
-
-                if (strncmp(source_filename, "EMMC:", 5) == 0) {
-                    // It's impossible to free space on the target filesystem by
-                    // deleting the source if the source is a partition.  If
-                    // we're ever in a state where we need to do this, fail.
-                    printf("not enough free space for target but source is partition\n");
-                    return 1;
-                }
-
-                if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
-                    printf("not enough free space on /cache\n");
-                    return 1;
-                }
-
-                if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
-                    printf("failed to back up source file\n");
-                    return 1;
-                }
-                made_copy = 1;
-                unlink(source_filename);
-
-                size_t free_space = FreeSpaceForFile(target_fs.c_str());
-                printf("(now %zu bytes free for target) ", free_space);
-            }
-        }
-
-
-        SinkFn sink = NULL;
-        void* token = NULL;
-        int output_fd = -1;
-        if (target_is_partition) {
-            // We store the decoded output in memory.
-            sink = MemorySink;
-            token = &memory_sink_str;
-        } else {
-            // We write the decoded output to "<tgt-file>.patch".
-            output_fd = ota_open(tmp_target_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC,
-                                 S_IRUSR | S_IWUSR);
-            if (output_fd < 0) {
-                printf("failed to open output file %s: %s\n", tmp_target_filename.c_str(),
-                       strerror(errno));
-                return 1;
-            }
-            sink = FileSink;
-            token = &output_fd;
-        }
-
-
-        SHA1_Init(&ctx);
-
-        int result;
-        if (use_bsdiff) {
-            result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(),
-                                      patch, 0, sink, token, &ctx);
-        } else {
-            result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(),
-                                     patch, sink, token, &ctx, bonus_data);
-        }
-
-        if (!target_is_partition) {
-            if (ota_fsync(output_fd) != 0) {
-                printf("failed to fsync file \"%s\" (%s)\n", tmp_target_filename.c_str(),
-                       strerror(errno));
-                result = 1;
-            }
-            if (ota_close(output_fd) != 0) {
-                printf("failed to close file \"%s\" (%s)\n", tmp_target_filename.c_str(),
-                       strerror(errno));
-                result = 1;
-            }
-        }
-
-        if (result != 0) {
-            if (retry == 0) {
-                printf("applying patch failed\n");
-                return result != 0;
-            } else {
-                printf("applying patch failed; retrying\n");
-            }
-            if (!target_is_partition) {
-                unlink(tmp_target_filename.c_str());
-            }
-        } else {
-            // succeeded; no need to retry
-            break;
-        }
-    } while (retry-- > 0);
-
-    uint8_t current_target_sha1[SHA_DIGEST_LENGTH];
-    SHA1_Final(current_target_sha1, &ctx);
-    if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
-        printf("patch did not produce expected sha1\n");
-        return 1;
-    } else {
-        printf("now %s\n", short_sha1(target_sha1).c_str());
-    }
+  int retry = 1;
+  bool made_copy = false;
+  SHA_CTX ctx;
+  std::string memory_sink_str;  // Don't need to reserve space.
+  do {
+    // Is there enough room in the target filesystem to hold the patched file?
 
     if (target_is_partition) {
-        // Copy the temp file to the partition.
-        if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()),
-                             memory_sink_str.size(), target_filename) != 0) {
-            printf("write of patched data to %s failed\n", target_filename);
-            return 1;
-        }
+      // If the target is a partition, we're actually going to
+      // write the output to /tmp and then copy it to the
+      // partition.  statfs() always returns 0 blocks free for
+      // /tmp, so instead we'll just assume that /tmp has enough
+      // space to hold the file.
+
+      // We still write the original source to cache, in case
+      // the partition write is interrupted.
+      if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
+        printf("not enough free space on /cache\n");
+        return 1;
+      }
+      if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
+        printf("failed to back up source file\n");
+        return 1;
+      }
+      made_copy = true;
+      retry = 0;
     } else {
-        // Give the .patch file the same owner, group, and mode of the
-        // original source file.
-        if (chmod(tmp_target_filename.c_str(), source_to_use->st.st_mode) != 0) {
-            printf("chmod of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno));
-            return 1;
+      bool enough_space = false;
+      if (retry > 0) {
+        size_t free_space = FreeSpaceForFile(target_fs.c_str());
+        enough_space = (free_space > (256 << 10)) &&          // 256k (two-block) minimum
+                       (free_space > (target_size * 3 / 2));  // 50% margin of error
+        if (!enough_space) {
+          printf("target %zu bytes; free space %zu bytes; retry %d; enough %d\n", target_size,
+                 free_space, retry, enough_space);
         }
-        if (chown(tmp_target_filename.c_str(), source_to_use->st.st_uid, source_to_use->st.st_gid) != 0) {
-            printf("chown of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno));
-            return 1;
+      }
+
+      if (!enough_space) {
+        retry = 0;
+      }
+
+      if (!enough_space && source_patch_value != nullptr) {
+        // Using the original source, but not enough free space.  First
+        // copy the source file to cache, then delete it from the original
+        // location.
+
+        if (strncmp(source_filename, "EMMC:", 5) == 0) {
+          // It's impossible to free space on the target filesystem by
+          // deleting the source if the source is a partition.  If
+          // we're ever in a state where we need to do this, fail.
+          printf("not enough free space for target but source is partition\n");
+          return 1;
         }
 
-        // Finally, rename the .patch file to replace the target file.
-        if (rename(tmp_target_filename.c_str(), target_filename) != 0) {
-            printf("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno));
-            return 1;
+        if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) {
+          printf("not enough free space on /cache\n");
+          return 1;
         }
+
+        if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) {
+          printf("failed to back up source file\n");
+          return 1;
+        }
+        made_copy = true;
+        unlink(source_filename);
+
+        size_t free_space = FreeSpaceForFile(target_fs.c_str());
+        printf("(now %zu bytes free for target) ", free_space);
+      }
     }
 
-    // If this run of applypatch created the copy, and we're here, we
-    // can delete it.
-    if (made_copy) {
-        unlink(CACHE_TEMP_SOURCE);
+    SinkFn sink = nullptr;
+    void* token = nullptr;
+    unique_fd output_fd;
+    if (target_is_partition) {
+      // We store the decoded output in memory.
+      sink = MemorySink;
+      token = &memory_sink_str;
+    } else {
+      // We write the decoded output to "<tgt-file>.patch".
+      output_fd.reset(ota_open(tmp_target_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC,
+                               S_IRUSR | S_IWUSR));
+      if (output_fd == -1) {
+        printf("failed to open output file %s: %s\n", tmp_target_filename.c_str(), strerror(errno));
+        return 1;
+      }
+      sink = FileSink;
+      token = &output_fd;
     }
 
-    // Success!
-    return 0;
+    SHA1_Init(&ctx);
+
+    int result;
+    if (use_bsdiff) {
+      result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(), patch, 0,
+                                sink, token, &ctx);
+    } else {
+      result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(), patch, sink,
+                               token, &ctx, bonus_data);
+    }
+
+    if (!target_is_partition) {
+      if (ota_fsync(output_fd) != 0) {
+        printf("failed to fsync file \"%s\": %s\n", tmp_target_filename.c_str(), strerror(errno));
+        result = 1;
+      }
+      if (ota_close(output_fd) != 0) {
+        printf("failed to close file \"%s\": %s\n", tmp_target_filename.c_str(), strerror(errno));
+        result = 1;
+      }
+    }
+
+    if (result != 0) {
+      if (retry == 0) {
+        printf("applying patch failed\n");
+        return 1;
+      } else {
+        printf("applying patch failed; retrying\n");
+      }
+      if (!target_is_partition) {
+        unlink(tmp_target_filename.c_str());
+      }
+    } else {
+      // succeeded; no need to retry
+      break;
+    }
+  } while (retry-- > 0);
+
+  uint8_t current_target_sha1[SHA_DIGEST_LENGTH];
+  SHA1_Final(current_target_sha1, &ctx);
+  if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
+    printf("patch did not produce expected sha1\n");
+    return 1;
+  } else {
+    printf("now %s\n", short_sha1(target_sha1).c_str());
+  }
+
+  if (target_is_partition) {
+    // Copy the temp file to the partition.
+    if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()),
+                         memory_sink_str.size(), target_filename) != 0) {
+      printf("write of patched data to %s failed\n", target_filename);
+      return 1;
+    }
+  } else {
+    // Give the .patch file the same owner, group, and mode of the original source file.
+    if (chmod(tmp_target_filename.c_str(), source_to_use->st.st_mode) != 0) {
+      printf("chmod of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno));
+      return 1;
+    }
+    if (chown(tmp_target_filename.c_str(), source_to_use->st.st_uid,
+              source_to_use->st.st_gid) != 0) {
+      printf("chown of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno));
+      return 1;
+    }
+
+    // Finally, rename the .patch file to replace the target file.
+    if (rename(tmp_target_filename.c_str(), target_filename) != 0) {
+      printf("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno));
+      return 1;
+    }
+  }
+
+  // If this run of applypatch created the copy, and we're here, we can delete it.
+  if (made_copy) {
+    unlink(CACHE_TEMP_SOURCE);
+  }
+
+  // Success!
+  return 0;
 }
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index e78a604..4dc4810 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -189,7 +189,7 @@
   }
 
   size_t sz = static_cast<size_t>(st.st_size);
-  unsigned char* img = reinterpret_cast<unsigned char*>(malloc(sz));
+  unsigned char* img = static_cast<unsigned char*>(malloc(sz));
   FILE* f = fopen(filename, "rb");
   if (fread(img, 1, sz, f) != sz) {
     printf("failed to read \"%s\" %s\n", filename, strerror(errno));
@@ -216,7 +216,7 @@
   int cdcount = Read2(img+i+8);
   int cdoffset = Read4(img+i+16);
 
-  ZipFileEntry* temp_entries = reinterpret_cast<ZipFileEntry*>(malloc(
+  ZipFileEntry* temp_entries = static_cast<ZipFileEntry*>(malloc(
       cdcount * sizeof(ZipFileEntry)));
   int entrycount = 0;
 
@@ -235,7 +235,7 @@
     int mlen = Read2(cd+32);   // file comment len
     int hoffset = Read4(cd+42);   // local header offset
 
-    char* filename = reinterpret_cast<char*>(malloc(nlen+1));
+    char* filename = static_cast<char*>(malloc(nlen+1));
     memcpy(filename, cd+46, nlen);
     filename[nlen] = '\0';
 
@@ -284,7 +284,7 @@
 #endif
 
   *num_chunks = 0;
-  *chunks = reinterpret_cast<ImageChunk*>(malloc((entrycount*2+2) * sizeof(ImageChunk)));
+  *chunks = static_cast<ImageChunk*>(malloc((entrycount*2+2) * sizeof(ImageChunk)));
   ImageChunk* curr = *chunks;
 
   if (include_pseudo_chunk) {
@@ -309,7 +309,7 @@
       curr->filename = temp_entries[nextentry].filename;
 
       curr->len = temp_entries[nextentry].uncomp_len;
-      curr->data = reinterpret_cast<unsigned char*>(malloc(curr->len));
+      curr->data = static_cast<unsigned char*>(malloc(curr->len));
 
       z_stream strm;
       strm.zalloc = Z_NULL;
@@ -379,7 +379,7 @@
   }
 
   size_t sz = static_cast<size_t>(st.st_size);
-  unsigned char* img = reinterpret_cast<unsigned char*>(malloc(sz + 4));
+  unsigned char* img = static_cast<unsigned char*>(malloc(sz + 4));
   FILE* f = fopen(filename, "rb");
   if (fread(img, 1, sz, f) != sz) {
     printf("failed to read \"%s\" %s\n", filename, strerror(errno));
@@ -409,7 +409,7 @@
       size_t chunk_offset = pos;
 
       *num_chunks += 3;
-      *chunks = reinterpret_cast<ImageChunk*>(realloc(*chunks,
+      *chunks = static_cast<ImageChunk*>(realloc(*chunks,
           *num_chunks * sizeof(ImageChunk)));
       ImageChunk* curr = *chunks + (*num_chunks-3);
 
@@ -432,7 +432,7 @@
 
       size_t allocated = 32768;
       curr->len = 0;
-      curr->data = reinterpret_cast<unsigned char*>(malloc(allocated));
+      curr->data = static_cast<unsigned char*>(malloc(allocated));
       curr->start = pos;
       curr->deflate_data = p;
 
@@ -460,7 +460,7 @@
         curr->len = allocated - strm.avail_out;
         if (strm.avail_out == 0) {
           allocated *= 2;
-          curr->data = reinterpret_cast<unsigned char*>(realloc(curr->data, allocated));
+          curr->data = static_cast<unsigned char*>(realloc(curr->data, allocated));
         }
       } while (ret != Z_STREAM_END);
 
@@ -503,7 +503,7 @@
       // Reallocate the list for every chunk; we expect the number of
       // chunks to be small (5 for typical boot and recovery images).
       ++*num_chunks;
-      *chunks = reinterpret_cast<ImageChunk*>(realloc(*chunks, *num_chunks * sizeof(ImageChunk)));
+      *chunks = static_cast<ImageChunk*>(realloc(*chunks, *num_chunks * sizeof(ImageChunk)));
       ImageChunk* curr = *chunks + (*num_chunks-1);
       curr->start = pos;
 
@@ -586,7 +586,7 @@
     return -1;
   }
 
-  unsigned char* out = reinterpret_cast<unsigned char*>(malloc(BUFFER_SIZE));
+  unsigned char* out = static_cast<unsigned char*>(malloc(BUFFER_SIZE));
 
   // We only check two combinations of encoder parameters:  level 6
   // (the default) and level 9 (the maximum).
@@ -647,7 +647,7 @@
 
   size_t sz = static_cast<size_t>(st.st_size);
   // TODO: Memory leak on error return.
-  unsigned char* data = reinterpret_cast<unsigned char*>(malloc(sz));
+  unsigned char* data = static_cast<unsigned char*>(malloc(sz));
 
   if (tgt->type == CHUNK_NORMAL && tgt->len <= sz) {
     unlink(ptemp);
@@ -814,7 +814,7 @@
       return 1;
     }
     bonus_size = st.st_size;
-    bonus_data = reinterpret_cast<unsigned char*>(malloc(bonus_size));
+    bonus_data = static_cast<unsigned char*>(malloc(bonus_size));
     FILE* f = fopen(argv[2], "rb");
     if (f == NULL) {
       printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno));
@@ -960,9 +960,9 @@
   DumpChunks(src_chunks, num_src_chunks);
 
   printf("Construct patches for %d chunks...\n", num_tgt_chunks);
-  unsigned char** patch_data = reinterpret_cast<unsigned char**>(malloc(
+  unsigned char** patch_data = static_cast<unsigned char**>(malloc(
       num_tgt_chunks * sizeof(unsigned char*)));
-  size_t* patch_size = reinterpret_cast<size_t*>(malloc(num_tgt_chunks * sizeof(size_t)));
+  size_t* patch_size = static_cast<size_t*>(malloc(num_tgt_chunks * sizeof(size_t)));
   for (i = 0; i < num_tgt_chunks; ++i) {
     if (zip_mode) {
       ImageChunk* src;
@@ -976,7 +976,7 @@
     } else {
       if (i == 1 && bonus_data) {
         printf("  using %zu bytes of bonus data for chunk %d\n", bonus_size, i);
-        src_chunks[i].data = reinterpret_cast<unsigned char*>(realloc(src_chunks[i].data,
+        src_chunks[i].data = static_cast<unsigned char*>(realloc(src_chunks[i].data,
             src_chunks[i].len + bonus_size));
         memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size);
         src_chunks[i].len += bonus_size;
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index f6f8005..9a56718 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -80,26 +80,23 @@
   return ret == 0;
 }
 
-static bool read_misc_partition(void* p, size_t size, size_t offset, std::string* err) {
-  std::string misc_blk_device = get_misc_blk_device(err);
-  if (misc_blk_device.empty()) {
-    return false;
-  }
+static bool read_misc_partition(void* p, size_t size, const std::string& misc_blk_device,
+                                size_t offset, std::string* err) {
   if (!wait_for_device(misc_blk_device, err)) {
     return false;
   }
   android::base::unique_fd fd(open(misc_blk_device.c_str(), O_RDONLY));
-  if (fd.get() == -1) {
+  if (fd == -1) {
     *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
+  if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
     *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  if (!android::base::ReadFully(fd.get(), p, size)) {
+  if (!android::base::ReadFully(fd, p, size)) {
     *err = android::base::StringPrintf("failed to read %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
@@ -107,29 +104,25 @@
   return true;
 }
 
-static bool write_misc_partition(const void* p, size_t size, size_t offset, std::string* err) {
-  std::string misc_blk_device = get_misc_blk_device(err);
-  if (misc_blk_device.empty()) {
-    return false;
-  }
-  android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC));
-  if (fd.get() == -1) {
+static bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device,
+                                 size_t offset, std::string* err) {
+  android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY));
+  if (fd == -1) {
     *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
+  if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
     *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  if (!android::base::WriteFully(fd.get(), p, size)) {
+  if (!android::base::WriteFully(fd, p, size)) {
     *err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
   }
-  // TODO: O_SYNC and fsync duplicates each other?
-  if (fsync(fd.get()) == -1) {
+  if (fsync(fd) == -1) {
     *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(),
                                        strerror(errno));
     return false;
@@ -137,12 +130,32 @@
   return true;
 }
 
+bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device,
+                                  std::string* err) {
+  return read_misc_partition(boot, sizeof(*boot), misc_blk_device,
+                             BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
+}
+
 bool read_bootloader_message(bootloader_message* boot, std::string* err) {
-  return read_misc_partition(boot, sizeof(*boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return read_bootloader_message_from(boot, misc_blk_device, err);
+}
+
+bool write_bootloader_message_to(const bootloader_message& boot, const std::string& misc_blk_device,
+                                 std::string* err) {
+  return write_misc_partition(&boot, sizeof(boot), misc_blk_device,
+                              BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
 }
 
 bool write_bootloader_message(const bootloader_message& boot, std::string* err) {
-  return write_misc_partition(&boot, sizeof(boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return write_bootloader_message_to(boot, misc_blk_device, err);
 }
 
 bool clear_bootloader_message(std::string* err) {
@@ -177,12 +190,21 @@
 }
 
 bool read_wipe_package(std::string* package_data, size_t size, std::string* err) {
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
   package_data->resize(size);
-  return read_misc_partition(&(*package_data)[0], size, WIPE_PACKAGE_OFFSET_IN_MISC, err);
+  return read_misc_partition(&(*package_data)[0], size, misc_blk_device,
+                             WIPE_PACKAGE_OFFSET_IN_MISC, err);
 }
 
 bool write_wipe_package(const std::string& package_data, std::string* err) {
-  return write_misc_partition(package_data.data(), package_data.size(),
+  std::string misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return write_misc_partition(package_data.data(), package_data.size(), misc_blk_device,
                               WIPE_PACKAGE_OFFSET_IN_MISC, err);
 }
 
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 5a5dd87..e45f424 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -178,15 +178,33 @@
 #include <string>
 #include <vector>
 
+// Read bootloader message into boot. Error message will be set in err.
 bool read_bootloader_message(bootloader_message* boot, std::string* err);
+
+// Read bootloader message from the specified misc device into boot.
+bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device,
+                                  std::string* err);
+
+// Write bootloader message to BCB.
 bool write_bootloader_message(const bootloader_message& boot, std::string* err);
+
+// Write bootloader message to the specified BCB device.
+bool write_bootloader_message_to(const bootloader_message& boot,
+                                 const std::string& misc_blk_device, std::string* err);
+
+// Write bootloader message (boots into recovery with the options) to BCB.
 bool write_bootloader_message(const std::vector<std::string>& options, std::string* err);
+
+// Clear BCB.
 bool clear_bootloader_message(std::string* err);
 
 // Writes the reboot-bootloader reboot reason to the bootloader_message.
 bool write_reboot_bootloader(std::string* err);
 
+// Read the wipe package from BCB (from offset WIPE_PACKAGE_OFFSET_IN_MISC).
 bool read_wipe_package(std::string* package_data, size_t size, std::string* err);
+
+// Write the wipe package into BCB (to offset WIPE_PACKAGE_OFFSET_IN_MISC).
 bool write_wipe_package(const std::string& package_data, std::string* err);
 
 #else
diff --git a/device.cpp b/device.cpp
index f8fbb8a..e717ddd 100644
--- a/device.cpp
+++ b/device.cpp
@@ -16,36 +16,15 @@
 
 #include "device.h"
 
-#if defined(AB_OTA_UPDATER)
-
-static const char* MENU_ITEMS[] = {
-    "Reboot system now",
-    "Reboot to bootloader",
-    "Wipe data/factory reset",
-    "Mount /system",
-    "Run graphics test",
-    "Power off",
-    NULL,
-};
-
-static const Device::BuiltinAction MENU_ACTIONS[] = {
-    Device::REBOOT,
-    Device::REBOOT_BOOTLOADER,
-    Device::WIPE_DATA,
-    Device::MOUNT_SYSTEM,
-    Device::RUN_GRAPHICS_TEST,
-    Device::SHUTDOWN,
-};
-
-#else
-
 static const char* MENU_ITEMS[] = {
     "Reboot system now",
     "Reboot to bootloader",
     "Apply update from ADB",
     "Apply update from SD card",
     "Wipe data/factory reset",
+#ifndef AB_OTA_UPDATER
     "Wipe cache partition",
+#endif  // !AB_OTA_UPDATER
     "Mount /system",
     "View recovery logs",
     "Run graphics test",
@@ -59,14 +38,19 @@
     Device::APPLY_ADB_SIDELOAD,
     Device::APPLY_SDCARD,
     Device::WIPE_DATA,
+#ifndef AB_OTA_UPDATER
     Device::WIPE_CACHE,
+#endif  // !AB_OTA_UPDATER
     Device::MOUNT_SYSTEM,
     Device::VIEW_RECOVERY_LOGS,
     Device::RUN_GRAPHICS_TEST,
     Device::SHUTDOWN,
 };
 
-#endif
+static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) ==
+              sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1,
+              "MENU_ITEMS and MENU_ACTIONS should have the same length, "
+              "except for the extra NULL entry in MENU_ITEMS.");
 
 const char* const* Device::GetMenuItems() {
   return MENU_ITEMS;
diff --git a/error_code.h b/error_code.h
index 92e93bd..92b1574 100644
--- a/error_code.h
+++ b/error_code.h
@@ -18,52 +18,53 @@
 #define _ERROR_CODE_H_
 
 enum ErrorCode {
-    kNoError = -1,
-    kLowBattery = 20,
-    kZipVerificationFailure,
-    kZipOpenFailure,
-    kBootreasonInBlacklist
+  kNoError = -1,
+  kLowBattery = 20,
+  kZipVerificationFailure,
+  kZipOpenFailure,
+  kBootreasonInBlacklist
 };
 
 enum CauseCode {
-    kNoCause = -1,
-    kArgsParsingFailure = 100,
-    kStashCreationFailure,
-    kFileOpenFailure,
-    kLseekFailure,
-    kFreadFailure,
-    kFwriteFailure,
-    kFsyncFailure,
-    kLibfecFailure,
-    kFileGetPropFailure,
-    kFileRenameFailure,
-    kSymlinkFailure,
-    kSetMetadataFailure,
-    kTune2FsFailure,
-    kRebootFailure,
-    kVendorFailure = 200
+  kNoCause = -1,
+  kArgsParsingFailure = 100,
+  kStashCreationFailure,
+  kFileOpenFailure,
+  kLseekFailure,
+  kFreadFailure,
+  kFwriteFailure,
+  kFsyncFailure,
+  kLibfecFailure,
+  kFileGetPropFailure,
+  kFileRenameFailure,
+  kSymlinkFailure,
+  kSetMetadataFailure,
+  kTune2FsFailure,
+  kRebootFailure,
+  kPackageExtractFileFailure,
+  kVendorFailure = 200
 };
 
 enum UncryptErrorCode {
-    kUncryptNoError = -1,
-    kUncryptErrorHolder = 50,
-    kUncryptTimeoutError = 100,
-    kUncryptFileRemoveError,
-    kUncryptFileOpenError,
-    kUncryptSocketOpenError,
-    kUncryptSocketWriteError,
-    kUncryptSocketListenError,
-    kUncryptSocketAcceptError,
-    kUncryptFstabReadError,
-    kUncryptFileStatError,
-    kUncryptBlockOpenError,
-    kUncryptIoctlError,
-    kUncryptReadError,
-    kUncryptWriteError,
-    kUncryptFileSyncError,
-    kUncryptFileCloseError,
-    kUncryptFileRenameError,
-    kUncryptPackageMissingError,
+  kUncryptNoError = -1,
+  kUncryptErrorHolder = 50,
+  kUncryptTimeoutError = 100,
+  kUncryptFileRemoveError,
+  kUncryptFileOpenError,
+  kUncryptSocketOpenError,
+  kUncryptSocketWriteError,
+  kUncryptSocketListenError,
+  kUncryptSocketAcceptError,
+  kUncryptFstabReadError,
+  kUncryptFileStatError,
+  kUncryptBlockOpenError,
+  kUncryptIoctlError,
+  kUncryptReadError,
+  kUncryptWriteError,
+  kUncryptFileSyncError,
+  kUncryptFileCloseError,
+  kUncryptFileRenameError,
+  kUncryptPackageMissingError,
 };
 
-#endif
+#endif // _ERROR_CODE_H_
diff --git a/install.cpp b/install.cpp
index 3871271..f124a26 100644
--- a/install.cpp
+++ b/install.cpp
@@ -17,6 +17,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <limits.h>
 #include <string.h>
 #include <sys/stat.h>
@@ -24,6 +25,8 @@
 #include <unistd.h>
 
 #include <chrono>
+#include <limits>
+#include <map>
 #include <string>
 #include <vector>
 
@@ -32,6 +35,7 @@
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <cutils/properties.h>
 #include <ziparchive/zip_archive.h>
 
 #include "common.h"
@@ -44,6 +48,8 @@
 #include "verifier.h"
 
 #define ASSUMED_UPDATE_BINARY_NAME  "META-INF/com/google/android/update-binary"
+static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
+static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
 #define PUBLIC_KEYS_FILE "/res/keys"
 static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
 static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
@@ -118,13 +124,146 @@
     }
 }
 
-// If the package contains an update binary, extract it and run it.
+// Extract the update binary from the open zip archive |zip| located at |path|
+// and store into |cmd| the command line that should be called. The |status_fd|
+// is the file descriptor the child process should use to report back the
+// progress of the update.
 static int
-try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
-                  std::vector<std::string>& log_buffer, int retry_count)
-{
-    read_source_target_build(zip, log_buffer);
+update_binary_command(const char* path, ZipArchiveHandle zip, int retry_count,
+                      int status_fd, std::vector<std::string>* cmd);
 
+#ifdef AB_OTA_UPDATER
+
+// Parses the metadata of the OTA package in |zip| and checks whether we are
+// allowed to accept this A/B package. Downgrading is not allowed unless
+// explicitly enabled in the package and only for incremental packages.
+static int check_newer_ab_build(ZipArchiveHandle zip)
+{
+    std::string metadata_str;
+    if (!read_metadata_from_package(zip, &metadata_str)) {
+        return INSTALL_CORRUPT;
+    }
+    std::map<std::string, std::string> metadata;
+    for (const std::string& line : android::base::Split(metadata_str, "\n")) {
+        size_t eq = line.find('=');
+        if (eq != std::string::npos) {
+            metadata[line.substr(0, eq)] = line.substr(eq + 1);
+        }
+    }
+    char value[PROPERTY_VALUE_MAX];
+
+    property_get("ro.product.device", value, "");
+    const std::string& pkg_device = metadata["pre-device"];
+    if (pkg_device != value || pkg_device.empty()) {
+        LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value;
+        return INSTALL_ERROR;
+    }
+
+    // We allow the package to not have any serialno, but if it has a non-empty
+    // value it should match.
+    property_get("ro.serialno", value, "");
+    const std::string& pkg_serial_no = metadata["serialno"];
+    if (!pkg_serial_no.empty() && pkg_serial_no != value) {
+        LOG(ERROR) << "Package is for serial " << pkg_serial_no;
+        return INSTALL_ERROR;
+    }
+
+    if (metadata["ota-type"] != "AB") {
+        LOG(ERROR) << "Package is not A/B";
+        return INSTALL_ERROR;
+    }
+
+    // Incremental updates should match the current build.
+    property_get("ro.build.version.incremental", value, "");
+    const std::string& pkg_pre_build = metadata["pre-build-incremental"];
+    if (!pkg_pre_build.empty() && pkg_pre_build != value) {
+        LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value;
+        return INSTALL_ERROR;
+    }
+    property_get("ro.build.fingerprint", value, "");
+    const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
+    if (!pkg_pre_build_fingerprint.empty() &&
+        pkg_pre_build_fingerprint != value) {
+        LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint
+                   << " but expected " << value;
+        return INSTALL_ERROR;
+    }
+
+    // Check for downgrade version.
+    int64_t build_timestamp = property_get_int64(
+            "ro.build.date.utc", std::numeric_limits<int64_t>::max());
+    int64_t pkg_post_timestamp = 0;
+    // We allow to full update to the same version we are running, in case there
+    // is a problem with the current copy of that version.
+    if (metadata["post-timestamp"].empty() ||
+        !android::base::ParseInt(metadata["post-timestamp"].c_str(),
+                                 &pkg_post_timestamp) ||
+        pkg_post_timestamp < build_timestamp) {
+        if (metadata["ota-downgrade"] != "yes") {
+            LOG(ERROR) << "Update package is older than the current build, expected a build "
+                       "newer than timestamp " << build_timestamp << " but package has "
+                       "timestamp " << pkg_post_timestamp << " and downgrade not allowed.";
+            return INSTALL_ERROR;
+        }
+        if (pkg_pre_build_fingerprint.empty()) {
+            LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
+            return INSTALL_ERROR;
+        }
+    }
+
+    return 0;
+}
+
+static int
+update_binary_command(const char* path, ZipArchiveHandle zip, int retry_count,
+                      int status_fd, std::vector<std::string>* cmd)
+{
+    int ret = check_newer_ab_build(zip);
+    if (ret) {
+        return ret;
+    }
+
+    // For A/B updates we extract the payload properties to a buffer and obtain
+    // the RAW payload offset in the zip file.
+    ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES);
+    ZipEntry properties_entry;
+    if (FindEntry(zip, property_name, &properties_entry) != 0) {
+        LOG(ERROR) << "Can't find " << AB_OTA_PAYLOAD_PROPERTIES;
+        return INSTALL_CORRUPT;
+    }
+    std::vector<uint8_t> payload_properties(
+            properties_entry.uncompressed_length);
+    if (ExtractToMemory(zip, &properties_entry, payload_properties.data(),
+                        properties_entry.uncompressed_length) != 0) {
+        LOG(ERROR) << "Can't extract " << AB_OTA_PAYLOAD_PROPERTIES;
+        return INSTALL_CORRUPT;
+    }
+
+    ZipString payload_name(AB_OTA_PAYLOAD);
+    ZipEntry payload_entry;
+    if (FindEntry(zip, payload_name, &payload_entry) != 0) {
+        LOG(ERROR) << "Can't find " << AB_OTA_PAYLOAD;
+        return INSTALL_CORRUPT;
+    }
+    long payload_offset = payload_entry.offset;
+    *cmd = {
+        "/sbin/update_engine_sideload",
+        android::base::StringPrintf("--payload=file://%s", path),
+        android::base::StringPrintf("--offset=%ld", payload_offset),
+        "--headers=" + std::string(payload_properties.begin(),
+                                   payload_properties.end()),
+        android::base::StringPrintf("--status_fd=%d", status_fd),
+    };
+    return 0;
+}
+
+#else  // !AB_OTA_UPDATER
+
+static int
+update_binary_command(const char* path, ZipArchiveHandle zip, int retry_count,
+                      int status_fd, std::vector<std::string>* cmd)
+{
+    // On traditional updates we extract the update binary from the package.
     ZipString binary_name(ASSUMED_UPDATE_BINARY_NAME);
     ZipEntry binary_entry;
     if (FindEntry(zip, binary_name, &binary_entry) != 0) {
@@ -147,9 +286,36 @@
         return INSTALL_ERROR;
     }
 
+    *cmd = {
+        binary,
+        EXPAND(RECOVERY_API_VERSION),   // defined in Android.mk
+        std::to_string(status_fd),
+        path,
+    };
+    if (retry_count > 0)
+        cmd->push_back("retry");
+    return 0;
+}
+#endif  // !AB_OTA_UPDATER
+
+// If the package contains an update binary, extract it and run it.
+static int
+try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
+                  std::vector<std::string>& log_buffer, int retry_count)
+{
+    read_source_target_build(zip, log_buffer);
+
     int pipefd[2];
     pipe(pipefd);
 
+    std::vector<std::string> args;
+    int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
+    if (ret) {
+        close(pipefd[0]);
+        close(pipefd[1]);
+        return ret;
+    }
+
     // When executing the update binary contained in the package, the
     // arguments passed are:
     //
@@ -199,22 +365,27 @@
     //   update attempt.
     //
 
-    const char* args[6];
-    args[0] = binary;
-    args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk
-    char temp[16];
-    snprintf(temp, sizeof(temp), "%d", pipefd[1]);
-    args[2] = temp;
-    args[3] = path;
-    args[4] = retry_count > 0 ? "retry" : NULL;
-    args[5] = NULL;
+    // Convert the vector to a NULL-terminated char* array suitable for execv.
+    const char* chr_args[args.size() + 1];
+    chr_args[args.size()] = NULL;
+    for (size_t i = 0; i < args.size(); i++) {
+        chr_args[i] = args[i].c_str();
+    }
 
     pid_t pid = fork();
+
+    if (pid == -1) {
+        close(pipefd[0]);
+        close(pipefd[1]);
+        PLOG(ERROR) << "Failed to fork update binary";
+        return INSTALL_ERROR;
+    }
+
     if (pid == 0) {
         umask(022);
         close(pipefd[0]);
-        execv(binary, const_cast<char**>(args));
-        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
+        execv(chr_args[0], const_cast<char**>(chr_args));
+        fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
         _exit(-1);
     }
     close(pipefd[1]);
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 003b519..426d982 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -66,7 +66,7 @@
         return -1;
     }
 
-    stinfo* sti = reinterpret_cast<stinfo*>(malloc(sizeof(stinfo)));
+    stinfo* sti = static_cast<stinfo*>(malloc(sizeof(stinfo)));
     if(sti == 0) fatal("cannot allocate stinfo");
     sti->func = func;
     sti->cookie = cookie;
diff --git a/minui/font_10x18.h b/minui/font_10x18.h
index 29d7053..30dfb9c 100644
--- a/minui/font_10x18.h
+++ b/minui/font_10x18.h
@@ -1,14 +1,14 @@
 struct {
   unsigned width;
   unsigned height;
-  unsigned cwidth;
-  unsigned cheight;
+  unsigned char_width;
+  unsigned char_height;
   unsigned char rundata[2973];
 } font = {
   .width = 960,
   .height = 18,
-  .cwidth = 10,
-  .cheight = 18,
+  .char_width = 10,
+  .char_height = 18,
   .rundata = {
 0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82,
 0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06,
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index c0eea9e..dcca3ec 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -35,12 +35,6 @@
 #include "minui.h"
 #include "graphics.h"
 
-struct GRFont {
-    GRSurface* texture;
-    int cwidth;
-    int cheight;
-};
-
 static GRFont* gr_font = NULL;
 static minui_backend* gr_backend = NULL;
 
@@ -60,15 +54,20 @@
     return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
 }
 
-int gr_measure(const char *s)
+const GRFont* gr_sys_font()
 {
-    return gr_font->cwidth * strlen(s);
+    return gr_font;
 }
 
-void gr_font_size(int *x, int *y)
+int gr_measure(const GRFont* font, const char *s)
 {
-    *x = gr_font->cwidth;
-    *y = gr_font->cheight;
+    return font->char_width * strlen(s);
+}
+
+void gr_font_size(const GRFont* font, int *x, int *y)
+{
+    *x = font->char_width;
+    *y = font->char_height;
 }
 
 static void text_blend(unsigned char* src_p, int src_row_bytes,
@@ -103,34 +102,32 @@
     }
 }
 
-void gr_text(int x, int y, const char *s, bool bold)
+void gr_text(const GRFont* font, int x, int y, const char *s, bool bold)
 {
-    GRFont* font = gr_font;
-
     if (!font->texture || gr_current_a == 0) return;
 
-    bold = bold && (font->texture->height != font->cheight);
+    bold = bold && (font->texture->height != font->char_height);
 
     x += overscan_offset_x;
     y += overscan_offset_y;
 
     unsigned char ch;
     while ((ch = *s++)) {
-        if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break;
+        if (outside(x, y) || outside(x+font->char_width-1, y+font->char_height-1)) break;
 
         if (ch < ' ' || ch > '~') {
             ch = '?';
         }
 
-        unsigned char* src_p = font->texture->data + ((ch - ' ') * font->cwidth) +
-                               (bold ? font->cheight * font->texture->row_bytes : 0);
+        unsigned char* src_p = font->texture->data + ((ch - ' ') * font->char_width) +
+                               (bold ? font->char_height * font->texture->row_bytes : 0);
         unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
 
         text_blend(src_p, font->texture->row_bytes,
                    dst_p, gr_draw->row_bytes,
-                   font->cwidth, font->cheight);
+                   font->char_width, font->char_height);
 
-        x += font->cwidth;
+        x += font->char_width;
     }
 }
 
@@ -267,40 +264,59 @@
     return surface->height;
 }
 
+int gr_init_font(const char* name, GRFont** dest) {
+    GRFont* font = reinterpret_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
+    if (font == nullptr) {
+        return -1;
+    }
+
+    int res = res_create_alpha_surface(name, &(font->texture));
+    if (res < 0) {
+        free(font);
+        return res;
+    }
+
+    // The font image should be a 96x2 array of character images.  The
+    // columns are the printable ASCII characters 0x20 - 0x7f.  The
+    // top row is regular text; the bottom row is bold.
+    font->char_width = font->texture->width / 96;
+    font->char_height = font->texture->height / 2;
+
+    *dest = font;
+
+    return 0;
+}
+
 static void gr_init_font(void)
 {
-    gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1));
-
-    int res = res_create_alpha_surface("font", &(gr_font->texture));
+    int res = gr_init_font("font", &gr_font);
     if (res == 0) {
-        // The font image should be a 96x2 array of character images.  The
-        // columns are the printable ASCII characters 0x20 - 0x7f.  The
-        // top row is regular text; the bottom row is bold.
-        gr_font->cwidth = gr_font->texture->width / 96;
-        gr_font->cheight = gr_font->texture->height / 2;
-    } else {
-        printf("failed to read font: res=%d\n", res);
-
-        // fall back to the compiled-in font.
-        gr_font->texture = reinterpret_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
-        gr_font->texture->width = font.width;
-        gr_font->texture->height = font.height;
-        gr_font->texture->row_bytes = font.width;
-        gr_font->texture->pixel_bytes = 1;
-
-        unsigned char* bits = reinterpret_cast<unsigned char*>(malloc(font.width * font.height));
-        gr_font->texture->data = reinterpret_cast<unsigned char*>(bits);
-
-        unsigned char data;
-        unsigned char* in = font.rundata;
-        while((data = *in++)) {
-            memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
-            bits += (data & 0x7f);
-        }
-
-        gr_font->cwidth = font.cwidth;
-        gr_font->cheight = font.cheight;
+        return;
     }
+
+    printf("failed to read font: res=%d\n", res);
+
+
+    // fall back to the compiled-in font.
+    gr_font = static_cast<GRFont*>(calloc(sizeof(*gr_font), 1));
+    gr_font->texture = static_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
+    gr_font->texture->width = font.width;
+    gr_font->texture->height = font.height;
+    gr_font->texture->row_bytes = font.width;
+    gr_font->texture->pixel_bytes = 1;
+
+    unsigned char* bits = static_cast<unsigned char*>(malloc(font.width * font.height));
+    gr_font->texture->data = bits;
+
+    unsigned char data;
+    unsigned char* in = font.rundata;
+    while((data = *in++)) {
+        memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
+        bits += (data & 0x7f);
+    }
+
+    gr_font->char_width = font.char_width;
+    gr_font->char_height = font.char_height;
 }
 
 #if 0
diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp
index 3c35410..9e262b0 100644
--- a/minui/graphics_adf.cpp
+++ b/minui/graphics_adf.cpp
@@ -68,9 +68,9 @@
     surf->base.row_bytes = surf->pitch;
     surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4;
 
-    surf->base.data = reinterpret_cast<uint8_t*>(mmap(NULL,
-                                                      surf->pitch * surf->base.height, PROT_WRITE,
-                                                      MAP_SHARED, surf->fd, surf->offset));
+    surf->base.data = static_cast<uint8_t*>(mmap(NULL,
+                                                 surf->pitch * surf->base.height, PROT_WRITE,
+                                                 MAP_SHARED, surf->fd, surf->offset));
     if (surf->base.data == MAP_FAILED) {
         close(surf->fd);
         return -errno;
@@ -259,7 +259,7 @@
 
 minui_backend *open_adf()
 {
-    adf_pdata* pdata = reinterpret_cast<adf_pdata*>(calloc(1, sizeof(*pdata)));
+    adf_pdata* pdata = static_cast<adf_pdata*>(calloc(1, sizeof(*pdata)));
     if (!pdata) {
         perror("allocating adf backend failed");
         return NULL;
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index 0788f75..631ef4e 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -133,7 +133,7 @@
     gr_framebuffer[0].height = vi.yres;
     gr_framebuffer[0].row_bytes = fi.line_length;
     gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
-    gr_framebuffer[0].data = reinterpret_cast<uint8_t*>(bits);
+    gr_framebuffer[0].data = static_cast<uint8_t*>(bits);
     memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
 
     /* check if we can use double buffering */
diff --git a/minui/minui.h b/minui/minui.h
index 5362d3f..78890b8 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -33,6 +33,12 @@
     unsigned char* data;
 };
 
+struct GRFont {
+    GRSurface* texture;
+    int char_width;
+    int char_height;
+};
+
 int gr_init();
 void gr_exit();
 
@@ -45,10 +51,14 @@
 void gr_clear();  // clear entire surface to current color
 void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
 void gr_fill(int x1, int y1, int x2, int y2);
-void gr_text(int x, int y, const char *s, bool bold);
+
 void gr_texticon(int x, int y, GRSurface* icon);
-int gr_measure(const char *s);
-void gr_font_size(int *x, int *y);
+
+const GRFont* gr_sys_font();
+int gr_init_font(const char* name, GRFont** dest);
+void gr_text(const GRFont* font, int x, int y, const char *s, bool bold);
+int gr_measure(const GRFont* font, const char *s);
+void gr_font_size(const GRFont* font, int *x, int *y);
 
 void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy);
 unsigned int gr_get_width(GRSurface* surface);
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 455ef27..9ccbf4b 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -37,7 +37,7 @@
 
 static GRSurface* malloc_surface(size_t data_size) {
     size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT;
-    unsigned char* temp = reinterpret_cast<unsigned char*>(malloc(size));
+    unsigned char* temp = static_cast<unsigned char*>(malloc(size));
     if (temp == NULL) return NULL;
     GRSurface* surface = reinterpret_cast<GRSurface*>(temp);
     surface->data = temp + sizeof(GRSurface) +
@@ -221,7 +221,7 @@
     png_set_bgr(png_ptr);
 #endif
 
-    p_row = reinterpret_cast<unsigned char*>(malloc(width * 4));
+    p_row = static_cast<unsigned char*>(malloc(width * 4));
     for (y = 0; y < height; ++y) {
         png_read_row(png_ptr, p_row, NULL);
         transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
@@ -281,7 +281,7 @@
         goto exit;
     }
 
-    surface = reinterpret_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
+    surface = static_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
     if (surface == NULL) {
         result = -8;
         goto exit;
@@ -298,7 +298,7 @@
     png_set_bgr(png_ptr);
 #endif
 
-    p_row = reinterpret_cast<unsigned char*>(malloc(width * 4));
+    p_row = static_cast<unsigned char*>(malloc(width * 4));
     for (y = 0; y < height; ++y) {
         png_read_row(png_ptr, p_row, NULL);
         int frame = y % *frames;
@@ -308,7 +308,7 @@
     }
     free(p_row);
 
-    *pSurface = reinterpret_cast<GRSurface**>(surface);
+    *pSurface = surface;
 
 exit:
     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
@@ -436,7 +436,7 @@
                 memcpy(surface->data + i*w, row.data(), w);
             }
 
-            *pSurface = reinterpret_cast<GRSurface*>(surface);
+            *pSurface = surface;
             break;
         } else {
             int i;
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
index 2efd300..f5b0113 100644
--- a/otafault/ota_io.cpp
+++ b/otafault/ota_io.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <map>
+#include "ota_io.h"
 
 #include <errno.h>
 #include <fcntl.h>
@@ -22,8 +22,10 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include <map>
+#include <memory>
+
 #include "config.h"
-#include "ota_io.h"
 
 static std::map<intptr_t, const char*> filename_cache;
 static std::string read_fault_file_name = "";
@@ -68,17 +70,33 @@
     return fh;
 }
 
-int ota_close(int fd) {
+static int __ota_close(int fd) {
     // descriptors can be reused, so make sure not to leave them in the cache
     filename_cache.erase(fd);
     return close(fd);
 }
 
-int ota_fclose(FILE* fh) {
-    filename_cache.erase((intptr_t)fh);
+void OtaCloser::Close(int fd) {
+    __ota_close(fd);
+}
+
+int ota_close(unique_fd& fd) {
+    return __ota_close(fd.release());
+}
+
+static int __ota_fclose(FILE* fh) {
+    filename_cache.erase(reinterpret_cast<intptr_t>(fh));
     return fclose(fh);
 }
 
+void OtaFcloser::operator()(FILE* f) {
+    __ota_fclose(f);
+};
+
+int ota_fclose(unique_file& fh) {
+  return __ota_fclose(fh.release());
+}
+
 size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) {
     if (should_fault_inject(OTAIO_READ)) {
         auto cached = filename_cache.find((intptr_t)stream);
diff --git a/otafault/ota_io.h b/otafault/ota_io.h
index 84187a7..395b423 100644
--- a/otafault/ota_io.h
+++ b/otafault/ota_io.h
@@ -26,6 +26,10 @@
 #include <stdio.h>
 #include <sys/stat.h>
 
+#include <memory>
+
+#include <android-base/unique_fd.h>
+
 #define OTAIO_CACHE_FNAME "/cache/saved.file"
 
 void ota_set_fault_files();
@@ -36,10 +40,6 @@
 
 FILE* ota_fopen(const char* filename, const char* mode);
 
-int ota_close(int fd);
-
-int ota_fclose(FILE* fh);
-
 size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream);
 
 ssize_t ota_read(int fd, void* buf, size_t nbyte);
@@ -50,4 +50,20 @@
 
 int ota_fsync(int fd);
 
+struct OtaCloser {
+  static void Close(int);
+};
+
+using unique_fd = android::base::unique_fd_impl<OtaCloser>;
+
+int ota_close(unique_fd& fd);
+
+struct OtaFcloser {
+  void operator()(FILE*);
+};
+
+using unique_file = std::unique_ptr<FILE, OtaFcloser>;
+
+int ota_fclose(unique_file& fh);
+
 #endif
diff --git a/screen_ui.cpp b/screen_ui.cpp
index c617272..fab3489 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -248,7 +248,7 @@
 }
 
 void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) {
-    gr_text(x, *y, line, bold);
+    gr_text(gr_sys_font(), x, *y, line, bold);
     *y += char_height_ + 4;
 }
 
@@ -304,10 +304,10 @@
                     gr_fill(0, y - 2, gr_fb_width(), y + char_height_ + 2);
                     // Bold white text for the selected item.
                     SetColor(MENU_SEL_FG);
-                    gr_text(4, y, menu_[i], true);
+                    gr_text(gr_sys_font(), 4, y, menu_[i], true);
                     SetColor(MENU);
                 } else {
-                    gr_text(4, y, menu_[i], false);
+                    gr_text(gr_sys_font(), 4, y, menu_[i], false);
                 }
                 y += char_height_ + 4;
             }
@@ -323,7 +323,7 @@
         for (int ty = gr_fb_height() - char_height_;
              ty >= y && count < text_rows_;
              ty -= char_height_, ++count) {
-            gr_text(0, ty, text_[row], false);
+            gr_text(gr_sys_font(), 0, ty, text_[row], false);
             --row;
             if (row < 0) row = text_rows_ - 1;
         }
@@ -442,7 +442,7 @@
     density_ = static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f;
     is_large_ = gr_fb_height() > PixelsFromDp(800);
 
-    gr_font_size(&char_width_, &char_height_);
+    gr_font_size(gr_sys_font(), &char_width_, &char_height_);
     text_rows_ = gr_fb_height() / char_height_;
     text_cols_ = gr_fb_width() / char_width_;
 
diff --git a/tests/Android.mk b/tests/Android.mk
index e87a229..5f6a7ce 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -80,10 +80,12 @@
     libedify \
     libotafault \
     libupdater \
+    libbootloader_message \
     libverifier \
     libminui \
     libotautil \
     libmounts \
+    libfs_mgr \
     liblog \
     libselinux \
     libext4_utils_static \
@@ -92,6 +94,8 @@
     libcrypto \
     libcutils \
     libbz \
+    libziparchive \
+    libutils \
     libz \
     libbase \
     libtune2fs \
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
index 97e74a3..f6b6922 100644
--- a/tests/common/test_constants.h
+++ b/tests/common/test_constants.h
@@ -19,6 +19,17 @@
 
 #include <stdlib.h>
 
+// Zip entries in ziptest_valid.zip.
+static const std::string kATxtContents("abcdefghabcdefgh\n");
+static const std::string kBTxtContents("abcdefgh\n");
+static const std::string kCTxtContents("abcdefghabcdefgh\n");
+static const std::string kDTxtContents("abcdefgh\n");
+
+// echo -n -e "abcdefghabcdefgh\n" | sha1sum
+static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643");
+// echo -n -e "abcdefgh\n" | sha1sum
+static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666");
+
 static const char* data_root = getenv("ANDROID_DATA");
 
 static std::string from_testdata_base(const std::string& fname) {
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
index 1a0b191..d178303 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -83,15 +83,11 @@
     ASSERT_TRUE(android::base::WriteStringToFile("hello", rand_file));
 
     // set up SHA constants
-    sha1sum(old_file, &old_sha1);
-    sha1sum(new_file, &new_sha1);
+    sha1sum(old_file, &old_sha1, &old_size);
+    sha1sum(new_file, &new_sha1, &new_size);
     srand(time(nullptr));
     bad_sha1_a = android::base::StringPrintf("%040x", rand());
     bad_sha1_b = android::base::StringPrintf("%040x", rand());
-
-    struct stat st;
-    stat(&new_file[0], &st);
-    new_size = st.st_size;
   }
 
   static std::string old_file;
@@ -105,6 +101,7 @@
   static std::string bad_sha1_a;
   static std::string bad_sha1_b;
 
+  static size_t old_size;
   static size_t new_size;
 };
 
@@ -184,7 +181,7 @@
 std::string ApplyPatchTest::new_sha1;
 std::string ApplyPatchTest::bad_sha1_a;
 std::string ApplyPatchTest::bad_sha1_b;
-
+size_t ApplyPatchTest::old_size;
 size_t ApplyPatchTest::new_size;
 
 std::vector<std::unique_ptr<Value>> ApplyPatchFullTest::patches;
@@ -211,6 +208,42 @@
   ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
 }
 
+TEST_F(ApplyPatchTest, CheckModeEmmcTarget) {
+  // EMMC:old_file:size:sha1 should pass the check.
+  std::string src_file =
+      "EMMC:" + old_file + ":" + std::to_string(old_size) + ":" + old_sha1;
+  std::vector<std::string> sha1s;
+  ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+  // EMMC:old_file:(size-1):sha1:(size+1):sha1 should fail the check.
+  src_file = "EMMC:" + old_file + ":" + std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size + 1) + ":" + old_sha1;
+  ASSERT_EQ(1, applypatch_check(src_file.c_str(), sha1s));
+
+  // EMMC:old_file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
+  src_file = "EMMC:" + old_file + ":" +
+             std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size) + ":" + old_sha1 + ":" +
+             std::to_string(old_size + 1) + ":" + old_sha1;
+  ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+  // EMMC:old_file:(size+1):sha1:(size-1):sha1:size:sha1 should pass the check.
+  src_file = "EMMC:" + old_file + ":" +
+             std::to_string(old_size + 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size) + ":" + old_sha1;
+  ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+  // EMMC:new_file:(size+1):old_sha1:(size-1):old_sha1:size:old_sha1:size:new_sha1
+  // should pass the check.
+  src_file = "EMMC:" + new_file + ":" +
+             std::to_string(old_size + 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+             std::to_string(old_size) + ":" + old_sha1 + ":" +
+             std::to_string(new_size) + ":" + new_sha1;
+  ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+}
+
 TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) {
   mangle_file(old_file);
   std::vector<std::string> sha1s = { old_sha1 };
@@ -307,7 +340,7 @@
   ASSERT_FALSE(file_cmp(output_loc, new_file));
 }
 
-TEST(ApplyPatchModes, InvalidArgs) {
+TEST(ApplyPatchModesTest, InvalidArgs) {
   // At least two args (including the filename).
   ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" }));
 
@@ -315,21 +348,22 @@
   ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" }));
 }
 
-TEST(ApplyPatchModes, PatchMode) {
+TEST(ApplyPatchModesTest, PatchMode) {
   std::string boot_img = from_testdata_base("boot.img");
   size_t boot_img_size;
   std::string boot_img_sha1;
   sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
 
   std::string recovery_img = from_testdata_base("recovery.img");
-  size_t recovery_img_size;
   std::string recovery_img_sha1;
-  sha1sum(recovery_img, &recovery_img_sha1, &recovery_img_size);
-
+  size_t size;
+  sha1sum(recovery_img, &recovery_img_sha1, &size);
+  std::string recovery_img_size = std::to_string(size);
   std::string bonus_file = from_testdata_base("bonus.file");
 
   // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
   TemporaryFile tmp1;
+  std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
   std::vector<const char*> args = {
     "applypatch",
     "-b",
@@ -337,28 +371,32 @@
     boot_img.c_str(),
     tmp1.path,
     recovery_img_sha1.c_str(),
-    std::to_string(recovery_img_size).c_str(),
-    (boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p")).c_str()
+    recovery_img_size.c_str(),
+    patch.c_str()
   };
   ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
 
   // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
   TemporaryFile tmp2;
+  patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
   std::vector<const char*> args2 = {
     "applypatch",
     boot_img.c_str(),
     tmp2.path,
     recovery_img_sha1.c_str(),
-    std::to_string(recovery_img_size).c_str(),
-    (boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p")).c_str()
+    recovery_img_size.c_str(),
+    patch.c_str()
   };
   ASSERT_EQ(0, applypatch_modes(args2.size(), args2.data()));
 
   // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> \
-    //               <src-sha1-fake>:<patch1> <src-sha1>:<patch2>
+  //               <src-sha1-fake>:<patch1> <src-sha1>:<patch2>
   TemporaryFile tmp3;
   std::string bad_sha1_a = android::base::StringPrintf("%040x", rand());
   std::string bad_sha1_b = android::base::StringPrintf("%040x", rand());
+  std::string patch1 = bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p");
+  std::string patch2 = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+  std::string patch3 = bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p");
   std::vector<const char*> args3 = {
     "applypatch",
     "-b",
@@ -366,15 +404,85 @@
     boot_img.c_str(),
     tmp3.path,
     recovery_img_sha1.c_str(),
-    std::to_string(recovery_img_size).c_str(),
-    (bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p")).c_str(),
-    (boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p")).c_str(),
-    (bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p")).c_str(),
+    recovery_img_size.c_str(),
+    patch1.c_str(),
+    patch2.c_str(),
+    patch3.c_str()
   };
   ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data()));
 }
 
-TEST(ApplyPatchModes, PatchModeInvalidArgs) {
+TEST(ApplyPatchModesTest, PatchModeEmmcTarget) {
+  std::string boot_img = from_testdata_base("boot.img");
+  size_t boot_img_size;
+  std::string boot_img_sha1;
+  sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
+
+  std::string recovery_img = from_testdata_base("recovery.img");
+  size_t size;
+  std::string recovery_img_sha1;
+  sha1sum(recovery_img, &recovery_img_sha1, &size);
+  std::string recovery_img_size = std::to_string(size);
+
+  std::string bonus_file = from_testdata_base("bonus.file");
+
+  // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+  TemporaryFile tmp1;
+  std::string src_file =
+      "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
+  std::string tgt_file = "EMMC:" + std::string(tmp1.path);
+  std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+  std::vector<const char*> args = {
+    "applypatch",
+    "-b",
+    bonus_file.c_str(),
+    src_file.c_str(),
+    tgt_file.c_str(),
+    recovery_img_sha1.c_str(),
+    recovery_img_size.c_str(),
+    patch.c_str()
+  };
+  ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
+
+  // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+  TemporaryFile tmp2;
+  patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
+  tgt_file = "EMMC:" + std::string(tmp2.path);
+  std::vector<const char*> args2 = {
+    "applypatch",
+    src_file.c_str(),
+    tgt_file.c_str(),
+    recovery_img_sha1.c_str(),
+    recovery_img_size.c_str(),
+    patch.c_str()
+  };
+  ASSERT_EQ(0, applypatch_modes(args2.size(), args2.data()));
+
+  // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> \
+  //               <src-sha1-fake>:<patch1> <src-sha1>:<patch2>
+  TemporaryFile tmp3;
+  tgt_file = "EMMC:" + std::string(tmp3.path);
+  std::string bad_sha1_a = android::base::StringPrintf("%040x", rand());
+  std::string bad_sha1_b = android::base::StringPrintf("%040x", rand());
+  std::string patch1 = bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p");
+  std::string patch2 = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+  std::string patch3 = bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p");
+  std::vector<const char*> args3 = {
+    "applypatch",
+    "-b",
+    bonus_file.c_str(),
+    src_file.c_str(),
+    tgt_file.c_str(),
+    recovery_img_sha1.c_str(),
+    recovery_img_size.c_str(),
+    patch1.c_str(),
+    patch2.c_str(),
+    patch3.c_str()
+  };
+  ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data()));
+}
+
+TEST(ApplyPatchModesTest, PatchModeInvalidArgs) {
   // Invalid bonus file.
   ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" }));
 
@@ -388,9 +496,10 @@
   sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
 
   std::string recovery_img = from_testdata_base("recovery.img");
-  size_t recovery_img_size;
+  size_t size;
   std::string recovery_img_sha1;
-  sha1sum(recovery_img, &recovery_img_sha1, &recovery_img_size);
+  sha1sum(recovery_img, &recovery_img_sha1, &size);
+  std::string recovery_img_size = std::to_string(size);
 
   // Bonus file is not supported in flash mode.
   // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size>
@@ -402,40 +511,44 @@
     boot_img.c_str(),
     tmp4.path,
     recovery_img_sha1.c_str(),
-    std::to_string(recovery_img_size).c_str() };
+    recovery_img_size.c_str()
+  };
   ASSERT_NE(0, applypatch_modes(args4.size(), args4.data()));
 
   // Failed to parse patch args.
   TemporaryFile tmp5;
+  std::string bad_arg1 =
+      "invalid-sha1:filename" + from_testdata_base("recovery-from-boot-with-bonus.p");
   std::vector<const char*> args5 = {
     "applypatch",
     boot_img.c_str(),
     tmp5.path,
     recovery_img_sha1.c_str(),
-    std::to_string(recovery_img_size).c_str(),
-    ("invalid-sha1:filename" + from_testdata_base("recovery-from-boot-with-bonus.p")).c_str(),
+    recovery_img_size.c_str(),
+    bad_arg1.c_str()
   };
   ASSERT_NE(0, applypatch_modes(args5.size(), args5.data()));
 
   // Target size cannot be zero.
   TemporaryFile tmp6;
+  std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
   std::vector<const char*> args6 = {
     "applypatch",
     boot_img.c_str(),
     tmp6.path,
     recovery_img_sha1.c_str(),
     "0",  // target size
-    (boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p")).c_str()
+    patch.c_str()
   };
   ASSERT_NE(0, applypatch_modes(args6.size(), args6.data()));
 }
 
-TEST(ApplyPatchModes, CheckModeInvalidArgs) {
+TEST(ApplyPatchModesTest, CheckModeInvalidArgs) {
   // Insufficient args.
   ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
 }
 
-TEST(ApplyPatchModes, SpaceModeInvalidArgs) {
+TEST(ApplyPatchModesTest, SpaceModeInvalidArgs) {
   // Insufficient args.
   ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-s" }));
 
@@ -449,6 +562,6 @@
   ASSERT_EQ(0, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0x10" }));
 }
 
-TEST(ApplyPatchModes, ShowLicenses) {
+TEST(ApplyPatchModesTest, ShowLicenses) {
   ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
 }
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 973c19d..f31f1f8 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -23,36 +23,42 @@
 #include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/test_utils.h>
+#include <bootloader_message/bootloader_message.h>
 #include <gtest/gtest.h>
+#include <ziparchive/zip_archive.h>
 
+#include "common/test_constants.h"
 #include "edify/expr.h"
 #include "error_code.h"
 #include "updater/install.h"
+#include "updater/updater.h"
 
 struct selabel_handle *sehandle = nullptr;
 
-static void expect(const char* expected, const char* expr_str, CauseCode cause_code) {
-    Expr* e;
-    int error_count;
-    EXPECT_EQ(parse_string(expr_str, &e, &error_count), 0);
+static void expect(const char* expected, const char* expr_str, CauseCode cause_code,
+                   UpdaterInfo* info = nullptr) {
+  Expr* e;
+  int error_count = 0;
+  ASSERT_EQ(0, parse_string(expr_str, &e, &error_count));
+  ASSERT_EQ(0, error_count);
 
-    State state(expr_str, nullptr);
+  State state(expr_str, info);
 
-    std::string result;
-    bool status = Evaluate(&state, e, &result);
+  std::string result;
+  bool status = Evaluate(&state, e, &result);
 
-    if (expected == nullptr) {
-        EXPECT_FALSE(status);
-    } else {
-        EXPECT_STREQ(expected, result.c_str());
-    }
+  if (expected == nullptr) {
+    ASSERT_FALSE(status);
+  } else {
+    ASSERT_TRUE(status);
+    ASSERT_STREQ(expected, result.c_str());
+  }
 
-    // Error code is set in updater/updater.cpp only, by parsing State.errmsg.
-    EXPECT_EQ(kNoError, state.error_code);
+  // Error code is set in updater/updater.cpp only, by parsing State.errmsg.
+  ASSERT_EQ(kNoError, state.error_code);
 
-    // Cause code should always be available.
-    EXPECT_EQ(cause_code, state.cause_code);
-
+  // Cause code should always be available.
+  ASSERT_EQ(cause_code, state.cause_code);
 }
 
 class UpdaterTest : public ::testing::Test {
@@ -264,3 +270,243 @@
     ASSERT_EQ(0, unlink(src1.c_str()));
     ASSERT_EQ(0, unlink(src2.c_str()));
 }
+
+TEST_F(UpdaterTest, package_extract_dir) {
+  // package_extract_dir expects 2 arguments.
+  expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
+  expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Need to set up the ziphandle.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+
+  // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
+  TemporaryDir td;
+  std::string temp_dir(td.path);
+  std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  // Verify.
+  std::string data;
+  std::string file_c = temp_dir + "/c.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+
+  std::string file_d = temp_dir + "/d.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  // Modify the contents in order to retry. It's expected to be overwritten.
+  ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
+  ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
+
+  // Extract again and verify.
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  // Extracting "b/" (with slash) should give the same result.
+  script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  // Extracting "" is allowed. The entries will carry the path name.
+  script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  std::string file_a = temp_dir + "/a.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
+  ASSERT_EQ(kATxtContents, data);
+  std::string file_b = temp_dir + "/b.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
+  ASSERT_EQ(kBTxtContents, data);
+  std::string file_b_c = temp_dir + "/b/c.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  std::string file_b_d = temp_dir + "/b/d.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  ASSERT_EQ(0, unlink(file_a.c_str()));
+  ASSERT_EQ(0, unlink(file_b.c_str()));
+  ASSERT_EQ(0, unlink(file_b_c.c_str()));
+  ASSERT_EQ(0, unlink(file_b_d.c_str()));
+  ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
+
+  // Extracting non-existent entry should still give "t".
+  script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  // Only relative zip_path is allowed.
+  script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  // Only absolute dest_path is allowed.
+  script = "package_extract_dir(\"b\", \"path\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  CloseArchive(handle);
+}
+
+// TODO: Test extracting to block device.
+TEST_F(UpdaterTest, package_extract_file) {
+  // package_extract_file expects 1 or 2 arguments.
+  expect(nullptr, "package_extract_file()", kArgsParsingFailure);
+  expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Need to set up the ziphandle.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+
+  // Two-argument version.
+  TemporaryFile temp_file1;
+  std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  // Verify the extracted entry.
+  std::string data;
+  ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
+  ASSERT_EQ(kATxtContents, data);
+
+  // Now extract another entry to the same location, which should overwrite.
+  script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
+  ASSERT_EQ(kBTxtContents, data);
+
+  // Missing zip entry. The two-argument version doesn't abort.
+  script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  // Extract to /dev/full should fail.
+  script = "package_extract_file(\"a.txt\", \"/dev/full\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  // One-argument version.
+  script = "sha1_check(package_extract_file(\"a.txt\"))";
+  expect(kATxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+
+  script = "sha1_check(package_extract_file(\"b.txt\"))";
+  expect(kBTxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+
+  // Missing entry. The one-argument version aborts the evaluation.
+  script = "package_extract_file(\"doesntexist\")";
+  expect(nullptr, script.c_str(), kPackageExtractFileFailure, &updater_info);
+
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, write_value) {
+  // write_value() expects two arguments.
+  expect(nullptr, "write_value()", kArgsParsingFailure);
+  expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // filename cannot be empty.
+  expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure);
+
+  // Write some value to file.
+  TemporaryFile temp_file;
+  std::string value = "magicvalue";
+  std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")");
+  expect("t", script.c_str(), kNoCause);
+
+  // Verify the content.
+  std::string content;
+  ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
+  ASSERT_EQ(value, content);
+
+  // Allow writing empty string.
+  script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")";
+  expect("t", script.c_str(), kNoCause);
+
+  // Verify the content.
+  ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
+  ASSERT_EQ("", content);
+
+  // It should fail gracefully when write fails.
+  script = "write_value(\"value\", \"/proc/0/file1\")";
+  expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, get_stage) {
+  // get_stage() expects one argument.
+  expect(nullptr, "get_stage()", kArgsParsingFailure);
+  expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure);
+  expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // Set up a local file as BCB.
+  TemporaryFile tf;
+  std::string temp_file(tf.path);
+  bootloader_message boot;
+  strlcpy(boot.stage, "2/3", sizeof(boot.stage));
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
+
+  // Can read the stage value.
+  std::string script("get_stage(\"" + temp_file + "\")");
+  expect("2/3", script.c_str(), kNoCause);
+
+  // Bad BCB path.
+  script = "get_stage(\"doesntexist\")";
+  expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, set_stage) {
+  // set_stage() expects two arguments.
+  expect(nullptr, "set_stage()", kArgsParsingFailure);
+  expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // Set up a local file as BCB.
+  TemporaryFile tf;
+  std::string temp_file(tf.path);
+  bootloader_message boot;
+  strlcpy(boot.command, "command", sizeof(boot.command));
+  strlcpy(boot.stage, "2/3", sizeof(boot.stage));
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
+
+  // Write with set_stage().
+  std::string script("set_stage(\"" + temp_file + "\", \"1/3\")");
+  expect(tf.path, script.c_str(), kNoCause);
+
+  // Verify.
+  bootloader_message boot_verify;
+  ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err));
+
+  // Stage should be updated, with command part untouched.
+  ASSERT_STREQ("1/3", boot_verify.stage);
+  ASSERT_STREQ(boot.command, boot_verify.command);
+
+  // Bad BCB path.
+  script = "set_stage(\"doesntexist\", \"1/3\")";
+  expect("", script.c_str(), kNoCause);
+
+  script = "set_stage(\"/dev/full\", \"1/3\")";
+  expect("", script.c_str(), kNoCause);
+}
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index 4972946..ef0ee4c 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -30,9 +30,6 @@
 
 #include "common/test_constants.h"
 
-static const std::string kATxtContents("abcdefghabcdefgh\n");
-static const std::string kBTxtContents("abcdefgh\n");
-
 TEST(ZipTest, ExtractPackageRecursive) {
   std::string zip_path = from_testdata_base("ziptest_valid.zip");
   ZipArchiveHandle handle;
diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk
index 8449c75..49d19b0 100644
--- a/update_verifier/Android.mk
+++ b/update_verifier/Android.mk
@@ -24,7 +24,10 @@
     libbase \
     libcutils \
     libhardware \
-    liblog
+    liblog \
+    libutils \
+    libhidlbase \
+    android.hardware.boot@1.0
 
 LOCAL_CFLAGS := -Werror
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index 93ac605..e97a3ad 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -45,7 +45,12 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <cutils/properties.h>
-#include <hardware/boot_control.h>
+#include <android/hardware/boot/1.0/IBootControl.h>
+
+using android::sp;
+using android::hardware::boot::V1_0::IBootControl;
+using android::hardware::boot::V1_0::BoolResult;
+using android::hardware::boot::V1_0::CommandResult;
 
 constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
 constexpr int BLOCKSIZE = 4096;
@@ -142,21 +147,18 @@
     LOG(INFO) << "Started with arg " << i << ": " << argv[i];
   }
 
-  const hw_module_t* hw_module;
-  if (hw_get_module("bootctrl", &hw_module) != 0) {
+  sp<IBootControl> module = IBootControl::getService("bootctrl");
+  if (module == nullptr) {
     LOG(ERROR) << "Error getting bootctrl module.";
     return -1;
   }
 
-  boot_control_module_t* module = reinterpret_cast<boot_control_module_t*>(
-      const_cast<hw_module_t*>(hw_module));
-  module->init(module);
+  uint32_t current_slot = module->getCurrentSlot();
+  BoolResult is_successful = module->isSlotMarkedSuccessful(current_slot);
+  LOG(INFO) << "Booting slot " << current_slot << ": isSlotMarkedSuccessful="
+            << static_cast<int32_t>(is_successful);
 
-  unsigned current_slot = module->getCurrentSlot(module);
-  int is_successful= module->isSlotMarkedSuccessful(module, current_slot);
-  LOG(INFO) << "Booting slot " << current_slot << ": isSlotMarkedSuccessful=" << is_successful;
-
-  if (is_successful == 0) {
+  if (is_successful == BoolResult::FALSE) {
     // The current slot has not booted successfully.
     char verity_mode[PROPERTY_VALUE_MAX];
     if (property_get("ro.boot.veritymode", verity_mode, "") == -1) {
@@ -175,9 +177,10 @@
       return -1;
     }
 
-    int ret = module->markBootSuccessful(module);
-    if (ret != 0) {
-      LOG(ERROR) << "Error marking booted successfully: " << strerror(-ret);
+    CommandResult cr;
+    module->markBootSuccessful([&cr](CommandResult result) { cr = result; });
+    if (!cr.success) {
+      LOG(ERROR) << "Error marking booted successfully: " << cr.errMsg;
       return -1;
     }
     LOG(INFO) << "Marked slot " << current_slot << " as booted successfully.";
diff --git a/updater/Android.mk b/updater/Android.mk
index 3c1d0d4..5d328a3 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -27,12 +27,14 @@
     libedify \
     libziparchive \
     libotautil \
+    libbootloader_message \
     libutils \
     libmounts \
     libotafault \
     libext4_utils_static \
     libfec \
     libfec_rs \
+    libfs_mgr \
     liblog \
     libselinux \
     libsparse_static \
diff --git a/updater/install.cpp b/updater/install.cpp
index 59c54dd..8db5c1f 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -39,11 +39,12 @@
 #include <string>
 #include <vector>
 
-#include <android-base/parseint.h>
+#include <android-base/file.h>
 #include <android-base/parsedouble.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
-#include <android-base/strings.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <cutils/android_reboot.h>
 #include <ext4_utils/make_ext4fs.h>
 #include <ext4_utils/wipe.h>
@@ -453,117 +454,114 @@
     return StringValue(frac_str);
 }
 
-// package_extract_dir(package_path, destination_path)
-Value* PackageExtractDirFn(const char* name, State* state,
-                          int argc, Expr* argv[]) {
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
+// package_extract_dir(package_dir, dest_dir)
+//   Extracts all files from the package underneath package_dir and writes them to the
+//   corresponding tree beneath dest_dir. Any existing files are overwritten.
+//   Example: package_extract_dir("system", "/system")
+//
+//   Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path.
+Value* PackageExtractDirFn(const char* name, State* state, int argc, Expr* argv[]) {
+  if (argc != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, 2, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& zip_path = args[0];
+  const std::string& dest_path = args[1];
+
+  ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+
+  // To create a consistent system image, never use the clock for timestamps.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
+
+  bool success = ExtractPackageRecursive(za, zip_path, dest_path, &timestamp, sehandle);
+
+  return StringValue(success ? "t" : "");
+}
+
+// package_extract_file(package_file[, dest_file])
+//   Extracts a single package_file from the update package and writes it to dest_file,
+//   overwriting existing files if necessary. Without the dest_file argument, returns the
+//   contents of the package file as a binary blob.
+Value* PackageExtractFileFn(const char* name, State* state, int argc, Expr* argv[]) {
+  if (argc < 1 || argc > 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %d", name, argc);
+  }
+
+  if (argc == 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 the argument(s)", name);
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %d args", name, argc);
     }
     const std::string& zip_path = args[0];
     const std::string& dest_path = args[1];
 
-    ZipArchiveHandle za = ((UpdaterInfo*)(state->cookie))->package_zip;
+    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+    ZipString zip_string_path(zip_path.c_str());
+    ZipEntry entry;
+    if (FindEntry(za, zip_string_path, &entry) != 0) {
+      printf("%s: no %s in package\n", name, zip_path.c_str());
+      return StringValue("");
+    }
 
-    // To create a consistent system image, never use the clock for timestamps.
-    struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
+    unique_fd fd(TEMP_FAILURE_RETRY(
+        ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
+    if (fd == -1) {
+      printf("%s: can't open %s for write: %s\n", name, dest_path.c_str(), strerror(errno));
+      return StringValue("");
+    }
 
-    bool success = ExtractPackageRecursive(za, zip_path, dest_path, &timestamp, sehandle);
+    bool success = true;
+    int32_t ret = ExtractEntryToFile(za, &entry, fd);
+    if (ret != 0) {
+      printf("%s: Failed to extract entry \"%s\" (%u bytes) to \"%s\": %s\n", name,
+             zip_path.c_str(), entry.uncompressed_length, dest_path.c_str(), ErrorCodeString(ret));
+      success = false;
+    }
+    if (ota_fsync(fd) == -1) {
+      printf("fsync of \"%s\" failed: %s\n", dest_path.c_str(), strerror(errno));
+      success = false;
+    }
+    if (ota_close(fd) == -1) {
+      printf("close of \"%s\" failed: %s\n", dest_path.c_str(), strerror(errno));
+      success = false;
+    }
 
     return StringValue(success ? "t" : "");
-}
+  } else {
+    // The one-argument version returns the contents of the file as the result.
 
-
-// package_extract_file(package_path, destination_path)
-//   or
-// package_extract_file(package_path)
-//   to return the entire contents of the file as the result of this
-//   function (the char* returned is actually a FileContents*).
-Value* PackageExtractFileFn(const char* name, State* state,
-                           int argc, Expr* argv[]) {
-    if (argc < 1 || argc > 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %d",
-                          name, argc);
+    std::vector<std::string> args;
+    if (!ReadArgs(state, 1, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %d args", name, argc);
     }
-    bool success = false;
+    const std::string& zip_path = args[0];
 
-    if (argc == 2) {
-        // The two-argument version extracts to a file.
-
-        ZipArchiveHandle za = ((UpdaterInfo*)(state->cookie))->package_zip;
-
-        std::vector<std::string> args;
-        if (!ReadArgs(state, 2, argv, &args)) {
-            return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %d args", name,
-                              argc);
-        }
-        const std::string& zip_path = args[0];
-        const std::string& dest_path = args[1];
-
-        ZipString zip_string_path(zip_path.c_str());
-        ZipEntry entry;
-        if (FindEntry(za, zip_string_path, &entry) != 0) {
-            printf("%s: no %s in package\n", name, zip_path.c_str());
-            return StringValue("");
-        }
-
-        int fd = TEMP_FAILURE_RETRY(ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
-              S_IRUSR | S_IWUSR));
-        if (fd == -1) {
-            printf("%s: can't open %s for write: %s\n", name, dest_path.c_str(), strerror(errno));
-            return StringValue("");
-        }
-        success = ExtractEntryToFile(za, &entry, fd);
-        if (ota_fsync(fd) == -1) {
-            printf("fsync of \"%s\" failed: %s\n", dest_path.c_str(), strerror(errno));
-            success = false;
-        }
-        if (ota_close(fd) == -1) {
-            printf("close of \"%s\" failed: %s\n", dest_path.c_str(), strerror(errno));
-            success = false;
-        }
-
-        return StringValue(success ? "t" : "");
-    } else {
-        // The one-argument version returns the contents of the file
-        // as the result.
-
-        std::vector<std::string> args;
-        if (!ReadArgs(state, 1, argv, &args)) {
-            return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %d args", name,
-                              argc);
-        }
-        const std::string& zip_path = args[0];
-
-        Value* v = new Value(VAL_INVALID, "");
-
-        ZipArchiveHandle za = ((UpdaterInfo*)(state->cookie))->package_zip;
-        ZipString zip_string_path(zip_path.c_str());
-        ZipEntry entry;
-        if (FindEntry(za, zip_string_path, &entry) != 0) {
-            printf("%s: no %s in package\n", name, zip_path.c_str());
-            return v;
-        }
-
-        v->data.resize(entry.uncompressed_length);
-        if (ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&v->data[0]),
-                            v->data.size()) != 0) {
-            printf("%s: faled to extract %zu bytes to memory\n", name, v->data.size());
-        } else {
-            success = true;
-        }
-
-        if (!success) {
-            v->data.clear();
-        } else {
-            v->type = VAL_BLOB;
-        }
-        return v;
+    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+    ZipString zip_string_path(zip_path.c_str());
+    ZipEntry entry;
+    if (FindEntry(za, zip_string_path, &entry) != 0) {
+      return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
+                        zip_path.c_str());
     }
+
+    std::string buffer;
+    buffer.resize(entry.uncompressed_length);
+
+    int32_t ret = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size());
+    if (ret != 0) {
+      return ErrorAbort(state, kPackageExtractFileFailure,
+                        "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name,
+                        zip_path.c_str(), buffer.size(), ErrorCodeString(ret));
+    }
+
+    return new Value(VAL_BLOB, buffer);
+  }
 }
 
 // symlink(target, [src1, src2, ...])
@@ -868,75 +866,73 @@
     return StringValue(value);
 }
 
-
 // file_getprop(file, key)
 //
 //   interprets 'file' as a getprop-style file (key=value pairs, one
 //   per line. # comment lines, blank lines, lines without '=' ignored),
 //   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);
+  if (argc != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, 2, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+  const std::string& key = args[1];
+
+  struct stat st;
+  if (stat(filename.c_str(), &st) < 0) {
+    return ErrorAbort(state, kFileGetPropFailure, "%s: failed to stat \"%s\": %s", name,
+                      filename.c_str(), strerror(errno));
+  }
+
+  constexpr off_t MAX_FILE_GETPROP_SIZE = 65536;
+  if (st.st_size > MAX_FILE_GETPROP_SIZE) {
+    return ErrorAbort(state, kFileGetPropFailure, "%s too large for %s (max %lld)",
+                      filename.c_str(), name, static_cast<long long>(MAX_FILE_GETPROP_SIZE));
+  }
+
+  std::string buffer(st.st_size, '\0');
+  unique_file f(ota_fopen(filename.c_str(), "rb"));
+  if (f == nullptr) {
+    return ErrorAbort(state, kFileOpenFailure, "%s: failed to open %s: %s", name, filename.c_str(),
+                      strerror(errno));
+  }
+
+  if (ota_fread(&buffer[0], 1, st.st_size, f.get()) != static_cast<size_t>(st.st_size)) {
+    ErrorAbort(state, kFreadFailure, "%s: failed to read %zu bytes from %s", name,
+               static_cast<size_t>(st.st_size), filename.c_str());
+    return nullptr;
+  }
+
+  ota_fclose(f);
+
+  std::vector<std::string> lines = android::base::Split(buffer, "\n");
+  for (size_t i = 0; i < lines.size(); i++) {
+    std::string line = android::base::Trim(lines[i]);
+
+    // comment or blank line: skip to next line
+    if (line.empty() || line[0] == '#') {
+      continue;
+    }
+    size_t equal_pos = line.find('=');
+    if (equal_pos == std::string::npos) {
+      continue;
     }
 
-    std::vector<std::string> args;
-    if (!ReadArgs(state, 2, argv, &args)) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
-    }
-    const std::string& filename = args[0];
-    const std::string& key = args[1];
+    // trim whitespace between key and '='
+    std::string str = android::base::Trim(line.substr(0, equal_pos));
 
-    struct stat st;
-    if (stat(filename.c_str(), &st) < 0) {
-        return ErrorAbort(state, kFileGetPropFailure, "%s: failed to stat \"%s\": %s", name,
-                          filename.c_str(), strerror(errno));
-    }
+    // not the key we're looking for
+    if (key != str) continue;
 
-    constexpr off_t MAX_FILE_GETPROP_SIZE = 65536;
-    if (st.st_size > MAX_FILE_GETPROP_SIZE) {
-        return ErrorAbort(state, kFileGetPropFailure, "%s too large for %s (max %lld)",
-                          filename.c_str(), name, static_cast<long long>(MAX_FILE_GETPROP_SIZE));
-    }
+    return StringValue(android::base::Trim(line.substr(equal_pos + 1)));
+  }
 
-    std::string buffer(st.st_size, '\0');
-    FILE* f = ota_fopen(filename.c_str(), "rb");
-    if (f == nullptr) {
-        return ErrorAbort(state, kFileOpenFailure, "%s: failed to open %s: %s", name,
-                          filename.c_str(), strerror(errno));
-    }
-
-    if (ota_fread(&buffer[0], 1, st.st_size, f) != static_cast<size_t>(st.st_size)) {
-        ErrorAbort(state, kFreadFailure, "%s: failed to read %zu bytes from %s",
-                   name, static_cast<size_t>(st.st_size), filename.c_str());
-        ota_fclose(f);
-        return nullptr;
-    }
-
-    ota_fclose(f);
-
-    std::vector<std::string> lines = android::base::Split(buffer, "\n");
-    for (size_t i = 0; i < lines.size(); i++) {
-        std::string line = android::base::Trim(lines[i]);
-
-        // comment or blank line: skip to next line
-        if (line.empty() || line[0] == '#') {
-            continue;
-        }
-        size_t equal_pos = line.find('=');
-        if (equal_pos == std::string::npos) {
-            continue;
-        }
-
-        // trim whitespace between key and '='
-        std::string str = android::base::Trim(line.substr(0, equal_pos));
-
-        // not the key we're looking for
-        if (key != str) continue;
-
-        return StringValue(android::base::Trim(line.substr(equal_pos + 1)));
-    }
-
-    return StringValue("");
+  return StringValue("");
 }
 
 // apply_patch_space(bytes)
@@ -1162,6 +1158,33 @@
     return v;
 }
 
+// write_value(value, filename)
+//   Writes 'value' to 'filename'.
+//   Example: write_value("960000", "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq")
+Value* WriteValueFn(const char* name, State* state, int argc, Expr* argv[]) {
+  if (argc != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, 2, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+  }
+
+  const std::string& filename = args[1];
+  if (filename.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Filename cannot be empty", name);
+  }
+
+  const std::string& value = args[0];
+  if (!android::base::WriteStringToFile(value, filename)) {
+    printf("%s: Failed to write to \"%s\": %s\n", name, filename.c_str(), strerror(errno));
+    return StringValue("");
+  } else {
+    return StringValue("t");
+  }
+}
+
 // Immediately reboot the device.  Recovery is not finished normally,
 // so if you reboot into recovery it will re-start applying the
 // current package (because nothing has cleared the copy of the
@@ -1172,31 +1195,35 @@
 // 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);
-    }
+  if (argc != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+  }
 
-    std::vector<std::string> args;
-    if (!ReadArgs(state, 2, argv, &args)) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
-    }
-    const std::string& filename = args[0];
-    const std::string& property = args[1];
+  std::vector<std::string> args;
+  if (!ReadArgs(state, 2, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+  const std::string& property = args[1];
 
-    // zero out the 'command' field of the bootloader message.
-    char buffer[80];
-    memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command));
-    FILE* f = ota_fopen(filename.c_str(), "r+b");
-    fseek(f, offsetof(struct bootloader_message, command), SEEK_SET);
-    ota_fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f);
-    ota_fclose(f);
+  // Zero out the 'command' field of the bootloader message. Leave the rest intact.
+  bootloader_message boot;
+  std::string err;
+  if (!read_bootloader_message_from(&boot, filename, &err)) {
+    printf("%s(): Failed to read from \"%s\": %s", name, filename.c_str(), err.c_str());
+    return StringValue("");
+  }
+  memset(boot.command, 0, sizeof(boot.command));
+  if (!write_bootloader_message_to(boot, filename, &err)) {
+    printf("%s(): Failed to write to \"%s\": %s", name, filename.c_str(), err.c_str());
+    return StringValue("");
+  }
 
-    std::string reboot_cmd = "reboot,";
-    reboot_cmd += property;
-    android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd);
+  const std::string reboot_cmd = "reboot," + property;
+  android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd);
 
-    sleep(5);
-    return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
+  sleep(5);
+  return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
 }
 
 // Store a string value somewhere that future invocations of recovery
@@ -1210,88 +1237,80 @@
 // 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);
-    }
+  if (argc != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+  }
 
-    std::vector<std::string> args;
-    if (!ReadArgs(state, 2, argv, &args)) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
-    }
-    const std::string& filename = args[0];
-    std::string& stagestr = args[1];
+  std::vector<std::string> args;
+  if (!ReadArgs(state, 2, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+  const std::string& stagestr = args[1];
 
-    // Store this value in the misc partition, immediately after the
-    // bootloader message that the main recovery uses to save its
-    // arguments in case of the device restarting midway through
-    // package installation.
-    FILE* f = ota_fopen(filename.c_str(), "r+b");
-    fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET);
-    size_t to_write = stagestr.size();
-    size_t max_size = sizeof(((struct bootloader_message*)0)->stage);
-    if (to_write > max_size) {
-        to_write = max_size;
-        stagestr = stagestr.substr(0, max_size-1);
-    }
-    size_t status = ota_fwrite(stagestr.c_str(), to_write, 1, f);
-    ota_fclose(f);
+  // Store this value in the misc partition, immediately after the
+  // bootloader message that the main recovery uses to save its
+  // arguments in case of the device restarting midway through
+  // package installation.
+  bootloader_message boot;
+  std::string err;
+  if (!read_bootloader_message_from(&boot, filename, &err)) {
+    printf("%s(): Failed to read from \"%s\": %s", name, filename.c_str(), err.c_str());
+    return StringValue("");
+  }
+  strlcpy(boot.stage, stagestr.c_str(), sizeof(boot.stage));
+  if (!write_bootloader_message_to(boot, filename, &err)) {
+    printf("%s(): Failed to write to \"%s\": %s", name, filename.c_str(), err.c_str());
+    return StringValue("");
+  }
 
-    if (status != to_write) {
-        return StringValue("");
-    }
-    return StringValue(filename);
+  return StringValue(filename);
 }
 
 // Return the value most recently saved with SetStageFn.  The argument
 // is the block device for the misc partition.
 Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
-    }
+  if (argc != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc);
+  }
 
-    std::vector<std::string> args;
-    if (!ReadArgs(state, 1, argv, &args)) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
-    }
-    const std::string& filename = args[0];
+  std::vector<std::string> args;
+  if (!ReadArgs(state, 1, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
 
-    char buffer[sizeof(((struct bootloader_message*)0)->stage)];
-    FILE* f = ota_fopen(filename.c_str(), "rb");
-    fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET);
-    size_t status = ota_fread(buffer, sizeof(buffer), 1, f);
-    ota_fclose(f);
-    if (status != sizeof(buffer)) {
-        return StringValue("");
-    }
+  bootloader_message boot;
+  std::string err;
+  if (!read_bootloader_message_from(&boot, filename, &err)) {
+    printf("%s(): Failed to read from \"%s\": %s", name, filename.c_str(), err.c_str());
+    return StringValue("");
+  }
 
-    buffer[sizeof(buffer)-1] = '\0';
-    return StringValue(buffer);
+  return StringValue(boot.stage);
 }
 
 Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) {
-    if (argc != 2) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
-    }
+  if (argc != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc);
+  }
 
-    std::vector<std::string> args;
-    if (!ReadArgs(state, 2, argv, &args)) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
-    }
-    const std::string& filename = args[0];
-    const std::string& len_str = args[1];
+  std::vector<std::string> args;
+  if (!ReadArgs(state, 2, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+  const std::string& len_str = args[1];
 
-    size_t len;
-    if (!android::base::ParseUint(len_str.c_str(), &len)) {
-        return nullptr;
-    }
-    int fd = ota_open(filename.c_str(), O_WRONLY, 0644);
-    // The wipe_block_device function in ext4_utils returns 0 on success and 1
-    // for failure.
-    int status = wipe_block_device(fd, len);
-
-    ota_close(fd);
-
-    return StringValue((status == 0) ? "t" : "");
+  size_t len;
+  if (!android::base::ParseUint(len_str.c_str(), &len)) {
+    return nullptr;
+  }
+  unique_fd fd(ota_open(filename.c_str(), O_WRONLY, 0644));
+  // The wipe_block_device function in ext4_utils returns 0 on success and 1
+  // for failure.
+  int status = wipe_block_device(fd, len);
+  return StringValue((status == 0) ? "t" : "");
 }
 
 Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) {
@@ -1370,6 +1389,7 @@
     RegisterFunction("read_file", ReadFileFn);
     RegisterFunction("sha1_check", Sha1CheckFn);
     RegisterFunction("rename", RenameFn);
+    RegisterFunction("write_value", WriteValueFn);
 
     RegisterFunction("wipe_cache", WipeCacheFn);
 
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 1788907..5433d11 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -177,7 +177,7 @@
             // items don't fit on the screen.
             if (menu_items > menu_end - menu_start) {
                 sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items);
-                gr_text(x+4, y, cur_selection_str, 1);
+                gr_text(gr_sys_font(), x+4, y, cur_selection_str, 1);
                 y += char_height_+4;
             }
 
@@ -192,10 +192,10 @@
                     gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2);
                     // white text of selected item
                     SetColor(MENU_SEL_FG);
-                    if (menu[i][0]) gr_text(x+4, y, menu[i], 1);
+                    if (menu[i][0]) gr_text(gr_sys_font(), x+4, y, menu[i], 1);
                     SetColor(MENU);
-                } else {
-                    if (menu[i][0]) gr_text(x+4, y, menu[i], 0);
+                } else if (menu[i][0]) {
+                    gr_text(gr_sys_font(), x+4, y, menu[i], 0);
                 }
                 y += char_height_+4;
             }
@@ -216,7 +216,7 @@
         for (int ty = gr_fb_height() - char_height_ - outer_height;
              ty > y+2 && count < text_rows;
              ty -= char_height_, ++count) {
-            gr_text(x+4, ty, text[row], 0);
+            gr_text(gr_sys_font(), x+4, ty, text[row], 0);
             --row;
             if (row < 0) row = text_rows-1;
         }
@@ -285,7 +285,7 @@
 {
     gr_init();
 
-    gr_font_size(&char_width_, &char_height_);
+    gr_font_size(gr_sys_font(), &char_width_, &char_height_);
 
     text_col = text_row = 0;
     text_rows = (gr_fb_height()) / char_height_;