Merge "Avoid key_queue_mutex deadlock in waitkey()"
diff --git a/CleanSpec.mk b/CleanSpec.mk
index a7ab0d9..6bd1eb1 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -51,6 +51,10 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinstall.recovery_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/system/lib64/libinstall.so)
 
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest/recovery_component_test)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest64/recovery_component_test)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/testcases/recovery_component_test)
+
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/README.md b/README.md
index efcd318..0ccc10b 100644
--- a/README.md
+++ b/README.md
@@ -22,11 +22,9 @@
 
     # 32-bit device
     adb shell /data/nativetest/recovery_unit_test/recovery_unit_test
-    adb shell /data/nativetest/recovery_component_test/recovery_component_test
 
     # Or 64-bit device
     adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test
-    adb shell /data/nativetest64/recovery_component_test/recovery_component_test
 
 Running the manual tests
 ------------------------
diff --git a/TEST_MAPPING b/TEST_MAPPING
index c87ece2..a304582 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,9 +7,6 @@
       "name": "recovery_unit_test"
     },
     {
-      "name": "recovery_component_test"
-    },
-    {
       "name": "recovery_host_test",
       "host": true
     }
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 415d95f..6ad4a61 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -675,7 +675,7 @@
 // Iterate the zip entries and compose the image chunks accordingly.
 bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandle handle) {
   void* cookie;
-  int ret = StartIteration(handle, &cookie, nullptr, nullptr);
+  int ret = StartIteration(handle, &cookie);
   if (ret != 0) {
     LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret);
     return false;
@@ -683,12 +683,11 @@
 
   // Create a list of deflated zip entries, sorted by offset.
   std::vector<std::pair<std::string, ZipEntry>> temp_entries;
-  ZipString name;
+  std::string name;
   ZipEntry entry;
   while ((ret = Next(cookie, &entry, &name)) == 0) {
     if (entry.method == kCompressDeflated || limit_ > 0) {
-      std::string entry_name(name.name, name.name + name.name_length);
-      temp_entries.emplace_back(entry_name, entry);
+      temp_entries.emplace_back(name, entry);
     }
   }
 
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index 5cd2132..8d72a11 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -14,9 +14,8 @@
 // limitations under the License.
 //
 
-cc_library {
-    name: "libbootloader_message",
-    recovery_available: true,
+cc_defaults {
+    name: "libbootloader_message_defaults",
     srcs: ["bootloader_message.cpp"],
     cflags: [
         "-Wall",
@@ -24,7 +23,37 @@
     ],
     shared_libs: [
         "libbase",
-        "libfs_mgr",
+    ],
+    static_libs: [
+        "libfstab",
     ],
     export_include_dirs: ["include"],
 }
+
+cc_library {
+    name: "libbootloader_message",
+    defaults: [
+        "libbootloader_message_defaults",
+    ],
+    recovery_available: true,
+    host_supported: true,
+
+    target: {
+        host: {
+            shared_libs: [
+                "libcutils", // for strlcpy
+            ],
+        },
+        darwin: {
+            enabled: false,
+        },
+    }
+}
+
+cc_library_static {
+    name: "libbootloader_message_vendor",
+    defaults: [
+        "libbootloader_message_defaults",
+    ],
+    vendor: true,
+}
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index 8c1d63b..e684abb 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -21,6 +21,7 @@
 #include <string.h>
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <android-base/file.h>
@@ -29,10 +30,24 @@
 #include <android-base/unique_fd.h>
 #include <fstab/fstab.h>
 
+#ifndef __ANDROID__
+#include <cutils/memory.h>  // for strlcpy
+#endif
+
 using android::fs_mgr::Fstab;
 using android::fs_mgr::ReadDefaultFstab;
 
+static std::string g_misc_device_for_test;
+
+// Exposed for test purpose.
+void SetMiscBlockDeviceForTest(std::string_view misc_device) {
+  g_misc_device_for_test = misc_device;
+}
+
 static std::string get_misc_blk_device(std::string* err) {
+  if (!g_misc_device_for_test.empty()) {
+    return g_misc_device_for_test;
+  }
   Fstab fstab;
   if (!ReadDefaultFstab(&fstab)) {
     *err = "failed to read default fstab";
@@ -168,6 +183,14 @@
   return write_bootloader_message(boot, err);
 }
 
+bool write_bootloader_message_to(const std::vector<std::string>& options,
+                                 const std::string& misc_blk_device, std::string* err) {
+  bootloader_message boot = {};
+  update_bootloader_message_in_struct(&boot, options);
+
+  return write_bootloader_message_to(boot, misc_blk_device, err);
+}
+
 bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
   bootloader_message boot;
   if (!read_bootloader_message(&boot, err)) {
@@ -186,13 +209,15 @@
   memset(boot->recovery, 0, sizeof(boot->recovery));
 
   strlcpy(boot->command, "boot-recovery", sizeof(boot->command));
-  strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery));
+
+  std::string recovery = "recovery\n";
   for (const auto& s : options) {
-    strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery));
+    recovery += s;
     if (s.back() != '\n') {
-      strlcat(boot->recovery, "\n", sizeof(boot->recovery));
+      recovery += '\n';
     }
   }
+  strlcpy(boot->recovery, recovery.c_str(), sizeof(boot->recovery));
   return true;
 }
 
@@ -228,6 +253,37 @@
                               WIPE_PACKAGE_OFFSET_IN_MISC, err);
 }
 
+static bool OffsetAndSizeInVendorSpace(size_t offset, size_t size) {
+  auto total_size = WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC;
+  return size <= total_size && offset <= total_size - size;
+}
+
+bool ReadMiscPartitionVendorSpace(void* data, size_t size, size_t offset, std::string* err) {
+  if (!OffsetAndSizeInVendorSpace(offset, size)) {
+    *err = android::base::StringPrintf("Out of bound read (offset %zu size %zu)", offset, size);
+    return false;
+  }
+  auto misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return read_misc_partition(data, size, misc_blk_device, VENDOR_SPACE_OFFSET_IN_MISC + offset,
+                             err);
+}
+
+bool WriteMiscPartitionVendorSpace(const void* data, size_t size, size_t offset, std::string* err) {
+  if (!OffsetAndSizeInVendorSpace(offset, size)) {
+    *err = android::base::StringPrintf("Out of bound write (offset %zu size %zu)", offset, size);
+    return false;
+  }
+  auto misc_blk_device = get_misc_blk_device(err);
+  if (misc_blk_device.empty()) {
+    return false;
+  }
+  return write_misc_partition(data, size, misc_blk_device, VENDOR_SPACE_OFFSET_IN_MISC + offset,
+                              err);
+}
+
 extern "C" bool write_reboot_bootloader(void) {
   std::string err;
   return write_reboot_bootloader(&err);
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 95c19ae..5c0a450 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -28,8 +28,9 @@
 // 16K - 64K    Used by uncrypt and recovery to store wipe_package for A/B devices
 // Note that these offsets are admitted by bootloader,recovery and uncrypt, so they
 // are not configurable without changing all of them.
-static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0;
-static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024;
+constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0;
+constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024;
+constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024;
 
 /* Bootloader Message (2-KiB)
  *
@@ -207,6 +208,11 @@
 // set the command and recovery fields, and reset the rest.
 bool write_bootloader_message(const std::vector<std::string>& options, std::string* err);
 
+// Write bootloader message (boots into recovery with the options) to the specific BCB device. Will
+// set the command and recovery fields, and reset the rest.
+bool write_bootloader_message_to(const std::vector<std::string>& options,
+                                 const std::string& misc_blk_device, std::string* err);
+
 // Update bootloader message (boots into recovery with the options) to BCB. Will
 // only update the command and recovery fields.
 bool update_bootloader_message(const std::vector<std::string>& options, std::string* err);
@@ -228,6 +234,14 @@
 // 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);
 
+// Reads data from the vendor space in /misc partition, with the given offset and size. Note that
+// offset is in relative to the start of vendor space.
+bool ReadMiscPartitionVendorSpace(void* data, size_t size, size_t offset, std::string* err);
+
+// Writes the given data to the vendor space in /misc partition, at the given offset. Note that
+// offset is in relative to the start of the vendor space.
+bool WriteMiscPartitionVendorSpace(const void* data, size_t size, size_t offset, std::string* err);
+
 #else
 
 #include <stdbool.h>
diff --git a/edify/expr.cpp b/edify/expr.cpp
index c090eb2..e5e0e24 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -421,5 +421,5 @@
   return nullptr;
 }
 
-State::State(const std::string& script, void* cookie)
-    : script(script), cookie(cookie), error_code(kNoError), cause_code(kNoCause) {}
+State::State(const std::string& script, UpdaterInterface* interface)
+    : script(script), updater(interface), error_code(kNoError), cause_code(kNoCause) {}
diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h
index 5cbd5e1..cd9c701 100644
--- a/edify/include/edify/expr.h
+++ b/edify/include/edify/expr.h
@@ -23,19 +23,20 @@
 #include <string>
 #include <vector>
 
+#include "edify/updater_interface.h"
+
 // Forward declaration to avoid including "otautil/error_code.h".
 enum ErrorCode : int;
 enum CauseCode : int;
 
 struct State {
-  State(const std::string& script, void* cookie);
+  State(const std::string& script, UpdaterInterface* cookie);
 
   // The source of the original script.
   const std::string& script;
 
-  // Optional pointer to app-specific data; the core of edify never
-  // uses this value.
-  void* cookie;
+  // A pointer to app-specific data; the libedify doesn't use this value.
+  UpdaterInterface* updater;
 
   // The error message (if any) returned if the evaluation aborts.
   // Should be empty initially, will be either empty or a string that
diff --git a/edify/include/edify/updater_interface.h b/edify/include/edify/updater_interface.h
new file mode 100644
index 0000000..a4d581e
--- /dev/null
+++ b/edify/include/edify/updater_interface.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <string>
+#include <string_view>
+
+struct ZipArchive;
+typedef ZipArchive* ZipArchiveHandle;
+
+class UpdaterRuntimeInterface;
+
+class UpdaterInterface {
+ public:
+  virtual ~UpdaterInterface() = default;
+
+  // Writes the message to command pipe, adds a new line in the end.
+  virtual void WriteToCommandPipe(const std::string_view message, bool flush = false) const = 0;
+
+  // Sends over the message to recovery to print it on the screen.
+  virtual void UiPrint(const std::string_view message) const = 0;
+
+  // Given the name of the block device, returns |name| for updates on the device; or the file path
+  // to the fake block device for simulations.
+  virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
+
+  virtual UpdaterRuntimeInterface* GetRuntime() const = 0;
+  virtual ZipArchiveHandle GetPackageHandle() const = 0;
+  virtual std::string GetResult() const = 0;
+  virtual uint8_t* GetMappedPackageAddress() const = 0;
+};
diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h
new file mode 100644
index 0000000..15ccd83
--- /dev/null
+++ b/edify/include/edify/updater_runtime_interface.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and
+// updates on device and host simulations can have different implementations. e.g. block devices
+// during host simulation merely a temporary file. With this class, the caller side in registered
+// updater's functions will stay the same for both update and simulation.
+class UpdaterRuntimeInterface {
+ public:
+  virtual ~UpdaterRuntimeInterface() = default;
+
+  // Returns true if it's a runtime instance for simulation.
+  virtual bool IsSimulator() const = 0;
+
+  // Returns the value of system property |key|. If the property doesn't exist, returns
+  // |default_value|.
+  virtual std::string GetProperty(const std::string_view key,
+                                  const std::string_view default_value) const = 0;
+
+  // Given the name of the block device, returns |name| for updates on the device; or the file path
+  // to the fake block device for simulations.
+  virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
+
+  // Mounts the |location| on |mount_point|. Returns 0 on success.
+  virtual int Mount(const std::string_view location, const std::string_view mount_point,
+                    const std::string_view fs_type, const std::string_view mount_options) = 0;
+
+  // Returns true if |mount_point| is mounted.
+  virtual bool IsMounted(const std::string_view mount_point) const = 0;
+
+  // Unmounts the |mount_point|. Returns a pair of results with the first value indicating
+  // if the |mount_point| is mounted, and the second value indicating the result of umount(2).
+  virtual std::pair<bool, int> Unmount(const std::string_view mount_point) = 0;
+
+  // Reads |filename| and puts its value to |content|.
+  virtual bool ReadFileToString(const std::string_view filename, std::string* content) const = 0;
+
+  // Updates the content of |filename| with |content|.
+  virtual bool WriteStringToFile(const std::string_view content,
+                                 const std::string_view filename) const = 0;
+
+  // Wipes the first |len| bytes of block device in |filename|.
+  virtual int WipeBlockDevice(const std::string_view filename, size_t len) const = 0;
+
+  // Starts a child process and runs the program with |args|. Uses vfork(2) if |is_vfork| is true.
+  virtual int RunProgram(const std::vector<std::string>& args, bool is_vfork) const = 0;
+
+  // Runs tune2fs with arguments |args|.
+  virtual int Tune2Fs(const std::vector<std::string>& args) const = 0;
+};
\ No newline at end of file
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 14f5e4b..2023349 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -30,10 +30,10 @@
 #include "recovery_ui/ui.h"
 
 static const std::vector<std::pair<std::string, Device::BuiltinAction>> kFastbootMenuActions{
-  { "Reboot system now", Device::REBOOT },
+  { "Reboot system now", Device::REBOOT_FROM_FASTBOOT },
   { "Enter recovery", Device::ENTER_RECOVERY },
   { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
-  { "Power off", Device::SHUTDOWN },
+  { "Power off", Device::SHUTDOWN_FROM_FASTBOOT },
 };
 
 Device::BuiltinAction StartFastboot(Device* device, const std::vector<std::string>& /* args */) {
diff --git a/fsck_unshare_blocks.cpp b/fsck_unshare_blocks.cpp
index 0f8fffa..9dc0fd8 100644
--- a/fsck_unshare_blocks.cpp
+++ b/fsck_unshare_blocks.cpp
@@ -34,6 +34,7 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/unique_fd.h>
+#include <fs_mgr/roots.h>
 
 #include "otautil/roots.h"
 
@@ -119,7 +120,7 @@
   std::vector<std::string> partitions = { "/odm", "/oem", "/product", "/vendor" };
 
   // Temporarily mount system so we can copy e2fsck_static.
-  std::string system_root = get_system_root();
+  auto system_root = android::fs_mgr::GetSystemRoot();
   bool mounted = ensure_path_mounted_at(system_root, "/mnt/system") != -1;
   partitions.push_back(system_root);
 
diff --git a/fuse_sideload/Android.bp b/fuse_sideload/Android.bp
index 8548548..9bf19eb 100644
--- a/fuse_sideload/Android.bp
+++ b/fuse_sideload/Android.bp
@@ -34,6 +34,10 @@
         "include",
     ],
 
+    static_libs: [
+        "libotautil",
+    ],
+
     shared_libs: [
         "libbase",
         "libcrypto",
diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp
index 58786f5..5ee6e24 100644
--- a/fuse_sideload/fuse_provider.cpp
+++ b/fuse_sideload/fuse_provider.cpp
@@ -27,8 +27,11 @@
 #include <functional>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 
 #include "fuse_sideload.h"
+#include "otautil/sysutil.h"
 
 FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t block_size) {
   struct stat sb;
@@ -69,3 +72,79 @@
 void FuseFileDataProvider::Close() {
   fd_.reset();
 }
+
+FuseBlockDataProvider::FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size,
+                                             android::base::unique_fd&& fd,
+                                             uint32_t source_block_size, RangeSet ranges)
+    : FuseDataProvider(file_size, fuse_block_size),
+      fd_(std::move(fd)),
+      source_block_size_(source_block_size),
+      ranges_(std::move(ranges)) {
+  // Make sure the offset is also aligned with the blocks on the block device when we call
+  // ReadBlockAlignedData().
+  CHECK_EQ(0, fuse_block_size_ % source_block_size_);
+}
+
+bool FuseBlockDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                                                 uint32_t start_block) const {
+  uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_;
+  if (fetch_size > file_size_ || offset > file_size_ - fetch_size) {
+    LOG(ERROR) << "Out of bound read, offset: " << offset << ", fetch size: " << fetch_size
+               << ", file size " << file_size_;
+    return false;
+  }
+
+  auto read_ranges =
+      ranges_.GetSubRanges(offset / source_block_size_, fetch_size / source_block_size_);
+  if (!read_ranges) {
+    return false;
+  }
+
+  uint8_t* next_out = buffer;
+  for (const auto& [range_start, range_end] : read_ranges.value()) {
+    uint64_t bytes_start = static_cast<uint64_t>(range_start) * source_block_size_;
+    uint64_t bytes_to_read = static_cast<uint64_t>(range_end - range_start) * source_block_size_;
+    if (!android::base::ReadFullyAtOffset(fd_, next_out, bytes_to_read, bytes_start)) {
+      PLOG(ERROR) << "Failed to read " << bytes_to_read << " bytes at offset " << bytes_start;
+      return false;
+    }
+
+    next_out += bytes_to_read;
+  }
+
+  if (uint64_t tailing_bytes = fetch_size % source_block_size_; tailing_bytes != 0) {
+    // Calculate the offset to last partial block.
+    uint64_t tailing_offset =
+        read_ranges.value()
+            ? static_cast<uint64_t>((read_ranges->cend() - 1)->second) * source_block_size_
+            : static_cast<uint64_t>(start_block) * source_block_size_;
+    if (!android::base::ReadFullyAtOffset(fd_, next_out, tailing_bytes, tailing_offset)) {
+      PLOG(ERROR) << "Failed to read tailing " << tailing_bytes << " bytes at offset "
+                  << tailing_offset;
+      return false;
+    }
+  }
+  return true;
+}
+
+std::unique_ptr<FuseBlockDataProvider> FuseBlockDataProvider::CreateFromBlockMap(
+    const std::string& block_map_path, uint32_t fuse_block_size) {
+  auto block_map = BlockMapData::ParseBlockMapFile(block_map_path);
+  if (!block_map) {
+    return nullptr;
+  }
+
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map.path().c_str(), O_RDONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << block_map.path();
+    return nullptr;
+  }
+
+  return std::unique_ptr<FuseBlockDataProvider>(
+      new FuseBlockDataProvider(block_map.file_size(), fuse_block_size, std::move(fd),
+                                block_map.block_size(), block_map.block_ranges()));
+}
+
+void FuseBlockDataProvider::Close() {
+  fd_.reset();
+}
diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h
index 59059cf..8d4ea40 100644
--- a/fuse_sideload/include/fuse_provider.h
+++ b/fuse_sideload/include/fuse_provider.h
@@ -18,10 +18,13 @@
 
 #include <stdint.h>
 
+#include <memory>
 #include <string>
 
 #include <android-base/unique_fd.h>
 
+#include "otautil/rangeset.h"
+
 // This is the base class to read data from source and provide the data to FUSE.
 class FuseDataProvider {
  public:
@@ -70,3 +73,28 @@
   // The underlying source to read data from.
   android::base::unique_fd fd_;
 };
+
+// This class parses a block map and reads data from the underlying block device.
+class FuseBlockDataProvider : public FuseDataProvider {
+ public:
+  // Constructs the fuse provider from the block map.
+  static std::unique_ptr<FuseBlockDataProvider> CreateFromBlockMap(
+      const std::string& block_map_path, uint32_t fuse_block_size);
+
+  RangeSet ranges() const {
+    return ranges_;
+  }
+  bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                            uint32_t start_block) const override;
+  void Close() override;
+
+ private:
+  FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size, android::base::unique_fd&& fd,
+                        uint32_t source_block_size, RangeSet ranges);
+  // The underlying block device to read data from.
+  android::base::unique_fd fd_;
+  // The block size of the source block device.
+  uint32_t source_block_size_;
+  // The block ranges from the source block device that consist of the file
+  RangeSet ranges_;
+};
diff --git a/install/Android.bp b/install/Android.bp
index b18e290..4696e50 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -66,6 +66,7 @@
         "package.cpp",
         "verifier.cpp",
         "wipe_data.cpp",
+        "wipe_device.cpp",
     ],
 
     shared_libs: [
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
index 9dfe040..2de1075 100644
--- a/install/adb_install.cpp
+++ b/install/adb_install.cpp
@@ -43,6 +43,7 @@
 
 #include "fuse_sideload.h"
 #include "install/install.h"
+#include "install/wipe_data.h"
 #include "minadbd_types.h"
 #include "otautil/sysutil.h"
 #include "recovery_ui/device.h"
@@ -89,7 +90,7 @@
 
 // Installs the package from FUSE. Returns the installation result and whether it should continue
 // waiting for new commands.
-static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) {
+static auto AdbInstallPackageHandler(RecoveryUI* ui, InstallResult* result) {
   // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long
   // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME
   // will start to exist once the host connects and starts serving a package. Poll for its
@@ -109,7 +110,7 @@
         break;
       }
     }
-    *result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0, ui);
+    *result = InstallPackage(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0, ui);
     break;
   }
 
@@ -119,7 +120,7 @@
   return std::make_pair(*result == INSTALL_SUCCESS, should_continue);
 }
 
-static auto AdbRebootHandler(MinadbdCommand command, int* result,
+static auto AdbRebootHandler(MinadbdCommand command, InstallResult* result,
                              Device::BuiltinAction* reboot_action) {
   // Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows
   // rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly
@@ -330,7 +331,7 @@
   signal(SIGPIPE, SIG_DFL);
 }
 
-int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot_action) {
+InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) {
   // Save the usb state to restore after the sideload operation.
   std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
   // Clean up state and stop adbd.
@@ -339,15 +340,9 @@
     return INSTALL_ERROR;
   }
 
-  if (!rescue_mode) {
-    ui->Print(
-        "\n\nNow send the package you want to apply\n"
-        "to the device with \"adb sideload <filename>\"...\n");
-  } else {
-    ui->Print("\n\nWaiting for rescue commands...\n");
-  }
+  RecoveryUI* ui = device->GetUI();
 
-  int install_result = INSTALL_ERROR;
+  InstallResult install_result = INSTALL_ERROR;
   std::map<MinadbdCommand, CommandFunction> command_map{
     { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) },
     { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
@@ -363,6 +358,18 @@
       std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) },
   };
 
+  if (!rescue_mode) {
+    ui->Print(
+        "\n\nNow send the package you want to apply\n"
+        "to the device with \"adb sideload <filename>\"...\n");
+  } else {
+    ui->Print("\n\nWaiting for rescue commands...\n");
+    command_map.emplace(MinadbdCommand::kWipeData, [&device]() {
+      bool result = WipeData(device, false);
+      return std::make_pair(result, true);
+    });
+  }
+
   CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode);
 
   // Clean up before switching to the older state, for example setting the state
diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_sdcard_install.cpp
index 1aa8768..a5caa6e 100644
--- a/install/fuse_sdcard_install.cpp
+++ b/install/fuse_sdcard_install.cpp
@@ -133,7 +133,7 @@
   return run_fuse_sideload(std::move(file_data_reader)) == 0;
 }
 
-int ApplyFromSdcard(Device* device, RecoveryUI* ui) {
+InstallResult ApplyFromSdcard(Device* device, RecoveryUI* ui) {
   if (ensure_path_mounted(SDCARD_ROOT) != 0) {
     LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
     return INSTALL_ERROR;
@@ -159,9 +159,8 @@
     _exit(status ? EXIT_SUCCESS : EXIT_FAILURE);
   }
 
-  // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child
-  // process is ready.
-  int result = INSTALL_ERROR;
+  // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child process is ready.
+  InstallResult result = INSTALL_ERROR;
   int status;
   bool waited = false;
   for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) {
@@ -184,7 +183,7 @@
       }
     }
 
-    result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /*retry_count*/, ui);
+    result = InstallPackage(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /* retry_count */, ui);
     break;
   }
 
diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h
index 49b32b5..8800223 100644
--- a/install/include/install/adb_install.h
+++ b/install/include/install/adb_install.h
@@ -16,10 +16,10 @@
 
 #pragma once
 
-#include <recovery_ui/device.h>
-#include <recovery_ui/ui.h>
+#include "install/install.h"
+#include "recovery_ui/device.h"
 
-// Applies a package via `adb sideload` or `adb rescue`. Returns the install result (in `enum
-// InstallResult`). When a reboot has been requested, INSTALL_REBOOT will be the return value, with
-// the reboot target set in reboot_action.
-int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot_action);
+// Applies a package via `adb sideload` or `adb rescue`. Returns the install result. When a reboot
+// has been requested, INSTALL_REBOOT will be the return value, with the reboot target set in
+// reboot_action.
+InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action);
diff --git a/install/include/install/fuse_sdcard_install.h b/install/include/install/fuse_sdcard_install.h
index d9214ca..e5bb01f 100644
--- a/install/include/install/fuse_sdcard_install.h
+++ b/install/include/install/fuse_sdcard_install.h
@@ -16,7 +16,8 @@
 
 #pragma once
 
+#include "install/install.h"
 #include "recovery_ui/device.h"
 #include "recovery_ui/ui.h"
 
-int ApplyFromSdcard(Device* device, RecoveryUI* ui);
+InstallResult ApplyFromSdcard(Device* device, RecoveryUI* ui);
diff --git a/install/include/install/install.h b/install/include/install/install.h
index c0a8f1f..44a5cde 100644
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -47,8 +47,8 @@
 // Installs the given update package. This function should also wipe the cache partition after a
 // successful installation if |should_wipe_cache| is true or an updater command asks to wipe the
 // cache.
-int install_package(const std::string& package, bool should_wipe_cache, bool needs_mount,
-                    int retry_count, RecoveryUI* ui);
+InstallResult InstallPackage(const std::string& package, bool should_wipe_cache, bool needs_mount,
+                             int retry_count, RecoveryUI* ui);
 
 // Verifies the package by ota keys. Returns true if the package is verified successfully,
 // otherwise returns false.
@@ -58,14 +58,11 @@
 // result to |metadata|. Return true if succeed, otherwise return false.
 bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
 
-// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe.
-std::vector<std::string> GetWipePartitionList(Package* wipe_package);
-
 // Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
 // entry doesn't exist.
 bool verify_package_compatibility(ZipArchiveHandle package_zip);
 
-// Checks if the the metadata in the OTA package has expected values. Returns 0 on success.
-// Mandatory checks: ota-type, pre-device and serial number(if presents)
-// AB OTA specific checks: pre-build version, fingerprint, timestamp.
-int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
+// Checks if the metadata in the OTA package has expected values. Mandatory checks: ota-type,
+// pre-device and serial number (if presents). A/B OTA specific checks: pre-build version,
+// fingerprint, timestamp.
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
diff --git a/install/include/install/wipe_device.h b/install/include/install/wipe_device.h
new file mode 100644
index 0000000..c60b999
--- /dev/null
+++ b/install/include/install/wipe_device.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "install/package.h"
+#include "recovery_ui/device.h"
+
+// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
+bool WipeAbDevice(Device* device, size_t wipe_package_size);
+
+// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe.
+std::vector<std::string> GetWipePartitionList(Package* wipe_package);
diff --git a/install/include/private/setup_commands.h b/install/include/private/setup_commands.h
index 7fdc741..dcff761 100644
--- a/install/include/private/setup_commands.h
+++ b/install/include/private/setup_commands.h
@@ -27,13 +27,13 @@
 // |zip| located at |package|. Stores the command line that should be called into |cmd|. The
 // |status_fd| is the file descriptor the child process should use to report back the progress of
 // the update.
-int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
-                             int status_fd, std::vector<std::string>* cmd);
+bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+                              int status_fd, std::vector<std::string>* cmd);
 
 // Sets up the commands for an A/B update. Extracts the needed entries from the open zip archive
 // |zip| located at |package|. Stores the command line that should be called into |cmd|. The
 // |status_fd| is the file descriptor the child process should use to report back the progress of
 // the update. Note that since this applies to the sideloading flow only, it takes one less
 // parameter |retry_count| than the non-A/B version.
-int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
-                          std::vector<std::string>* cmd);
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+                           std::vector<std::string>* cmd);
diff --git a/install/install.cpp b/install/install.cpp
index e2d4700..8d46641 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -73,9 +73,8 @@
   CHECK(metadata != nullptr);
 
   static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
-  ZipString path(METADATA_PATH);
   ZipEntry entry;
-  if (FindEntry(zip, path, &entry) != 0) {
+  if (FindEntry(zip, METADATA_PATH, &entry) != 0) {
     LOG(ERROR) << "Failed to find " << METADATA_PATH;
     return false;
   }
@@ -139,14 +138,14 @@
 // Checks the build version, fingerprint and timestamp in the metadata of the A/B package.
 // Downgrading is not allowed unless explicitly enabled in the package and only for
 // incremental packages.
-static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
+static bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
   // Incremental updates should match the current build.
   auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");
   auto pkg_pre_build = get_value(metadata, "pre-build-incremental");
   if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) {
     LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected "
                << device_pre_build;
-    return INSTALL_ERROR;
+    return false;
   }
 
   auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
@@ -154,7 +153,7 @@
   if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) {
     LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
                << device_fingerprint;
-    return INSTALL_ERROR;
+    return false;
   }
 
   // Check for downgrade version.
@@ -172,36 +171,36 @@
                     "newer than timestamp "
                  << build_timestamp << " but package has timestamp " << pkg_post_timestamp
                  << " and downgrade not allowed.";
-      return INSTALL_ERROR;
+      return false;
     }
     if (pkg_pre_build_fingerprint.empty()) {
       LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
-      return INSTALL_ERROR;
+      return false;
     }
   }
 
-  return 0;
+  return true;
 }
 
-int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
   auto package_ota_type = get_value(metadata, "ota-type");
   auto expected_ota_type = OtaTypeToString(ota_type);
   if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) {
     LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type;
-    return 0;
+    return true;
   }
 
   if (package_ota_type != expected_ota_type) {
     LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual "
                << package_ota_type;
-    return INSTALL_ERROR;
+    return false;
   }
 
   auto device = android::base::GetProperty("ro.product.device", "");
   auto pkg_device = get_value(metadata, "pre-device");
   if (pkg_device != device || pkg_device.empty()) {
     LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
-    return INSTALL_ERROR;
+    return false;
   }
 
   // We allow the package to not have any serialno; and we also allow it to carry multiple serial
@@ -218,7 +217,7 @@
     }
     if (!serial_number_match) {
       LOG(ERROR) << "Package is for serial " << pkg_serial_no;
-      return INSTALL_ERROR;
+      return false;
     }
   }
 
@@ -226,21 +225,20 @@
     return CheckAbSpecificMetadata(metadata);
   }
 
-  return 0;
+  return true;
 }
 
-int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
-                          std::vector<std::string>* cmd) {
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+                           std::vector<std::string>* cmd) {
   CHECK(cmd != nullptr);
 
   // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
   // in the zip file.
   static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
-  ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES);
   ZipEntry properties_entry;
-  if (FindEntry(zip, property_name, &properties_entry) != 0) {
+  if (FindEntry(zip, AB_OTA_PAYLOAD_PROPERTIES, &properties_entry) != 0) {
     LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES;
-    return INSTALL_CORRUPT;
+    return false;
   }
   uint32_t properties_entry_length = properties_entry.uncompressed_length;
   std::vector<uint8_t> payload_properties(properties_entry_length);
@@ -248,15 +246,14 @@
       ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length);
   if (err != 0) {
     LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err);
-    return INSTALL_CORRUPT;
+    return false;
   }
 
   static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
-  ZipString payload_name(AB_OTA_PAYLOAD);
   ZipEntry payload_entry;
-  if (FindEntry(zip, payload_name, &payload_entry) != 0) {
+  if (FindEntry(zip, AB_OTA_PAYLOAD, &payload_entry) != 0) {
     LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD;
-    return INSTALL_CORRUPT;
+    return false;
   }
   long payload_offset = payload_entry.offset;
   *cmd = {
@@ -266,20 +263,19 @@
     "--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
     android::base::StringPrintf("--status_fd=%d", status_fd),
   };
-  return 0;
+  return true;
 }
 
-int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
-                             int status_fd, std::vector<std::string>* cmd) {
+bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+                              int status_fd, std::vector<std::string>* cmd) {
   CHECK(cmd != nullptr);
 
   // In non-A/B updates we extract the update binary from the package.
   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
-  ZipString binary_name(UPDATE_BINARY_NAME);
   ZipEntry binary_entry;
-  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
+  if (FindEntry(zip, UPDATE_BINARY_NAME, &binary_entry) != 0) {
     LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
-    return INSTALL_CORRUPT;
+    return false;
   }
 
   const std::string binary_path = Paths::Get().temporary_update_binary();
@@ -288,13 +284,12 @@
       open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
   if (fd == -1) {
     PLOG(ERROR) << "Failed to create " << binary_path;
-    return INSTALL_ERROR;
+    return false;
   }
 
-  int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
-  if (error != 0) {
+  if (auto error = ExtractEntryToFile(zip, &binary_entry, fd); error != 0) {
     LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
-    return INSTALL_ERROR;
+    return false;
   }
 
   // When executing the update binary contained in the package, the arguments passed are:
@@ -311,7 +306,7 @@
   if (retry_count > 0) {
     cmd->push_back("retry");
   }
-  return 0;
+  return true;
 }
 
 static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) {
@@ -325,9 +320,9 @@
 }
 
 // If the package contains an update binary, extract it and run it.
-static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
-                             std::vector<std::string>* log_buffer, int retry_count,
-                             int* max_temperature, RecoveryUI* ui) {
+static InstallResult TryUpdateBinary(const std::string& package, ZipArchiveHandle zip,
+                                     bool* wipe_cache, std::vector<std::string>* log_buffer,
+                                     int retry_count, int* max_temperature, RecoveryUI* ui) {
   std::map<std::string, std::string> metadata;
   if (!ReadMetadataFromPackage(zip, &metadata)) {
     LOG(ERROR) << "Failed to parse metadata in the zip file";
@@ -335,11 +330,10 @@
   }
 
   bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
-  // Verifies against the metadata in the package first.
-  if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0;
-      check_status != 0) {
+  // Verify against the metadata in the package first.
+  if (is_ab && !CheckPackageMetadata(metadata, OtaType::AB)) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
-    return check_status;
+    return INSTALL_ERROR;
   }
 
   ReadSourceTargetBuild(metadata, log_buffer);
@@ -386,12 +380,12 @@
   //
 
   std::vector<std::string> args;
-  if (int update_status =
+  if (auto setup_result =
           is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args)
                 : SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args);
-      update_status != 0) {
+      !setup_result) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
-    return update_status;
+    return INSTALL_CORRUPT;
   }
 
   pid_t pid = fork();
@@ -510,9 +504,8 @@
   LOG(INFO) << "Verifying package compatibility...";
 
   static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip";
-  ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY);
   ZipEntry compatibility_entry;
-  if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) {
+  if (FindEntry(package_zip, COMPATIBILITY_ZIP_ENTRY, &compatibility_entry) != 0) {
     LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry";
     return true;
   }
@@ -536,7 +529,7 @@
 
   // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
   void* cookie;
-  ret = StartIteration(zip_handle, &cookie, nullptr, nullptr);
+  ret = StartIteration(zip_handle, &cookie);
   if (ret != 0) {
     LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret);
     CloseArchive(zip_handle);
@@ -546,13 +539,13 @@
 
   std::vector<std::string> compatibility_info;
   ZipEntry info_entry;
-  ZipString info_name;
+  std::string info_name;
   while (Next(cookie, &info_entry, &info_name) == 0) {
     std::string content(info_entry.uncompressed_length, '\0');
     int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]),
                                   info_entry.uncompressed_length);
     if (ret != 0) {
-      LOG(ERROR) << "Failed to read " << info_name.name << ": " << ErrorCodeString(ret);
+      LOG(ERROR) << "Failed to read " << info_name << ": " << ErrorCodeString(ret);
       CloseArchive(zip_handle);
       return false;
     }
@@ -571,9 +564,10 @@
   return false;
 }
 
-static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
-                                  std::vector<std::string>* log_buffer, int retry_count,
-                                  int* max_temperature, RecoveryUI* ui) {
+static InstallResult VerifyAndInstallPackage(const std::string& path, bool* wipe_cache,
+                                             bool needs_mount, std::vector<std::string>* log_buffer,
+                                             int retry_count, int* max_temperature,
+                                             RecoveryUI* ui) {
   ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
   ui->Print("Finding update package...\n");
   // Give verification half the progress bar...
@@ -624,16 +618,16 @@
     ui->Print("Retry attempt: %d\n", retry_count);
   }
   ui->SetEnableReboot(false);
-  int result =
-      try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
+  auto result =
+      TryUpdateBinary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
   ui->SetEnableReboot(true);
   ui->Print("\n");
 
   return result;
 }
 
-int install_package(const std::string& path, bool should_wipe_cache, bool needs_mount,
-                    int retry_count, RecoveryUI* ui) {
+InstallResult InstallPackage(const std::string& path, bool should_wipe_cache, bool needs_mount,
+                             int retry_count, RecoveryUI* ui) {
   CHECK(!path.empty());
 
   auto start = std::chrono::system_clock::now();
@@ -641,15 +635,15 @@
   int start_temperature = GetMaxValueFromThermalZone();
   int max_temperature = start_temperature;
 
-  int result;
+  InstallResult result;
   std::vector<std::string> log_buffer;
   if (setup_install_mounts() != 0) {
     LOG(ERROR) << "failed to set up expected mounts for install; aborting";
     result = INSTALL_ERROR;
   } else {
     bool updater_wipe_cache = false;
-    result = really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
-                                    retry_count, &max_temperature, ui);
+    result = VerifyAndInstallPackage(path, &updater_wipe_cache, needs_mount, &log_buffer,
+                                     retry_count, &max_temperature, ui);
     should_wipe_cache = should_wipe_cache || updater_wipe_cache;
   }
 
diff --git a/install/verifier.cpp b/install/verifier.cpp
index 6ba1d77..02759cd 100644
--- a/install/verifier.cpp
+++ b/install/verifier.cpp
@@ -311,8 +311,7 @@
 
 static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) {
   void* cookie;
-  ZipString suffix("x509.pem");
-  int32_t iter_status = StartIteration(handle, &cookie, nullptr, &suffix);
+  int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem");
   if (iter_status != 0) {
     LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: "
                << ErrorCodeString(iter_status);
@@ -321,22 +320,21 @@
 
   std::vector<Certificate> result;
 
-  ZipString name;
+  std::string name;
   ZipEntry entry;
   while ((iter_status = Next(cookie, &entry, &name)) == 0) {
     std::vector<uint8_t> pem_content(entry.uncompressed_length);
     if (int32_t extract_status =
             ExtractToMemory(handle, &entry, pem_content.data(), pem_content.size());
         extract_status != 0) {
-      LOG(ERROR) << "Failed to extract " << std::string(name.name, name.name + name.name_length);
+      LOG(ERROR) << "Failed to extract " << name;
       return {};
     }
 
     Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
     // Aborts the parsing if we fail to load one of the key file.
     if (!LoadCertificateFromBuffer(pem_content, &cert)) {
-      LOG(ERROR) << "Failed to load keys from "
-                 << std::string(name.name, name.name + name.name_length);
+      LOG(ERROR) << "Failed to load keys from " << name;
       return {};
     }
 
diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp
new file mode 100644
index 0000000..89d5d31
--- /dev/null
+++ b/install/wipe_device.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/wipe_device.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ziparchive/zip_archive.h>
+
+#include "bootloader_message/bootloader_message.h"
+#include "install/install.h"
+#include "install/package.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+std::vector<std::string> GetWipePartitionList(Package* wipe_package) {
+  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
+  if (!zip) {
+    LOG(ERROR) << "Failed to get ZipArchiveHandle";
+    return {};
+  }
+
+  constexpr char RECOVERY_WIPE_ENTRY_NAME[] = "recovery.wipe";
+
+  std::string partition_list_content;
+  ZipEntry entry;
+  if (FindEntry(zip, RECOVERY_WIPE_ENTRY_NAME, &entry) == 0) {
+    uint32_t length = entry.uncompressed_length;
+    partition_list_content = std::string(length, '\0');
+    if (auto err = ExtractToMemory(
+            zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length);
+        err != 0) {
+      LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": "
+                 << ErrorCodeString(err);
+      return {};
+    }
+  } else {
+    LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME
+              << ", falling back to use the partition list on device.";
+
+    constexpr char RECOVERY_WIPE_ON_DEVICE[] = "/etc/recovery.wipe";
+    if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) {
+      PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\"";
+      return {};
+    }
+  }
+
+  std::vector<std::string> result;
+  auto lines = android::base::Split(partition_list_content, "\n");
+  for (const auto& line : lines) {
+    auto partition = android::base::Trim(line);
+    // Ignore '#' comment or empty lines.
+    if (android::base::StartsWith(partition, "#") || partition.empty()) {
+      continue;
+    }
+    result.push_back(line);
+  }
+
+  return result;
+}
+
+// Secure-wipes a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
+// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
+static bool SecureWipePartition(const std::string& partition) {
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open \"" << partition << "\"";
+    return false;
+  }
+
+  uint64_t range[2] = { 0, 0 };
+  if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
+    PLOG(ERROR) << "Failed to get partition size";
+    return false;
+  }
+  LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
+
+  LOG(INFO) << "  Trying BLKSECDISCARD...";
+  if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
+    PLOG(WARNING) << "  Failed";
+
+    // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
+    unsigned int zeroes;
+    if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
+      LOG(INFO) << "  Trying BLKDISCARD...";
+      if (ioctl(fd, BLKDISCARD, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
+        return false;
+      }
+    } else {
+      LOG(INFO) << "  Trying BLKZEROOUT...";
+      if (ioctl(fd, BLKZEROOUT, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
+        return false;
+      }
+    }
+  }
+
+  LOG(INFO) << "  Done";
+  return true;
+}
+
+static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) {
+  if (wipe_package_size == 0) {
+    LOG(ERROR) << "wipe_package_size is zero";
+    return nullptr;
+  }
+
+  std::string wipe_package;
+  if (std::string err_str; !read_wipe_package(&wipe_package, wipe_package_size, &err_str)) {
+    PLOG(ERROR) << "Failed to read wipe package" << err_str;
+    return nullptr;
+  }
+
+  return Package::CreateMemoryPackage(
+      std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
+}
+
+// Checks if the wipe package matches expectation. If the check passes, reads the list of
+// partitions to wipe from the package. Checks include
+// 1. verify the package.
+// 2. check metadata (ota-type, pre-device and serial number if having one).
+static bool CheckWipePackage(Package* wipe_package, RecoveryUI* ui) {
+  if (!verify_package(wipe_package, ui)) {
+    LOG(ERROR) << "Failed to verify package";
+    return false;
+  }
+
+  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
+  if (!zip) {
+    LOG(ERROR) << "Failed to get ZipArchiveHandle";
+    return false;
+  }
+
+  std::map<std::string, std::string> metadata;
+  if (!ReadMetadataFromPackage(zip, &metadata)) {
+    LOG(ERROR) << "Failed to parse metadata in the zip file";
+    return false;
+  }
+
+  return CheckPackageMetadata(metadata, OtaType::BRICK);
+}
+
+bool WipeAbDevice(Device* device, size_t wipe_package_size) {
+  auto ui = device->GetUI();
+  ui->SetBackground(RecoveryUI::ERASING);
+  ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+  auto wipe_package = ReadWipePackage(wipe_package_size);
+  if (!wipe_package) {
+    LOG(ERROR) << "Failed to open wipe package";
+    return false;
+  }
+
+  if (!CheckWipePackage(wipe_package.get(), ui)) {
+    LOG(ERROR) << "Failed to verify wipe package";
+    return false;
+  }
+
+  auto partition_list = GetWipePartitionList(wipe_package.get());
+  if (partition_list.empty()) {
+    LOG(ERROR) << "Empty wipe ab partition list";
+    return false;
+  }
+
+  for (const auto& partition : partition_list) {
+    // Proceed anyway even if it fails to wipe some partition.
+    SecureWipePartition(partition);
+  }
+  return true;
+}
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
index 007e505..afd57ad 100644
--- a/minadbd/Android.bp
+++ b/minadbd/Android.bp
@@ -43,6 +43,10 @@
         "minadbd_services.cpp",
     ],
 
+    static_libs: [
+        "libotautil",
+    ],
+
     shared_libs: [
         "libadbd",
         "libbase",
@@ -96,6 +100,7 @@
     static_libs: [
         "libminadbd_services",
         "libfusesideload",
+        "libotautil",
         "libadbd",
         "libcrypto",
     ],
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 9b1999d..1e6eb31 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -25,10 +25,10 @@
 
 #include <functional>
 #include <memory>
+#include <set>
 #include <string>
 #include <string_view>
 #include <thread>
-#include <unordered_set>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -104,7 +104,7 @@
   if (pieces.size() != 2 || !android::base::ParseInt(pieces[0], &file_size) || file_size <= 0 ||
       !android::base::ParseInt(pieces[1], &block_size) || block_size <= 0) {
     LOG(ERROR) << "bad sideload-host arguments: " << args;
-    return kMinadbdPackageSizeError;
+    return kMinadbdHostCommandArgumentError;
   }
 
   LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size;
@@ -124,17 +124,17 @@
     return kMinadbdMessageFormatError;
   }
 
-  // Signal host-side adb to stop. For sideload mode, we always send kSideloadServiceExitSuccess
+  // Signal host-side adb to stop. For sideload mode, we always send kMinadbdServicesExitSuccess
   // (i.e. "DONEDONE") regardless of the install result. For rescue mode, we send failure message on
   // install error.
   if (!rescue_mode || *status == MinadbdCommandStatus::kSuccess) {
-    if (!android::base::WriteFully(sfd, kSideloadServiceExitSuccess,
-                                   strlen(kSideloadServiceExitSuccess))) {
+    if (!android::base::WriteFully(sfd, kMinadbdServicesExitSuccess,
+                                   strlen(kMinadbdServicesExitSuccess))) {
       return kMinadbdHostSocketIOError;
     }
   } else {
-    if (!android::base::WriteFully(sfd, kSideloadServiceExitFailure,
-                                   strlen(kSideloadServiceExitFailure))) {
+    if (!android::base::WriteFully(sfd, kMinadbdServicesExitFailure,
+                                   strlen(kMinadbdServicesExitFailure))) {
       return kMinadbdHostSocketIOError;
     }
   }
@@ -156,19 +156,36 @@
   }
 }
 
+// Answers the query on a given property |prop|, by writing the result to the given |sfd|. The
+// result will be newline-terminated, so nonexistent or nonallowed query will be answered with "\n".
+// If given an empty string, dumps all the supported properties (analogous to `adb shell getprop`)
+// in lines, e.g. "[prop]: [value]".
 static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) {
-  static const std::unordered_set<std::string> kGetpropAllowedProps = {
-    "ro.build.fingerprint",
+  static const std::set<std::string> kGetpropAllowedProps = {
     "ro.build.date.utc",
+    "ro.build.fingerprint",
+    "ro.build.flavor",
+    "ro.build.id",
+    "ro.build.product",
+    "ro.build.tags",
+    "ro.build.version.incremental",
+    "ro.product.device",
+    "ro.product.vendor.device",
   };
-  auto allowed = kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end();
-  if (!allowed) {
-    return;
+  std::string result;
+  if (prop.empty()) {
+    for (const auto& key : kGetpropAllowedProps) {
+      auto value = android::base::GetProperty(key, "");
+      if (value.empty()) {
+        continue;
+      }
+      result += "[" + key + "]: [" + value + "]\n";
+    }
+  } else if (kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end()) {
+    result = android::base::GetProperty(prop, "") + "\n";
   }
-
-  auto result = android::base::GetProperty(prop, "");
   if (result.empty()) {
-    return;
+    result = "\n";
   }
   if (!android::base::WriteFully(sfd, result.data(), result.size())) {
     exit(kMinadbdHostSocketIOError);
@@ -200,9 +217,37 @@
   }
 }
 
+static void WipeDeviceService(unique_fd fd, const std::string& args) {
+  auto pieces = android::base::Split(args, ":");
+  if (pieces.size() != 2 || pieces[0] != "userdata") {
+    LOG(ERROR) << "Failed to parse wipe device command arguments " << args;
+    exit(kMinadbdHostCommandArgumentError);
+  }
+
+  size_t message_size;
+  if (!android::base::ParseUint(pieces[1], &message_size) ||
+      message_size < strlen(kMinadbdServicesExitSuccess)) {
+    LOG(ERROR) << "Failed to parse wipe device message size in " << args;
+    exit(kMinadbdHostCommandArgumentError);
+  }
+
+  WriteCommandToFd(MinadbdCommand::kWipeData, minadbd_socket);
+  MinadbdCommandStatus status;
+  if (!WaitForCommandStatus(minadbd_socket, &status)) {
+    exit(kMinadbdMessageFormatError);
+  }
+
+  std::string response = (status == MinadbdCommandStatus::kSuccess) ? kMinadbdServicesExitSuccess
+                                                                    : kMinadbdServicesExitFailure;
+  response += std::string(message_size - response.size(), '\0');
+  if (!android::base::WriteFully(fd, response.c_str(), response.size())) {
+    exit(kMinadbdHostSocketIOError);
+  }
+}
+
 unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) {
   // Common services that are supported both in sideload and rescue modes.
-  if (ConsumePrefix(&name, "reboot:")) {
+  if (android::base::ConsumePrefix(&name, "reboot:")) {
     // "reboot:<target>", where target must be one of the following.
     std::string args(name);
     if (args.empty() || args == "bootloader" || args == "rescue" || args == "recovery" ||
@@ -215,17 +260,23 @@
 
   // Rescue-specific services.
   if (rescue_mode) {
-    if (ConsumePrefix(&name, "rescue-install:")) {
+    if (android::base::ConsumePrefix(&name, "rescue-install:")) {
       // rescue-install:<file-size>:<block-size>
       std::string args(name);
       return create_service_thread(
           "rescue-install", std::bind(RescueInstallHostService, std::placeholders::_1, args));
-    } else if (ConsumePrefix(&name, "rescue-getprop:")) {
+    } else if (android::base::ConsumePrefix(&name, "rescue-getprop:")) {
       // rescue-getprop:<prop>
       std::string args(name);
       return create_service_thread(
           "rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args));
+    } else if (android::base::ConsumePrefix(&name, "rescue-wipe:")) {
+      // rescue-wipe:target:<message-size>
+      std::string args(name);
+      return create_service_thread("rescue-wipe",
+                                   std::bind(WipeDeviceService, std::placeholders::_1, args));
     }
+
     return unique_fd{};
   }
 
@@ -234,7 +285,7 @@
     // This exit status causes recovery to print a special error message saying to use a newer adb
     // (that supports sideload-host).
     exit(kMinadbdAdbVersionError);
-  } else if (ConsumePrefix(&name, "sideload-host:")) {
+  } else if (android::base::ConsumePrefix(&name, "sideload-host:")) {
     // sideload-host:<file-size>:<block-size>
     std::string args(name);
     return create_service_thread("sideload-host",
diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp
index 593180b..f878737 100644
--- a/minadbd/minadbd_services_test.cpp
+++ b/minadbd/minadbd_services_test.cpp
@@ -122,7 +122,7 @@
 
 TEST_F(MinadbdServicesTest, SideloadHostService_wrong_size_argument) {
   ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:abc:4096"),
-              ::testing::ExitedWithCode(kMinadbdPackageSizeError), "");
+              ::testing::ExitedWithCode(kMinadbdHostCommandArgumentError), "");
 }
 
 TEST_F(MinadbdServicesTest, SideloadHostService_wrong_block_size) {
diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h
index b370b79..99fd45e 100644
--- a/minadbd/minadbd_types.h
+++ b/minadbd/minadbd_types.h
@@ -30,7 +30,7 @@
   kMinadbdSocketIOError = 2,
   kMinadbdMessageFormatError = 3,
   kMinadbdAdbVersionError = 4,
-  kMinadbdPackageSizeError = 5,
+  kMinadbdHostCommandArgumentError = 5,
   kMinadbdFuseStartError = 6,
   kMinadbdUnsupportedCommandError = 7,
   kMinadbdCommandExecutionError = 8,
@@ -51,6 +51,8 @@
   kRebootFastboot = 4,
   kRebootRecovery = 5,
   kRebootRescue = 6,
+  kWipeCache = 7,
+  kWipeData = 8,
 
   // Last but invalid command.
   kError,
diff --git a/misc_writer/Android.bp b/misc_writer/Android.bp
new file mode 100644
index 0000000..567143c
--- /dev/null
+++ b/misc_writer/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_binary {
+    name: "misc_writer",
+    vendor: true,
+
+    srcs: [
+        "misc_writer.cpp",
+    ],
+
+    cpp_std: "experimental",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    shared_libs: [
+        "libbase",
+    ],
+
+    static_libs: [
+        "libbootloader_message_vendor",
+        "libfstab",
+    ],
+}
diff --git a/misc_writer/misc_writer.cpp b/misc_writer/misc_writer.cpp
new file mode 100644
index 0000000..1d9702e
--- /dev/null
+++ b/misc_writer/misc_writer.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <bootloader_message/bootloader_message.h>
+
+using namespace std::string_literals;
+
+static std::vector<uint8_t> ParseHexString(std::string_view hex_string) {
+  auto length = hex_string.size();
+  if (length % 2 != 0 || length == 0) {
+    return {};
+  }
+
+  std::vector<uint8_t> result(length / 2);
+  for (size_t i = 0; i < length / 2; i++) {
+    auto sub = "0x" + std::string(hex_string.substr(i * 2, 2));
+    if (!android::base::ParseUint(sub, &result[i])) {
+      return {};
+    }
+  }
+  return result;
+}
+
+static int Usage(std::string_view name) {
+  std::cerr << name << " usage:\n";
+  std::cerr << name << " [--vendor-space-offset <offset>] --hex-string 0xABCDEF\n";
+  std::cerr << "Writes the given hex string to the specified offset in vendor space in /misc "
+               "partition. Offset defaults to 0 if unspecified.\n";
+  return EXIT_FAILURE;
+}
+
+// misc_writer is a vendor tool that writes data to the vendor space in /misc.
+int main(int argc, char** argv) {
+  constexpr struct option OPTIONS[] = {
+    { "vendor-space-offset", required_argument, nullptr, 0 },
+    { "hex-string", required_argument, nullptr, 0 },
+    { nullptr, 0, nullptr, 0 },
+  };
+
+  // Offset defaults to 0 if unspecified.
+  size_t offset = 0;
+  std::string_view hex_string;
+
+  int arg;
+  int option_index;
+  while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
+    if (arg != 0) {
+      LOG(ERROR) << "Invalid command argument";
+      return Usage(argv[0]);
+    }
+    auto option_name = OPTIONS[option_index].name;
+    if (option_name == "vendor-space-offset"s) {
+      if (!android::base::ParseUint(optarg, &offset)) {
+        LOG(ERROR) << "Failed to parse the offset: " << optarg;
+        return Usage(argv[0]);
+      }
+    } else if (option_name == "hex-string"s) {
+      hex_string = optarg;
+    }
+  }
+
+  if (hex_string.starts_with("0x") || hex_string.starts_with("0X")) {
+    hex_string = hex_string.substr(2);
+  }
+  if (hex_string.empty()) {
+    LOG(ERROR) << "Invalid input hex string: " << hex_string;
+    return Usage(argv[0]);
+  }
+
+  auto data = ParseHexString(hex_string);
+  if (data.empty()) {
+    LOG(ERROR) << "Failed to parse the input hex string: " << hex_string;
+    return EXIT_FAILURE;
+  }
+  if (std::string err; !WriteMiscPartitionVendorSpace(data.data(), data.size(), offset, &err)) {
+    LOG(ERROR) << "Failed to write to misc partition: " << err;
+    return EXIT_FAILURE;
+  }
+  return EXIT_SUCCESS;
+}
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 73398c3..871dcae 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -24,12 +24,16 @@
 
     // Minimal set of files to support host build.
     srcs: [
+        "dirutil.cpp",
         "paths.cpp",
         "rangeset.cpp",
+        "sysutil.cpp",
     ],
 
     shared_libs: [
         "libbase",
+        "libcutils",
+        "libselinux",
     ],
 
     export_include_dirs: [
@@ -39,12 +43,10 @@
     target: {
         android: {
             srcs: [
-                "dirutil.cpp",
                 "logging.cpp",
                 "mounts.cpp",
                 "parse_install_logs.cpp",
                 "roots.cpp",
-                "sysutil.cpp",
                 "thermalutil.cpp",
             ],
 
@@ -57,10 +59,8 @@
             ],
 
             shared_libs: [
-                "libcutils",
                 "libext4_utils",
                 "libfs_mgr",
-                "libselinux",
             ],
 
             export_static_lib_headers: [
diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h
index e91d02c..a18c30e 100644
--- a/otautil/include/otautil/rangeset.h
+++ b/otautil/include/otautil/rangeset.h
@@ -18,6 +18,7 @@
 
 #include <stddef.h>
 
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -49,6 +50,12 @@
   // bounds. For example, "3,5" contains blocks 3 and 4. So "3,5" and "5,7" are not overlapped.
   bool Overlaps(const RangeSet& other) const;
 
+  // Returns a subset of ranges starting from |start_index| with respect to the original range. The
+  // output range will have |num_of_blocks| blocks in size. Returns std::nullopt if the input is
+  // invalid. e.g. RangeSet({{0, 5}, {10, 15}}).GetSubRanges(1, 5) returns
+  // RangeSet({{1, 5}, {10, 11}}).
+  std::optional<RangeSet> GetSubRanges(size_t start_index, size_t num_of_blocks) const;
+
   // Returns a vector of RangeSets that contain the same set of blocks represented by the current
   // RangeSet. The RangeSets in the vector contain similar number of blocks, with a maximum delta
   // of 1-block between any two of them. For example, 14 blocks would be split into 4 + 4 + 3 + 3,
diff --git a/otautil/include/otautil/roots.h b/otautil/include/otautil/roots.h
index 482f3d0..2ab3f45 100644
--- a/otautil/include/otautil/roots.h
+++ b/otautil/include/otautil/roots.h
@@ -53,7 +53,3 @@
 // Ensure that all and only the volumes that packages expect to find
 // mounted (/tmp and /cache) are mounted.  Returns 0 on success.
 int setup_install_mounts();
-
-bool logical_partitions_mapped();
-
-std::string get_system_root();
diff --git a/otautil/include/otautil/sysutil.h b/otautil/include/otautil/sysutil.h
index 692a99e..326db86 100644
--- a/otautil/include/otautil/sysutil.h
+++ b/otautil/include/otautil/sysutil.h
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef _OTAUTIL_SYSUTIL
-#define _OTAUTIL_SYSUTIL
+#pragma once
 
 #include <sys/types.h>
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "rangeset.h"
@@ -101,13 +101,14 @@
   std::vector<MappedRange> ranges_;
 };
 
-// Wrapper function to trigger a reboot, by additionally handling quiescent reboot mode. The
-// command should start with "reboot," (e.g. "reboot,bootloader" or "reboot,").
-bool reboot(const std::string& command);
+// Reboots the device into the specified target, by additionally handling quiescent reboot mode.
+// All unknown targets reboot into Android.
+bool Reboot(std::string_view target);
+
+// Triggers a shutdown.
+bool Shutdown(std::string_view target);
 
 // Returns a null-terminated char* array, where the elements point to the C-strings in the given
 // vector, plus an additional nullptr at the end. This is a helper function that facilitates
 // calling C functions (such as getopt(3)) that expect an array of C-strings.
 std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args);
-
-#endif  // _OTAUTIL_SYSUTIL
diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp
index 5ab8e08..8ee99dd 100644
--- a/otautil/rangeset.cpp
+++ b/otautil/rangeset.cpp
@@ -184,6 +184,58 @@
   return false;
 }
 
+std::optional<RangeSet> RangeSet::GetSubRanges(size_t start_index, size_t num_of_blocks) const {
+  size_t end_index = start_index + num_of_blocks;  // The index of final block to read plus one
+  if (start_index > end_index || end_index > blocks_) {
+    LOG(ERROR) << "Failed to get the sub ranges for start_index " << start_index
+               << " num_of_blocks " << num_of_blocks
+               << " total number of blocks the range contains is " << blocks_;
+    return std::nullopt;
+  }
+
+  if (num_of_blocks == 0) {
+    LOG(WARNING) << "num_of_blocks is zero when calling GetSubRanges()";
+    return RangeSet();
+  }
+
+  RangeSet result;
+  size_t current_index = 0;
+  for (const auto& [range_start, range_end] : ranges_) {
+    CHECK_LT(range_start, range_end);
+    size_t blocks_in_range = range_end - range_start;
+    // Linear search to skip the ranges until we reach start_block.
+    if (current_index + blocks_in_range <= start_index) {
+      current_index += blocks_in_range;
+      continue;
+    }
+
+    size_t trimmed_range_start = range_start;
+    // We have found the first block range to read, trim the heading blocks.
+    if (current_index < start_index) {
+      trimmed_range_start += start_index - current_index;
+    }
+    // Trim the trailing blocks if the last range has more blocks than desired; also return the
+    // result.
+    if (current_index + blocks_in_range >= end_index) {
+      size_t trimmed_range_end = range_end - (current_index + blocks_in_range - end_index);
+      if (!result.PushBack({ trimmed_range_start, trimmed_range_end })) {
+        return std::nullopt;
+      }
+
+      return result;
+    }
+
+    if (!result.PushBack({ trimmed_range_start, range_end })) {
+      return std::nullopt;
+    }
+    current_index += blocks_in_range;
+  }
+
+  LOG(ERROR) << "Failed to construct byte ranges to read, start_block: " << start_index
+             << ", num_of_blocks: " << num_of_blocks << " total number of blocks: " << blocks_;
+  return std::nullopt;
+}
+
 // Ranges in the the set should be mutually exclusive; and they're sorted by the start block.
 SortedRangeSet::SortedRangeSet(std::vector<Range>&& pairs) : RangeSet(std::move(pairs)) {
   std::sort(ranges_.begin(), ranges_.end());
diff --git a/otautil/roots.cpp b/otautil/roots.cpp
index 815d644..a778e05 100644
--- a/otautil/roots.cpp
+++ b/otautil/roots.cpp
@@ -275,11 +275,3 @@
   }
   return 0;
 }
-
-bool logical_partitions_mapped() {
-  return android::fs_mgr::LogicalPartitionsMapped();
-}
-
-std::string get_system_root() {
-  return android::fs_mgr::GetSystemRoot();
-}
diff --git a/otautil/sysutil.cpp b/otautil/sysutil.cpp
index 8366fa0..a882985 100644
--- a/otautil/sysutil.cpp
+++ b/otautil/sysutil.cpp
@@ -94,6 +94,11 @@
     remaining_blocks -= range_blocks;
   }
 
+  if (remaining_blocks != 0) {
+    LOG(ERROR) << "Invalid ranges: remaining blocks " << remaining_blocks;
+    return {};
+  }
+
   return BlockMapData(block_dev, file_size, blksize, std::move(ranges));
 }
 
@@ -214,14 +219,21 @@
   ranges_.clear();
 }
 
-bool reboot(const std::string& command) {
-  std::string cmd = command;
-  if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+bool Reboot(std::string_view target) {
+  std::string cmd = "reboot," + std::string(target);
+  // Honor the quiescent mode if applicable.
+  if (target != "bootloader" && target != "fastboot" &&
+      android::base::GetBoolProperty("ro.boot.quiescent", false)) {
     cmd += ",quiescent";
   }
   return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
 }
 
+bool Shutdown(std::string_view target) {
+  std::string cmd = "shutdown," + std::string(target);
+  return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
+}
+
 std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args) {
   std::vector<char*> result(args.size());
   std::transform(args.cbegin(), args.cend(), result.begin(),
diff --git a/recovery.cpp b/recovery.cpp
index f9b3bfc..eb0c2b2 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -18,11 +18,9 @@
 
 #include <ctype.h>
 #include <errno.h>
-#include <fcntl.h>
 #include <getopt.h>
 #include <inttypes.h>
 #include <limits.h>
-#include <linux/fs.h>
 #include <linux/input.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -30,8 +28,8 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <algorithm>
 #include <functional>
+#include <iterator>
 #include <memory>
 #include <string>
 #include <vector>
@@ -42,12 +40,12 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <bootloader_message/bootloader_message.h>
 #include <cutils/properties.h> /* for property_list */
+#include <fs_mgr/roots.h>
 #include <healthhalutils/HealthHalUtils.h>
 #include <ziparchive/zip_archive.h>
 
+#include "bootloader_message/bootloader_message.h"
 #include "common.h"
 #include "fsck_unshare_blocks.h"
 #include "install/adb_install.h"
@@ -55,6 +53,7 @@
 #include "install/install.h"
 #include "install/package.h"
 #include "install/wipe_data.h"
+#include "install/wipe_device.h"
 #include "otautil/error_code.h"
 #include "otautil/logging.h"
 #include "otautil/paths.h"
@@ -115,12 +114,12 @@
  * 3. main system reboots into recovery
  * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
  *    -- after this, rebooting will attempt to reinstall the update --
- * 5. install_package() attempts to install the update
+ * 5. InstallPackage() attempts to install the update
  *    NOTE: the package install must itself be restartable from any point
  * 6. finish_recovery() erases BCB
  *    -- after this, rebooting will (try to) restart the main system --
  * 7. ** if install failed **
- *    7a. prompt_and_wait() shows an error icon and waits for the user
+ *    7a. PromptAndWait() shows an error icon and waits for the user
  *    7b. the user reboots (pulling the battery, etc) into the main system
  */
 
@@ -221,165 +220,6 @@
   }
 }
 
-// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
-// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
-static bool secure_wipe_partition(const std::string& partition) {
-  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
-  if (fd == -1) {
-    PLOG(ERROR) << "Failed to open \"" << partition << "\"";
-    return false;
-  }
-
-  uint64_t range[2] = { 0, 0 };
-  if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
-    PLOG(ERROR) << "Failed to get partition size";
-    return false;
-  }
-  LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
-
-  LOG(INFO) << "  Trying BLKSECDISCARD...";
-  if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
-    PLOG(WARNING) << "  Failed";
-
-    // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
-    unsigned int zeroes;
-    if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
-      LOG(INFO) << "  Trying BLKDISCARD...";
-      if (ioctl(fd, BLKDISCARD, &range) == -1) {
-        PLOG(ERROR) << "  Failed";
-        return false;
-      }
-    } else {
-      LOG(INFO) << "  Trying BLKZEROOUT...";
-      if (ioctl(fd, BLKZEROOUT, &range) == -1) {
-        PLOG(ERROR) << "  Failed";
-        return false;
-      }
-    }
-  }
-
-  LOG(INFO) << "  Done";
-  return true;
-}
-
-static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) {
-  if (wipe_package_size == 0) {
-    LOG(ERROR) << "wipe_package_size is zero";
-    return nullptr;
-  }
-
-  std::string wipe_package;
-  std::string err_str;
-  if (!read_wipe_package(&wipe_package, wipe_package_size, &err_str)) {
-    PLOG(ERROR) << "Failed to read wipe package" << err_str;
-    return nullptr;
-  }
-
-  return Package::CreateMemoryPackage(
-      std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
-}
-
-// Checks if the wipe package matches expectation. If the check passes, reads the list of
-// partitions to wipe from the package. Checks include
-// 1. verify the package.
-// 2. check metadata (ota-type, pre-device and serial number if having one).
-static bool CheckWipePackage(Package* wipe_package) {
-  if (!verify_package(wipe_package, ui)) {
-    LOG(ERROR) << "Failed to verify package";
-    return false;
-  }
-
-  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
-  if (!zip) {
-    LOG(ERROR) << "Failed to get ZipArchiveHandle";
-    return false;
-  }
-
-  std::map<std::string, std::string> metadata;
-  if (!ReadMetadataFromPackage(zip, &metadata)) {
-    LOG(ERROR) << "Failed to parse metadata in the zip file";
-    return false;
-  }
-
-  return CheckPackageMetadata(metadata, OtaType::BRICK) == 0;
-}
-
-std::vector<std::string> GetWipePartitionList(Package* wipe_package) {
-  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
-  if (!zip) {
-    LOG(ERROR) << "Failed to get ZipArchiveHandle";
-    return {};
-  }
-
-  static constexpr const char* RECOVERY_WIPE_ENTRY_NAME = "recovery.wipe";
-
-  std::string partition_list_content;
-  ZipString path(RECOVERY_WIPE_ENTRY_NAME);
-  ZipEntry entry;
-  if (FindEntry(zip, path, &entry) == 0) {
-    uint32_t length = entry.uncompressed_length;
-    partition_list_content = std::string(length, '\0');
-    if (auto err = ExtractToMemory(
-            zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length);
-        err != 0) {
-      LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": "
-                 << ErrorCodeString(err);
-      return {};
-    }
-  } else {
-    LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME
-              << ", falling back to use the partition list on device.";
-
-    static constexpr const char* RECOVERY_WIPE_ON_DEVICE = "/etc/recovery.wipe";
-    if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) {
-      PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\"";
-      return {};
-    }
-  }
-
-  std::vector<std::string> result;
-  std::vector<std::string> lines = android::base::Split(partition_list_content, "\n");
-  for (const std::string& line : lines) {
-    std::string partition = android::base::Trim(line);
-    // Ignore '#' comment or empty lines.
-    if (android::base::StartsWith(partition, "#") || partition.empty()) {
-      continue;
-    }
-    result.push_back(line);
-  }
-
-  return result;
-}
-
-// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
-static bool wipe_ab_device(size_t wipe_package_size) {
-  ui->SetBackground(RecoveryUI::ERASING);
-  ui->SetProgressType(RecoveryUI::INDETERMINATE);
-
-  auto wipe_package = ReadWipePackage(wipe_package_size);
-  if (!wipe_package) {
-    LOG(ERROR) << "Failed to open wipe package";
-    return false;
-  }
-
-  if (!CheckWipePackage(wipe_package.get())) {
-    LOG(ERROR) << "Failed to verify wipe package";
-    return false;
-  }
-
-  auto partition_list = GetWipePartitionList(wipe_package.get());
-  if (partition_list.empty()) {
-    LOG(ERROR) << "Empty wipe ab partition list";
-    return false;
-  }
-
-  for (const auto& partition : partition_list) {
-    // Proceed anyway even if it fails to wipe some partition.
-    secure_wipe_partition(partition);
-  }
-  return true;
-}
-
 static void choose_recovery_file(Device* device) {
   std::vector<std::string> entries;
   if (has_cache) {
@@ -473,14 +313,18 @@
   ui->ShowText(true);
 }
 
-// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default,
-// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
-static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
+// Shows the recovery UI and waits for user input. Returns one of the device builtin actions, such
+// as REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, which
+// is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
+static Device::BuiltinAction PromptAndWait(Device* device, InstallResult status) {
   for (;;) {
     finish_recovery();
     switch (status) {
       case INSTALL_SUCCESS:
       case INSTALL_NONE:
+      case INSTALL_SKIPPED:
+      case INSTALL_RETRY:
+      case INSTALL_KEY_INTERRUPTED:
         ui->SetBackground(RecoveryUI::NO_COMMAND);
         break;
 
@@ -488,6 +332,12 @@
       case INSTALL_CORRUPT:
         ui->SetBackground(RecoveryUI::ERROR);
         break;
+
+      case INSTALL_REBOOT:
+        // All the reboots should have been handled prior to entering PromptAndWait() or immediately
+        // after installing a package.
+        LOG(FATAL) << "Invalid status code of INSTALL_REBOOT";
+        break;
     }
     ui->SetProgressType(RecoveryUI::EMPTY);
 
@@ -506,6 +356,8 @@
             : device->InvokeMenuItem(chosen_item);
 
     switch (chosen_action) {
+      case Device::REBOOT_FROM_FASTBOOT:    // Can not happen
+      case Device::SHUTDOWN_FROM_FASTBOOT:  // Can not happen
       case Device::NO_ACTION:
         break;
 
@@ -551,9 +403,9 @@
         if (chosen_action == Device::ENTER_RESCUE) {
           // Switch to graphics screen.
           ui->ShowText(false);
-          status = ApplyFromAdb(ui, true /* rescue_mode */, &reboot_action);
+          status = ApplyFromAdb(device, true /* rescue_mode */, &reboot_action);
         } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) {
-          status = ApplyFromAdb(ui, false /* rescue_mode */, &reboot_action);
+          status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action);
         } else {
           adb = false;
           status = ApplyFromSdcard(device, ui);
@@ -588,8 +440,7 @@
         break;
       }
       case Device::MOUNT_SYSTEM:
-        // the system partition is mounted at /mnt/system
-        if (ensure_path_mounted_at(get_system_root(), "/mnt/system") != -1) {
+        if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
           ui->Print("Mounted /system.\n");
         }
         break;
@@ -851,7 +702,7 @@
 
   ui->Print("Supported API: %d\n", kRecoveryApiVersion);
 
-  int status = INSTALL_SUCCESS;
+  InstallResult status = INSTALL_SUCCESS;
   // next_action indicates the next target to reboot into upon finishing the install. It could be
   // overridden to a different reboot target per user request.
   Device::BuiltinAction next_action = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
@@ -881,7 +732,7 @@
         set_retry_bootloader_message(retry_count + 1, args);
       }
 
-      status = install_package(update_package, should_wipe_cache, true, retry_count, ui);
+      status = InstallPackage(update_package, should_wipe_cache, true, retry_count, ui);
       if (status != INSTALL_SUCCESS) {
         ui->Print("Installation aborted.\n");
 
@@ -895,8 +746,8 @@
           // Print retry count on screen.
           ui->Print("Retry attempt %d\n", retry_count);
 
-          // Reboot and retry the update
-          if (!reboot("reboot,recovery")) {
+          // Reboot back into recovery to retry the update.
+          if (!Reboot("recovery")) {
             ui->Print("Reboot failed\n");
           } else {
             while (true) {
@@ -934,7 +785,7 @@
       status = INSTALL_ERROR;
     }
   } else if (should_wipe_ab) {
-    if (!wipe_ab_device(wipe_package_size)) {
+    if (!WipeAbDevice(device, wipe_package_size)) {
       status = INSTALL_ERROR;
     }
   } else if (sideload) {
@@ -946,7 +797,7 @@
     if (!sideload_auto_reboot) {
       ui->ShowText(true);
     }
-    status = ApplyFromAdb(ui, false /* rescue_mode */, &next_action);
+    status = ApplyFromAdb(device, false /* rescue_mode */, &next_action);
     ui->Print("\nInstall from ADB complete (status: %d).\n", status);
     if (sideload_auto_reboot) {
       status = INSTALL_REBOOT;
@@ -954,7 +805,7 @@
     }
   } else if (rescue) {
     save_current_log = true;
-    status = ApplyFromAdb(ui, true /* rescue_mode */, &next_action);
+    status = ApplyFromAdb(device, true /* rescue_mode */, &next_action);
     ui->Print("\nInstall from ADB complete (status: %d).\n", status);
   } else if (fsck_unshare_blocks) {
     if (!do_fsck_unshare_blocks()) {
@@ -989,7 +840,7 @@
   //    for 5s followed by an automatic reboot.
   if (status != INSTALL_REBOOT) {
     if (status == INSTALL_NONE || ui->IsTextVisible()) {
-      Device::BuiltinAction temp = prompt_and_wait(device, status);
+      auto temp = PromptAndWait(device, status);
       if (temp != Device::NO_ACTION) {
         next_action = temp;
       }
diff --git a/recovery_main.cpp b/recovery_main.cpp
index 6e69b70..7fbdf9a 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -41,8 +41,8 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <bootloader_message/bootloader_message.h>
-#include <cutils/android_reboot.h>
 #include <cutils/sockets.h>
+#include <fs_mgr/roots.h>
 #include <private/android_logger.h> /* private pmsg functions */
 #include <selinux/android.h>
 #include <selinux/label.h>
@@ -471,27 +471,31 @@
     switch (ret) {
       case Device::SHUTDOWN:
         ui->Print("Shutting down...\n");
-        // TODO: Move all the reboots to reboot(), which should conditionally set quiescent flag.
-        android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
+        Shutdown("userrequested,recovery");
+        break;
+
+      case Device::SHUTDOWN_FROM_FASTBOOT:
+        ui->Print("Shutting down...\n");
+        Shutdown("userrequested,fastboot");
         break;
 
       case Device::REBOOT_BOOTLOADER:
         ui->Print("Rebooting to bootloader...\n");
-        android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
+        Reboot("bootloader");
         break;
 
       case Device::REBOOT_FASTBOOT:
         ui->Print("Rebooting to recovery/fastboot...\n");
-        android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot");
+        Reboot("fastboot");
         break;
 
       case Device::REBOOT_RECOVERY:
         ui->Print("Rebooting to recovery...\n");
-        reboot("reboot,recovery");
+        Reboot("recovery");
         break;
 
       case Device::REBOOT_RESCUE: {
-        // Not using `reboot("reboot,rescue")`, as it requires matching support in kernel and/or
+        // Not using `Reboot("rescue")`, as it requires matching support in kernel and/or
         // bootloader.
         bootloader_message boot = {};
         strlcpy(boot.command, "boot-rescue", sizeof(boot.command));
@@ -502,14 +506,14 @@
           continue;
         }
         ui->Print("Rebooting to recovery/rescue...\n");
-        reboot("reboot,recovery");
+        Reboot("recovery");
         break;
       }
 
       case Device::ENTER_FASTBOOT:
-        if (logical_partitions_mapped()) {
+        if (android::fs_mgr::LogicalPartitionsMapped()) {
           ui->Print("Partitions may be mounted - rebooting to enter fastboot.");
-          android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot");
+          Reboot("fastboot");
         } else {
           LOG(INFO) << "Entering fastboot";
           fastboot = true;
@@ -521,9 +525,19 @@
         fastboot = false;
         break;
 
+      case Device::REBOOT:
+        ui->Print("Rebooting...\n");
+        Reboot("userrequested,recovery");
+        break;
+
+      case Device::REBOOT_FROM_FASTBOOT:
+        ui->Print("Rebooting...\n");
+        Reboot("userrequested,fastboot");
+        break;
+
       default:
         ui->Print("Rebooting...\n");
-        reboot("reboot,");
+        Reboot("unknown" + std::to_string(ret));
         break;
     }
   }
diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h
index 7c76cdb..9a4edf2 100644
--- a/recovery_ui/include/recovery_ui/device.h
+++ b/recovery_ui/include/recovery_ui/device.h
@@ -58,6 +58,8 @@
     REBOOT_FASTBOOT = 17,
     REBOOT_RECOVERY = 18,
     REBOOT_RESCUE = 19,
+    REBOOT_FROM_FASTBOOT = 20,
+    SHUTDOWN_FROM_FASTBOOT = 21,
   };
 
   explicit Device(RecoveryUI* ui);
diff --git a/recovery_ui/ui.cpp b/recovery_ui/ui.cpp
index f1a79dc..cf0d3b5 100644
--- a/recovery_ui/ui.cpp
+++ b/recovery_ui/ui.cpp
@@ -375,7 +375,7 @@
 
       case RecoveryUI::REBOOT:
         if (reboot_enabled) {
-          reboot("reboot,");
+          Reboot("userrequested,recovery,ui");
           while (true) {
             pause();
           }
diff --git a/tests/Android.bp b/tests/Android.bp
index 2e5334d..4969c08 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -79,6 +79,8 @@
     "libinstall",
     "librecovery_ui",
     "libminui",
+    "libfusesideload",
+    "libbootloader_message",
     "libotautil",
 
     "libhealthhalutils",
@@ -87,10 +89,8 @@
 
     "android.hardware.health@2.0",
     "android.hardware.health@1.0",
-    "libbootloader_message",
     "libext4_utils",
     "libfs_mgr",
-    "libfusesideload",
     "libhidl-gen-utils",
     "libhidlbase",
     "libhidltransport",
@@ -107,6 +107,8 @@
 
     defaults: [
         "recovery_test_defaults",
+        "libupdater_defaults",
+        "libupdater_device_defaults",
     ],
 
     test_suites: ["device-tests"],
@@ -115,16 +117,23 @@
         "unit/*.cpp",
     ],
 
-    static_libs: libapplypatch_static_libs + [
-        "libinstall",
+    static_libs: libapplypatch_static_libs + librecovery_static_libs + [
         "librecovery_ui",
+        "libfusesideload",
         "libminui",
         "libotautil",
-        "libupdater",
+        "libupdater_device",
+        "libupdater_core",
+        "libupdate_verifier",
+
         "libgtest_prod",
+        "libprotobuf-cpp-lite",
     ],
 
-    data: ["testdata/*"],
+    data: [
+        "testdata/*",
+        ":res-testdata",
+    ],
 }
 
 cc_test {
@@ -142,33 +151,6 @@
     ],
 }
 
-cc_test {
-    name: "recovery_component_test",
-    isolated: true,
-
-    defaults: [
-        "recovery_test_defaults",
-        "libupdater_defaults",
-    ],
-
-    test_suites: ["device-tests"],
-
-    srcs: [
-        "component/*.cpp",
-    ],
-
-    static_libs: libapplypatch_static_libs + librecovery_static_libs + [
-        "libupdater",
-        "libupdate_verifier",
-        "libprotobuf-cpp-lite",
-    ],
-
-    data: [
-        "testdata/*",
-        ":res-testdata",
-    ],
-}
-
 cc_test_host {
     name: "recovery_host_test",
     isolated: true,
@@ -178,7 +160,7 @@
     ],
 
     srcs: [
-        "component/imgdiff_test.cpp",
+        "unit/imgdiff_test.cpp",
     ],
 
     static_libs: [
diff --git a/tests/component/resources_test.cpp b/tests/component/resources_test.cpp
deleted file mode 100644
index d7fdb8f..0000000
--- a/tests/component/resources_test.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <dirent.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/strings.h>
-#include <gtest/gtest.h>
-#include <png.h>
-
-#include "minui/minui.h"
-#include "private/resources.h"
-
-static const std::string kLocale = "zu";
-
-static const std::vector<std::string> kResourceImagesDirs{
-  "res-mdpi/images/",   "res-hdpi/images/",    "res-xhdpi/images/",
-  "res-xxhdpi/images/", "res-xxxhdpi/images/",
-};
-
-static int png_filter(const dirent* de) {
-  if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
-    return 0;
-  }
-  return 1;
-}
-
-// Finds out all the PNG files to test, which stay under the same dir with the executabl..
-static std::vector<std::string> add_files() {
-  std::vector<std::string> files;
-  for (const std::string& images_dir : kResourceImagesDirs) {
-    static std::string exec_dir = android::base::GetExecutableDirectory();
-    std::string dir_path = exec_dir + "/" + images_dir;
-    dirent** namelist;
-    int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
-    if (n == -1) {
-      printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno));
-      continue;
-    }
-    if (n == 0) {
-      printf("No file is added for test in %s\n", dir_path.c_str());
-    }
-
-    while (n--) {
-      std::string file_path = dir_path + namelist[n]->d_name;
-      files.push_back(file_path);
-      free(namelist[n]);
-    }
-    free(namelist);
-  }
-  return files;
-}
-
-class ResourcesTest : public testing::TestWithParam<std::string> {
- public:
-  static std::vector<std::string> png_list;
-
- protected:
-  void SetUp() override {
-    png_ = std::make_unique<PngHandler>(GetParam());
-    ASSERT_TRUE(png_);
-
-    ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file.";
-    ASSERT_LT(static_cast<png_uint_32>(5), png_->width());
-    ASSERT_LT(static_cast<png_uint_32>(0), png_->height());
-    ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file.";
-  }
-
-  std::unique_ptr<PngHandler> png_{ nullptr };
-};
-
-// Parses a png file and tests if it's qualified for the background text image under recovery.
-TEST_P(ResourcesTest, ValidateLocale) {
-  std::vector<unsigned char> row(png_->width());
-  for (png_uint_32 y = 0; y < png_->height(); ++y) {
-    png_read_row(png_->png_ptr(), row.data(), nullptr);
-    int w = (row[1] << 8) | row[0];
-    int h = (row[3] << 8) | row[2];
-    int len = row[4];
-    EXPECT_LT(0, w);
-    EXPECT_LT(0, h);
-    EXPECT_LT(0, len) << "Locale string should be non-empty.";
-    EXPECT_NE(0, row[5]) << "Locale string is missing.";
-
-    ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
-    char* loc = reinterpret_cast<char*>(&row[5]);
-    if (matches_locale(loc, kLocale.c_str())) {
-      EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
-      break;
-    }
-    for (int i = 0; i < h; ++i, ++y) {
-      png_read_row(png_->png_ptr(), row.data(), nullptr);
-    }
-  }
-}
-
-std::vector<std::string> ResourcesTest::png_list = add_files();
-
-INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest,
-                        ::testing::ValuesIn(ResourcesTest::png_list.cbegin(),
-                                            ResourcesTest::png_list.cend()));
diff --git a/tests/component/applypatch_modes_test.cpp b/tests/unit/applypatch_modes_test.cpp
similarity index 100%
rename from tests/component/applypatch_modes_test.cpp
rename to tests/unit/applypatch_modes_test.cpp
diff --git a/tests/component/bootloader_message_test.cpp b/tests/unit/bootloader_message_test.cpp
similarity index 73%
rename from tests/component/bootloader_message_test.cpp
rename to tests/unit/bootloader_message_test.cpp
index b005d19..95d875e 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/unit/bootloader_message_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <android-base/file.h>
@@ -22,6 +23,10 @@
 #include <bootloader_message/bootloader_message.h>
 #include <gtest/gtest.h>
 
+using namespace std::string_literals;
+
+extern void SetMiscBlockDeviceForTest(std::string_view misc_device);
+
 TEST(BootloaderMessageTest, read_and_write_bootloader_message) {
   TemporaryFile temp_misc;
 
@@ -114,3 +119,36 @@
             std::string(boot.reserved, sizeof(boot.reserved)));
 }
 
+TEST(BootloaderMessageTest, WriteMiscPartitionVendorSpace) {
+  TemporaryFile temp_misc;
+  ASSERT_TRUE(android::base::WriteStringToFile(std::string(4096, '\x00'), temp_misc.path));
+  SetMiscBlockDeviceForTest(temp_misc.path);
+
+  constexpr std::string_view kTestMessage = "kTestMessage";
+  std::string err;
+  ASSERT_TRUE(WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), 0, &err));
+
+  std::string message;
+  message.resize(kTestMessage.size());
+  ASSERT_TRUE(ReadMiscPartitionVendorSpace(message.data(), message.size(), 0, &err));
+  ASSERT_EQ(kTestMessage, message);
+
+  // Write with an offset.
+  ASSERT_TRUE(WriteMiscPartitionVendorSpace("\x00\x00", 2, 5, &err));
+  ASSERT_TRUE(ReadMiscPartitionVendorSpace(message.data(), message.size(), 0, &err));
+  ASSERT_EQ("kTest\x00\x00ssage"s, message);
+
+  // Write with the right size.
+  auto start_offset =
+      WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC - kTestMessage.size();
+  ASSERT_TRUE(
+      WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), start_offset, &err));
+
+  // Out-of-bound write.
+  ASSERT_FALSE(WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(),
+                                             start_offset + 1, &err));
+
+  // Message won't fit.
+  std::string long_message(WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC + 1, 'a');
+  ASSERT_FALSE(WriteMiscPartitionVendorSpace(long_message.data(), long_message.size(), 0, &err));
+}
diff --git a/tests/component/edify_test.cpp b/tests/unit/edify_test.cpp
similarity index 100%
rename from tests/component/edify_test.cpp
rename to tests/unit/edify_test.cpp
diff --git a/tests/unit/fuse_provider_test.cpp b/tests/unit/fuse_provider_test.cpp
new file mode 100644
index 0000000..c5995dd
--- /dev/null
+++ b/tests/unit/fuse_provider_test.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "fuse_provider.h"
+#include "fuse_sideload.h"
+#include "install/install.h"
+
+TEST(FuseBlockMapTest, CreateFromBlockMap_smoke) {
+  TemporaryFile fake_block_device;
+  std::vector<std::string> lines = {
+    fake_block_device.path, "10000 4096", "3", "10 11", "20 21", "22 23",
+  };
+
+  TemporaryFile temp_file;
+  android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path);
+  auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096);
+
+  ASSERT_TRUE(block_map_data);
+  ASSERT_EQ(10000, block_map_data->file_size());
+  ASSERT_EQ(4096, block_map_data->fuse_block_size());
+  ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 21 }, { 22, 23 } }), block_map_data->ranges());
+}
+
+TEST(FuseBlockMapTest, ReadBlockAlignedData_smoke) {
+  std::string content;
+  content.reserve(40960);
+  for (char c = 0; c < 10; c++) {
+    content += std::string(4096, c);
+  }
+  TemporaryFile fake_block_device;
+  ASSERT_TRUE(android::base::WriteStringToFile(content, fake_block_device.path));
+
+  std::vector<std::string> lines = {
+    fake_block_device.path,
+    "20000 4096",
+    "1",
+    "0 5",
+  };
+  TemporaryFile temp_file;
+  android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path);
+  auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096);
+
+  std::vector<uint8_t> result(2000);
+  ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 2000, 1));
+  ASSERT_EQ(std::vector<uint8_t>(content.begin() + 4096, content.begin() + 6096), result);
+
+  result.resize(20000);
+  ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 0));
+  ASSERT_EQ(std::vector<uint8_t>(content.begin(), content.begin() + 20000), result);
+}
+
+TEST(FuseBlockMapTest, ReadBlockAlignedData_large_fuse_block) {
+  std::string content;
+  for (char c = 0; c < 10; c++) {
+    content += std::string(4096, c);
+  }
+
+  TemporaryFile temp_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+
+  std::vector<std::string> lines = {
+    temp_file.path, "36384 4096", "2", "0 5", "6 10",
+  };
+  TemporaryFile block_map;
+  ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(lines, '\n'), block_map.path));
+
+  auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(block_map.path, 16384);
+  ASSERT_TRUE(block_map_data);
+
+  std::vector<uint8_t> result(20000);
+  // Out of bound read
+  ASSERT_FALSE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 2));
+  ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 1));
+  // expected source block contains: 4, 6-9
+  std::string expected = content.substr(16384, 4096) + content.substr(24576, 15904);
+  ASSERT_EQ(std::vector<uint8_t>(expected.begin(), expected.end()), result);
+}
diff --git a/tests/component/sideload_test.cpp b/tests/unit/fuse_sideload_test.cpp
similarity index 100%
rename from tests/component/sideload_test.cpp
rename to tests/unit/fuse_sideload_test.cpp
diff --git a/tests/component/imgdiff_test.cpp b/tests/unit/imgdiff_test.cpp
similarity index 100%
rename from tests/component/imgdiff_test.cpp
rename to tests/unit/imgdiff_test.cpp
diff --git a/tests/component/install_test.cpp b/tests/unit/install_test.cpp
similarity index 90%
rename from tests/component/install_test.cpp
rename to tests/unit/install_test.cpp
index 3851329..4ec4099 100644
--- a/tests/component/install_test.cpp
+++ b/tests/unit/install_test.cpp
@@ -33,6 +33,7 @@
 #include <ziparchive/zip_writer.h>
 
 #include "install/install.h"
+#include "install/wipe_device.h"
 #include "otautil/paths.h"
 #include "private/setup_commands.h"
 
@@ -204,7 +205,7 @@
   std::string binary_path = std::string(td.path) + "/update_binary";
   Paths::Get().set_temporary_update_binary(binary_path);
   std::vector<std::string> cmd;
-  ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+  ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
   ASSERT_EQ(4U, cmd.size());
   ASSERT_EQ(binary_path, cmd[0]);
   ASSERT_EQ("3", cmd[1]);  // RECOVERY_API_VERSION
@@ -216,7 +217,7 @@
 
   // With non-zero retry count. update_binary will be removed automatically.
   cmd.clear();
-  ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd));
+  ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd));
   ASSERT_EQ(5U, cmd.size());
   ASSERT_EQ(binary_path, cmd[0]);
   ASSERT_EQ("3", cmd[1]);  // RECOVERY_API_VERSION
@@ -243,7 +244,7 @@
   TemporaryDir td;
   Paths::Get().set_temporary_update_binary(std::string(td.path) + "/update_binary");
   std::vector<std::string> cmd;
-  ASSERT_EQ(INSTALL_CORRUPT, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+  ASSERT_FALSE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
   CloseArchive(zip);
 }
 
@@ -270,19 +271,18 @@
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  ZipString payload_name("payload.bin");
   ZipEntry payload_entry;
-  ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
+  ASSERT_EQ(0, FindEntry(zip, "payload.bin", &payload_entry));
 
   std::map<std::string, std::string> metadata;
   ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
   if (success) {
-    ASSERT_EQ(0, CheckPackageMetadata(metadata, OtaType::AB));
+    ASSERT_TRUE(CheckPackageMetadata(metadata, OtaType::AB));
 
     int status_fd = 10;
     std::string package = "/path/to/update.zip";
     std::vector<std::string> cmd;
-    ASSERT_EQ(0, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+    ASSERT_TRUE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
     ASSERT_EQ(5U, cmd.size());
     ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]);
     ASSERT_EQ("--payload=file://" + package, cmd[1]);
@@ -290,7 +290,7 @@
     ASSERT_EQ("--headers=" + properties, cmd[3]);
     ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
   } else {
-    ASSERT_EQ(INSTALL_ERROR, CheckPackageMetadata(metadata, OtaType::AB));
+    ASSERT_FALSE(CheckPackageMetadata(metadata, OtaType::AB));
   }
   CloseArchive(zip);
 }
@@ -325,7 +325,7 @@
   int status_fd = 10;
   std::string package = "/path/to/update.zip";
   std::vector<std::string> cmd;
-  ASSERT_EQ(INSTALL_CORRUPT, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+  ASSERT_FALSE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
   CloseArchive(zip);
 }
 
@@ -358,8 +358,8 @@
   VerifyAbUpdateCommands(long_serialno);
 }
 
-static void test_check_package_metadata(const std::string& metadata_string, OtaType ota_type,
-                                        int exptected_result) {
+static void TestCheckPackageMetadata(const std::string& metadata_string, OtaType ota_type,
+                                     bool exptected_result) {
   TemporaryFile temp_file;
   BuildZipArchive(
       {
@@ -387,7 +387,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 
   // Checks if ota-type matches
   metadata = android::base::Join(
@@ -397,9 +397,9 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 }
 
 TEST(InstallTest, CheckPackageMetadata_device_type) {
@@ -409,7 +409,7 @@
           "ota-type=BRICK",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 
   // device type mismatches
   metadata = android::base::Join(
@@ -418,7 +418,7 @@
           "pre-device=dummy_device_type",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 }
 
 TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
@@ -432,7 +432,7 @@
           "pre-device=" + device,
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
 
   // Serial number mismatches
   metadata = android::base::Join(
@@ -442,7 +442,7 @@
           "serialno=dummy_serial",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 
   std::string serialno = android::base::GetProperty("ro.serialno", "");
   ASSERT_NE("", serialno);
@@ -453,7 +453,7 @@
           "serialno=" + serialno,
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
 }
 
 TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
@@ -477,7 +477,7 @@
           "serialno=" + android::base::Join(serial_numbers, '|'),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 
   serial_numbers.emplace_back(serialno);
   std::shuffle(serial_numbers.begin(), serial_numbers.end(), std::default_random_engine());
@@ -488,7 +488,7 @@
           "serialno=" + android::base::Join(serial_numbers, '|'),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
 }
 
 TEST(InstallTest, CheckPackageMetadata_ab_build_version) {
@@ -506,7 +506,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 
   metadata = android::base::Join(
       std::vector<std::string>{
@@ -516,7 +516,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 }
 
 TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) {
@@ -534,7 +534,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 
   metadata = android::base::Join(
       std::vector<std::string>{
@@ -544,7 +544,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 }
 
 TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
@@ -558,7 +558,7 @@
           "pre-device=" + device,
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 
   // post timestamp should be larger than the timestamp on device.
   metadata = android::base::Join(
@@ -568,7 +568,7 @@
           "post-timestamp=0",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 
   // fingerprint is required for downgrade
   metadata = android::base::Join(
@@ -579,7 +579,7 @@
           "ota-downgrade=yes",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 
   std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
   ASSERT_NE("", finger_print);
@@ -593,5 +593,5 @@
           "ota-downgrade=yes",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 }
diff --git a/tests/unit/package_test.cpp b/tests/unit/package_test.cpp
index a735a69..5e31f7f 100644
--- a/tests/unit/package_test.cpp
+++ b/tests/unit/package_test.cpp
@@ -105,10 +105,9 @@
     ASSERT_TRUE(zip);
 
     // Check that we can extract one zip entry.
-    std::string entry_name = "dir1/file3.txt";
-    ZipString path(entry_name.c_str());
+    std::string_view entry_name = "dir1/file3.txt";
     ZipEntry entry;
-    ASSERT_EQ(0, FindEntry(zip, path, &entry));
+    ASSERT_EQ(0, FindEntry(zip, entry_name, &entry));
 
     std::vector<uint8_t> extracted(entry_name.size());
     ASSERT_EQ(0, ExtractToMemory(zip, &entry, extracted.data(), extracted.size()));
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
index fc72f2f..699f933 100644
--- a/tests/unit/rangeset_test.cpp
+++ b/tests/unit/rangeset_test.cpp
@@ -18,6 +18,7 @@
 #include <sys/types.h>
 
 #include <limits>
+#include <optional>
 #include <vector>
 
 #include <gtest/gtest.h>
@@ -248,6 +249,29 @@
   ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString());
 }
 
+TEST(RangeSetTest, GetSubRanges_invalid) {
+  RangeSet range0({ { 1, 11 }, { 20, 30 } });
+  ASSERT_FALSE(range0.GetSubRanges(0, 21));  // too many blocks
+  ASSERT_FALSE(range0.GetSubRanges(21, 1));  // start block OOB
+}
+
+TEST(RangeSetTest, GetSubRanges_empty) {
+  RangeSet range0({ { 1, 11 }, { 20, 30 } });
+  ASSERT_EQ(RangeSet{}, range0.GetSubRanges(1, 0));  // empty num_of_blocks
+}
+
+TEST(RangeSetTest, GetSubRanges_smoke) {
+  RangeSet range0({ { 10, 11 } });
+  ASSERT_EQ(RangeSet({ { 10, 11 } }), range0.GetSubRanges(0, 1));
+
+  RangeSet range1({ { 10, 11 }, { 20, 21 }, { 30, 31 } });
+  ASSERT_EQ(range1, range1.GetSubRanges(0, 3));
+  ASSERT_EQ(RangeSet({ { 20, 21 } }), range1.GetSubRanges(1, 1));
+
+  RangeSet range2({ { 1, 11 }, { 20, 25 }, { 30, 35 } });
+  ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 25 }, { 30, 31 } }), range2.GetSubRanges(9, 7));
+}
+
 TEST(SortedRangeSetTest, Insert) {
   SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } });
   rs.Insert({ 1, 2 });
diff --git a/tests/unit/resources_test.cpp b/tests/unit/resources_test.cpp
index c3f7271..3027443 100644
--- a/tests/unit/resources_test.cpp
+++ b/tests/unit/resources_test.cpp
@@ -14,12 +14,62 @@
  * limitations under the License.
  */
 
-#include <string>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
 
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
+#include <png.h>
 
 #include "common/test_constants.h"
 #include "minui/minui.h"
+#include "private/resources.h"
+
+static const std::string kLocale = "zu";
+
+static const std::vector<std::string> kResourceImagesDirs{
+  "res-mdpi/images/",   "res-hdpi/images/",    "res-xhdpi/images/",
+  "res-xxhdpi/images/", "res-xxxhdpi/images/",
+};
+
+static int png_filter(const dirent* de) {
+  if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
+    return 0;
+  }
+  return 1;
+}
+
+// Finds out all the PNG files to test, which stay under the same dir with the executabl..
+static std::vector<std::string> add_files() {
+  std::vector<std::string> files;
+  for (const std::string& images_dir : kResourceImagesDirs) {
+    static std::string exec_dir = android::base::GetExecutableDirectory();
+    std::string dir_path = exec_dir + "/" + images_dir;
+    dirent** namelist;
+    int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
+    if (n == -1) {
+      printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno));
+      continue;
+    }
+    if (n == 0) {
+      printf("No file is added for test in %s\n", dir_path.c_str());
+    }
+
+    while (n--) {
+      std::string file_path = dir_path + namelist[n]->d_name;
+      files.push_back(file_path);
+      free(namelist[n]);
+    }
+    free(namelist);
+  }
+  return files;
+}
 
 TEST(ResourcesTest, res_create_multi_display_surface) {
   GRSurface** frames;
@@ -35,3 +85,52 @@
   }
   free(frames);
 }
+
+class ResourcesTest : public testing::TestWithParam<std::string> {
+ public:
+  static std::vector<std::string> png_list;
+
+ protected:
+  void SetUp() override {
+    png_ = std::make_unique<PngHandler>(GetParam());
+    ASSERT_TRUE(png_);
+
+    ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file.";
+    ASSERT_LT(static_cast<png_uint_32>(5), png_->width());
+    ASSERT_LT(static_cast<png_uint_32>(0), png_->height());
+    ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file.";
+  }
+
+  std::unique_ptr<PngHandler> png_{ nullptr };
+};
+
+// Parses a png file and tests if it's qualified for the background text image under recovery.
+TEST_P(ResourcesTest, ValidateLocale) {
+  std::vector<unsigned char> row(png_->width());
+  for (png_uint_32 y = 0; y < png_->height(); ++y) {
+    png_read_row(png_->png_ptr(), row.data(), nullptr);
+    int w = (row[1] << 8) | row[0];
+    int h = (row[3] << 8) | row[2];
+    int len = row[4];
+    EXPECT_LT(0, w);
+    EXPECT_LT(0, h);
+    EXPECT_LT(0, len) << "Locale string should be non-empty.";
+    EXPECT_NE(0, row[5]) << "Locale string is missing.";
+
+    ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
+    char* loc = reinterpret_cast<char*>(&row[5]);
+    if (matches_locale(loc, kLocale.c_str())) {
+      EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
+      break;
+    }
+    for (int i = 0; i < h; ++i, ++y) {
+      png_read_row(png_->png_ptr(), row.data(), nullptr);
+    }
+  }
+}
+
+std::vector<std::string> ResourcesTest::png_list = add_files();
+
+INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest,
+                        ::testing::ValuesIn(ResourcesTest::png_list.cbegin(),
+                                            ResourcesTest::png_list.cend()));
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
index 3466e8e..64b8956 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -67,7 +67,7 @@
     "/dev/abc",
     "42949672950 4294967295",
     "1",
-    "0 9",
+    "0 10",
   };
 
   TemporaryFile temp_file;
diff --git a/tests/component/uncrypt_test.cpp b/tests/unit/uncrypt_test.cpp
similarity index 100%
rename from tests/component/uncrypt_test.cpp
rename to tests/unit/uncrypt_test.cpp
diff --git a/tests/component/update_verifier_test.cpp b/tests/unit/update_verifier_test.cpp
similarity index 100%
rename from tests/component/update_verifier_test.cpp
rename to tests/unit/update_verifier_test.cpp
diff --git a/tests/component/updater_test.cpp b/tests/unit/updater_test.cpp
similarity index 92%
rename from tests/component/updater_test.cpp
rename to tests/unit/updater_test.cpp
index a0a7b66..8993dd8 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/unit/updater_test.cpp
@@ -52,21 +52,20 @@
 #include "updater/blockimg.h"
 #include "updater/install.h"
 #include "updater/updater.h"
+#include "updater/updater_runtime.h"
 
 using namespace std::string_literals;
 
 using PackageEntries = std::unordered_map<std::string, std::string>;
 
-struct selabel_handle* sehandle = nullptr;
-
 static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code,
-                   UpdaterInfo* info = nullptr) {
+                   Updater* updater) {
   std::unique_ptr<Expr> e;
   int error_count = 0;
   ASSERT_EQ(0, ParseString(expr_str, &e, &error_count));
   ASSERT_EQ(0, error_count);
 
-  State state(expr_str, info);
+  State state(expr_str, updater);
 
   std::string result;
   bool status = Evaluate(&state, e, &result);
@@ -85,6 +84,11 @@
   ASSERT_EQ(cause_code, state.cause_code);
 }
 
+static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code) {
+  Updater updater(std::make_unique<UpdaterRuntime>(nullptr));
+  expect(expected, expr_str, cause_code, &updater);
+}
+
 static void BuildUpdatePackage(const PackageEntries& entries, int fd) {
   FILE* zip_file_ptr = fdopen(fd, "wb");
   ZipWriter zip_writer(zip_file_ptr);
@@ -102,38 +106,6 @@
   ASSERT_EQ(0, fclose(zip_file_ptr));
 }
 
-static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries,
-                                const std::string& image_file, const std::string& result,
-                                CauseCode cause_code = kNoCause) {
-  CHECK(entries.find("transfer_list") != entries.end());
-
-  // Build the update package.
-  TemporaryFile zip_file;
-  BuildUpdatePackage(entries, zip_file.release());
-
-  MemMapping map;
-  ASSERT_TRUE(map.MapFile(zip_file.path));
-  ZipArchiveHandle handle;
-  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
-
-  // Set up the handler, command_pipe, patch offset & length.
-  UpdaterInfo updater_info;
-  updater_info.package_zip = handle;
-  TemporaryFile temp_pipe;
-  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
-  updater_info.package_zip_addr = map.addr;
-  updater_info.package_zip_len = map.length;
-
-  std::string new_data = entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
-  std::string script = is_verify ? "block_image_verify" : "block_image_update";
-  script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
-            R"(", "patch_data"))";
-  expect(result.c_str(), script, cause_code, &updater_info);
-
-  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
-  CloseArchive(handle);
-}
-
 static std::string GetSha1(std::string_view content) {
   uint8_t digest[SHA_DIGEST_LENGTH];
   SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest);
@@ -159,29 +131,26 @@
   return args[0].release();
 }
 
-class UpdaterTest : public ::testing::Test {
+class UpdaterTestBase {
  protected:
-  void SetUp() override {
+  UpdaterTestBase() : updater_(std::make_unique<UpdaterRuntime>(nullptr)) {}
+
+  void SetUp() {
     RegisterBuiltins();
     RegisterInstallFunctions();
     RegisterBlockImageFunctions();
 
-    RegisterFunction("blob_to_string", BlobToString);
-
     // Each test is run in a separate process (isolated mode). Shared temporary files won't cause
     // conflicts.
     Paths::Get().set_cache_temp_source(temp_saved_source_.path);
     Paths::Get().set_last_command_file(temp_last_command_.path);
     Paths::Get().set_stash_directory_base(temp_stash_base_.path);
 
-    // Enable a special command "abort" to simulate interruption.
-    Command::abort_allowed_ = true;
-
     last_command_file_ = temp_last_command_.path;
     image_file_ = image_temp_file_.path;
   }
 
-  void TearDown() override {
+  void TearDown() {
     // Clean up the last_command_file if any.
     ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
 
@@ -191,16 +160,80 @@
     ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
   }
 
+  void RunBlockImageUpdate(bool is_verify, PackageEntries entries, const std::string& image_file,
+                           const std::string& result, CauseCode cause_code = kNoCause) {
+    CHECK(entries.find("transfer_list") != entries.end());
+    std::string new_data =
+        entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
+    std::string script = is_verify ? "block_image_verify" : "block_image_update";
+    script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
+              R"(", "patch_data"))";
+    entries.emplace(Updater::SCRIPT_NAME, script);
+
+    // Build the update package.
+    TemporaryFile zip_file;
+    BuildUpdatePackage(entries, zip_file.release());
+
+    // Set up the handler, command_pipe, patch offset & length.
+    TemporaryFile temp_pipe;
+    ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false));
+    ASSERT_TRUE(updater_.RunUpdate());
+    ASSERT_EQ(result, updater_.GetResult());
+
+    // Parse the cause code written to the command pipe.
+    int received_cause_code = kNoCause;
+    std::string pipe_content;
+    ASSERT_TRUE(android::base::ReadFileToString(temp_pipe.path, &pipe_content));
+    auto lines = android::base::Split(pipe_content, "\n");
+    for (std::string_view line : lines) {
+      if (android::base::ConsumePrefix(&line, "log cause: ")) {
+        ASSERT_TRUE(android::base::ParseInt(line.data(), &received_cause_code));
+      }
+    }
+    ASSERT_EQ(cause_code, received_cause_code);
+  }
+
   TemporaryFile temp_saved_source_;
   TemporaryDir temp_stash_base_;
   std::string last_command_file_;
   std::string image_file_;
 
+  Updater updater_;
+
  private:
   TemporaryFile temp_last_command_;
   TemporaryFile image_temp_file_;
 };
 
+class UpdaterTest : public UpdaterTestBase, public ::testing::Test {
+ protected:
+  void SetUp() override {
+    UpdaterTestBase::SetUp();
+
+    RegisterFunction("blob_to_string", BlobToString);
+    // Enable a special command "abort" to simulate interruption.
+    Command::abort_allowed_ = true;
+  }
+
+  void TearDown() override {
+    UpdaterTestBase::TearDown();
+  }
+
+  void SetUpdaterCmdPipe(int fd) {
+    FILE* cmd_pipe = fdopen(fd, "w");
+    ASSERT_NE(nullptr, cmd_pipe);
+    updater_.cmd_pipe_.reset(cmd_pipe);
+  }
+
+  void SetUpdaterOtaPackageHandle(ZipArchiveHandle handle) {
+    updater_.package_handle_ = handle;
+  }
+
+  void FlushUpdaterCommandPipe() const {
+    fflush(updater_.cmd_pipe_.get());
+  }
+};
+
 TEST_F(UpdaterTest, getprop) {
     expect(android::base::GetProperty("ro.product.device", "").c_str(),
            "getprop(\"ro.product.device\")",
@@ -317,13 +350,12 @@
   ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
 
   // Need to set up the ziphandle.
-  UpdaterInfo updater_info;
-  updater_info.package_zip = handle;
+  SetUpdaterOtaPackageHandle(handle);
 
   // Two-argument version.
   TemporaryFile temp_file1;
   std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
-  expect("t", script, kNoCause, &updater_info);
+  expect("t", script, kNoCause, &updater_);
 
   // Verify the extracted entry.
   std::string data;
@@ -332,32 +364,30 @@
 
   // 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, kNoCause, &updater_info);
+  expect("t", script, kNoCause, &updater_);
 
   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, kNoCause, &updater_info);
+  expect("", script, kNoCause, &updater_);
 
   // Extract to /dev/full should fail.
   script = "package_extract_file(\"a.txt\", \"/dev/full\")";
-  expect("", script, kNoCause, &updater_info);
+  expect("", script, kNoCause, &updater_);
 
   // One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to
   // VAL_STRING for equality test.
   script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\"";
-  expect("t", script, kNoCause, &updater_info);
+  expect("t", script, kNoCause, &updater_);
 
   script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\"";
-  expect("t", script, kNoCause, &updater_info);
+  expect("t", script, kNoCause, &updater_);
 
   // Missing entry. The one-argument version aborts the evaluation.
   script = "package_extract_file(\"doesntexist\")";
-  expect(nullptr, script, kPackageExtractFileFailure, &updater_info);
-
-  CloseArchive(handle);
+  expect(nullptr, script, kPackageExtractFileFailure, &updater_);
 }
 
 TEST_F(UpdaterTest, read_file) {
@@ -563,17 +593,15 @@
   expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure);
 
   TemporaryFile tf;
-  UpdaterInfo updater_info;
-  updater_info.cmd_pipe = fdopen(tf.release(), "w");
-  expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
-  fflush(updater_info.cmd_pipe);
+  SetUpdaterCmdPipe(tf.release());
+  expect(".52", "set_progress(\".52\")", kNoCause, &updater_);
+  FlushUpdaterCommandPipe();
 
   std::string cmd;
   ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
   ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
   // recovery-updater protocol expects 2 tokens ("set_progress <frac>").
   ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
-  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
 }
 
 TEST_F(UpdaterTest, show_progress) {
@@ -588,17 +616,15 @@
   expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure);
 
   TemporaryFile tf;
-  UpdaterInfo updater_info;
-  updater_info.cmd_pipe = fdopen(tf.release(), "w");
-  expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
-  fflush(updater_info.cmd_pipe);
+  SetUpdaterCmdPipe(tf.release());
+  expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_);
+  FlushUpdaterCommandPipe();
 
   std::string cmd;
   ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
   ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
   // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
   ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
-  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
 }
 
 TEST_F(UpdaterTest, block_image_update_parsing_error) {
@@ -993,44 +1019,20 @@
   ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
 }
 
-class ResumableUpdaterTest : public testing::TestWithParam<size_t> {
+class ResumableUpdaterTest : public UpdaterTestBase, public testing::TestWithParam<size_t> {
  protected:
   void SetUp() override {
-    RegisterBuiltins();
-    RegisterInstallFunctions();
-    RegisterBlockImageFunctions();
-
-    Paths::Get().set_cache_temp_source(temp_saved_source_.path);
-    Paths::Get().set_last_command_file(temp_last_command_.path);
-    Paths::Get().set_stash_directory_base(temp_stash_base_.path);
-
+    UpdaterTestBase::SetUp();
     // Enable a special command "abort" to simulate interruption.
     Command::abort_allowed_ = true;
-
     index_ = GetParam();
-    image_file_ = image_temp_file_.path;
-    last_command_file_ = temp_last_command_.path;
   }
 
   void TearDown() override {
-    // Clean up the last_command_file if any.
-    ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
-
-    // Clear partition updated marker if any.
-    std::string updated_marker{ temp_stash_base_.path };
-    updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED";
-    ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
+    UpdaterTestBase::TearDown();
   }
 
-  TemporaryFile temp_saved_source_;
-  TemporaryDir temp_stash_base_;
-  std::string last_command_file_;
-  std::string image_file_;
   size_t index_;
-
- private:
-  TemporaryFile temp_last_command_;
-  TemporaryFile image_temp_file_;
 };
 
 static std::string g_source_image;
diff --git a/tests/component/verifier_test.cpp b/tests/unit/verifier_test.cpp
similarity index 100%
rename from tests/component/verifier_test.cpp
rename to tests/unit/verifier_test.cpp
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index dfe617e..0753d64 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -37,10 +37,9 @@
   ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle));
 
   static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary";
-  ZipString binary_path(BINARY_PATH);
   ZipEntry binary_entry;
   // Make sure the package opens correctly and its entry can be read.
-  ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry));
+  ASSERT_EQ(0, FindEntry(handle, BINARY_PATH, &binary_entry));
 
   TemporaryFile tmp_binary;
   ASSERT_NE(-1, tmp_binary.fd);
diff --git a/tools/image_generator/Android.bp b/tools/image_generator/Android.bp
index 2afdd5a..8300040 100644
--- a/tools/image_generator/Android.bp
+++ b/tools/image_generator/Android.bp
@@ -19,7 +19,7 @@
 
     static_libs: [
         "commons-cli-1.2",
-        "icu4j-host",
+        "icu4j",
     ],
 
     srcs: [
diff --git a/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml
index e6f2ffd..e51b36d 100644
--- a/tools/recovery_l10n/res/values-gl/strings.xml
+++ b/tools/recovery_l10n/res/values-gl/strings.xml
@@ -6,9 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Non hai ningún comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erro"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando actualización de seguranza"</string>
-    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos do usuario almacenados neste dispositivo."</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos de usuario almacenados neste dispositivo."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"Tentar de novo"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Restablecemento dos datos de fábrica"</string>
-    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos do usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos de usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string>
     <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml
index 2d6c0ab..3d66372 100644
--- a/tools/recovery_l10n/res/values-ja/strings.xml
+++ b/tools/recovery_l10n/res/values-ja/strings.xml
@@ -6,7 +6,7 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"コマンドが指定されていません"</string>
     <string name="recovery_error" msgid="5748178989622716736">"エラーが発生しました。"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"セキュリティ アップデートをインストールしています"</string>
-    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、このデバイスに保存されているすべてのユーザー データを消去することが必要な場合があります。"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、この端末に保存されているすべてのユーザー データを消去することが必要な場合があります。"</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"再試行"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"データの初期化"</string>
     <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"すべてのユーザー データをワイプしますか?\n\nこの操作は元に戻せません。"</string>
diff --git a/updater/Android.bp b/updater/Android.bp
index b80cdb3..b279068 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -30,7 +30,6 @@
         "libfec",
         "libfec_rs",
         "libverity_tree",
-        "libfs_mgr",
         "libgtest_prod",
         "liblog",
         "liblp",
@@ -46,6 +45,14 @@
         "libcrypto_utils",
         "libcutils",
         "libutils",
+    ],
+}
+
+cc_defaults {
+    name: "libupdater_device_defaults",
+
+    static_libs: [
+        "libfs_mgr",
         "libtune2fs",
 
         "libext2_com_err",
@@ -54,11 +61,13 @@
         "libext2_uuid",
         "libext2_e2p",
         "libext2fs",
-    ],
+    ]
 }
 
 cc_library_static {
-    name: "libupdater",
+    name: "libupdater_core",
+
+    host_supported: true,
 
     defaults: [
         "recovery_defaults",
@@ -68,8 +77,37 @@
     srcs: [
         "blockimg.cpp",
         "commands.cpp",
-        "dynamic_partitions.cpp",
         "install.cpp",
+        "updater.cpp",
+    ],
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+
+    export_include_dirs: [
+        "include",
+    ],
+}
+
+cc_library_static {
+    name: "libupdater_device",
+
+    defaults: [
+        "recovery_defaults",
+        "libupdater_defaults",
+        "libupdater_device_defaults",
+    ],
+
+    srcs: [
+        "dynamic_partitions.cpp",
+        "updater_runtime.cpp",
+    ],
+
+    static_libs: [
+        "libupdater_core",
     ],
 
     include_dirs: [
@@ -80,3 +118,32 @@
         "include",
     ],
 }
+
+cc_library_host_static {
+    name: "libupdater_host",
+
+    defaults: [
+        "recovery_defaults",
+        "libupdater_defaults",
+    ],
+
+    srcs: [
+        "simulator_runtime.cpp",
+        "target_files.cpp",
+    ],
+
+    static_libs: [
+        "libupdater_core",
+        "libfstab",
+    ],
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+
+    export_include_dirs: [
+        "include",
+    ],
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index c7a6ba9..e969d1c 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -33,7 +33,6 @@
     libfec \
     libfec_rs \
     libverity_tree \
-    libfs_mgr \
     libgtest_prod \
     liblog \
     liblp \
@@ -48,9 +47,24 @@
     libcrypto \
     libcrypto_utils \
     libcutils \
-    libutils \
-    libtune2fs \
-    $(tune2fs_static_libraries)
+    libutils
+
+
+# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
+# named "Register_<libname>()".  Here we emit a little C function that
+# gets #included by updater.cpp.  It calls all those registration
+# functions.
+# $(1): the path to the register.inc file
+# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS
+define generate-register-inc
+    $(hide) mkdir -p $(dir $(1))
+    $(hide) echo "" > $(1)
+    $(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);)
+    $(hide) echo "void RegisterDeviceExtensions() {" >> $(1)
+    $(hide) $(foreach lib,$(2),echo "  Register_$(lib)();" >> $(1);)
+    $(hide) echo "}" >> $(1)
+endef
+
 
 # updater (static executable)
 # ===============================
@@ -59,7 +73,7 @@
 LOCAL_MODULE := updater
 
 LOCAL_SRC_FILES := \
-    updater.cpp
+    updater_main.cpp
 
 LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include
@@ -69,33 +83,26 @@
     -Werror
 
 LOCAL_STATIC_LIBRARIES := \
-    libupdater \
+    libupdater_device \
+    libupdater_core \
     $(TARGET_RECOVERY_UPDATER_LIBS) \
     $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \
-    $(updater_common_static_libraries)
+    $(updater_common_static_libraries) \
+    libfs_mgr \
+    libtune2fs \
+    $(tune2fs_static_libraries)
 
-# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
-# named "Register_<libname>()".  Here we emit a little C function that
-# gets #included by updater.c.  It calls all those registration
-# functions.
+LOCAL_MODULE_CLASS := EXECUTABLES
+inc := $(call local-generated-sources-dir)/register.inc
 
 # Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS.
 # These libs are also linked in with updater, but we don't try to call
 # any sort of registration function for these.  Use this variable for
 # any subsidiary static libraries required for your registered
 # extension libs.
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-inc := $(call local-generated-sources-dir)/register.inc
-
 $(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
 $(inc) :
-	$(hide) mkdir -p $(dir $@)
-	$(hide) echo "" > $@
-	$(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;)
-	$(hide) echo "void RegisterDeviceExtensions() {" >> $@
-	$(hide) $(foreach lib,$(libs),echo "  Register_$(lib)();" >> $@;)
-	$(hide) echo "}" >> $@
+	$(call generate-register-inc,$@,$(libs))
 
 LOCAL_GENERATED_SOURCES := $(inc)
 
@@ -104,3 +111,42 @@
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
 include $(BUILD_EXECUTABLE)
+
+
+# update_host_simulator (static executable)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := update_host_simulator
+LOCAL_MODULE_HOST_OS := linux
+
+LOCAL_SRC_FILES := \
+    update_simulator_main.cpp
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+
+LOCAL_CFLAGS := \
+    -Wall \
+    -Werror
+
+LOCAL_STATIC_LIBRARIES := \
+    libupdater_host \
+    libupdater_core \
+    $(TARGET_RECOVERY_UPDATER_HOST_LIBS) \
+    $(TARGET_RECOVERY_UPDATER_HOST_EXTRA_LIBS) \
+    $(updater_common_static_libraries) \
+    libfstab
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+inc := $(call local-generated-sources-dir)/register.inc
+
+$(inc) : libs := $(TARGET_RECOVERY_UPDATER_HOST_LIBS)
+$(inc) :
+	$(call generate-register-inc,$@,$(libs))
+
+LOCAL_GENERATED_SOURCES := $(inc)
+
+inc :=
+
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 07c3c7b..2d41f61 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -42,17 +42,18 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <applypatch/applypatch.h>
 #include <brotli/decode.h>
 #include <fec/io.h>
 #include <openssl/sha.h>
-#include <private/android_filesystem_config.h>
 #include <verity/hash_tree_builder.h>
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
+#include "edify/updater_interface.h"
 #include "otautil/dirutil.h"
 #include "otautil/error_code.h"
 #include "otautil/paths.h"
@@ -60,12 +61,16 @@
 #include "otautil/rangeset.h"
 #include "private/commands.h"
 #include "updater/install.h"
-#include "updater/updater.h"
 
-// Set this to 0 to interpret 'erase' transfers to mean do a
-// BLKDISCARD ioctl (the normal behavior).  Set to 1 to interpret
-// erase to mean fill the region with zeroes.
+#ifdef __ANDROID__
+#include <private/android_filesystem_config.h>
+// Set this to 0 to interpret 'erase' transfers to mean do a BLKDISCARD ioctl (the normal behavior).
+// Set to 1 to interpret erase to mean fill the region with zeroes.
 #define DEBUG_ERASE  0
+#else
+#define DEBUG_ERASE 1
+#define AID_SYSTEM -1
+#endif  // __ANDROID__
 
 static constexpr size_t BLOCKSIZE = 4096;
 static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
@@ -1668,42 +1673,43 @@
     return StringValue("");
   }
 
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-  if (ui == nullptr) {
+  auto updater = state->updater;
+  auto block_device_path = updater->FindBlockDeviceName(blockdev_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+               << " failed.";
     return StringValue("");
   }
 
-  FILE* cmd_pipe = ui->cmd_pipe;
-  ZipArchiveHandle za = ui->package_zip;
-
-  if (cmd_pipe == nullptr || za == nullptr) {
+  ZipArchiveHandle za = updater->GetPackageHandle();
+  if (za == nullptr) {
     return StringValue("");
   }
 
-  ZipString path_data(patch_data_fn->data.c_str());
+  std::string_view path_data(patch_data_fn->data);
   ZipEntry patch_entry;
   if (FindEntry(za, path_data, &patch_entry) != 0) {
     LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package";
     return StringValue("");
   }
+  params.patch_start = updater->GetMappedPackageAddress() + patch_entry.offset;
 
-  params.patch_start = ui->package_zip_addr + patch_entry.offset;
-  ZipString new_data(new_data_fn->data.c_str());
+  std::string_view new_data(new_data_fn->data);
   ZipEntry new_entry;
   if (FindEntry(za, new_data, &new_entry) != 0) {
     LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package";
     return StringValue("");
   }
 
-  params.fd.reset(TEMP_FAILURE_RETRY(open(blockdev_filename->data.c_str(), O_RDWR)));
+  params.fd.reset(TEMP_FAILURE_RETRY(open(block_device_path.c_str(), O_RDWR)));
   if (params.fd == -1) {
     failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
-    PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
+    PLOG(ERROR) << "open \"" << block_device_path << "\" failed";
     return StringValue("");
   }
 
   uint8_t digest[SHA_DIGEST_LENGTH];
-  if (!Sha1DevicePath(blockdev_filename->data, digest)) {
+  if (!Sha1DevicePath(block_device_path, digest)) {
     return StringValue("");
   }
   params.stashbase = print_sha1(digest);
@@ -1716,8 +1722,7 @@
     struct stat sb;
     int result = stat(updated_marker.c_str(), &sb);
     if (result == 0) {
-      LOG(INFO) << "Skipping already updated partition " << blockdev_filename->data
-                << " based on marker";
+      LOG(INFO) << "Skipping already updated partition " << block_device_path << " based on marker";
       return StringValue("t");
     }
   } else {
@@ -1887,8 +1892,10 @@
         LOG(WARNING) << "Failed to update the last command file.";
       }
 
-      fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks);
-      fflush(cmd_pipe);
+      updater->WriteToCommandPipe(
+          android::base::StringPrintf("set_progress %.4f",
+                                      static_cast<double>(params.written) / total_blocks),
+          true);
     }
   }
 
@@ -1913,13 +1920,15 @@
       LOG(INFO) << "stashed " << params.stashed << " blocks";
       LOG(INFO) << "max alloc needed was " << params.buffer.size();
 
-      const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+      const char* partition = strrchr(block_device_path.c_str(), '/');
       if (partition != nullptr && *(partition + 1) != 0) {
-        fprintf(cmd_pipe, "log bytes_written_%s: %" PRIu64 "\n", partition + 1,
-                static_cast<uint64_t>(params.written) * BLOCKSIZE);
-        fprintf(cmd_pipe, "log bytes_stashed_%s: %" PRIu64 "\n", partition + 1,
-                static_cast<uint64_t>(params.stashed) * BLOCKSIZE);
-        fflush(cmd_pipe);
+        updater->WriteToCommandPipe(
+            android::base::StringPrintf("log bytes_written_%s: %" PRIu64, partition + 1,
+                                        static_cast<uint64_t>(params.written) * BLOCKSIZE));
+        updater->WriteToCommandPipe(
+            android::base::StringPrintf("log bytes_stashed_%s: %" PRIu64, partition + 1,
+                                        static_cast<uint64_t>(params.stashed) * BLOCKSIZE),
+            true);
       }
       // Delete stash only after successfully completing the update, as it may contain blocks needed
       // to complete the update later.
@@ -2019,7 +2028,7 @@
     // clang-format off
     { Command::Type::ABORT,             PerformCommandAbort },
     { Command::Type::BSDIFF,            PerformCommandDiff },
-    { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree },
+    { Command::Type::COMPUTE_HASH_TREE, nullptr },
     { Command::Type::ERASE,             nullptr },
     { Command::Type::FREE,              PerformCommandFree },
     { Command::Type::IMGDIFF,           PerformCommandDiff },
@@ -2079,10 +2088,17 @@
     return StringValue("");
   }
 
-  android::base::unique_fd fd(open(blockdev_filename->data.c_str(), O_RDWR));
+  auto block_device_path = state->updater->FindBlockDeviceName(blockdev_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
+  android::base::unique_fd fd(open(block_device_path.c_str(), O_RDWR));
   if (fd == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
-    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", blockdev_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2096,7 +2112,7 @@
   std::vector<uint8_t> buffer(BLOCKSIZE);
   for (const auto& [begin, end] : rs) {
     if (!check_lseek(fd, static_cast<off64_t>(begin) * BLOCKSIZE, SEEK_SET)) {
-      ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(),
+      ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", block_device_path.c_str(),
                  strerror(errno));
       return StringValue("");
     }
@@ -2104,7 +2120,7 @@
     for (size_t j = begin; j < end; ++j) {
       if (!android::base::ReadFully(fd, buffer.data(), BLOCKSIZE)) {
         CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
-        ErrorAbort(state, cause_code, "failed to read %s: %s", blockdev_filename->data.c_str(),
+        ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
                    strerror(errno));
         return StringValue("");
       }
@@ -2143,10 +2159,17 @@
     return StringValue("");
   }
 
-  android::base::unique_fd fd(open(arg_filename->data.c_str(), O_RDONLY));
+  auto block_device_path = state->updater->FindBlockDeviceName(arg_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << arg_filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
+  android::base::unique_fd fd(open(block_device_path.c_str(), O_RDONLY));
   if (fd == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
-    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", arg_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2156,7 +2179,7 @@
 
   if (ReadBlocks(blk0, &block0_buffer, fd) == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
-    ErrorAbort(state, cause_code, "failed to read %s: %s", arg_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2172,8 +2195,10 @@
   uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]);
 
   if (mount_count > 0) {
-    uiPrintf(state, "Device was remounted R/W %" PRIu16 " times", mount_count);
-    uiPrintf(state, "Last remount happened on %s", ctime(&mount_time));
+    state->updater->UiPrint(
+        android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count));
+    state->updater->UiPrint(
+        android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time)));
   }
 
   return StringValue("t");
@@ -2209,14 +2234,21 @@
     return StringValue("");
   }
 
+  auto block_device_path = state->updater->FindBlockDeviceName(filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
   // Output notice to log when recover is attempted
-  LOG(INFO) << filename->data << " image corrupted, attempting to recover...";
+  LOG(INFO) << block_device_path << " image corrupted, attempting to recover...";
 
   // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read
-  fec::io fh(filename->data, O_RDWR);
+  fec::io fh(block_device_path, O_RDWR);
 
   if (!fh) {
-    ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(),
+    ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2242,7 +2274,7 @@
 
       if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) {
         ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s",
-                   filename->data.c_str(), j, strerror(errno));
+                   block_device_path.c_str(), j, strerror(errno));
         return StringValue("");
       }
 
@@ -2258,7 +2290,7 @@
       //     read and check if the errors field value has increased.
     }
   }
-  LOG(INFO) << "..." << filename->data << " image recovered successfully.";
+  LOG(INFO) << "..." << block_device_path << " image recovered successfully.";
   return StringValue("t");
 }
 
diff --git a/updater/include/updater/install.h b/updater/include/updater/install.h
index 8d6ca47..9fe2031 100644
--- a/updater/include/updater/install.h
+++ b/updater/include/updater/install.h
@@ -14,15 +14,6 @@
  * limitations under the License.
  */
 
-#ifndef _UPDATER_INSTALL_H_
-#define _UPDATER_INSTALL_H_
-
-struct State;
+#pragma once
 
 void RegisterInstallFunctions();
-
-// uiPrintf function prints msg to screen as well as logs
-void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...)
-    __attribute__((__format__(printf, 2, 3)));
-
-#endif
diff --git a/updater/include/updater/simulator_runtime.h b/updater/include/updater/simulator_runtime.h
new file mode 100644
index 0000000..93fa2a4
--- /dev/null
+++ b/updater/include/updater/simulator_runtime.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+#include "updater/target_files.h"
+
+class SimulatorRuntime : public UpdaterRuntimeInterface {
+ public:
+  explicit SimulatorRuntime(TargetFiles* source) : source_(source) {}
+
+  bool IsSimulator() const override {
+    return true;
+  }
+
+  std::string GetProperty(const std::string_view key,
+                          const std::string_view default_value) const override;
+
+  int Mount(const std::string_view location, const std::string_view mount_point,
+            const std::string_view fs_type, const std::string_view mount_options) override;
+  bool IsMounted(const std::string_view mount_point) const override;
+  std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+  bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+  bool WriteStringToFile(const std::string_view content,
+                         const std::string_view filename) const override;
+
+  int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+  int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+  int Tune2Fs(const std::vector<std::string>& args) const override;
+
+ private:
+  std::string FindBlockDeviceName(const std::string_view name) const override;
+
+  TargetFiles* source_;
+  std::map<std::string, std::string, std::less<>> mounted_partitions_;
+};
diff --git a/updater/include/updater/target_files.h b/updater/include/updater/target_files.h
new file mode 100644
index 0000000..9ef1a5b
--- /dev/null
+++ b/updater/include/updater/target_files.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+// This class parses a given target file for the build properties and image files. Then it creates
+// and maintains the temporary files to simulate the block devices on host.
+class TargetFiles {
+ public:
+  TargetFiles(std::string path, std::string work_dir)
+      : path_(std::move(path)), work_dir_(std::move(work_dir)) {}
+
+  std::string GetProperty(const std::string_view key, const std::string_view default_value) const;
+
+  std::string FindBlockDeviceName(const std::string_view name) const;
+
+ private:
+  std::string path_;  // Path to the target file.
+
+  std::string work_dir_;  // A temporary directory to store the extracted image files
+};
diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h
index f4a2fe8..08816bf 100644
--- a/updater/include/updater/updater.h
+++ b/updater/include/updater/updater.h
@@ -14,22 +14,81 @@
  * limitations under the License.
  */
 
-#ifndef _UPDATER_UPDATER_H_
-#define _UPDATER_UPDATER_H_
+#pragma once
 
+#include <stdint.h>
 #include <stdio.h>
+
+#include <memory>
+#include <string>
+#include <string_view>
+
 #include <ziparchive/zip_archive.h>
 
-typedef struct {
-    FILE* cmd_pipe;
-    ZipArchiveHandle package_zip;
-    int version;
+#include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "otautil/error_code.h"
+#include "otautil/sysutil.h"
 
-    uint8_t* package_zip_addr;
-    size_t package_zip_len;
-} UpdaterInfo;
+class Updater : public UpdaterInterface {
+ public:
+  explicit Updater(std::unique_ptr<UpdaterRuntimeInterface> run_time)
+      : runtime_(std::move(run_time)) {}
 
-struct selabel_handle;
-extern struct selabel_handle *sehandle;
+  ~Updater() override;
 
-#endif
+  // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and
+  // UpdaterRuntime.
+  bool Init(int fd, const std::string_view package_filename, bool is_retry);
+
+  // Parses and evaluates the updater-script in the OTA package. Reports the error code if the
+  // evaluation fails.
+  bool RunUpdate();
+
+  // Writes the message to command pipe, adds a new line in the end.
+  void WriteToCommandPipe(const std::string_view message, bool flush = false) const override;
+
+  // Sends over the message to recovery to print it on the screen.
+  void UiPrint(const std::string_view message) const override;
+
+  std::string FindBlockDeviceName(const std::string_view name) const override;
+
+  UpdaterRuntimeInterface* GetRuntime() const override {
+    return runtime_.get();
+  }
+  ZipArchiveHandle GetPackageHandle() const override {
+    return package_handle_;
+  }
+  std::string GetResult() const override {
+    return result_;
+  }
+
+  uint8_t* GetMappedPackageAddress() const override {
+    return mapped_package_.addr;
+  }
+
+ private:
+  friend class UpdaterTestBase;
+  friend class UpdaterTest;
+  // Where in the package we expect to find the edify script to execute.
+  // (Note it's "updateR-script", not the older "update-script".)
+  static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
+
+  // Reads the entry |name| in the zip archive and put the result in |content|.
+  bool ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, std::string* content);
+
+  // Parses the error code embedded in state->errmsg; and reports the error code and cause code.
+  void ParseAndReportErrorCode(State* state);
+
+  std::unique_ptr<UpdaterRuntimeInterface> runtime_;
+
+  MemMapping mapped_package_;
+  ZipArchiveHandle package_handle_{ nullptr };
+  std::string updater_script_;
+
+  bool is_retry_{ false };
+  std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose };
+
+  std::string result_;
+  std::vector<std::string> skipped_functions_;
+};
diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h
new file mode 100644
index 0000000..e97eb49
--- /dev/null
+++ b/updater/include/updater/updater_runtime.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+
+struct selabel_handle;
+
+class UpdaterRuntime : public UpdaterRuntimeInterface {
+ public:
+  explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {}
+  ~UpdaterRuntime() override = default;
+
+  bool IsSimulator() const override {
+    return false;
+  }
+
+  std::string GetProperty(const std::string_view key,
+                          const std::string_view default_value) const override;
+
+  std::string FindBlockDeviceName(const std::string_view name) const override;
+
+  int Mount(const std::string_view location, const std::string_view mount_point,
+            const std::string_view fs_type, const std::string_view mount_options) override;
+  bool IsMounted(const std::string_view mount_point) const override;
+  std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+  bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+  bool WriteStringToFile(const std::string_view content,
+                         const std::string_view filename) const override;
+
+  int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+  int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+  int Tune2Fs(const std::vector<std::string>& args) const override;
+
+  struct selabel_handle* sehandle_{ nullptr };
+};
diff --git a/updater/install.cpp b/updater/install.cpp
index 20a204a..c82351e 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -53,45 +53,31 @@
 #include <openssl/sha.h>
 #include <selinux/label.h>
 #include <selinux/selinux.h>
-#include <tune2fs.h>
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "edify/updater_runtime_interface.h"
 #include "otautil/dirutil.h"
 #include "otautil/error_code.h"
 #include "otautil/mounts.h"
 #include "otautil/print_sha1.h"
 #include "otautil/sysutil.h"
-#include "updater/updater.h"
 
-// Send over the buffer to recovery though the command pipe.
-static void uiPrint(State* state, const std::string& buffer) {
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
+#ifndef __ANDROID__
+#include <cutils/memory.h>  // for strlcpy
+#endif
 
-  // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
-  // So skip sending empty strings to UI.
-  std::vector<std::string> lines = android::base::Split(buffer, "\n");
-  for (auto& line : lines) {
-    if (!line.empty()) {
-      fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str());
-    }
+static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) {
+  CHECK(updater);
+  std::string name = updater->FindBlockDeviceName(partition->name);
+  if (name.empty()) {
+    LOG(ERROR) << "Failed to find the block device " << partition->name;
+    return false;
   }
 
-  // On the updater side, we need to dump the contents to stderr (which has
-  // been redirected to the log file). Because the recovery will only print
-  // the contents to screen when processing pipe command ui_print.
-  LOG(INFO) << buffer;
-}
-
-void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) {
-  std::string error_msg;
-
-  va_list ap;
-  va_start(ap, format);
-  android::base::StringAppendV(&error_msg, format, ap);
-  va_end(ap);
-
-  uiPrint(state, error_msg);
+  partition->name = std::move(name);
+  return true;
 }
 
 // This is the updater side handler for ui_print() in edify script. Contents will be sent over to
@@ -103,7 +89,7 @@
   }
 
   std::string buffer = android::base::Join(args, "");
-  uiPrint(state, buffer);
+  state->updater->UiPrint(buffer);
   return StringValue(buffer);
 }
 
@@ -129,10 +115,9 @@
     const std::string& zip_path = args[0];
     const std::string& dest_path = args[1];
 
-    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
-    ZipString zip_string_path(zip_path.c_str());
+    ZipArchiveHandle za = state->updater->GetPackageHandle();
     ZipEntry entry;
-    if (FindEntry(za, zip_string_path, &entry) != 0) {
+    if (FindEntry(za, zip_path, &entry) != 0) {
       LOG(ERROR) << name << ": no " << zip_path << " in package";
       return StringValue("");
     }
@@ -173,10 +158,9 @@
     }
     const std::string& zip_path = args[0];
 
-    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
-    ZipString zip_string_path(zip_path.c_str());
+    ZipArchiveHandle za = state->updater->GetPackageHandle();
     ZipEntry entry;
-    if (FindEntry(za, zip_string_path, &entry) != 0) {
+    if (FindEntry(za, zip_path, &entry) != 0) {
       return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
                         zip_path.c_str());
     }
@@ -229,6 +213,11 @@
                       args[1].c_str(), err.c_str());
   }
 
+  if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+      !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+    return StringValue("");
+  }
+
   bool result = PatchPartitionCheck(target, source);
   return StringValue(result ? "t" : "");
 }
@@ -270,6 +259,11 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name);
   }
 
+  if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+      !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+    return StringValue("");
+  }
+
   bool result = PatchPartition(target, source, *values[0], nullptr);
   return StringValue(result ? "t" : "");
 }
@@ -313,26 +307,11 @@
                       name);
   }
 
-  {
-    char* secontext = nullptr;
-
-    if (sehandle) {
-      selabel_lookup(sehandle, &secontext, mount_point.c_str(), 0755);
-      setfscreatecon(secontext);
-    }
-
-    mkdir(mount_point.c_str(), 0755);
-
-    if (secontext) {
-      freecon(secontext);
-      setfscreatecon(nullptr);
-    }
-  }
-
-  if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(),
-            MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) {
-    uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(),
-             strerror(errno));
+  auto updater = state->updater;
+  if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) {
+    updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name,
+                                                 location.c_str(), mount_point.c_str(),
+                                                 strerror(errno)));
     return StringValue("");
   }
 
@@ -355,9 +334,8 @@
                       "mount_point argument to unmount() can't be empty");
   }
 
-  scan_mounted_volumes();
-  MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
-  if (vol == nullptr) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->IsMounted(mount_point)) {
     return StringValue("");
   }
 
@@ -378,39 +356,20 @@
                       "mount_point argument to unmount() can't be empty");
   }
 
-  scan_mounted_volumes();
-  MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
-  if (vol == nullptr) {
-    uiPrintf(state, "Failed to unmount %s: No such volume", mount_point.c_str());
+  auto updater = state->updater;
+  auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point);
+  if (!mounted) {
+    updater->UiPrint(
+        android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str()));
     return nullptr;
-  } else {
-    int ret = unmount_mounted_volume(vol);
-    if (ret != 0) {
-      uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno));
-    }
+  } else if (result != 0) {
+    updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(),
+                                                 strerror(errno)));
   }
 
   return StringValue(mount_point);
 }
 
-static int exec_cmd(const std::vector<std::string>& args) {
-  CHECK(!args.empty());
-  auto argv = StringVectorToNullTerminatedArray(args);
-
-  pid_t child;
-  if ((child = vfork()) == 0) {
-    execv(argv[0], argv.data());
-    _exit(EXIT_FAILURE);
-  }
-
-  int status;
-  waitpid(child, &status, 0);
-  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-    LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
-  }
-  return WEXITSTATUS(status);
-}
-
 // format(fs_type, partition_type, location, fs_size, mount_point)
 //
 //    fs_type="ext4"  partition_type="EMMC"  location=device  fs_size=<bytes> mount_point=<location>
@@ -455,6 +414,7 @@
                       fs_size.c_str());
   }
 
+  auto updater_runtime = state->updater->GetRuntime();
   if (fs_type == "ext4") {
     std::vector<std::string> mke2fs_args = {
       "/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location
@@ -463,12 +423,13 @@
       mke2fs_args.push_back(std::to_string(size / 4096LL));
     }
 
-    if (auto status = exec_cmd(mke2fs_args); status != 0) {
+    if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) {
       LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
       return StringValue("");
     }
 
-    if (auto status = exec_cmd({ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location });
+    if (auto status = updater_runtime->RunProgram(
+            { "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true);
         status != 0) {
       LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
       return StringValue("");
@@ -487,12 +448,13 @@
     if (size >= 512) {
       f2fs_args.push_back(std::to_string(size / 512));
     }
-    if (auto status = exec_cmd(f2fs_args); status != 0) {
+    if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) {
       LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location;
       return StringValue("");
     }
 
-    if (auto status = exec_cmd({ "/system/bin/sload_f2fs", "-t", mount_point, location });
+    if (auto status = updater_runtime->RunProgram(
+            { "/system/bin/sload_f2fs", "-t", mount_point, location }, true);
         status != 0) {
       LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location;
       return StringValue("");
@@ -531,8 +493,7 @@
                       sec_str.c_str());
   }
 
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-  fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
+  state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec));
 
   return StringValue(frac_str);
 }
@@ -555,8 +516,7 @@
                       frac_str.c_str());
   }
 
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-  fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
+  state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac));
 
   return StringValue(frac_str);
 }
@@ -569,7 +529,9 @@
   if (!Evaluate(state, argv[0], &key)) {
     return nullptr;
   }
-  std::string value = android::base::GetProperty(key, "");
+
+  auto updater_runtime = state->updater->GetRuntime();
+  std::string value = updater_runtime->GetProperty(key, "");
 
   return StringValue(value);
 }
@@ -594,7 +556,8 @@
   const std::string& key = args[1];
 
   std::string buffer;
-  if (!android::base::ReadFileToString(filename, &buffer)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->ReadFileToString(filename, &buffer)) {
     ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str());
     return nullptr;
   }
@@ -655,7 +618,8 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
                       argv.size());
   }
-  fprintf(static_cast<UpdaterInfo*>(state->cookie)->cmd_pipe, "wipe_cache\n");
+
+  state->updater->WriteToCommandPipe("wipe_cache");
   return StringValue("t");
 }
 
@@ -669,26 +633,8 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
 
-  auto exec_args = StringVectorToNullTerminatedArray(args);
-  LOG(INFO) << "about to run program [" << exec_args[0] << "] with " << argv.size() << " args";
-
-  pid_t child = fork();
-  if (child == 0) {
-    execv(exec_args[0], exec_args.data());
-    PLOG(ERROR) << "run_program: execv failed";
-    _exit(EXIT_FAILURE);
-  }
-
-  int status;
-  waitpid(child, &status, 0);
-  if (WIFEXITED(status)) {
-    if (WEXITSTATUS(status) != 0) {
-      LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
-    }
-  } else if (WIFSIGNALED(status)) {
-    LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
-  }
-
+  auto updater_runtime = state->updater->GetRuntime();
+  auto status = updater_runtime->RunProgram(args, false);
   return StringValue(std::to_string(status));
 }
 
@@ -706,7 +652,8 @@
   const std::string& filename = args[0];
 
   std::string contents;
-  if (android::base::ReadFileToString(filename, &contents)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (updater_runtime->ReadFileToString(filename, &contents)) {
     return new Value(Value::Type::STRING, std::move(contents));
   }
 
@@ -735,12 +682,12 @@
   }
 
   const std::string& value = args[0];
-  if (!android::base::WriteStringToFile(value, filename)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->WriteStringToFile(value, filename)) {
     PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\"";
     return StringValue("");
-  } else {
-    return StringValue("t");
   }
+  return StringValue("t");
 }
 
 // Immediately reboot the device.  Recovery is not finished normally,
@@ -778,7 +725,7 @@
     return StringValue("");
   }
 
-  reboot("reboot," + property);
+  Reboot(property);
 
   sleep(5);
   return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
@@ -866,16 +813,10 @@
   if (!android::base::ParseUint(len_str.c_str(), &len)) {
     return nullptr;
   }
-  android::base::unique_fd fd(open(filename.c_str(), O_WRONLY));
-  if (fd == -1) {
-    PLOG(ERROR) << "Failed to open " << filename;
-    return StringValue("");
-  }
 
-  // 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" : "");
+  auto updater_runtime = state->updater->GetRuntime();
+  int status = updater_runtime->WipeBlockDevice(filename, len);
+  return StringValue(status == 0 ? "t" : "");
 }
 
 Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -883,8 +824,7 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
                       argv.size());
   }
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-  fprintf(ui->cmd_pipe, "enable_reboot\n");
+  state->updater->WriteToCommandPipe("enable_reboot");
   return StringValue("t");
 }
 
@@ -900,10 +840,8 @@
 
   // tune2fs expects the program name as its first arg.
   args.insert(args.begin(), "tune2fs");
-  auto tune2fs_args = StringVectorToNullTerminatedArray(args);
-
-  // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
-  if (auto result = tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); result != 0) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (auto result = updater_runtime->Tune2Fs(args); result != 0) {
     return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
   }
   return StringValue("t");
diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp
new file mode 100644
index 0000000..c3b3d95
--- /dev/null
+++ b/updater/simulator_runtime.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/simulator_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+
+#include "otautil/mounts.h"
+#include "otautil/sysutil.h"
+
+std::string SimulatorRuntime::GetProperty(const std::string_view key,
+                                          const std::string_view default_value) const {
+  return source_->GetProperty(key, default_value);
+}
+
+int SimulatorRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+                            const std::string_view /* fs_type */,
+                            const std::string_view /* mount_options */) {
+  if (auto mounted_location = mounted_partitions_.find(mount_point);
+      mounted_location != mounted_partitions_.end() && mounted_location->second != location) {
+    LOG(ERROR) << mount_point << " has been mounted at " << mounted_location->second;
+    return -1;
+  }
+
+  mounted_partitions_.emplace(mount_point, location);
+  return 0;
+}
+
+bool SimulatorRuntime::IsMounted(const std::string_view mount_point) const {
+  return mounted_partitions_.find(mount_point) != mounted_partitions_.end();
+}
+
+std::pair<bool, int> SimulatorRuntime::Unmount(const std::string_view mount_point) {
+  if (!IsMounted(mount_point)) {
+    return { false, -1 };
+  }
+
+  mounted_partitions_.erase(std::string(mount_point));
+  return { true, 0 };
+}
+
+std::string SimulatorRuntime::FindBlockDeviceName(const std::string_view name) const {
+  return source_->FindBlockDeviceName(name);
+}
+
+// TODO(xunchang) implement the utility functions in simulator.
+int SimulatorRuntime::RunProgram(const std::vector<std::string>& args, bool /* is_vfork */) const {
+  LOG(INFO) << "Running program with args " << android::base::Join(args, " ");
+  return 0;
+}
+
+int SimulatorRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+  LOG(INFO) << "Running Tune2Fs with args " << android::base::Join(args, " ");
+  return 0;
+}
+
+int SimulatorRuntime::WipeBlockDevice(const std::string_view filename, size_t /* len */) const {
+  LOG(INFO) << "SKip wiping block device " << filename;
+  return 0;
+}
+
+bool SimulatorRuntime::ReadFileToString(const std::string_view filename,
+                                        std::string* /* content */) const {
+  LOG(INFO) << "SKip reading filename " << filename;
+  return true;
+}
+
+bool SimulatorRuntime::WriteStringToFile(const std::string_view content,
+                                         const std::string_view filename) const {
+  LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename;
+  return true;
+}
diff --git a/updater/target_files.cpp b/updater/target_files.cpp
new file mode 100644
index 0000000..53671dd
--- /dev/null
+++ b/updater/target_files.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/target_files.h"
+
+std::string TargetFiles::GetProperty(const std::string_view /*key*/,
+                                     const std::string_view default_value) const {
+  return std::string(default_value);
+}
+
+std::string TargetFiles::FindBlockDeviceName(const std::string_view name) const {
+  return std::string(name);
+}
diff --git a/updater/update_simulator_main.cpp b/updater/update_simulator_main.cpp
new file mode 100644
index 0000000..d10453c
--- /dev/null
+++ b/updater/update_simulator_main.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+#include "otautil/error_code.h"
+#include "otautil/paths.h"
+#include "updater/blockimg.h"
+#include "updater/install.h"
+#include "updater/simulator_runtime.h"
+#include "updater/target_files.h"
+#include "updater/updater.h"
+
+int main(int argc, char** argv) {
+  // Write the logs to stdout.
+  android::base::InitLogging(argv, &android::base::StderrLogger);
+
+  if (argc != 3 && argc != 4) {
+    LOG(ERROR) << "unexpected number of arguments: " << argc << std::endl
+               << "Usage: " << argv[0] << " <source_target-file> <ota_package>";
+    return 1;
+  }
+
+  // TODO(xunchang) implement a commandline parser, e.g. it can take an oem property so that the
+  // file_getprop() will return correct value.
+
+  std::string source_target_file = argv[1];
+  std::string package_name = argv[2];
+
+  // Configure edify's functions.
+  RegisterBuiltins();
+  RegisterInstallFunctions();
+  RegisterBlockImageFunctions();
+
+  TemporaryFile temp_saved_source;
+  TemporaryFile temp_last_command;
+  TemporaryDir temp_stash_base;
+
+  Paths::Get().set_cache_temp_source(temp_saved_source.path);
+  Paths::Get().set_last_command_file(temp_last_command.path);
+  Paths::Get().set_stash_directory_base(temp_stash_base.path);
+
+  TemporaryFile cmd_pipe;
+
+  TemporaryDir source_temp_dir;
+  TargetFiles source(source_target_file, source_temp_dir.path);
+
+  Updater updater(std::make_unique<SimulatorRuntime>(&source));
+  if (!updater.Init(cmd_pipe.release(), package_name, false)) {
+    return 1;
+  }
+
+  if (!updater.RunUpdate()) {
+    return 1;
+  }
+
+  LOG(INFO) << "\nscript succeeded, result: " << updater.GetResult();
+
+  return 0;
+}
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 7b5a3f9..8f4a6ed 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -16,8 +16,6 @@
 
 #include "updater/updater.h"
 
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -25,198 +23,162 @@
 
 #include <android-base/logging.h>
 #include <android-base/strings.h>
-#include <selinux/android.h>
-#include <selinux/label.h>
-#include <selinux/selinux.h>
-#include <ziparchive/zip_archive.h>
 
-#include "edify/expr.h"
-#include "otautil/dirutil.h"
-#include "otautil/error_code.h"
-#include "otautil/sysutil.h"
-#include "updater/blockimg.h"
-#include "updater/dynamic_partitions.h"
-#include "updater/install.h"
+#include "edify/updater_runtime_interface.h"
 
-// Generated by the makefile, this function defines the
-// RegisterDeviceExtensions() function, which calls all the
-// registration functions for device-specific extensions.
-#include "register.inc"
-
-// Where in the package we expect to find the edify script to execute.
-// (Note it's "updateR-script", not the older "update-script".)
-static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
-
-struct selabel_handle *sehandle;
-
-static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
-                          const char* /* tag */, const char* /* file */, unsigned int /* line */,
-                          const char* message) {
-  fprintf(stdout, "%s\n", message);
+Updater::~Updater() {
+  if (package_handle_) {
+    CloseArchive(package_handle_);
+  }
 }
 
-int main(int argc, char** argv) {
-  // Various things log information to stdout or stderr more or less
-  // at random (though we've tried to standardize on stdout).  The
-  // log file makes more sense if buffering is turned off so things
-  // appear in the right order.
-  setbuf(stdout, nullptr);
-  setbuf(stderr, nullptr);
-
-  // We don't have logcat yet under recovery. Update logs will always be written to stdout
-  // (which is redirected to recovery.log).
-  android::base::InitLogging(argv, &UpdaterLogger);
-
-  if (argc != 4 && argc != 5) {
-    LOG(ERROR) << "unexpected number of arguments: " << argc;
-    return 1;
-  }
-
-  char* version = argv[1];
-  if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
-    // We support version 1, 2, or 3.
-    LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
-    return 2;
-  }
-
+bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) {
   // Set up the pipe for sending commands back to the parent process.
-
-  int fd = atoi(argv[2]);
-  FILE* cmd_pipe = fdopen(fd, "wb");
-  setlinebuf(cmd_pipe);
-
-  // Extract the script from the package.
-
-  const char* package_filename = argv[3];
-  MemMapping map;
-  if (!map.MapFile(package_filename)) {
-    LOG(ERROR) << "failed to map package " << argv[3];
-    return 3;
-  }
-  ZipArchiveHandle za;
-  int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
-  if (open_err != 0) {
-    LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
-    CloseArchive(za);
-    return 3;
+  cmd_pipe_.reset(fdopen(fd, "wb"));
+  if (!cmd_pipe_) {
+    LOG(ERROR) << "Failed to open the command pipe";
+    return false;
   }
 
-  ZipString script_name(SCRIPT_NAME);
-  ZipEntry script_entry;
-  int find_err = FindEntry(za, script_name, &script_entry);
-  if (find_err != 0) {
-    LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
-               << ErrorCodeString(find_err);
-    CloseArchive(za);
-    return 4;
+  setlinebuf(cmd_pipe_.get());
+
+  if (!mapped_package_.MapFile(std::string(package_filename))) {
+    LOG(ERROR) << "failed to map package " << package_filename;
+    return false;
+  }
+  if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length,
+                                           std::string(package_filename).c_str(), &package_handle_);
+      open_err != 0) {
+    LOG(ERROR) << "failed to open package " << package_filename << ": "
+               << ErrorCodeString(open_err);
+    return false;
+  }
+  if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) {
+    return false;
   }
 
-  std::string script;
-  script.resize(script_entry.uncompressed_length);
-  int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
-                                    script_entry.uncompressed_length);
-  if (extract_err != 0) {
-    LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
-    CloseArchive(za);
-    return 5;
-  }
+  is_retry_ = is_retry;
 
-  // Configure edify's functions.
+  return true;
+}
 
-  RegisterBuiltins();
-  RegisterInstallFunctions();
-  RegisterBlockImageFunctions();
-  RegisterDynamicPartitionsFunctions();
-  RegisterDeviceExtensions();
+bool Updater::RunUpdate() {
+  CHECK(runtime_);
 
   // Parse the script.
-
   std::unique_ptr<Expr> root;
   int error_count = 0;
-  int error = ParseString(script, &root, &error_count);
+  int error = ParseString(updater_script_, &root, &error_count);
   if (error != 0 || error_count > 0) {
     LOG(ERROR) << error_count << " parse errors";
-    CloseArchive(za);
-    return 6;
-  }
-
-  sehandle = selinux_android_file_context_handle();
-  selinux_android_set_sehandle(sehandle);
-
-  if (!sehandle) {
-    fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
+    return false;
   }
 
   // Evaluate the parsed script.
+  State state(updater_script_, this);
+  state.is_retry = is_retry_;
 
-  UpdaterInfo updater_info;
-  updater_info.cmd_pipe = cmd_pipe;
-  updater_info.package_zip = za;
-  updater_info.version = atoi(version);
-  updater_info.package_zip_addr = map.addr;
-  updater_info.package_zip_len = map.length;
+  bool status = Evaluate(&state, root, &result_);
+  if (status) {
+    fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str());
+    // Even though the script doesn't abort, still log the cause code if result is empty.
+    if (result_.empty() && state.cause_code != kNoCause) {
+      fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code);
+    }
+    for (const auto& func : skipped_functions_) {
+      LOG(WARNING) << "Skipped executing function " << func;
+    }
+    return true;
+  }
 
-  State state(script, &updater_info);
+  ParseAndReportErrorCode(&state);
+  return false;
+}
 
-  if (argc == 5) {
-    if (strcmp(argv[4], "retry") == 0) {
-      state.is_retry = true;
-    } else {
-      printf("unexpected argument: %s", argv[4]);
+void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const {
+  fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str());
+  if (flush) {
+    fflush(cmd_pipe_.get());
+  }
+}
+
+void Updater::UiPrint(const std::string_view message) const {
+  // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
+  // so skip sending empty strings to ui.
+  std::vector<std::string> lines = android::base::Split(std::string(message), "\n");
+  for (const auto& line : lines) {
+    if (!line.empty()) {
+      fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
     }
   }
 
-  std::string result;
-  bool status = Evaluate(&state, root, &result);
+  // on the updater side, we need to dump the contents to stderr (which has
+  // been redirected to the log file). because the recovery will only print
+  // the contents to screen when processing pipe command ui_print.
+  LOG(INFO) << message;
+}
 
-  if (!status) {
-    if (state.errmsg.empty()) {
-      LOG(ERROR) << "script aborted (no error message)";
-      fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
-    } else {
-      LOG(ERROR) << "script aborted: " << state.errmsg;
-      const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
-      for (const std::string& line : lines) {
-        // Parse the error code in abort message.
-        // Example: "E30: This package is for bullhead devices."
-        if (!line.empty() && line[0] == 'E') {
-          if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
-            LOG(ERROR) << "Failed to parse error code: [" << line << "]";
-          }
-        }
-        fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
-      }
-    }
+std::string Updater::FindBlockDeviceName(const std::string_view name) const {
+  return runtime_->FindBlockDeviceName(name);
+}
 
-    // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
-    // a more specific code has been set in errmsg.
-    if (state.error_code == kNoError) {
-      state.error_code = kScriptExecutionFailure;
-    }
-    fprintf(cmd_pipe, "log error: %d\n", state.error_code);
-    // Cause code should provide additional information about the abort.
-    if (state.cause_code != kNoCause) {
-      fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
-      if (state.cause_code == kPatchApplicationFailure) {
-        LOG(INFO) << "Patch application failed, retry update.";
-        fprintf(cmd_pipe, "retry_update\n");
-      } else if (state.cause_code == kEioFailure) {
-        LOG(INFO) << "Update failed due to EIO, retry update.";
-        fprintf(cmd_pipe, "retry_update\n");
-      }
-    }
-
-    if (updater_info.package_zip) {
-      CloseArchive(updater_info.package_zip);
-    }
-    return 7;
+void Updater::ParseAndReportErrorCode(State* state) {
+  CHECK(state);
+  if (state->errmsg.empty()) {
+    LOG(ERROR) << "script aborted (no error message)";
+    fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n");
   } else {
-    fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
+    LOG(ERROR) << "script aborted: " << state->errmsg;
+    const std::vector<std::string> lines = android::base::Split(state->errmsg, "\n");
+    for (const std::string& line : lines) {
+      // Parse the error code in abort message.
+      // Example: "E30: This package is for bullhead devices."
+      if (!line.empty() && line[0] == 'E') {
+        if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) {
+          LOG(ERROR) << "Failed to parse error code: [" << line << "]";
+        }
+      }
+      fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
+    }
   }
 
-  if (updater_info.package_zip) {
-    CloseArchive(updater_info.package_zip);
+  // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
+  // a more specific code has been set in errmsg.
+  if (state->error_code == kNoError) {
+    state->error_code = kScriptExecutionFailure;
+  }
+  fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code);
+  // Cause code should provide additional information about the abort.
+  if (state->cause_code != kNoCause) {
+    fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code);
+    if (state->cause_code == kPatchApplicationFailure) {
+      LOG(INFO) << "Patch application failed, retry update.";
+      fprintf(cmd_pipe_.get(), "retry_update\n");
+    } else if (state->cause_code == kEioFailure) {
+      LOG(INFO) << "Update failed due to EIO, retry update.";
+      fprintf(cmd_pipe_.get(), "retry_update\n");
+    }
+  }
+}
+
+bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name,
+                                std::string* content) {
+  ZipEntry entry;
+  int find_err = FindEntry(za, entry_name, &entry);
+  if (find_err != 0) {
+    LOG(ERROR) << "failed to find " << entry_name
+               << " in the package: " << ErrorCodeString(find_err);
+    return false;
   }
 
-  return 0;
+  content->resize(entry.uncompressed_length);
+  int extract_err = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&content->at(0)),
+                                    entry.uncompressed_length);
+  if (extract_err != 0) {
+    LOG(ERROR) << "failed to read " << entry_name
+               << " from package: " << ErrorCodeString(extract_err);
+    return false;
+  }
+
+  return true;
 }
diff --git a/updater/updater_main.cpp b/updater/updater_main.cpp
new file mode 100644
index 0000000..055a8ac
--- /dev/null
+++ b/updater/updater_main.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <selinux/android.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+
+#include "edify/expr.h"
+#include "updater/blockimg.h"
+#include "updater/dynamic_partitions.h"
+#include "updater/install.h"
+#include "updater/updater.h"
+#include "updater/updater_runtime.h"
+
+// Generated by the makefile, this function defines the
+// RegisterDeviceExtensions() function, which calls all the
+// registration functions for device-specific extensions.
+#include "register.inc"
+
+static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
+                          const char* /* tag */, const char* /* file */, unsigned int /* line */,
+                          const char* message) {
+  fprintf(stdout, "%s\n", message);
+}
+
+int main(int argc, char** argv) {
+  // Various things log information to stdout or stderr more or less
+  // at random (though we've tried to standardize on stdout).  The
+  // log file makes more sense if buffering is turned off so things
+  // appear in the right order.
+  setbuf(stdout, nullptr);
+  setbuf(stderr, nullptr);
+
+  // We don't have logcat yet under recovery. Update logs will always be written to stdout
+  // (which is redirected to recovery.log).
+  android::base::InitLogging(argv, &UpdaterLogger);
+
+  if (argc != 4 && argc != 5) {
+    LOG(ERROR) << "unexpected number of arguments: " << argc;
+    return 1;
+  }
+
+  char* version = argv[1];
+  if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
+    // We support version 1, 2, or 3.
+    LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
+    return 1;
+  }
+
+  int fd;
+  if (!android::base::ParseInt(argv[2], &fd)) {
+    LOG(ERROR) << "Failed to parse fd in " << argv[2];
+    return 1;
+  }
+
+  std::string package_name = argv[3];
+
+  bool is_retry = false;
+  if (argc == 5) {
+    if (strcmp(argv[4], "retry") == 0) {
+      is_retry = true;
+    } else {
+      LOG(ERROR) << "unexpected argument: " << argv[4];
+      return 1;
+    }
+  }
+
+  // Configure edify's functions.
+  RegisterBuiltins();
+  RegisterInstallFunctions();
+  RegisterBlockImageFunctions();
+  RegisterDynamicPartitionsFunctions();
+  RegisterDeviceExtensions();
+
+  auto sehandle = selinux_android_file_context_handle();
+  selinux_android_set_sehandle(sehandle);
+
+  Updater updater(std::make_unique<UpdaterRuntime>(sehandle));
+  if (!updater.Init(fd, package_name, is_retry)) {
+    return 1;
+  }
+
+  if (!updater.RunUpdate()) {
+    return 1;
+  }
+
+  return 0;
+}
\ No newline at end of file
diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp
new file mode 100644
index 0000000..761f999
--- /dev/null
+++ b/updater/updater_runtime.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/updater_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+#include <tune2fs.h>
+
+#include "otautil/mounts.h"
+#include "otautil/sysutil.h"
+
+std::string UpdaterRuntime::GetProperty(const std::string_view key,
+                                        const std::string_view default_value) const {
+  return android::base::GetProperty(std::string(key), std::string(default_value));
+}
+
+std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const {
+  return std::string(name);
+}
+
+int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+                          const std::string_view fs_type, const std::string_view mount_options) {
+  std::string mount_point_string(mount_point);
+  char* secontext = nullptr;
+  if (sehandle_) {
+    selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755);
+    setfscreatecon(secontext);
+  }
+
+  mkdir(mount_point_string.c_str(), 0755);
+
+  if (secontext) {
+    freecon(secontext);
+    setfscreatecon(nullptr);
+  }
+
+  return mount(std::string(location).c_str(), mount_point_string.c_str(),
+               std::string(fs_type).c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME,
+               std::string(mount_options).c_str());
+}
+
+bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const {
+  scan_mounted_volumes();
+  MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+  return vol != nullptr;
+}
+
+std::pair<bool, int> UpdaterRuntime::Unmount(const std::string_view mount_point) {
+  scan_mounted_volumes();
+  MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+  if (vol == nullptr) {
+    return { false, -1 };
+  }
+
+  int ret = unmount_mounted_volume(vol);
+  return { true, ret };
+}
+
+bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const {
+  return android::base::ReadFileToString(std::string(filename), content);
+}
+
+bool UpdaterRuntime::WriteStringToFile(const std::string_view content,
+                                       const std::string_view filename) const {
+  return android::base::WriteStringToFile(std::string(content), std::string(filename));
+}
+
+int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const {
+  android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << filename;
+    return false;
+  }
+  // The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure.
+  return wipe_block_device(fd, len);
+}
+
+int UpdaterRuntime::RunProgram(const std::vector<std::string>& args, bool is_vfork) const {
+  CHECK(!args.empty());
+  auto argv = StringVectorToNullTerminatedArray(args);
+  LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args";
+
+  pid_t child = is_vfork ? vfork() : fork();
+  if (child == 0) {
+    execv(argv[0], argv.data());
+    PLOG(ERROR) << "run_program: execv failed";
+    _exit(EXIT_FAILURE);
+  }
+
+  int status;
+  waitpid(child, &status, 0);
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != 0) {
+      LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
+    }
+  } else if (WIFSIGNALED(status)) {
+    LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
+  }
+
+  return status;
+}
+
+int UpdaterRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+  auto tune2fs_args = StringVectorToNullTerminatedArray(args);
+  // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
+  return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data());
+}