Revert "Fix deadlock caused by two-threaded property controls" am: 3196be61fc am: abc6e1240d am: 6d5e9dfda4

Original change: https://googleplex-android-review.googlesource.com/c/platform/system/core/+/24028968

Change-Id: I82b0e9b836f8dc5b0528082794a998aee8835f59
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 844357c..3b8866e 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -459,6 +459,8 @@
     {"reboot,sys_ldo_ok,pmic,main", 227},
     {"reboot,sys_ldo_ok,pmic,sub", 228},
     {"reboot,smpl_timeout,pmic,main", 229},
+    {"reboot,ota,.*", 230},
+    {"reboot,periodic,.*", 231},
 };
 
 // Converts a string value representing the reason the system booted to an
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index c9e097e..bd1e91d 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -276,6 +276,13 @@
       return false;
     }
 
+    // WARNING: It's not possible to replace the below with a splice call.
+    // Due to the way debuggerd does many small writes across the pipe,
+    // this would cause splice to copy a page for each write. The second
+    // pipe fills up based on the number of pages being copied, even
+    // though there is not much data being transferred per page. When
+    // the second pipe is full, everything stops since there is nothing
+    // reading the second pipe to clear it.
     char buf[1024];
     rc = TEMP_FAILURE_RETRY(read(pipe_read.get(), buf, sizeof(buf)));
     if (rc == 0) {
diff --git a/debuggerd/client/debuggerd_client_test.cpp b/debuggerd/client/debuggerd_client_test.cpp
index ebb8d86..33ff05f 100644
--- a/debuggerd/client/debuggerd_client_test.cpp
+++ b/debuggerd/client/debuggerd_client_test.cpp
@@ -18,6 +18,7 @@
 
 #include <fcntl.h>
 #include <stdio.h>
+#include <sys/eventfd.h>
 #include <unistd.h>
 
 #include <chrono>
@@ -51,23 +52,35 @@
 
 TEST(debuggerd_client, race) {
   static int THREAD_COUNT = getThreadCount();
+
+  // Semaphore incremented once per thread started.
+  unique_fd barrier(eventfd(0, EFD_SEMAPHORE));
+  ASSERT_NE(-1, barrier.get());
+
   pid_t forkpid = fork();
-
   ASSERT_NE(-1, forkpid);
-
   if (forkpid == 0) {
     // Spawn a bunch of threads, to make crash_dump take longer.
     std::vector<std::thread> threads;
+    threads.reserve(THREAD_COUNT);
     for (int i = 0; i < THREAD_COUNT; ++i) {
-      threads.emplace_back([]() {
-        while (true) {
-          std::this_thread::sleep_for(60s);
+      threads.emplace_back([&barrier]() {
+        uint64_t count = 1;
+        ASSERT_NE(-1, write(barrier.get(), &count, sizeof(count)));
+        for (;;) {
+          pause();
         }
       });
     }
+    for (;;) {
+      pause();
+    }
+  }
 
-    std::this_thread::sleep_for(60s);
-    exit(0);
+  // Wait for the child to spawn all of its threads.
+  for (int i = 0; i < THREAD_COUNT; ++i) {
+    uint64_t count;
+    ASSERT_NE(-1, read(barrier.get(), &count, sizeof(count)));
   }
 
   unique_fd pipe_read, pipe_write;
@@ -77,9 +90,6 @@
   constexpr int PIPE_SIZE = 16 * 1024 * 1024;
   ASSERT_EQ(PIPE_SIZE, fcntl(pipe_read.get(), F_SETPIPE_SZ, PIPE_SIZE));
 
-  // Wait for a bit to let the child spawn all of its threads.
-  std::this_thread::sleep_for(1s);
-
   ASSERT_TRUE(
       debuggerd_trigger_dump(forkpid, kDebuggerdNativeBacktrace, 60000, std::move(pipe_write)));
   // Immediately kill the forked child, to make sure that the dump didn't return early.
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 6a19878..12ba502 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -148,7 +148,7 @@
 noinline void leak() {
     while (true) {
         void* mapping =
-            mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+            mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
         static_cast<volatile char*>(mapping)[0] = 'a';
     }
 }
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index e20e8d9..26726cf 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -41,22 +41,6 @@
   _exit(exit_code);
 }
 
-static std::thread spawn_redirect_thread(unique_fd fd) {
-  return std::thread([fd{ std::move(fd) }]() {
-    while (true) {
-      char buf[BUFSIZ];
-      ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), buf, sizeof(buf)));
-      if (rc <= 0) {
-        return;
-      }
-
-      if (!android::base::WriteFully(STDOUT_FILENO, buf, rc)) {
-        return;
-      }
-    }
-  });
-}
-
 int main(int argc, char* argv[]) {
   if (argc <= 1) usage(0);
   if (argc > 3) usage(1);
@@ -107,14 +91,11 @@
     }
   }
 
-  unique_fd piperead, pipewrite;
-  if (!Pipe(&piperead, &pipewrite)) {
-    err(1, "failed to create pipe");
+  unique_fd output_fd(fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0));
+  if (output_fd.get() == -1) {
+    err(1, "failed to fcntl dup stdout");
   }
-
-  std::thread redirect_thread = spawn_redirect_thread(std::move(piperead));
-  if (!debuggerd_trigger_dump(proc_info.pid, dump_type, 0, std::move(pipewrite))) {
-    redirect_thread.join();
+  if (!debuggerd_trigger_dump(proc_info.pid, dump_type, 0, std::move(output_fd))) {
     if (pid == proc_info.pid) {
       errx(1, "failed to dump process %d", pid);
     } else {
@@ -122,6 +103,5 @@
     }
   }
 
-  redirect_thread.join();
   return 0;
 }
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 4cd6193..52c1c25 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -300,24 +300,7 @@
 }
 
 static void ConsumeFd(unique_fd fd, std::string* output) {
-  constexpr size_t read_length = PAGE_SIZE;
-  std::string result;
-
-  while (true) {
-    size_t offset = result.size();
-    result.resize(result.size() + PAGE_SIZE);
-    ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &result[offset], read_length));
-    if (rc == -1) {
-      FAIL() << "read failed: " << strerror(errno);
-    } else if (rc == 0) {
-      result.resize(result.size() - PAGE_SIZE);
-      break;
-    }
-
-    result.resize(result.size() - PAGE_SIZE + rc);
-  }
-
-  *output = std::move(result);
+  ASSERT_TRUE(android::base::ReadFdToString(fd, output));
 }
 
 class LogcatCollector {
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index c6a535a..1e5365d 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -721,19 +721,19 @@
   }
 
   size_t thread_stack_pages = 8;
-  void* thread_stack_allocation = mmap(nullptr, PAGE_SIZE * (thread_stack_pages + 2), PROT_NONE,
+  void* thread_stack_allocation = mmap(nullptr, getpagesize() * (thread_stack_pages + 2), PROT_NONE,
                                        MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
   if (thread_stack_allocation == MAP_FAILED) {
     fatal_errno("failed to allocate debuggerd thread stack");
   }
 
-  char* stack = static_cast<char*>(thread_stack_allocation) + PAGE_SIZE;
-  if (mprotect(stack, PAGE_SIZE * thread_stack_pages, PROT_READ | PROT_WRITE) != 0) {
+  char* stack = static_cast<char*>(thread_stack_allocation) + getpagesize();
+  if (mprotect(stack, getpagesize() * thread_stack_pages, PROT_READ | PROT_WRITE) != 0) {
     fatal_errno("failed to mprotect debuggerd thread stack");
   }
 
   // Stack grows negatively, set it to the last byte in the page...
-  stack = (stack + thread_stack_pages * PAGE_SIZE - 1);
+  stack = (stack + thread_stack_pages * getpagesize() - 1);
   // and align it.
   stack -= 15;
   pseudothread_stack = stack;
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index 5a62fe1..837f406 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -22,6 +22,7 @@
 
 #include <android-base/macros.h>
 #include <bionic/macros.h>
+#include <unistd.h>
 
 #include "tombstone.pb.h"
 
@@ -54,21 +55,21 @@
   }
 
   untagged_fault_addr_ = process_info.untagged_fault_address;
-  uintptr_t fault_page = untagged_fault_addr_ & ~(PAGE_SIZE - 1);
+  uintptr_t fault_page = untagged_fault_addr_ & ~(getpagesize() - 1);
 
-  uintptr_t memory_begin = fault_page - PAGE_SIZE * 16;
+  uintptr_t memory_begin = fault_page - getpagesize() * 16;
   if (memory_begin > fault_page) {
     return;
   }
 
-  uintptr_t memory_end = fault_page + PAGE_SIZE * 16;
+  uintptr_t memory_end = fault_page + getpagesize() * 16;
   if (memory_end < fault_page) {
     return;
   }
 
   auto memory = std::make_unique<char[]>(memory_end - memory_begin);
-  for (auto i = memory_begin; i != memory_end; i += PAGE_SIZE) {
-    process_memory->ReadFully(i, memory.get() + i - memory_begin, PAGE_SIZE);
+  for (auto i = memory_begin; i != memory_end; i += getpagesize()) {
+    process_memory->ReadFully(i, memory.get() + i - memory_begin, getpagesize());
   }
 
   auto memory_tags = std::make_unique<char[]>((memory_end - memory_begin) / kTagGranuleSize);
diff --git a/debuggerd/rust/tombstoned_client/src/lib.rs b/debuggerd/rust/tombstoned_client/src/lib.rs
index 5c8abef..d1b5e69 100644
--- a/debuggerd/rust/tombstoned_client/src/lib.rs
+++ b/debuggerd/rust/tombstoned_client/src/lib.rs
@@ -39,20 +39,26 @@
 }
 
 impl TombstonedConnection {
+    /// # Safety
+    ///
+    /// The file descriptors must be valid and open.
     unsafe fn from_raw_fds(
         tombstoned_socket: RawFd,
         text_output_fd: RawFd,
         proto_output_fd: RawFd,
     ) -> Self {
         Self {
-            tombstoned_socket: File::from_raw_fd(tombstoned_socket),
+            // SAFETY: The caller guarantees that the file descriptor is valid and open.
+            tombstoned_socket: unsafe { File::from_raw_fd(tombstoned_socket) },
             text_output: if text_output_fd >= 0 {
-                Some(File::from_raw_fd(text_output_fd))
+                // SAFETY: The caller guarantees that the file descriptor is valid and open.
+                Some(unsafe { File::from_raw_fd(text_output_fd) })
             } else {
                 None
             },
             proto_output: if proto_output_fd >= 0 {
-                Some(File::from_raw_fd(proto_output_fd))
+                // SAFETY: The caller guarantees that the file descriptor is valid and open.
+                Some(unsafe { File::from_raw_fd(proto_output_fd) })
             } else {
                 None
             },
@@ -71,6 +77,8 @@
             &mut proto_output_fd,
             dump_type,
         ) {
+            // SAFETY: If tombstoned_connect_files returns successfully then they file descriptors
+            // are valid and open.
             Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) })
         } else {
             Err(Error)
@@ -146,8 +154,6 @@
             .write_all(b"test data")
             .expect("Failed to write to text output FD.");
 
-        connection
-            .notify_completion()
-            .expect("Failed to notify completion.");
+        connection.notify_completion().expect("Failed to notify completion.");
     }
 }
diff --git a/debuggerd/seccomp_policy/crash_dump.riscv64.policy b/debuggerd/seccomp_policy/crash_dump.riscv64.policy
index 21887ab..281e231 100644
--- a/debuggerd/seccomp_policy/crash_dump.riscv64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.riscv64.policy
@@ -19,12 +19,13 @@
 faccessat: 1
 recvmsg: 1
 recvfrom: 1
+sysinfo: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
 rt_sigaction: 1
 rt_tgsigqueueinfo: 1
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41
 madvise: 1
 mprotect: arg2 in 0x1|0x2
 munmap: 1
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 7794c4b..56cac88 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -196,6 +196,7 @@
         "libfastbootshim",
         "libsnapshot_cow",
         "liblz4",
+        "libzstd",
         "libsnapshot_nobinder",
         "update_metadata-protos",
         "liburing",
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index cdcd036..61c7204 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -76,6 +76,7 @@
 #include "constants.h"
 #include "diagnose_usb.h"
 #include "fastboot_driver.h"
+#include "fastboot_driver_interface.h"
 #include "fs.h"
 #include "storage.h"
 #include "super_flash_helper.h"
@@ -95,6 +96,8 @@
 using namespace std::string_literals;
 using namespace std::placeholders;
 
+#define FASTBOOT_INFO_VERSION 1
+
 static const char* serial = nullptr;
 
 static bool g_long_listing = false;
@@ -102,7 +105,6 @@
 // libsparse will support INT_MAX, but this results in large allocations, so
 // let's keep it at 1GB to avoid memory pressure on the host.
 static constexpr int64_t RESPARSE_LIMIT = 1 * 1024 * 1024 * 1024;
-static uint64_t sparse_limit = 0;
 static int64_t target_sparse_limit = -1;
 
 static unsigned g_base_addr = 0x10000000;
@@ -172,7 +174,7 @@
         // clang-format on
 };
 
-static char* get_android_product_out() {
+char* get_android_product_out() {
     char* dir = getenv("ANDROID_PRODUCT_OUT");
     if (dir == nullptr || dir[0] == '\0') {
         return nullptr;
@@ -394,7 +396,7 @@
 
     ConnectedDevicesStorage storage;
     std::set<std::string> devices;
-    {
+    if (storage.Exists()) {
         FileLock lock = storage.Lock();
         devices = storage.ReadDevices(lock);
     }
@@ -773,7 +775,7 @@
 
 #endif
 
-static unique_fd unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
+static unique_fd UnzipToFile(ZipArchiveHandle zip, const char* entry_name) {
     unique_fd fd(make_temporary_fd(entry_name));
 
     ZipEntry64 zip_entry;
@@ -995,7 +997,7 @@
     return resparse_file(s.get(), max_size);
 }
 
-static uint64_t get_uint_var(const char* var_name) {
+static uint64_t get_uint_var(const char* var_name, fastboot::IFastBootDriver* fb) {
     std::string value_str;
     if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) {
         verbose("target didn't report %s", var_name);
@@ -1014,13 +1016,13 @@
     return value;
 }
 
-int64_t get_sparse_limit(int64_t size) {
-    int64_t limit = sparse_limit;
+int64_t get_sparse_limit(int64_t size, const FlashingPlan* fp) {
+    int64_t limit = int64_t(fp->sparse_limit);
     if (limit == 0) {
         // Unlimited, so see what the target device's limit is.
         // TODO: shouldn't we apply this limit even if you've used -S?
         if (target_sparse_limit == -1) {
-            target_sparse_limit = static_cast<int64_t>(get_uint_var("max-download-size"));
+            target_sparse_limit = static_cast<int64_t>(get_uint_var("max-download-size", fp->fb));
         }
         if (target_sparse_limit > 0) {
             limit = target_sparse_limit;
@@ -1036,7 +1038,7 @@
     return 0;
 }
 
-static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf) {
+static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf, const FlashingPlan* fp) {
     int64_t sz = get_file_size(fd);
     if (sz == -1) {
         return false;
@@ -1054,7 +1056,7 @@
     }
 
     lseek(fd.get(), 0, SEEK_SET);
-    int64_t limit = get_sparse_limit(sz);
+    int64_t limit = get_sparse_limit(sz, fp);
     buf->fd = std::move(fd);
     if (limit) {
         buf->files = load_sparse_files(buf->fd.get(), limit);
@@ -1070,13 +1072,11 @@
     return true;
 }
 
-static bool load_buf(const char* fname, struct fastboot_buffer* buf) {
+static bool load_buf(const char* fname, struct fastboot_buffer* buf, const FlashingPlan* fp) {
     unique_fd fd(TEMP_FAILURE_RETRY(open(fname, O_RDONLY | O_BINARY)));
 
     if (fd == -1) {
-        auto path = find_item_given_name(fname);
-        fd = unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
-        if (fd == -1) return false;
+        return false;
     }
 
     struct stat s;
@@ -1088,7 +1088,7 @@
         return false;
     }
 
-    return load_buf_fd(std::move(fd), buf);
+    return load_buf_fd(std::move(fd), buf, fp);
 }
 
 static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) {
@@ -1411,7 +1411,7 @@
     }
 }
 
-bool is_retrofit_device() {
+bool is_retrofit_device(fastboot::IFastBootDriver* fb) {
     std::string value;
     if (fb->GetVar("super-partition-name", &value) != fastboot::SUCCESS) {
         return false;
@@ -1421,8 +1421,9 @@
 
 // Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch
 // the full image.
-static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd) {
-    uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE);
+static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd,
+                                fastboot::IFastBootDriver* fb) {
+    uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE, fb);
     if (fetch_size == 0) {
         die("Unable to get %s. Device does not support fetch command.", FB_VAR_MAX_FETCH_SIZE);
     }
@@ -1444,17 +1445,18 @@
 }
 
 static void do_fetch(const std::string& partition, const std::string& slot_override,
-                     const std::string& outfile) {
+                     const std::string& outfile, fastboot::IFastBootDriver* fb) {
     unique_fd fd(TEMP_FAILURE_RETRY(
             open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0644)));
-    auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd));
+    auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd), fb);
     do_for_partitions(partition, slot_override, fetch, false /* force slot */);
 }
 
 // Return immediately if not flashing a vendor boot image. If flashing a vendor boot image,
 // repack vendor_boot image with an updated ramdisk. After execution, buf is set
 // to the new image to flash, and return value is the real partition name to flash.
-static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf) {
+static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf,
+                                  fastboot::IFastBootDriver* fb) {
     std::string_view pname_sv{pname};
 
     if (!android::base::StartsWith(pname_sv, "vendor_boot:") &&
@@ -1472,7 +1474,7 @@
     std::string ramdisk(pname_sv.substr(pname_sv.find(':') + 1));
 
     unique_fd vendor_boot(make_temporary_fd("vendor boot repack"));
-    uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot);
+    uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot, fb);
     auto repack_res = replace_vendor_ramdisk(vendor_boot, vendor_boot_size, ramdisk, buf->fd,
                                              static_cast<uint64_t>(buf->sz));
     if (!repack_res.ok()) {
@@ -1485,17 +1487,34 @@
     return partition;
 }
 
-void do_flash(const char* pname, const char* fname, const bool apply_vbmeta) {
+void do_flash(const char* pname, const char* fname, const bool apply_vbmeta,
+              const FlashingPlan* fp) {
+    if (!fp) {
+        die("do flash was called without a valid flashing plan");
+    }
     verbose("Do flash %s %s", pname, fname);
     struct fastboot_buffer buf;
 
-    if (!load_buf(fname, &buf)) {
+    if (fp->source) {
+        unique_fd fd = fp->source->OpenFile(fname);
+        if (fd < 0 || !load_buf_fd(std::move(fd), &buf, fp)) {
+            die("could not load '%s': %s", fname, strerror(errno));
+        }
+        std::vector<char> signature_data;
+        std::string file_string(fname);
+        if (fp->source->ReadFile(file_string.substr(0, file_string.find('.')) + ".sig",
+                                 &signature_data)) {
+            fb->Download("signature", signature_data);
+            fb->RawCommand("signature", "installing signature");
+        }
+    } else if (!load_buf(fname, &buf, fp)) {
         die("cannot load '%s': %s", fname, strerror(errno));
     }
+
     if (is_logical(pname)) {
         fb->ResizePartition(pname, std::to_string(buf.image_size));
     }
-    std::string flash_pname = repack_ramdisk(pname, &buf);
+    std::string flash_pname = repack_ramdisk(pname, &buf, fp->fb);
     flash_buf(flash_pname, &buf, apply_vbmeta);
 }
 
@@ -1590,7 +1609,7 @@
     if (img_name.empty()) {
         img_name = partition + ".img";
     }
-    return std::make_unique<FlashTask>(slot, partition, img_name, apply_vbmeta);
+    return std::make_unique<FlashTask>(slot, partition, img_name, apply_vbmeta, fp);
 }
 
 std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
@@ -1637,14 +1656,22 @@
     return task;
 }
 
-void AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>* tasks) {
+bool AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>* tasks) {
     // expands "resize-partitions" into individual commands : resize {os_partition_1}, resize
     // {os_partition_2}, etc.
     std::vector<std::unique_ptr<Task>> resize_tasks;
     std::optional<size_t> loc;
+    std::vector<char> contents;
+    if (!fp->source->ReadFile("super_empty.img", &contents)) {
+        return false;
+    }
+    auto metadata = android::fs_mgr::ReadFromImageBlob(contents.data(), contents.size());
+    if (!metadata) {
+        return false;
+    }
     for (size_t i = 0; i < tasks->size(); i++) {
         if (auto flash_task = tasks->at(i)->AsFlashTask()) {
-            if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
+            if (should_flash_in_userspace(*metadata.get(), flash_task->GetPartitionAndSlot())) {
                 if (!loc) {
                     loc = i;
                 }
@@ -1656,35 +1683,22 @@
     // if no logical partitions (although should never happen since system will always need to be
     // flashed)
     if (!loc) {
-        return;
+        return false;
     }
     tasks->insert(tasks->begin() + loc.value(), std::make_move_iterator(resize_tasks.begin()),
                   std::make_move_iterator(resize_tasks.end()));
-    return;
-}
-
-static bool IsNumber(const std::string& s) {
-    bool period = false;
-    for (size_t i = 0; i < s.length(); i++) {
-        if (!isdigit(s[i])) {
-            if (!period && s[i] == '.' && i != 0 && i != s.length() - 1) {
-                period = true;
-            } else {
-                return false;
-            }
-        }
-    }
     return true;
 }
 
 static bool IsIgnore(const std::vector<std::string>& command) {
-    if (command[0][0] == '#') {
+    if (command.size() == 0 || command[0][0] == '#') {
         return true;
     }
     return false;
 }
 
-bool CheckFastbootInfoRequirements(const std::vector<std::string>& command) {
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command,
+                                   uint32_t host_tool_version) {
     if (command.size() != 2) {
         LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
                    << android::base::Join(command, " ");
@@ -1696,18 +1710,20 @@
         return false;
     }
 
-    if (!IsNumber(command[1])) {
-        LOG(ERROR) << "version number contains non-numeric values in fastboot-info.txt -> "
+    uint32_t fastboot_info_version;
+    if (!android::base::ParseUint(command[1], &fastboot_info_version)) {
+        LOG(ERROR) << "version number contains non-numeric characters in fastboot-info.txt -> "
                    << android::base::Join(command, " ");
         return false;
     }
 
     LOG(VERBOSE) << "Checking 'fastboot-info.txt version'";
-    if (command[1] < PLATFORM_TOOLS_VERSION) {
+    if (fastboot_info_version <= host_tool_version) {
         return true;
     }
+
     LOG(ERROR) << "fasboot-info.txt version: " << command[1]
-               << " not compatible with host tool version --> " << PLATFORM_TOOLS_VERSION;
+               << " not compatible with host tool version --> " << host_tool_version;
     return false;
 }
 
@@ -1721,7 +1737,7 @@
             continue;
         }
         if (command.size() > 1 && command[0] == "version") {
-            if (!CheckFastbootInfoRequirements(command)) {
+            if (!CheckFastbootInfoRequirements(command, FASTBOOT_INFO_VERSION)) {
                 return {};
             }
             continue;
@@ -1733,13 +1749,11 @@
         }
         auto task = ParseFastbootInfoLine(fp, command);
         if (!task) {
-            LOG(ERROR) << "Error when parsing fastboot-info.txt, falling back on Hardcoded list: "
-                       << text;
             return {};
         }
         tasks.emplace_back(std::move(task));
     }
-    if (auto flash_super_task = FlashSuperLayoutTask::InitializeFromTasks(fp, tasks)) {
+    if (auto flash_super_task = OptimizedFlashSuperTask::InitializeFromTasks(fp, tasks)) {
         auto it = tasks.begin();
         for (size_t i = 0; i < tasks.size(); i++) {
             if (auto flash_task = tasks[i]->AsFlashTask()) {
@@ -1754,23 +1768,13 @@
         }
         tasks.insert(it, std::move(flash_super_task));
     } else {
-        AddResizeTasks(fp, &tasks);
+        if (!AddResizeTasks(fp, &tasks)) {
+            LOG(WARNING) << "Failed to add resize tasks";
+        };
     }
     return tasks;
 }
 
-std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp, std::ifstream& fs) {
-    if (!fs || fs.eof()) return {};
-
-    std::string text;
-    std::vector<std::string> file;
-    // Get os_partitions that need to be resized
-    while (std::getline(fs, text)) {
-        file.emplace_back(text);
-    }
-    return ParseFastbootInfo(fp, file);
-}
-
 FlashAllTool::FlashAllTool(FlashingPlan* fp) : fp_(fp) {}
 
 void FlashAllTool::Flash() {
@@ -1786,27 +1790,26 @@
     }
 
     DetermineSlot();
-    CollectImages();
 
     CancelSnapshotIfNeeded();
 
-    std::string path = find_item_given_name("fastboot-info.txt");
-    std::ifstream stream(path);
-    std::vector<std::unique_ptr<Task>> tasks = ParseFastbootInfo(fp_, stream);
-    if (tasks.empty()) {
-        LOG(VERBOSE) << "Flashing from hardcoded images. fastboot-info.txt is empty or does not "
-                        "exist";
-        HardcodedFlash();
-        return;
-    }
-    LOG(VERBOSE) << "Flashing from fastboot-info.txt";
-    for (auto& task : tasks) {
+    tasks_ = CollectTasks();
+    for (auto& task : tasks_) {
         task->Run();
     }
-    if (fp_->wants_wipe) {
-        // avoid adding duplicate wipe tasks in fastboot main code.
-        fp_->wants_wipe = false;
+    return;
+}
+
+std::vector<std::unique_ptr<Task>> FlashAllTool::CollectTasks() {
+    std::vector<std::unique_ptr<Task>> tasks;
+    if (fp_->should_use_fastboot_info) {
+        tasks = CollectTasksFromFastbootInfo();
+
+    } else {
+        tasks = CollectTasksFromImageList();
     }
+
+    return tasks;
 }
 
 void FlashAllTool::CheckRequirements() {
@@ -1857,15 +1860,13 @@
     }
 }
 
-void FlashAllTool::HardcodedFlash() {
+std::vector<std::unique_ptr<Task>> FlashAllTool::CollectTasksFromImageList() {
     CollectImages();
     // First flash boot partitions. We allow this to happen either in userspace
     // or in bootloader fastboot.
-    FlashImages(boot_images_);
-
     std::vector<std::unique_ptr<Task>> tasks;
-
-    if (auto flash_super_task = FlashSuperLayoutTask::Initialize(fp_, os_images_)) {
+    AddFlashTasks(boot_images_, tasks);
+    if (auto flash_super_task = OptimizedFlashSuperTask::Initialize(fp_, os_images_)) {
         tasks.emplace_back(std::move(flash_super_task));
     } else {
         // Sync the super partition. This will reboot to userspace fastboot if needed.
@@ -1877,7 +1878,7 @@
             // On these devices, secondary slots must be flashed as physical
             // partitions (otherwise they would not mount on first boot). To enforce
             // this, we delete any logical partitions for the "other" slot.
-            if (is_retrofit_device()) {
+            if (is_retrofit_device(fp_->fb)) {
                 std::string partition_name = image->part_name + "_"s + slot;
                 if (image->IsSecondary() && should_flash_in_userspace(partition_name)) {
                     fp_->fb->DeletePartition(partition_name);
@@ -1887,59 +1888,45 @@
             tasks.emplace_back(std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
         }
     }
-    for (auto& i : tasks) {
-        i->Run();
-    }
-    FlashImages(os_images_);
+
+    AddFlashTasks(os_images_, tasks);
+    return tasks;
 }
 
-void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
+std::vector<std::unique_ptr<Task>> FlashAllTool::CollectTasksFromFastbootInfo() {
+    std::vector<std::unique_ptr<Task>> tasks;
+    std::vector<char> contents;
+    if (!fp_->source->ReadFile("fastboot-info.txt", &contents)) {
+        LOG(VERBOSE) << "Flashing from hardcoded images. fastboot-info.txt is empty or does not "
+                        "exist";
+        return CollectTasksFromImageList();
+    }
+    tasks = ParseFastbootInfo(fp_, Split({contents.data(), contents.size()}, "\n"));
+    return tasks;
+}
+
+void FlashAllTool::AddFlashTasks(const std::vector<std::pair<const Image*, std::string>>& images,
+                                 std::vector<std::unique_ptr<Task>>& tasks) {
     for (const auto& [image, slot] : images) {
         fastboot_buffer buf;
         unique_fd fd = fp_->source->OpenFile(image->img_name);
-        if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) {
+        if (fd < 0 || !load_buf_fd(std::move(fd), &buf, fp_)) {
             if (image->optional_if_no_image) {
                 continue;
             }
             die("could not load '%s': %s", image->img_name.c_str(), strerror(errno));
         }
-        FlashImage(*image, slot, &buf);
+        tasks.emplace_back(std::make_unique<FlashTask>(slot, image->part_name, image->img_name,
+                                                       is_vbmeta_partition(image->part_name), fp_));
     }
 }
 
-void FlashAllTool::FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf) {
-    auto flash = [&, this](const std::string& partition_name) {
-        std::vector<char> signature_data;
-        if (fp_->source->ReadFile(image.sig_name, &signature_data)) {
-            fb->Download("signature", signature_data);
-            fb->RawCommand("signature", "installing signature");
-        }
-
-        if (is_logical(partition_name)) {
-            fb->ResizePartition(partition_name, std::to_string(buf->image_size));
-        }
-
-        flash_buf(partition_name.c_str(), buf, is_vbmeta_partition(partition_name));
-    };
-    do_for_partitions(image.part_name, slot, flash, false);
-}
-
-class ZipImageSource final : public ImageSource {
-  public:
-    explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
-    bool ReadFile(const std::string& name, std::vector<char>* out) const override;
-    unique_fd OpenFile(const std::string& name) const override;
-
-  private:
-    ZipArchiveHandle zip_;
-};
-
 bool ZipImageSource::ReadFile(const std::string& name, std::vector<char>* out) const {
     return UnzipToMemory(zip_, name, out);
 }
 
 unique_fd ZipImageSource::OpenFile(const std::string& name) const {
-    return unzip_to_file(zip_, name.c_str());
+    return UnzipToFile(zip_, name.c_str());
 }
 
 static void do_update(const char* filename, FlashingPlan* fp) {
@@ -1957,12 +1944,6 @@
     CloseArchive(zip);
 }
 
-class LocalImageSource final : public ImageSource {
-  public:
-    bool ReadFile(const std::string& name, std::vector<char>* out) const override;
-    unique_fd OpenFile(const std::string& name) const override;
-};
-
 bool LocalImageSource::ReadFile(const std::string& name, std::vector<char>* out) const {
     auto path = find_item_given_name(name);
     if (path.empty()) {
@@ -2022,7 +2003,7 @@
 
 void fb_perform_format(const std::string& partition, int skip_if_not_supported,
                        const std::string& type_override, const std::string& size_override,
-                       const unsigned fs_options) {
+                       const unsigned fs_options, const FlashingPlan* fp) {
     std::string partition_type, partition_size;
 
     struct fastboot_buffer buf;
@@ -2035,8 +2016,8 @@
     if (target_sparse_limit > 0 && target_sparse_limit < limit) {
         limit = target_sparse_limit;
     }
-    if (sparse_limit > 0 && sparse_limit < limit) {
-        limit = sparse_limit;
+    if (fp->sparse_limit > 0 && fp->sparse_limit < limit) {
+        limit = fp->sparse_limit;
     }
 
     if (fb->GetVar("partition-type:" + partition, &partition_type) != fastboot::SUCCESS) {
@@ -2091,7 +2072,7 @@
     if (fd == -1) {
         die("Cannot open generated image: %s", strerror(errno));
     }
-    if (!load_buf_fd(std::move(fd), &buf)) {
+    if (!load_buf_fd(std::move(fd), &buf, fp)) {
         die("Cannot read image: %s", strerror(errno));
     }
     flash_buf(partition, &buf, is_vbmeta_partition(partition));
@@ -2124,7 +2105,7 @@
 }
 
 static bool wipe_super(const android::fs_mgr::LpMetadata& metadata, const std::string& slot,
-                       std::string* message) {
+                       std::string* message, const FlashingPlan* fp) {
     auto super_device = GetMetadataSuperBlockDevice(metadata);
     auto block_size = metadata.geometry.logical_block_size;
     auto super_bdev_name = android::fs_mgr::GetBlockDevicePartitionName(*super_device);
@@ -2164,7 +2145,7 @@
 
         auto image_path = temp_dir.path + "/"s + image_name;
         auto flash = [&](const std::string& partition_name) {
-            do_flash(partition_name.c_str(), image_path.c_str(), false);
+            do_flash(partition_name.c_str(), image_path.c_str(), false, fp);
         };
         do_for_partitions(partition, slot, flash, force_slot);
 
@@ -2173,7 +2154,8 @@
     return true;
 }
 
-static void do_wipe_super(const std::string& image, const std::string& slot_override) {
+static void do_wipe_super(const std::string& image, const std::string& slot_override,
+                          const FlashingPlan* fp) {
     if (access(image.c_str(), R_OK) != 0) {
         die("Could not read image: %s", image.c_str());
     }
@@ -2188,7 +2170,7 @@
     }
 
     std::string message;
-    if (!wipe_super(*metadata.get(), slot, &message)) {
+    if (!wipe_super(*metadata.get(), slot, &message, fp)) {
         die(message);
     }
 }
@@ -2230,6 +2212,8 @@
                                       {"cmdline", required_argument, 0, 0},
                                       {"disable-verification", no_argument, 0, 0},
                                       {"disable-verity", no_argument, 0, 0},
+                                      {"disable-super-optimization", no_argument, 0, 0},
+                                      {"disable-fastboot-info", no_argument, 0, 0},
                                       {"force", no_argument, 0, 0},
                                       {"fs-options", required_argument, 0, 0},
                                       {"header-version", required_argument, 0, 0},
@@ -2251,7 +2235,10 @@
                                       {"version", no_argument, 0, 0},
                                       {0, 0, 0, 0}};
 
-    serial = getenv("ANDROID_SERIAL");
+    serial = getenv("FASTBOOT_DEVICE");
+    if (!serial) {
+        serial = getenv("ANDROID_SERIAL");
+    }
 
     int c;
     while ((c = getopt_long(argc, argv, "a::hls:S:vw", longopts, &longindex)) != -1) {
@@ -2265,6 +2252,10 @@
                 g_disable_verification = true;
             } else if (name == "disable-verity") {
                 g_disable_verity = true;
+            } else if (name == "disable-super-optimization") {
+                fp->should_optimize_flash_super = false;
+            } else if (name == "disable-fastboot-info") {
+                fp->should_use_fastboot_info = false;
             } else if (name == "force") {
                 fp->force_flash = true;
             } else if (name == "fs-options") {
@@ -2320,7 +2311,7 @@
                     serial = optarg;
                     break;
                 case 'S':
-                    if (!android::base::ParseByteCount(optarg, &sparse_limit)) {
+                    if (!android::base::ParseByteCount(optarg, &fp->sparse_limit)) {
                         die("invalid sparse limit %s", optarg);
                     }
                     break;
@@ -2438,7 +2429,8 @@
             std::string partition = next_arg(&args);
 
             auto format = [&](const std::string& partition) {
-                fb_perform_format(partition, 0, type_override, size_override, fp->fs_options);
+                fb_perform_format(partition, 0, type_override, size_override, fp->fs_options,
+                                  fp.get());
             };
             do_for_partitions(partition, fp->slot_override, format, true);
         } else if (command == "signature") {
@@ -2485,7 +2477,7 @@
             }
             if (fname.empty()) die("cannot determine image filename for '%s'", pname.c_str());
 
-            FlashTask task(fp->slot_override, pname, fname, is_vbmeta_partition(pname));
+            FlashTask task(fp->slot_override, pname, fname, is_vbmeta_partition(pname), fp.get());
             task.Run();
         } else if (command == "flash:raw") {
             std::string partition = next_arg(&args);
@@ -2532,7 +2524,7 @@
             std::string filename = next_arg(&args);
 
             struct fastboot_buffer buf;
-            if (!load_buf(filename.c_str(), &buf) || buf.type != FB_BUFFER_FD) {
+            if (!load_buf(filename.c_str(), &buf, fp.get()) || buf.type != FB_BUFFER_FD) {
                 die("cannot load '%s'", filename.c_str());
             }
             fb->Download(filename, buf.fd.get(), buf.sz);
@@ -2580,7 +2572,7 @@
             } else {
                 image = next_arg(&args);
             }
-            do_wipe_super(image, fp->slot_override);
+            do_wipe_super(image, fp->slot_override, fp.get());
         } else if (command == "snapshot-update") {
             std::string arg;
             if (!args.empty()) {
@@ -2593,7 +2585,7 @@
         } else if (command == FB_CMD_FETCH) {
             std::string partition = next_arg(&args);
             std::string outfile = next_arg(&args);
-            do_fetch(partition, fp->slot_override, outfile);
+            do_fetch(partition, fp->slot_override, outfile, fp->fb);
         } else {
             syntax_error("unknown command %s", command.c_str());
         }
@@ -2603,10 +2595,13 @@
         if (fp->force_flash) {
             CancelSnapshotIfNeeded();
         }
+        std::vector<std::unique_ptr<Task>> wipe_tasks;
         std::vector<std::string> partitions = {"userdata", "cache", "metadata"};
         for (const auto& partition : partitions) {
-            tasks.emplace_back(std::make_unique<WipeTask>(fp.get(), partition));
+            wipe_tasks.emplace_back(std::make_unique<WipeTask>(fp.get(), partition));
         }
+        tasks.insert(tasks.begin(), std::make_move_iterator(wipe_tasks.begin()),
+                     std::make_move_iterator(wipe_tasks.end()));
     }
     if (fp->wants_set_active) {
         fb->SetActive(next_active);
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index d6afb9e..f6ffb64 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -40,6 +40,7 @@
 #include "result.h"
 #include "socket.h"
 #include "util.h"
+#include "ziparchive/zip_archive.h"
 
 class FastBootTool {
   public:
@@ -95,6 +96,9 @@
     bool wants_set_active = false;
     bool skip_secondary = false;
     bool force_flash = false;
+    bool should_optimize_flash_super = true;
+    bool should_use_fastboot_info = true;
+    uint64_t sparse_limit = 0;
 
     std::string slot_override;
     std::string current_slot;
@@ -108,23 +112,46 @@
     FlashAllTool(FlashingPlan* fp);
 
     void Flash();
+    std::vector<std::unique_ptr<Task>> CollectTasks();
 
   private:
     void CheckRequirements();
     void DetermineSlot();
     void CollectImages();
-    void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
-    void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
-    void HardcodedFlash();
+    void AddFlashTasks(const std::vector<std::pair<const Image*, std::string>>& images,
+                       std::vector<std::unique_ptr<Task>>& tasks);
+
+    std::vector<std::unique_ptr<Task>> CollectTasksFromFastbootInfo();
+    std::vector<std::unique_ptr<Task>> CollectTasksFromImageList();
 
     std::vector<ImageEntry> boot_images_;
     std::vector<ImageEntry> os_images_;
+    std::vector<std::unique_ptr<Task>> tasks_;
+
     FlashingPlan* fp_;
 };
 
+class ZipImageSource final : public ImageSource {
+  public:
+    explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
+    bool ReadFile(const std::string& name, std::vector<char>* out) const override;
+    unique_fd OpenFile(const std::string& name) const override;
+
+  private:
+    ZipArchiveHandle zip_;
+};
+
+class LocalImageSource final : public ImageSource {
+  public:
+    bool ReadFile(const std::string& name, std::vector<char>* out) const override;
+    unique_fd OpenFile(const std::string& name) const override;
+};
+
+char* get_android_product_out();
 bool should_flash_in_userspace(const std::string& partition_name);
 bool is_userspace_fastboot();
-void do_flash(const char* pname, const char* fname, const bool apply_vbmeta);
+void do_flash(const char* pname, const char* fname, const bool apply_vbmeta,
+              const FlashingPlan* fp);
 void do_for_partitions(const std::string& part, const std::string& slot,
                        const std::function<void(const std::string&)>& func, bool force_slot);
 std::string find_item(const std::string& item);
@@ -133,7 +160,8 @@
 std::string get_current_slot();
 
 // Code for Parsing fastboot-info.txt
-bool CheckFastbootInfoRequirements(const std::vector<std::string>& command);
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command,
+                                   uint32_t host_tool_version);
 std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
                                              const std::vector<std::string>& parts);
 std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
@@ -142,7 +170,7 @@
                                            const std::vector<std::string>& parts);
 std::unique_ptr<Task> ParseFastbootInfoLine(const FlashingPlan* fp,
                                             const std::vector<std::string>& command);
-void AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
+bool AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
 std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp,
                                                      const std::vector<std::string>& file);
 
@@ -156,11 +184,11 @@
 bool supports_AB();
 std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot_);
 void flash_partition_files(const std::string& partition, const std::vector<SparsePtr>& files);
-int64_t get_sparse_limit(int64_t size);
+int64_t get_sparse_limit(int64_t size, const FlashingPlan* fp);
 std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size);
 
-bool is_retrofit_device();
+bool is_retrofit_device(fastboot::IFastBootDriver* fb);
 bool is_logical(const std::string& partition);
 void fb_perform_format(const std::string& partition, int skip_if_not_supported,
                        const std::string& type_override, const std::string& size_override,
-                       const unsigned fs_options);
+                       const unsigned fs_options, const FlashingPlan* fp);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index 3d6c7b0..8774ead 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -28,6 +28,7 @@
 #pragma once
 #include <cstdlib>
 #include <deque>
+#include <functional>
 #include <limits>
 #include <string>
 #include <vector>
@@ -104,7 +105,7 @@
                                   std::vector<std::string>* info = nullptr);
     RetCode FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
                       int64_t offset = -1, int64_t size = -1, std::string* response = nullptr,
-                      std::vector<std::string>* info = nullptr);
+                      std::vector<std::string>* info = nullptr) override;
 
     /* HIGHER LEVEL COMMANDS -- Composed of the commands above */
     RetCode FlashPartition(const std::string& partition, const std::vector<char>& data);
diff --git a/fastboot/fastboot_driver_interface.h b/fastboot/fastboot_driver_interface.h
index 795938f..7cb8a6b 100644
--- a/fastboot/fastboot_driver_interface.h
+++ b/fastboot/fastboot_driver_interface.h
@@ -45,6 +45,10 @@
                              std::vector<std::string>* info = nullptr) = 0;
     RetCode virtual GetVar(const std::string& key, std::string* val,
                            std::vector<std::string>* info = nullptr) = 0;
+    RetCode virtual FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
+                              int64_t offset = -1, int64_t size = -1,
+                              std::string* response = nullptr,
+                              std::vector<std::string>* info = nullptr) = 0;
     RetCode virtual Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
                              std::string* response = nullptr,
                              std::vector<std::string>* info = nullptr) = 0;
diff --git a/fastboot/fastboot_driver_mock.h b/fastboot/fastboot_driver_mock.h
index 62a5708..7c41d78 100644
--- a/fastboot/fastboot_driver_mock.h
+++ b/fastboot/fastboot_driver_mock.h
@@ -22,22 +22,22 @@
 
 class MockFastbootDriver : public IFastBootDriver {
   public:
-    MOCK_METHOD(RetCode, FlashPartition,
-                (const std::string& partition, android::base::borrowed_fd fd, uint32_t sz),
+    MOCK_METHOD(RetCode, FlashPartition, (const std::string&, android::base::borrowed_fd, uint32_t),
                 (override));
     MOCK_METHOD(RetCode, DeletePartition, (const std::string&), (override));
     MOCK_METHOD(RetCode, Reboot, (std::string*, std::vector<std::string>*), (override));
     MOCK_METHOD(RetCode, RebootTo, (std::string, std::string*, std::vector<std::string>*),
                 (override));
-
     MOCK_METHOD(RetCode, GetVar, (const std::string&, std::string*, std::vector<std::string>*),
                 (override));
-
+    MOCK_METHOD(RetCode, FetchToFd,
+                (const std::string&, android::base::borrowed_fd, int64_t offset, int64_t size,
+                 std::string*, std::vector<std::string>*),
+                (override));
     MOCK_METHOD(RetCode, Download,
                 (const std::string&, android::base::borrowed_fd, size_t, std::string*,
                  std::vector<std::string>*),
                 (override));
-
     MOCK_METHOD(RetCode, RawCommand,
                 (const std::string&, const std::string&, std::string*, std::vector<std::string>*,
                  int*),
diff --git a/fastboot/filesystem.h b/fastboot/filesystem.h
index 5f41fbc..c5f9c94 100644
--- a/fastboot/filesystem.h
+++ b/fastboot/filesystem.h
@@ -31,6 +31,7 @@
 #endif
 
 std::string GetHomeDirPath();
+bool FileExists(const std::string& path);
 bool EnsureDirectoryExists(const std::string& directory_path);
 
 class FileLock {
diff --git a/fastboot/storage.cpp b/fastboot/storage.cpp
index d6e00cf..629ebc8 100644
--- a/fastboot/storage.cpp
+++ b/fastboot/storage.cpp
@@ -18,29 +18,25 @@
 #include <android-base/logging.h>
 
 #include <fstream>
+#include <iterator>
 
 #include "storage.h"
 #include "util.h"
 
 ConnectedDevicesStorage::ConnectedDevicesStorage() {
-    const std::string home_path = GetHomeDirPath();
-    if (home_path.empty()) {
-        return;
-    }
-
-    const std::string home_fastboot_path = home_path + kPathSeparator + ".fastboot";
-
-    if (!EnsureDirectoryExists(home_fastboot_path)) {
-        LOG(FATAL) << "Cannot create directory: " << home_fastboot_path;
-    }
+    home_fastboot_path_ = GetHomeDirPath() + kPathSeparator + ".fastboot";
+    devices_path_ = home_fastboot_path_ + kPathSeparator + "devices";
 
     // We're using a separate file for locking because the Windows LockFileEx does not
     // permit opening a file stream for the locked file, even within the same process. So,
     // we have to use fd or handle API to manipulate the storage files, which makes it
     // nearly impossible to fully rewrite a file content without having to recreate it.
     // Unfortunately, this is not an option during holding a lock.
-    devices_path_ = home_fastboot_path + kPathSeparator + "devices";
-    devices_lock_path_ = home_fastboot_path + kPathSeparator + "devices.lock";
+    devices_lock_path_ = home_fastboot_path_ + kPathSeparator + "devices.lock";
+}
+
+bool ConnectedDevicesStorage::Exists() const {
+    return FileExists(devices_path_);
 }
 
 void ConnectedDevicesStorage::WriteDevices(const FileLock&, const std::set<std::string>& devices) {
@@ -63,5 +59,8 @@
 }
 
 FileLock ConnectedDevicesStorage::Lock() const {
+    if (!EnsureDirectoryExists(home_fastboot_path_)) {
+        LOG(FATAL) << "Cannot create directory: " << home_fastboot_path_;
+    }
     return FileLock(devices_lock_path_);
-}
\ No newline at end of file
+}
diff --git a/fastboot/storage.h b/fastboot/storage.h
index 0cc3d86..ae9d846 100644
--- a/fastboot/storage.h
+++ b/fastboot/storage.h
@@ -24,13 +24,15 @@
 class ConnectedDevicesStorage {
   public:
     ConnectedDevicesStorage();
+
+    bool Exists() const;
     void WriteDevices(const FileLock&, const std::set<std::string>& devices);
     std::set<std::string> ReadDevices(const FileLock&);
     void Clear(const FileLock&);
-
     FileLock Lock() const;
 
   private:
+    std::string home_fastboot_path_;
     std::string devices_path_;
     std::string devices_lock_path_;
 };
\ No newline at end of file
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 054c1ed..bf64f0e 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -26,9 +26,9 @@
 #include "util.h"
 
 using namespace std::string_literals;
-FlashTask::FlashTask(const std::string& _slot, const std::string& _pname, const std::string& _fname,
-                     const bool apply_vbmeta)
-    : pname_(_pname), fname_(_fname), slot_(_slot), apply_vbmeta_(apply_vbmeta) {}
+FlashTask::FlashTask(const std::string& slot, const std::string& pname, const std::string& fname,
+                     const bool apply_vbmeta, const FlashingPlan* fp)
+    : pname_(pname), fname_(fname), slot_(slot), apply_vbmeta_(apply_vbmeta), fp_(fp) {}
 
 void FlashTask::Run() {
     auto flash = [&](const std::string& partition) {
@@ -41,11 +41,19 @@
                 "And try again. If you are intentionally trying to "
                 "overwrite a fixed partition, use --force.");
         }
-        do_flash(partition.c_str(), fname_.c_str(), apply_vbmeta_);
+        do_flash(partition.c_str(), fname_.c_str(), apply_vbmeta_, fp_);
     };
     do_for_partitions(pname_, slot_, flash, true);
 }
 
+std::string FlashTask::ToString() {
+    std::string apply_vbmeta_string = "";
+    if (apply_vbmeta_) {
+        apply_vbmeta_string = " --apply_vbmeta";
+    }
+    return "flash" + apply_vbmeta_string + " " + pname_ + " " + fname_;
+}
+
 std::string FlashTask::GetPartitionAndSlot() {
     auto slot = slot_;
     if (slot.empty()) {
@@ -65,7 +73,7 @@
     : reboot_target_(reboot_target), fp_(fp){};
 
 void RebootTask::Run() {
-    if ((reboot_target_ == "userspace" || reboot_target_ == "fastboot")) {
+    if (reboot_target_ == "fastboot") {
         if (!is_userspace_fastboot()) {
             reboot_to_userspace_fastboot();
             fp_->fb->WaitForDisconnect();
@@ -84,20 +92,26 @@
     }
 }
 
-FlashSuperLayoutTask::FlashSuperLayoutTask(const std::string& super_name,
-                                           std::unique_ptr<SuperFlashHelper> helper,
-                                           SparsePtr sparse_layout, uint64_t super_size)
+std::string RebootTask::ToString() {
+    return "reboot " + reboot_target_;
+}
+
+OptimizedFlashSuperTask::OptimizedFlashSuperTask(const std::string& super_name,
+                                                 std::unique_ptr<SuperFlashHelper> helper,
+                                                 SparsePtr sparse_layout, uint64_t super_size,
+                                                 const FlashingPlan* fp)
     : super_name_(super_name),
       helper_(std::move(helper)),
       sparse_layout_(std::move(sparse_layout)),
-      super_size_(super_size) {}
+      super_size_(super_size),
+      fp_(fp) {}
 
-void FlashSuperLayoutTask::Run() {
+void OptimizedFlashSuperTask::Run() {
     // Use the reported super partition size as the upper limit, rather than
     // sparse_file_len, which (1) can fail and (2) is kind of expensive, since
     // it will map in all of the embedded fds.
     std::vector<SparsePtr> files;
-    if (int limit = get_sparse_limit(super_size_)) {
+    if (int limit = get_sparse_limit(super_size_, fp_)) {
         files = resparse_file(sparse_layout_.get(), limit);
     } else {
         files.emplace_back(std::move(sparse_layout_));
@@ -106,9 +120,16 @@
     // Send the data to the device.
     flash_partition_files(super_name_, files);
 }
+std::string OptimizedFlashSuperTask::ToString() {
+    return "optimized-flash-super";
+}
 
-std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::Initialize(
+std::unique_ptr<OptimizedFlashSuperTask> OptimizedFlashSuperTask::Initialize(
         const FlashingPlan* fp, std::vector<ImageEntry>& os_images) {
+    if (!fp->should_optimize_flash_super) {
+        LOG(INFO) << "super optimization is disabled";
+        return nullptr;
+    }
     if (!supports_AB()) {
         LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
         return nullptr;
@@ -167,12 +188,16 @@
     };
     os_images.erase(std::remove_if(os_images.begin(), os_images.end(), remove_if_callback),
                     os_images.end());
-    return std::make_unique<FlashSuperLayoutTask>(super_name, std::move(helper), std::move(s),
-                                                  partition_size);
+    return std::make_unique<OptimizedFlashSuperTask>(super_name, std::move(helper), std::move(s),
+                                                     partition_size, fp);
 }
 
-std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::InitializeFromTasks(
+std::unique_ptr<OptimizedFlashSuperTask> OptimizedFlashSuperTask::InitializeFromTasks(
         const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks) {
+    if (!fp->should_optimize_flash_super) {
+        LOG(INFO) << "super optimization is disabled";
+        return nullptr;
+    }
     if (!supports_AB()) {
         LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
         return nullptr;
@@ -214,11 +239,9 @@
 
     for (const auto& task : tasks) {
         if (auto flash_task = task->AsFlashTask()) {
-            if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
-                auto partition = flash_task->GetPartitionAndSlot();
-                if (!helper->AddPartition(partition, flash_task->GetImageName(), false)) {
-                    return nullptr;
-                }
+            auto partition = flash_task->GetPartitionAndSlot();
+            if (!helper->AddPartition(partition, flash_task->GetImageName(), false)) {
+                return nullptr;
             }
         }
     }
@@ -238,8 +261,8 @@
     };
     tasks.erase(std::remove_if(tasks.begin(), tasks.end(), remove_if_callback), tasks.end());
 
-    return std::make_unique<FlashSuperLayoutTask>(super_name, std::move(helper), std::move(s),
-                                                  partition_size);
+    return std::make_unique<OptimizedFlashSuperTask>(super_name, std::move(helper), std::move(s),
+                                                     partition_size, fp);
 }
 
 UpdateSuperTask::UpdateSuperTask(const FlashingPlan* fp) : fp_(fp) {}
@@ -265,6 +288,9 @@
     }
     fp_->fb->RawCommand(command, "Updating super partition");
 }
+std::string UpdateSuperTask::ToString() {
+    return "update-super";
+}
 
 ResizeTask::ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
                        const std::string& slot)
@@ -279,12 +305,20 @@
     do_for_partitions(pname_, slot_, resize_partition, false);
 }
 
+std::string ResizeTask::ToString() {
+    return "resize " + pname_;
+}
+
 DeleteTask::DeleteTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
 
 void DeleteTask::Run() {
     fp_->fb->DeletePartition(pname_);
 }
 
+std::string DeleteTask::ToString() {
+    return "delete " + pname_;
+}
+
 WipeTask::WipeTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
 
 void WipeTask::Run() {
@@ -298,5 +332,9 @@
         LOG(ERROR) << "wipe task erase failed with partition: " << pname_;
         return;
     }
-    fb_perform_format(pname_, 1, partition_type, "", fp_->fs_options);
+    fb_perform_format(pname_, 1, partition_type, "", fp_->fs_options, fp_);
+}
+
+std::string WipeTask::ToString() {
+    return "erase " + pname_;
 }
diff --git a/fastboot/task.h b/fastboot/task.h
index 34e3e92..f7c8801 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -35,6 +35,8 @@
   public:
     Task() = default;
     virtual void Run() = 0;
+    virtual std::string ToString() = 0;
+
     virtual FlashTask* AsFlashTask() { return nullptr; }
     virtual RebootTask* AsRebootTask() { return nullptr; }
     virtual UpdateSuperTask* AsUpdateSuperTask() { return nullptr; }
@@ -46,20 +48,22 @@
 class FlashTask : public Task {
   public:
     FlashTask(const std::string& slot, const std::string& pname, const std::string& fname,
-              const bool apply_vbmeta);
+              const bool apply_vbmeta, const FlashingPlan* fp);
     virtual FlashTask* AsFlashTask() override { return this; }
 
+    void Run() override;
+    std::string ToString() override;
     std::string GetPartition() { return pname_; }
     std::string GetImageName() { return fname_; }
-    std::string GetPartitionAndSlot();
     std::string GetSlot() { return slot_; }
-    void Run() override;
+    std::string GetPartitionAndSlot();
 
   private:
     const std::string pname_;
     const std::string fname_;
     const std::string slot_;
     const bool apply_vbmeta_;
+    const FlashingPlan* fp_;
 };
 
 class RebootTask : public Task {
@@ -68,28 +72,31 @@
     RebootTask(const FlashingPlan* fp, const std::string& reboot_target);
     virtual RebootTask* AsRebootTask() override { return this; }
     void Run() override;
+    std::string ToString() override;
 
   private:
     const std::string reboot_target_ = "";
     const FlashingPlan* fp_;
 };
 
-class FlashSuperLayoutTask : public Task {
+class OptimizedFlashSuperTask : public Task {
   public:
-    FlashSuperLayoutTask(const std::string& super_name, std::unique_ptr<SuperFlashHelper> helper,
-                         SparsePtr sparse_layout, uint64_t super_size);
-    static std::unique_ptr<FlashSuperLayoutTask> Initialize(const FlashingPlan* fp,
-                                                            std::vector<ImageEntry>& os_images);
-    static std::unique_ptr<FlashSuperLayoutTask> InitializeFromTasks(
+    OptimizedFlashSuperTask(const std::string& super_name, std::unique_ptr<SuperFlashHelper> helper,
+                            SparsePtr sparse_layout, uint64_t super_size, const FlashingPlan* fp);
+    static std::unique_ptr<OptimizedFlashSuperTask> Initialize(const FlashingPlan* fp,
+                                                               std::vector<ImageEntry>& os_images);
+    static std::unique_ptr<OptimizedFlashSuperTask> InitializeFromTasks(
             const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
     using ImageEntry = std::pair<const Image*, std::string>;
     void Run() override;
+    std::string ToString() override;
 
   private:
     const std::string super_name_;
     std::unique_ptr<SuperFlashHelper> helper_;
     SparsePtr sparse_layout_;
     uint64_t super_size_;
+    const FlashingPlan* fp_;
 };
 
 class UpdateSuperTask : public Task {
@@ -98,6 +105,7 @@
     virtual UpdateSuperTask* AsUpdateSuperTask() override { return this; }
 
     void Run() override;
+    std::string ToString() override;
 
   private:
     const FlashingPlan* fp_;
@@ -108,6 +116,7 @@
     ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
                const std::string& slot);
     void Run() override;
+    std::string ToString() override;
 
   private:
     const FlashingPlan* fp_;
@@ -118,8 +127,9 @@
 
 class DeleteTask : public Task {
   public:
-    DeleteTask(const FlashingPlan* _fp, const std::string& _pname);
+    DeleteTask(const FlashingPlan* fp, const std::string& pname);
     void Run() override;
+    std::string ToString() override;
 
   private:
     const FlashingPlan* fp_;
@@ -130,8 +140,8 @@
   public:
     WipeTask(const FlashingPlan* fp, const std::string& pname);
     virtual WipeTask* AsWipeTask() override { return this; }
-
     void Run() override;
+    std::string ToString() override;
 
   private:
     const FlashingPlan* fp_;
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
index 145b4d7..1ba3f4a 100644
--- a/fastboot/task_test.cpp
+++ b/fastboot/task_test.cpp
@@ -16,6 +16,7 @@
 
 #include "task.h"
 #include "fastboot.h"
+#include "fastboot_driver_mock.h"
 
 #include <gtest/gtest.h>
 #include <fstream>
@@ -23,7 +24,9 @@
 #include <memory>
 #include <unordered_map>
 #include "android-base/strings.h"
+
 using android::base::Split;
+using testing::_;
 
 class ParseTest : public ::testing ::Test {
   protected:
@@ -58,7 +61,34 @@
     return ParseFastbootInfoLine(fp, vec_command);
 }
 
-TEST_F(ParseTest, CORRECT_FlASH_TASK_FORMED) {
+// tests if tasks_a is a superset of tasks_b. Used for checking to ensure all partitions flashed
+// from hardcoded image list is also flashed in new fastboot-info.txt
+static bool compareTaskList(std::vector<std::unique_ptr<Task>>& tasks_a,
+                            std::vector<std::unique_ptr<Task>>& tasks_b) {
+    std::set<std::string> list;
+    for (auto& task : tasks_a) {
+        list.insert(task->ToString());
+    }
+    for (auto& task : tasks_b) {
+        if (list.find(task->ToString()) == list.end()) {
+            std::cout << "ERROR: " << task->ToString()
+                      << " not found in task list created by fastboot-info.txt";
+            return false;
+        }
+    }
+    return true;
+}
+
+static std::string tasksToString(std::vector<std::unique_ptr<Task>>& tasks) {
+    std::string output;
+    for (auto& task : tasks) {
+        output.append(task->ToString());
+        output.append("\n");
+    }
+    return output;
+}
+
+TEST_F(ParseTest, CorrectFlashTaskFormed) {
     std::vector<std::string> commands = {"flash dtbo", "flash --slot-other system system_other.img",
                                          "flash system", "flash --apply-vbmeta vbmeta"};
 
@@ -86,25 +116,32 @@
     }
 }
 
-TEST_F(ParseTest, VERSION_CHECK_CORRRECT) {
-    std::vector<std::string> correct_versions = {
-            "version 1.0",
-            "version 22.00",
-    };
+TEST_F(ParseTest, VersionCheckCorrect) {
+    std::vector<std::string> correct_versions = {"version 1", "version 22", "version 5",
+                                                 "version 17"};
 
-    std::vector<std::string> bad_versions = {"version",        "version .01", "version x1",
-                                             "version 1.0.1",  "version 1.",  "s 1.0",
-                                             "version 1.0 2.0"};
+    std::vector<std::string> bad_versions = {"version",         "version .01",    "version x1",
+                                             "version 1.0.1",   "version 1.",     "s 1.0",
+                                             "version 1.0 2.0", "version 100.00", "version 1 2"};
 
     for (auto& version : correct_versions) {
-        ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
+        ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 26))
+                << version;
     }
+
+    // returning False for failing version check
+    for (auto& version : correct_versions) {
+        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 0))
+                << version;
+    }
+    // returning False for bad format
     for (auto& version : bad_versions) {
-        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
+        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 100))
+                << version;
     }
 }
 
-TEST_F(ParseTest, BAD_FASTBOOT_INFO_INPUT) {
+TEST_F(ParseTest, BadFastbootInput) {
     ASSERT_EQ(ParseCommand(fp.get(), "flash"), nullptr);
     ASSERT_EQ(ParseCommand(fp.get(), "flash --slot-other --apply-vbmeta"), nullptr);
     ASSERT_EQ(ParseCommand(fp.get(), "flash --apply-vbmeta"), nullptr);
@@ -121,3 +158,80 @@
     ASSERT_EQ(ParseCommand(fp.get(), "erase dtbo dtbo"), nullptr);
     ASSERT_EQ(ParseCommand(fp.get(), "wipe this"), nullptr);
 }
+
+TEST_F(ParseTest, CorrectTaskFormed) {
+    std::vector<std::string> commands = {"flash dtbo", "flash --slot-other system system_other.img",
+                                         "reboot bootloader", "update-super", "erase cache"};
+    std::vector<std::unique_ptr<Task>> tasks = collectTasks(fp.get(), commands);
+
+    ASSERT_TRUE(tasks[0]->AsFlashTask());
+    ASSERT_TRUE(tasks[0]->AsFlashTask());
+    ASSERT_TRUE(tasks[1]->AsFlashTask());
+    ASSERT_TRUE(tasks[2]->AsRebootTask());
+    ASSERT_TRUE(tasks[3]->AsUpdateSuperTask());
+    ASSERT_TRUE(tasks[4]->AsWipeTask());
+}
+
+TEST_F(ParseTest, CorrectDriverCalls) {
+    fastboot::MockFastbootDriver fb;
+    fp->fb = &fb;
+
+    EXPECT_CALL(fb, RebootTo(_, _, _)).Times(1);
+    EXPECT_CALL(fb, Reboot(_, _)).Times(1);
+    EXPECT_CALL(fb, WaitForDisconnect()).Times(2);
+
+    std::vector<std::string> commands = {"reboot bootloader", "reboot"};
+    std::vector<std::unique_ptr<Task>> tasks = collectTasks(fp.get(), commands);
+
+    for (auto& task : tasks) {
+        task->Run();
+    }
+}
+
+TEST_F(ParseTest, CorrectTaskLists) {
+    if (!get_android_product_out()) {
+        GTEST_SKIP();
+    }
+
+    LocalImageSource s;
+    fp->source = &s;
+    fp->sparse_limit = std::numeric_limits<int64_t>::max();
+
+    fastboot::MockFastbootDriver fb;
+    fp->fb = &fb;
+    fp->should_optimize_flash_super = false;
+
+    ON_CALL(fb, GetVar("super-partition-name", _, _))
+            .WillByDefault(testing::Return(fastboot::BAD_ARG));
+
+    FlashAllTool tool(fp.get());
+
+    fp->should_use_fastboot_info = false;
+    auto hardcoded_tasks = tool.CollectTasks();
+    fp->should_use_fastboot_info = true;
+    auto fastboot_info_tasks = tool.CollectTasks();
+
+    auto is_non_flash_task = [](const auto& task) -> bool {
+        return task->AsFlashTask() == nullptr;
+    };
+
+    // remove non flash tasks for testing purposes
+    hardcoded_tasks.erase(
+            std::remove_if(hardcoded_tasks.begin(), hardcoded_tasks.end(), is_non_flash_task),
+            hardcoded_tasks.end());
+    fastboot_info_tasks.erase(std::remove_if(fastboot_info_tasks.begin(), fastboot_info_tasks.end(),
+                                             is_non_flash_task),
+                              fastboot_info_tasks.end());
+
+    if (!compareTaskList(fastboot_info_tasks, hardcoded_tasks)) {
+        std::cout << "\n\n---Hardcoded Task List---\n"
+                  << tasksToString(hardcoded_tasks) << "\n---Fastboot-Info Task List---\n"
+                  << tasksToString(fastboot_info_tasks);
+    }
+
+    ASSERT_TRUE(compareTaskList(fastboot_info_tasks, hardcoded_tasks));
+
+    ASSERT_TRUE(fastboot_info_tasks.size() >= hardcoded_tasks.size())
+            << "size of fastboot-info task list: " << fastboot_info_tasks.size()
+            << " size of hardcoded task list: " << hardcoded_tasks.size();
+}
diff --git a/fastboot/usb_osx.cpp b/fastboot/usb_osx.cpp
index 5b9e5c8..8b852f5 100644
--- a/fastboot/usb_osx.cpp
+++ b/fastboot/usb_osx.cpp
@@ -436,12 +436,7 @@
 
     for (;;) {
         if (! IOIteratorIsValid(iterator)) {
-            /*
-             * Apple documentation advises resetting the iterator if
-             * it should become invalid during iteration.
-             */
-            IOIteratorReset(iterator);
-            continue;
+            break;
         }
 
         io_service_t device = IOIteratorNext(iterator);
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index dd61272..15600fd 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -70,8 +70,9 @@
         "fs_mgr.cpp",
         "fs_mgr_format.cpp",
         "fs_mgr_dm_linear.cpp",
-        "fs_mgr_overlayfs.cpp",
         "fs_mgr_roots.cpp",
+        "fs_mgr_overlayfs_control.cpp",
+        "fs_mgr_overlayfs_mount.cpp",
         "fs_mgr_vendor_overlay.cpp",
         ":libfiemap_srcs",
     ],
@@ -181,6 +182,10 @@
     ramdisk_available: true,
     vendor_ramdisk_available: true,
     recovery_available: true,
+    apex_available: [
+        "//apex_available:anyapex",
+        "//apex_available:platform",
+    ],
     host_supported: true,
     defaults: ["fs_mgr_defaults"],
     srcs: [
@@ -206,6 +211,7 @@
         "libbase_headers",
         "libgsi_headers",
     ],
+    min_sdk_version: "31",
 }
 
 cc_binary {
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 742cdfa..e568a9b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -613,7 +613,6 @@
 
 // Read the primary superblock from an f2fs filesystem.  On failure return
 // false.  If it's not an f2fs filesystem, also set FS_STAT_INVALID_MAGIC.
-#define F2FS_BLKSIZE 4096
 #define F2FS_SUPER_OFFSET 1024
 static bool read_f2fs_superblock(const std::string& blk_device, int* fs_stat) {
     android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY | O_CLOEXEC)));
@@ -628,7 +627,9 @@
         PERROR << "Can't read '" << blk_device << "' superblock1";
         return false;
     }
-    if (TEMP_FAILURE_RETRY(pread(fd, &sb2, sizeof(sb2), F2FS_BLKSIZE + F2FS_SUPER_OFFSET)) !=
+    // F2FS only supports block_size=page_size case. So, it is safe to call
+    // `getpagesize()` and use that as size of super block.
+    if (TEMP_FAILURE_RETRY(pread(fd, &sb2, sizeof(sb2), getpagesize() + F2FS_SUPER_OFFSET)) !=
         sizeof(sb2)) {
         PERROR << "Can't read '" << blk_device << "' superblock2";
         return false;
@@ -652,7 +653,7 @@
         return false;
     }
     if (sb == cpu_to_le32(F2FS_SUPER_MAGIC)) return true;
-    if (TEMP_FAILURE_RETRY(pread(fd, &sb, sizeof(sb), F2FS_BLKSIZE + F2FS_SUPER_OFFSET)) !=
+    if (TEMP_FAILURE_RETRY(pread(fd, &sb, sizeof(sb), getpagesize() + F2FS_SUPER_OFFSET)) !=
         sizeof(sb)) {
         return false;
     }
@@ -1096,8 +1097,11 @@
 
 class CheckpointManager {
   public:
-    CheckpointManager(int needs_checkpoint = -1, bool metadata_encrypted = false)
-        : needs_checkpoint_(needs_checkpoint), metadata_encrypted_(metadata_encrypted) {}
+    CheckpointManager(int needs_checkpoint = -1, bool metadata_encrypted = false,
+                      bool needs_encrypt = false)
+        : needs_checkpoint_(needs_checkpoint),
+          metadata_encrypted_(metadata_encrypted),
+          needs_encrypt_(needs_encrypt) {}
 
     bool NeedsCheckpoint() {
         if (needs_checkpoint_ != UNKNOWN) {
@@ -1160,7 +1164,7 @@
             } else {
                 LERROR << entry->fs_type << " does not implement checkpoints.";
             }
-        } else if (entry->fs_mgr_flags.checkpoint_blk) {
+        } else if (entry->fs_mgr_flags.checkpoint_blk && !needs_encrypt_) {
             auto actual_block_device = block_device.empty() ? entry->blk_device : block_device;
             if (fs_mgr_find_bow_device(actual_block_device).empty()) {
                 unique_fd fd(
@@ -1228,6 +1232,7 @@
     enum { UNKNOWN = -1, NO = 0, YES = 1 };
     int needs_checkpoint_;
     bool metadata_encrypted_;
+    bool needs_encrypt_;
     std::map<std::string, std::string> device_map_;
 };
 
@@ -1845,17 +1850,14 @@
     return ret;
 }
 
-// If tmp_mount_point is non-null, mount the filesystem there.  This is for the
-// tmp mount we do to check the user password
 // If multiple fstab entries are to be mounted on "n_name", it will try to mount each one
 // in turn, and stop on 1st success, or no more match.
-static int fs_mgr_do_mount_helper(Fstab* fstab, const std::string& n_name,
-                                  const std::string& n_blk_device, const char* tmp_mount_point,
-                                  int needs_checkpoint, bool metadata_encrypted) {
+int fs_mgr_do_mount(Fstab* fstab, const std::string& n_name, const std::string& n_blk_device,
+                    int needs_checkpoint, bool needs_encrypt) {
     int mount_errors = 0;
     int first_mount_errno = 0;
     std::string mount_point;
-    CheckpointManager checkpoint_manager(needs_checkpoint, metadata_encrypted);
+    CheckpointManager checkpoint_manager(needs_checkpoint, true, needs_encrypt);
     AvbUniquePtr avb_handle(nullptr);
 
     if (!fstab) {
@@ -1897,11 +1899,7 @@
         }
 
         // Now mount it where requested */
-        if (tmp_mount_point) {
-            mount_point = tmp_mount_point;
-        } else {
-            mount_point = fstab_entry.mount_point;
-        }
+        mount_point = fstab_entry.mount_point;
 
         int fs_stat = prepare_fs_for_mount(n_blk_device, fstab_entry, mount_point);
 
@@ -1958,35 +1956,6 @@
     return FS_MGR_DOMNT_FAILED;
 }
 
-int fs_mgr_do_mount(Fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point) {
-    return fs_mgr_do_mount_helper(fstab, n_name, n_blk_device, tmp_mount_point, -1, false);
-}
-
-int fs_mgr_do_mount(Fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point,
-                    bool needs_checkpoint, bool metadata_encrypted) {
-    return fs_mgr_do_mount_helper(fstab, n_name, n_blk_device, tmp_mount_point, needs_checkpoint,
-                                  metadata_encrypted);
-}
-
-/*
- * mount a tmpfs filesystem at the given point.
- * return 0 on success, non-zero on failure.
- */
-int fs_mgr_do_tmpfs_mount(const char *n_name)
-{
-    int ret;
-
-    ret = mount("tmpfs", n_name, "tmpfs", MS_NOATIME | MS_NOSUID | MS_NODEV | MS_NOEXEC,
-                CRYPTO_TMPFS_OPTIONS);
-    if (ret < 0) {
-        LERROR << "Cannot mount tmpfs filesystem at " << n_name;
-        return -1;
-    }
-
-    /* Success */
-    return 0;
-}
-
 static bool ConfigureIoScheduler(const std::string& device_path) {
     if (!StartsWith(device_path, "/dev/")) {
         LERROR << __func__ << ": invalid argument " << device_path;
diff --git a/fs_mgr/fs_mgr_format.cpp b/fs_mgr/fs_mgr_format.cpp
index 7385f79..622f181 100644
--- a/fs_mgr/fs_mgr_format.cpp
+++ b/fs_mgr/fs_mgr_format.cpp
@@ -32,6 +32,7 @@
 #include <selinux/android.h>
 #include <selinux/label.h>
 #include <selinux/selinux.h>
+#include <string>
 
 #include "fs_mgr_priv.h"
 
@@ -68,6 +69,13 @@
 
     /* Format the partition using the calculated length */
 
+    // EXT4 supports 4K block size on 16K page sizes. A 4K block
+    // size formatted EXT4 partition will mount fine on both 4K and 16K page
+    // size kernels.
+    // However, EXT4 does not support 16K block size on 4K systems.
+    // If we want the same userspace code to work on both 4k/16k kernels,
+    // using a hardcoded 4096 block size is a simple solution. Using
+    // getpagesize() here would work as well, but 4096 is simpler.
     std::string size_str = std::to_string(dev_sz / 4096);
 
     std::vector<const char*> mke2fs_args = {"/system/bin/mke2fs", "-t", "ext4", "-b", "4096"};
@@ -127,7 +135,7 @@
 
     /* Format the partition using the calculated length */
 
-    std::string size_str = std::to_string(dev_sz / 4096);
+    const auto size_str = std::to_string(dev_sz / getpagesize());
 
     std::vector<const char*> args = {"/system/bin/make_f2fs", "-g", "android"};
     if (needs_projid) {
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 598a3d2..c85e831 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -51,6 +51,7 @@
 namespace {
 
 constexpr char kDefaultAndroidDtDir[] = "/proc/device-tree/firmware/android";
+constexpr char kProcMountsPath[] = "/proc/mounts";
 
 struct FlagList {
     const char *name;
@@ -242,7 +243,9 @@
                     LWARNING << "Warning: zramsize= flag malformed: " << arg;
                 }
             }
-        } else if (StartsWith(flag, "fileencryption=")) {
+        } else if (StartsWith(flag, "fileencryption=") || flag == "fileencryption") {
+            // "fileencryption" enables file-based encryption.  It's normally followed by an = and
+            // then the encryption options.  But that can be omitted to use the default options.
             ParseFileEncryption(arg, entry);
         } else if (StartsWith(flag, "max_comp_streams=")) {
             if (!ParseInt(arg, &entry->max_comp_streams)) {
@@ -697,9 +700,7 @@
     }
 }
 
-bool ReadFstabFromFile(const std::string& path, Fstab* fstab_out) {
-    const bool is_proc_mounts = (path == "/proc/mounts");
-
+static bool ReadFstabFromFileCommon(const std::string& path, Fstab* fstab_out) {
     std::string fstab_str;
     if (!android::base::ReadFileToString(path, &fstab_str, /* follow_symlinks = */ true)) {
         PERROR << __FUNCTION__ << "(): failed to read file: '" << path << "'";
@@ -707,11 +708,22 @@
     }
 
     Fstab fstab;
-    if (!ParseFstabFromString(fstab_str, is_proc_mounts, &fstab)) {
+    if (!ParseFstabFromString(fstab_str, path == kProcMountsPath, &fstab)) {
         LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'";
         return false;
     }
-    if (!is_proc_mounts) {
+
+    EnableMandatoryFlags(&fstab);
+
+    *fstab_out = std::move(fstab);
+    return true;
+}
+
+bool ReadFstabFromFile(const std::string& path, Fstab* fstab) {
+    if (!ReadFstabFromFileCommon(path, fstab)) {
+        return false;
+    }
+    if (path != kProcMountsPath) {
         if (!access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
             std::string dsu_slot;
             if (!android::gsi::GetActiveDsu(&dsu_slot)) {
@@ -723,20 +735,23 @@
                 PERROR << __FUNCTION__ << "(): failed to read DSU LP names";
                 return false;
             }
-            TransformFstabForDsu(&fstab, dsu_slot, Split(lp_names, ","));
+            TransformFstabForDsu(fstab, dsu_slot, Split(lp_names, ","));
         } else if (errno != ENOENT) {
             PERROR << __FUNCTION__ << "(): failed to access() DSU booted indicator";
             return false;
         }
+
+        SkipMountingPartitions(fstab, false /* verbose */);
     }
-
-    SkipMountingPartitions(&fstab, false /* verbose */);
-    EnableMandatoryFlags(&fstab);
-
-    *fstab_out = std::move(fstab);
     return true;
 }
 
+bool ReadFstabFromProcMounts(Fstab* fstab) {
+    // Don't call `ReadFstabFromFile` because the code for `path != kProcMountsPath` has an extra
+    // code size cost, even if it's never executed.
+    return ReadFstabFromFileCommon(kProcMountsPath, fstab);
+}
+
 // Returns fstab entries parsed from the device tree if they exist
 bool ReadFstabFromDt(Fstab* fstab, bool verbose) {
     std::string fstab_buf = ReadFstabFromDt();
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs_control.cpp
similarity index 60%
rename from fs_mgr/fs_mgr_overlayfs.cpp
rename to fs_mgr/fs_mgr_overlayfs_control.cpp
index 6349c20..69a2ac0 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_control.cpp
@@ -55,8 +55,9 @@
 #include <liblp/liblp.h>
 #include <storage_literals/storage_literals.h>
 
+#include "fs_mgr_overlayfs_control.h"
+#include "fs_mgr_overlayfs_mount.h"
 #include "fs_mgr_priv.h"
-#include "fs_mgr_priv_overlayfs.h"
 #include "libfiemap/utility.h"
 
 using namespace std::literals;
@@ -69,100 +70,18 @@
 namespace {
 
 constexpr char kDataScratchSizeMbProp[] = "fs_mgr.overlayfs.data_scratch_size_mb";
-constexpr char kPreferCacheBackingStorageProp[] = "fs_mgr.overlayfs.prefer_cache_backing_storage";
-
-bool fs_mgr_access(const std::string& path) {
-    return access(path.c_str(), F_OK) == 0;
-}
-
-const auto kLowerdirOption = "lowerdir="s;
-const auto kUpperdirOption = "upperdir="s;
-
-bool fs_mgr_in_recovery() {
-    // Check the existence of recovery binary instead of using the compile time
-    // __ANDROID_RECOVERY__ macro.
-    // If BOARD_USES_RECOVERY_AS_BOOT is true, both normal and recovery boot
-    // mode would use the same init binary, which would mean during normal boot
-    // the '/init' binary is actually a symlink pointing to
-    // init_second_stage.recovery, which would be compiled with
-    // __ANDROID_RECOVERY__ defined.
-    return fs_mgr_access("/system/bin/recovery");
-}
-
-bool fs_mgr_is_dsu_running() {
-    // Since android::gsi::CanBootIntoGsi() or android::gsi::MarkSystemAsGsi() is
-    // never called in recovery, the return value of android::gsi::IsGsiRunning()
-    // is not well-defined. In this case, just return false as being in recovery
-    // implies not running a DSU system.
-    if (fs_mgr_in_recovery()) return false;
-    return android::gsi::IsGsiRunning();
-}
-
-// list of acceptable overlayfs backing storage
-const auto kScratchMountPoint = "/mnt/scratch"s;
-const auto kCacheMountPoint = "/cache"s;
-
-bool IsABDevice() {
-    return !android::base::GetProperty("ro.boot.slot_suffix", "").empty();
-}
-
-std::vector<const std::string> OverlayMountPoints() {
-    // Never fallback to legacy cache mount point if within a DSU system,
-    // because running a DSU system implies the device supports dynamic
-    // partitions, which means legacy cache mustn't be used.
-    if (fs_mgr_is_dsu_running()) {
-        return {kScratchMountPoint};
-    }
-
-    // For non-A/B devices prefer cache backing storage if
-    // kPreferCacheBackingStorageProp property set.
-    if (!IsABDevice() && android::base::GetBoolProperty(kPreferCacheBackingStorageProp, false) &&
-        android::base::GetIntProperty("ro.vendor.api_level", -1) < __ANDROID_API_T__) {
-        return {kCacheMountPoint, kScratchMountPoint};
-    }
-
-    return {kScratchMountPoint, kCacheMountPoint};
-}
 
 // Return true if everything is mounted, but before adb is started.  Right
 // after 'trigger load_persist_props_action' is done.
-bool fs_mgr_boot_completed() {
+static bool fs_mgr_boot_completed() {
     return android::base::GetBoolProperty("ro.persistent_properties.ready", false);
 }
 
-bool fs_mgr_is_dir(const std::string& path) {
-    struct stat st;
-    return !stat(path.c_str(), &st) && S_ISDIR(st.st_mode);
-}
-
-bool fs_mgr_rw_access(const std::string& path) {
-    if (path.empty()) return false;
-    return access(path.c_str(), R_OK | W_OK) == 0;
-}
-
-// At less than 1% or 8MB of free space return value of false,
-// means we will try to wrap with overlayfs.
-bool fs_mgr_filesystem_has_space(const std::string& mount_point) {
-    // If we have access issues to find out space remaining, return true
-    // to prevent us trying to override with overlayfs.
-    struct statvfs vst;
-    if (statvfs(mount_point.c_str(), &vst)) {
-        PLOG(ERROR) << "statvfs " << mount_point;
-        return true;
-    }
-
-    static constexpr int kPercentThreshold = 1;                       // 1%
-    static constexpr unsigned long kSizeThreshold = 8 * 1024 * 1024;  // 8MB
-
-    return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100)) &&
-           (static_cast<uint64_t>(vst.f_bfree) * vst.f_frsize) >= kSizeThreshold;
-}
-
-const auto kPhysicalDevice = "/dev/block/by-name/"s;
+constexpr auto kPhysicalDevice = "/dev/block/by-name/";
 constexpr char kScratchImageMetadata[] = "/metadata/gsi/remount/lp_metadata";
 
 // Note: this is meant only for recovery/first-stage init.
-bool ScratchIsOnData() {
+static bool ScratchIsOnData() {
     // The scratch partition of DSU is managed by gsid.
     if (fs_mgr_is_dsu_running()) {
         return false;
@@ -170,97 +89,7 @@
     return fs_mgr_access(kScratchImageMetadata);
 }
 
-bool fs_mgr_update_blk_device(FstabEntry* entry) {
-    if (entry->fs_mgr_flags.logical) {
-        fs_mgr_update_logical_partition(entry);
-    }
-    if (fs_mgr_access(entry->blk_device)) {
-        return true;
-    }
-    if (entry->blk_device != "/dev/root") {
-        return false;
-    }
-
-    // special case for system-as-root (taimen and others)
-    auto blk_device = kPhysicalDevice + "system";
-    if (!fs_mgr_access(blk_device)) {
-        blk_device += fs_mgr_get_slot_suffix();
-        if (!fs_mgr_access(blk_device)) {
-            return false;
-        }
-    }
-    entry->blk_device = blk_device;
-    return true;
-}
-
-bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev) {
-    struct statfs fs;
-    if ((statfs((mount_point + "/lost+found").c_str(), &fs) == -1) ||
-        (fs.f_type != EXT4_SUPER_MAGIC)) {
-        return false;
-    }
-
-    android::base::unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC));
-    if (fd < 0) return false;
-
-    struct ext4_super_block sb;
-    if ((TEMP_FAILURE_RETRY(lseek64(fd, 1024, SEEK_SET)) < 0) ||
-        (TEMP_FAILURE_RETRY(read(fd, &sb, sizeof(sb))) < 0)) {
-        return false;
-    }
-
-    struct fs_info info;
-    if (ext4_parse_sb(&sb, &info) < 0) return false;
-
-    return (info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS) != 0;
-}
-
-#define F2FS_SUPER_OFFSET 1024
-#define F2FS_FEATURE_OFFSET 2180
-#define F2FS_FEATURE_RO 0x4000
-bool fs_mgr_is_read_only_f2fs(const std::string& dev) {
-    if (!fs_mgr_is_f2fs(dev)) return false;
-
-    android::base::unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC));
-    if (fd < 0) return false;
-
-    __le32 feat;
-    if ((TEMP_FAILURE_RETRY(lseek64(fd, F2FS_SUPER_OFFSET + F2FS_FEATURE_OFFSET, SEEK_SET)) < 0) ||
-        (TEMP_FAILURE_RETRY(read(fd, &feat, sizeof(feat))) < 0)) {
-        return false;
-    }
-
-    return (feat & cpu_to_le32(F2FS_FEATURE_RO)) != 0;
-}
-
-bool fs_mgr_overlayfs_enabled(FstabEntry* entry) {
-    // readonly filesystem, can not be mount -o remount,rw
-    // for squashfs, erofs or if free space is (near) zero making such a remount
-    // virtually useless, or if there are shared blocks that prevent remount,rw
-    if (!fs_mgr_filesystem_has_space(entry->mount_point)) {
-        return true;
-    }
-
-    // blk_device needs to be setup so we can check superblock.
-    // If we fail here, because during init first stage and have doubts.
-    if (!fs_mgr_update_blk_device(entry)) {
-        return true;
-    }
-
-    // f2fs read-only mode doesn't support remount,rw
-    if (fs_mgr_is_read_only_f2fs(entry->blk_device)) {
-        return true;
-    }
-
-    // check if ext4 de-dupe
-    auto has_shared_blocks = fs_mgr_has_shared_blocks(entry->mount_point, entry->blk_device);
-    if (!has_shared_blocks && (entry->mount_point == "/system")) {
-        has_shared_blocks = fs_mgr_has_shared_blocks("/", entry->blk_device);
-    }
-    return has_shared_blocks;
-}
-
-bool fs_mgr_rm_all(const std::string& path, bool* change = nullptr, int level = 0) {
+static bool fs_mgr_rm_all(const std::string& path, bool* change = nullptr, int level = 0) {
     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(path.c_str()), closedir);
     if (!dir) {
         if (errno == ENOENT) {
@@ -301,96 +130,6 @@
     return ret;
 }
 
-const auto kUpperName = "upper"s;
-const auto kWorkName = "work"s;
-const auto kOverlayTopDir = "/overlay"s;
-
-std::string fs_mgr_get_overlayfs_candidate(const std::string& mount_point) {
-    if (!fs_mgr_is_dir(mount_point)) return "";
-    const auto base = android::base::Basename(mount_point) + "/";
-    for (const auto& overlay_mount_point : OverlayMountPoints()) {
-        auto dir = overlay_mount_point + kOverlayTopDir + "/" + base;
-        auto upper = dir + kUpperName;
-        if (!fs_mgr_is_dir(upper)) continue;
-        auto work = dir + kWorkName;
-        if (!fs_mgr_is_dir(work)) continue;
-        if (!fs_mgr_rw_access(work)) continue;
-        return dir;
-    }
-    return "";
-}
-
-static inline bool KernelSupportsUserXattrs() {
-    struct utsname uts;
-    uname(&uts);
-
-    int major, minor;
-    if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) {
-        return false;
-    }
-    return major > 5 || (major == 5 && minor >= 15);
-}
-
-const std::string fs_mgr_mount_point(const std::string& mount_point) {
-    if ("/"s != mount_point) return mount_point;
-    return "/system";
-}
-
-// default options for mount_point, returns empty string for none available.
-std::string fs_mgr_get_overlayfs_options(const FstabEntry& entry) {
-    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
-    auto candidate = fs_mgr_get_overlayfs_candidate(mount_point);
-    if (candidate.empty()) return "";
-    auto ret = kLowerdirOption + mount_point + "," + kUpperdirOption + candidate + kUpperName +
-               ",workdir=" + candidate + kWorkName;
-    if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kOverrideCredsRequired) {
-        ret += ",override_creds=off";
-    }
-    if (KernelSupportsUserXattrs()) {
-        ret += ",userxattr";
-    }
-    for (const auto& flag : android::base::Split(entry.fs_options, ",")) {
-        if (android::base::StartsWith(flag, "context=")) {
-            ret += "," + flag;
-        }
-    }
-    return ret;
-}
-
-constexpr char kOverlayfsFileContext[] = "u:object_r:overlayfs_file:s0";
-
-class AutoSetFsCreateCon final {
-  public:
-    AutoSetFsCreateCon() {}
-    AutoSetFsCreateCon(const std::string& context) { Set(context); }
-    ~AutoSetFsCreateCon() { Restore(); }
-
-    bool Ok() const { return ok_; }
-    bool Set(const std::string& context) {
-        if (setfscreatecon(context.c_str())) {
-            PLOG(ERROR) << "setfscreatecon " << context;
-            return false;
-        }
-        ok_ = true;
-        return true;
-    }
-    bool Restore() {
-        if (restored_ || !ok_) {
-            return true;
-        }
-        if (setfscreatecon(nullptr)) {
-            PLOG(ERROR) << "setfscreatecon null";
-            return false;
-        }
-        restored_ = true;
-        return true;
-    }
-
-  private:
-    bool ok_ = false;
-    bool restored_ = false;
-};
-
 std::string fs_mgr_overlayfs_setup_dir(const std::string& dir) {
     auto top = dir + kOverlayTopDir;
 
@@ -452,15 +191,15 @@
     return true;
 }
 
-uint32_t fs_mgr_overlayfs_slot_number() {
+static uint32_t fs_mgr_overlayfs_slot_number() {
     return SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
 }
 
-std::string fs_mgr_overlayfs_super_device(uint32_t slot_number) {
+static std::string fs_mgr_overlayfs_super_device(uint32_t slot_number) {
     return kPhysicalDevice + fs_mgr_get_super_partition_name(slot_number);
 }
 
-bool fs_mgr_overlayfs_has_logical(const Fstab& fstab) {
+static bool fs_mgr_overlayfs_has_logical(const Fstab& fstab) {
     for (const auto& entry : fstab) {
         if (entry.fs_mgr_flags.logical) {
             return true;
@@ -469,18 +208,6 @@
     return false;
 }
 
-// Returns true if immediate unmount succeeded and the scratch mount point was
-// removed.
-bool fs_mgr_overlayfs_umount_scratch() {
-    if (umount(kScratchMountPoint.c_str()) != 0) {
-        return false;
-    }
-    if (rmdir(kScratchMountPoint.c_str()) != 0 && errno != ENOENT) {
-        PLOG(ERROR) << "rmdir " << kScratchMountPoint;
-    }
-    return true;
-}
-
 OverlayfsTeardownResult TeardownDataScratch(IImageManager* images,
                                             const std::string& partition_name, bool was_mounted) {
     if (!images) {
@@ -573,7 +300,7 @@
     auto cleanup_all = mount_point.empty();
     const auto partition_name = android::base::Basename(mount_point);
     const auto oldpath = top + (cleanup_all ? "" : ("/" + partition_name));
-    const auto newpath = cleanup_all ? overlay + "/." + kOverlayTopDir.substr(1) + ".teardown"
+    const auto newpath = cleanup_all ? overlay + "/." + (kOverlayTopDir + 1) + ".teardown"
                                      : top + "/." + partition_name + ".teardown";
     auto ret = fs_mgr_rm_all(newpath);
     if (!rename(oldpath.c_str(), newpath.c_str())) {
@@ -619,214 +346,6 @@
     return ret;
 }
 
-bool fs_mgr_overlayfs_set_shared_mount(const std::string& mount_point, bool shared_flag) {
-    auto ret = mount(nullptr, mount_point.c_str(), nullptr, shared_flag ? MS_SHARED : MS_PRIVATE,
-                     nullptr);
-    if (ret) {
-        PERROR << "__mount(target=" << mount_point
-               << ",flag=" << (shared_flag ? "MS_SHARED" : "MS_PRIVATE") << ")=" << ret;
-        // If "/system" doesn't look like a mountpoint, retry with "/".
-        if (errno == EINVAL && mount_point == "/system") {
-            return fs_mgr_overlayfs_set_shared_mount("/", shared_flag);
-        }
-        return false;
-    }
-    return true;
-}
-
-bool fs_mgr_overlayfs_move_mount(const std::string& source, const std::string& target) {
-    auto ret = mount(source.c_str(), target.c_str(), nullptr, MS_MOVE, nullptr);
-    if (ret) {
-        PERROR << "__mount(source=" << source << ",target=" << target << ",flag=MS_MOVE)=" << ret;
-        return false;
-    }
-    return true;
-}
-
-struct mount_info {
-    std::string mount_point;
-    bool shared_flag;
-};
-
-std::vector<mount_info> ReadMountinfoFromFile(const std::string& path) {
-    std::vector<mount_info> info;
-
-    auto file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
-    if (!file) {
-        PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'";
-        return info;
-    }
-
-    ssize_t len;
-    size_t alloc_len = 0;
-    char* line = nullptr;
-    while ((len = getline(&line, &alloc_len, file.get())) != -1) {
-        /* if the last character is a newline, shorten the string by 1 byte */
-        if (line[len - 1] == '\n') {
-            line[len - 1] = '\0';
-        }
-
-        static constexpr char delim[] = " \t";
-        char* save_ptr;
-        if (!strtok_r(line, delim, &save_ptr)) {
-            LERROR << "Error parsing mount ID";
-            break;
-        }
-        if (!strtok_r(nullptr, delim, &save_ptr)) {
-            LERROR << "Error parsing parent ID";
-            break;
-        }
-        if (!strtok_r(nullptr, delim, &save_ptr)) {
-            LERROR << "Error parsing mount source";
-            break;
-        }
-        if (!strtok_r(nullptr, delim, &save_ptr)) {
-            LERROR << "Error parsing root";
-            break;
-        }
-
-        char* p;
-        if (!(p = strtok_r(nullptr, delim, &save_ptr))) {
-            LERROR << "Error parsing mount_point";
-            break;
-        }
-        mount_info entry = {p, false};
-
-        if (!strtok_r(nullptr, delim, &save_ptr)) {
-            LERROR << "Error parsing mount_flags";
-            break;
-        }
-
-        while ((p = strtok_r(nullptr, delim, &save_ptr))) {
-            if ((p[0] == '-') && (p[1] == '\0')) break;
-            if (android::base::StartsWith(p, "shared:")) entry.shared_flag = true;
-        }
-        if (!p) {
-            LERROR << "Error parsing fields";
-            break;
-        }
-        info.emplace_back(std::move(entry));
-    }
-
-    free(line);
-    if (info.empty()) {
-        LERROR << __FUNCTION__ << "(): failed to load mountinfo from : '" << path << "'";
-    }
-    return info;
-}
-
-bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
-    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
-    const auto options = fs_mgr_get_overlayfs_options(entry);
-    if (options.empty()) return false;
-
-    auto retval = true;
-
-    struct move_entry {
-        std::string mount_point;
-        std::string dir;
-        bool shared_flag;
-    };
-    std::vector<move_entry> move;
-    auto parent_private = false;
-    auto parent_made_private = false;
-    auto dev_private = false;
-    auto dev_made_private = false;
-    for (auto& entry : ReadMountinfoFromFile("/proc/self/mountinfo")) {
-        if ((entry.mount_point == mount_point) && !entry.shared_flag) {
-            parent_private = true;
-        }
-        if ((entry.mount_point == "/dev") && !entry.shared_flag) {
-            dev_private = true;
-        }
-
-        if (!android::base::StartsWith(entry.mount_point, mount_point + "/")) {
-            continue;
-        }
-        if (std::find_if(move.begin(), move.end(), [&entry](const auto& it) {
-                return android::base::StartsWith(entry.mount_point, it.mount_point + "/");
-            }) != move.end()) {
-            continue;
-        }
-
-        // use as the bound directory in /dev.
-        AutoSetFsCreateCon createcon;
-        auto new_context = fs_mgr_get_context(entry.mount_point);
-        if (new_context.empty() || !createcon.Set(new_context)) {
-            continue;
-        }
-        move_entry new_entry = {std::move(entry.mount_point), "/dev/TemporaryDir-XXXXXX",
-                                entry.shared_flag};
-        const auto target = mkdtemp(new_entry.dir.data());
-        if (!createcon.Restore()) {
-            return false;
-        }
-        if (!target) {
-            retval = false;
-            PERROR << "temporary directory for MS_BIND";
-            continue;
-        }
-
-        if (!parent_private && !parent_made_private) {
-            parent_made_private = fs_mgr_overlayfs_set_shared_mount(mount_point, false);
-        }
-        if (new_entry.shared_flag) {
-            new_entry.shared_flag = fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, false);
-        }
-        if (!fs_mgr_overlayfs_move_mount(new_entry.mount_point, new_entry.dir)) {
-            retval = false;
-            if (new_entry.shared_flag) {
-                fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, true);
-            }
-            continue;
-        }
-        move.emplace_back(std::move(new_entry));
-    }
-
-    // hijack __mount() report format to help triage
-    auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
-    const auto opt_list = android::base::Split(options, ",");
-    for (const auto& opt : opt_list) {
-        if (android::base::StartsWith(opt, kUpperdirOption)) {
-            report = report + "," + opt;
-            break;
-        }
-    }
-    report = report + ")=";
-
-    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
-                     options.c_str());
-    if (ret) {
-        retval = false;
-        PERROR << report << ret;
-    } else {
-        LINFO << report << ret;
-    }
-
-    // Move submounts back.
-    for (const auto& entry : move) {
-        if (!dev_private && !dev_made_private) {
-            dev_made_private = fs_mgr_overlayfs_set_shared_mount("/dev", false);
-        }
-
-        if (!fs_mgr_overlayfs_move_mount(entry.dir, entry.mount_point)) {
-            retval = false;
-        } else if (entry.shared_flag &&
-                   !fs_mgr_overlayfs_set_shared_mount(entry.mount_point, true)) {
-            retval = false;
-        }
-        rmdir(entry.dir.c_str());
-    }
-    if (dev_made_private) {
-        fs_mgr_overlayfs_set_shared_mount("/dev", true);
-    }
-    if (parent_made_private) {
-        fs_mgr_overlayfs_set_shared_mount(mount_point, true);
-    }
-
-    return retval;
-}
-
 // Mount kScratchMountPoint
 bool MountScratch(const std::string& device_path, bool readonly = false) {
     if (readonly) {
@@ -853,7 +372,7 @@
     if (!createcon.Ok()) {
         return false;
     }
-    if (mkdir(kScratchMountPoint.c_str(), 0755) && (errno != EEXIST)) {
+    if (mkdir(kScratchMountPoint, 0755) && (errno != EEXIST)) {
         PERROR << "create " << kScratchMountPoint;
         return false;
     }
@@ -884,7 +403,7 @@
         return false;
     }
     if (!mounted) {
-        rmdir(kScratchMountPoint.c_str());
+        rmdir(kScratchMountPoint);
         return false;
     }
     return true;
@@ -939,7 +458,9 @@
     auto command = ""s;
     if (!access(kMkF2fs.c_str(), X_OK) && fs_mgr_filesystem_available("f2fs")) {
         fs_type = "f2fs";
-        command = kMkF2fs + " -w 4096 -f -d1 -l" + android::base::Basename(kScratchMountPoint);
+        command = kMkF2fs + " -w ";
+        command += std::to_string(getpagesize());
+        command += " -f -d1 -l" + android::base::Basename(kScratchMountPoint);
     } else if (!access(kMkExt4.c_str(), X_OK) && fs_mgr_filesystem_available("ext4")) {
         fs_type = "ext4";
         command = kMkExt4 + " -F -b 4096 -t ext4 -m 0 -O has_journal -M " + kScratchMountPoint;
@@ -1008,21 +529,7 @@
         auto partition_size =
                 builder->AllocatableSpace() - builder->UsedSpace() + partition->size();
         if ((partition_size > kMinimumSize) || !partition->size()) {
-            // Leave some space for free space jitter of a few erase
-            // blocks, in case they are needed for any individual updates
-            // to any other partition that needs to be flashed while
-            // overlayfs is in force.  Of course if margin_size is not
-            // enough could normally get a flash failure, so
-            // ResizePartition() will delete the scratch partition in
-            // order to fulfill.  Deleting scratch will destroy all of
-            // the adb remount overrides :-( .
-            auto margin_size = uint64_t(3 * 256 * 1024);
-            BlockDeviceInfo info;
-            if (builder->GetBlockDeviceInfo(fs_mgr_get_super_partition_name(slot_number), &info)) {
-                margin_size = 3 * info.logical_block_size;
-            }
-            partition_size = std::max(std::min(kMinimumSize, partition_size - margin_size),
-                                      partition_size / 2);
+            partition_size = std::max(std::min(kMinimumSize, partition_size), partition_size / 2);
             if (partition_size > partition->size()) {
                 if (!builder->ResizePartition(partition, partition_size)) {
                     // Try to free up space by deallocating partitions in the other slot.
@@ -1030,8 +537,8 @@
 
                     partition_size =
                             builder->AllocatableSpace() - builder->UsedSpace() + partition->size();
-                    partition_size = std::max(std::min(kMinimumSize, partition_size - margin_size),
-                                              partition_size / 2);
+                    partition_size =
+                            std::max(std::min(kMinimumSize, partition_size), partition_size / 2);
                     if (!builder->ResizePartition(partition, partition_size)) {
                         LERROR << "resize " << partition_name;
                         return false;
@@ -1083,7 +590,7 @@
         return 0;
     }
 
-    auto ideal_size = std::min(super_info.size, (uint64_t(s.f_frsize) * s.f_bfree) / 2);
+    auto ideal_size = std::min(super_info.size, uint64_t(uint64_t(s.f_frsize) * s.f_bfree * 0.85));
 
     // Align up to the filesystem block size.
     if (auto remainder = ideal_size % s.f_bsize; remainder > 0) {
@@ -1190,7 +697,7 @@
     // If the partition exists, assume first that it can be mounted.
     if (partition_exists) {
         if (MountScratch(scratch_device)) {
-            if (fs_mgr_access(kScratchMountPoint + kOverlayTopDir) ||
+            if (fs_mgr_access(std::string(kScratchMountPoint) + kOverlayTopDir) ||
                 fs_mgr_filesystem_has_space(kScratchMountPoint)) {
                 return true;
             }
@@ -1210,12 +717,6 @@
     return MountScratch(scratch_device);
 }
 
-#if ALLOW_ADBD_DISABLE_VERITY
-constexpr bool kAllowOverlayfs = true;
-#else
-constexpr bool kAllowOverlayfs = false;
-#endif
-
 // NOTE: OverlayfsSetupAllowed() must be "stricter" than OverlayfsTeardownAllowed().
 // Setup is allowed only if teardown is also allowed.
 bool OverlayfsSetupAllowed(bool verbose = false) {
@@ -1249,114 +750,6 @@
 
 }  // namespace
 
-bool fs_mgr_wants_overlayfs(FstabEntry* entry) {
-    // Don't check entries that are managed by vold.
-    if (entry->fs_mgr_flags.vold_managed || entry->fs_mgr_flags.recovery_only) return false;
-
-    // *_other doesn't want overlayfs.
-    if (entry->fs_mgr_flags.slot_select_other) return false;
-
-    // Only concerned with readonly partitions.
-    if (!(entry->flags & MS_RDONLY)) return false;
-
-    // If unbindable, do not allow overlayfs as this could expose us to
-    // security issues.  On Android, this could also be used to turn off
-    // the ability to overlay an otherwise acceptable filesystem since
-    // /system and /vendor are never bound(sic) to.
-    if (entry->flags & MS_UNBINDABLE) return false;
-
-    if (!fs_mgr_overlayfs_enabled(entry)) return false;
-
-    return true;
-}
-
-Fstab fs_mgr_overlayfs_candidate_list(const Fstab& fstab) {
-    android::fs_mgr::Fstab mounts;
-    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
-        PLOG(ERROR) << "Failed to read /proc/mounts";
-        return {};
-    }
-
-    Fstab candidates;
-    for (const auto& entry : fstab) {
-        // Filter out partitions whose type doesn't match what's mounted.
-        // This avoids spammy behavior on devices which can mount different
-        // filesystems for each partition.
-        auto proc_mount_point = (entry.mount_point == "/system") ? "/" : entry.mount_point;
-        auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point);
-        if (!mounted || mounted->fs_type != entry.fs_type) {
-            continue;
-        }
-
-        FstabEntry new_entry = entry;
-        if (!fs_mgr_overlayfs_already_mounted(entry.mount_point) &&
-            !fs_mgr_wants_overlayfs(&new_entry)) {
-            continue;
-        }
-        auto new_mount_point = fs_mgr_mount_point(entry.mount_point);
-        auto duplicate_or_more_specific = false;
-        for (auto it = candidates.begin(); it != candidates.end();) {
-            auto it_mount_point = fs_mgr_mount_point(it->mount_point);
-            if ((it_mount_point == new_mount_point) ||
-                (android::base::StartsWith(new_mount_point, it_mount_point + "/"))) {
-                duplicate_or_more_specific = true;
-                break;
-            }
-            if (android::base::StartsWith(it_mount_point, new_mount_point + "/")) {
-                it = candidates.erase(it);
-            } else {
-                ++it;
-            }
-        }
-        if (!duplicate_or_more_specific) candidates.emplace_back(std::move(new_entry));
-    }
-    return candidates;
-}
-
-static void TryMountScratch() {
-    // Note we get the boot scratch device here, which means if scratch was
-    // just created through ImageManager, this could fail. In practice this
-    // should not happen because "remount" detects this scenario (by checking
-    // if verity is still disabled, i.e. no reboot occurred), and skips calling
-    // fs_mgr_overlayfs_mount_all().
-    auto scratch_device = GetBootScratchDevice();
-    if (!fs_mgr_rw_access(scratch_device)) {
-        return;
-    }
-    if (!WaitForFile(scratch_device, 10s)) {
-        return;
-    }
-    if (!MountScratch(scratch_device, true /* readonly */)) {
-        return;
-    }
-    auto has_overlayfs_dir = fs_mgr_access(kScratchMountPoint + kOverlayTopDir);
-    fs_mgr_overlayfs_umount_scratch();
-    if (has_overlayfs_dir) {
-        MountScratch(scratch_device);
-    }
-}
-
-bool fs_mgr_overlayfs_mount_all(Fstab* fstab) {
-    if (!OverlayfsSetupAllowed()) {
-        return false;
-    }
-    auto ret = true;
-    auto scratch_can_be_mounted = true;
-    for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
-        if (fs_mgr_is_verity_enabled(entry)) continue;
-        auto mount_point = fs_mgr_mount_point(entry.mount_point);
-        if (fs_mgr_overlayfs_already_mounted(mount_point)) {
-            continue;
-        }
-        if (scratch_can_be_mounted) {
-            scratch_can_be_mounted = false;
-            TryMountScratch();
-        }
-        ret &= fs_mgr_overlayfs_mount(entry);
-    }
-    return ret;
-}
-
 bool fs_mgr_overlayfs_setup(const Fstab& fstab, const char* mount_point, bool* want_reboot,
                             bool just_disabled_verity) {
     if (!OverlayfsSetupAllowed(/*verbose=*/true)) {
@@ -1580,22 +973,6 @@
     return rv;
 }
 
-bool fs_mgr_overlayfs_is_setup() {
-    if (!OverlayfsSetupAllowed()) {
-        return false;
-    }
-    if (fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) return true;
-    Fstab fstab;
-    if (!ReadDefaultFstab(&fstab)) {
-        return false;
-    }
-    for (const auto& entry : fs_mgr_overlayfs_candidate_list(fstab)) {
-        if (fs_mgr_is_verity_enabled(entry)) continue;
-        if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) return true;
-    }
-    return false;
-}
-
 namespace android {
 namespace fs_mgr {
 
@@ -1712,23 +1089,3 @@
 
 }  // namespace fs_mgr
 }  // namespace android
-
-bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only) {
-    Fstab fstab;
-    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
-        return false;
-    }
-    const auto lowerdir = kLowerdirOption + mount_point;
-    for (const auto& entry : fstab) {
-        if (overlay_only && "overlay" != entry.fs_type && "overlayfs" != entry.fs_type) continue;
-        if (mount_point != entry.mount_point) continue;
-        if (!overlay_only) return true;
-        const auto options = android::base::Split(entry.fs_options, ",");
-        for (const auto& opt : options) {
-            if (opt == lowerdir) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
diff --git a/fs_mgr/fs_mgr_overlayfs_control.h b/fs_mgr/fs_mgr_overlayfs_control.h
new file mode 100644
index 0000000..50e83e8
--- /dev/null
+++ b/fs_mgr/fs_mgr_overlayfs_control.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 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 <fstab/fstab.h>
+
+// If "mount_point" is non-null, set up exactly one overlay.
+//
+// If "mount_point" is null, setup any overlays.
+//
+// If |want_reboot| is non-null, and a reboot is needed to apply overlays, then
+// it will be true on return. The caller is responsible for initializing it.
+bool fs_mgr_overlayfs_setup(const android::fs_mgr::Fstab& fstab, const char* mount_point = nullptr,
+                            bool* want_reboot = nullptr, bool just_disabled_verity = true);
+
+enum class OverlayfsTeardownResult {
+    Ok,
+    Busy,  // Indicates that overlays are still in use.
+    Error
+};
+OverlayfsTeardownResult fs_mgr_overlayfs_teardown(const char* mount_point = nullptr,
+                                                  bool* want_reboot = nullptr);
+
+namespace android {
+namespace fs_mgr {
+
+void CleanupOldScratchFiles();
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/fs_mgr_overlayfs_mount.cpp b/fs_mgr/fs_mgr_overlayfs_mount.cpp
new file mode 100644
index 0000000..c057c2b
--- /dev/null
+++ b/fs_mgr/fs_mgr_overlayfs_mount.cpp
@@ -0,0 +1,802 @@
+/*
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <selinux/selinux.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/macros.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <fs_mgr.h>
+#include <fs_mgr/file_wait.h>
+#include <fs_mgr_dm_linear.h>
+#include <fs_mgr_overlayfs.h>
+#include <fstab/fstab.h>
+#include <libdm/dm.h>
+#include <libgsi/libgsi.h>
+#include <storage_literals/storage_literals.h>
+
+#include "fs_mgr_overlayfs_mount.h"
+#include "fs_mgr_priv.h"
+
+using namespace std::literals;
+using namespace android::dm;
+using namespace android::fs_mgr;
+using namespace android::storage_literals;
+
+constexpr char kPreferCacheBackingStorageProp[] = "fs_mgr.overlayfs.prefer_cache_backing_storage";
+
+bool fs_mgr_access(const std::string& path) {
+    return access(path.c_str(), F_OK) == 0;
+}
+
+const auto kLowerdirOption = "lowerdir=";
+const auto kUpperdirOption = "upperdir=";
+
+bool fs_mgr_in_recovery() {
+    // Check the existence of recovery binary instead of using the compile time
+    // __ANDROID_RECOVERY__ macro.
+    // If BOARD_USES_RECOVERY_AS_BOOT is true, both normal and recovery boot
+    // mode would use the same init binary, which would mean during normal boot
+    // the '/init' binary is actually a symlink pointing to
+    // init_second_stage.recovery, which would be compiled with
+    // __ANDROID_RECOVERY__ defined.
+    return fs_mgr_access("/system/bin/recovery");
+}
+
+bool fs_mgr_is_dsu_running() {
+    // Since android::gsi::CanBootIntoGsi() or android::gsi::MarkSystemAsGsi() is
+    // never called in recovery, the return value of android::gsi::IsGsiRunning()
+    // is not well-defined. In this case, just return false as being in recovery
+    // implies not running a DSU system.
+    if (fs_mgr_in_recovery()) return false;
+    return android::gsi::IsGsiRunning();
+}
+
+const auto kCacheMountPoint = "/cache";
+
+static bool IsABDevice() {
+    return !android::base::GetProperty("ro.boot.slot_suffix", "").empty();
+}
+
+std::vector<const std::string> OverlayMountPoints() {
+    // Never fallback to legacy cache mount point if within a DSU system,
+    // because running a DSU system implies the device supports dynamic
+    // partitions, which means legacy cache mustn't be used.
+    if (fs_mgr_is_dsu_running()) {
+        return {kScratchMountPoint};
+    }
+
+    // For non-A/B devices prefer cache backing storage if
+    // kPreferCacheBackingStorageProp property set.
+    if (!IsABDevice() && android::base::GetBoolProperty(kPreferCacheBackingStorageProp, false) &&
+        android::base::GetIntProperty("ro.vendor.api_level", -1) < __ANDROID_API_T__) {
+        return {kCacheMountPoint, kScratchMountPoint};
+    }
+
+    return {kScratchMountPoint, kCacheMountPoint};
+}
+
+static bool fs_mgr_is_dir(const std::string& path) {
+    struct stat st;
+    return !stat(path.c_str(), &st) && S_ISDIR(st.st_mode);
+}
+
+bool fs_mgr_rw_access(const std::string& path) {
+    if (path.empty()) return false;
+    return access(path.c_str(), R_OK | W_OK) == 0;
+}
+
+// At less than 1% or 8MB of free space return value of false,
+// means we will try to wrap with overlayfs.
+bool fs_mgr_filesystem_has_space(const std::string& mount_point) {
+    // If we have access issues to find out space remaining, return true
+    // to prevent us trying to override with overlayfs.
+    struct statvfs vst;
+    if (statvfs(mount_point.c_str(), &vst)) {
+        PLOG(ERROR) << "statvfs " << mount_point;
+        return true;
+    }
+
+    static constexpr int kPercentThreshold = 1;                       // 1%
+    static constexpr unsigned long kSizeThreshold = 8 * 1024 * 1024;  // 8MB
+
+    return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100)) &&
+           (static_cast<uint64_t>(vst.f_bfree) * vst.f_frsize) >= kSizeThreshold;
+}
+
+const auto kPhysicalDevice = "/dev/block/by-name/";
+
+static bool fs_mgr_update_blk_device(FstabEntry* entry) {
+    if (entry->fs_mgr_flags.logical) {
+        fs_mgr_update_logical_partition(entry);
+    }
+    if (fs_mgr_access(entry->blk_device)) {
+        return true;
+    }
+    if (entry->blk_device != "/dev/root") {
+        return false;
+    }
+
+    // special case for system-as-root (taimen and others)
+    auto blk_device = std::string(kPhysicalDevice) + "system";
+    if (!fs_mgr_access(blk_device)) {
+        blk_device += fs_mgr_get_slot_suffix();
+        if (!fs_mgr_access(blk_device)) {
+            return false;
+        }
+    }
+    entry->blk_device = blk_device;
+    return true;
+}
+
+static bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev) {
+    struct statfs fs;
+    if ((statfs((mount_point + "/lost+found").c_str(), &fs) == -1) ||
+        (fs.f_type != EXT4_SUPER_MAGIC)) {
+        return false;
+    }
+
+    android::base::unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC));
+    if (fd < 0) return false;
+
+    struct ext4_super_block sb;
+    if ((TEMP_FAILURE_RETRY(lseek64(fd, 1024, SEEK_SET)) < 0) ||
+        (TEMP_FAILURE_RETRY(read(fd, &sb, sizeof(sb))) < 0)) {
+        return false;
+    }
+
+    struct fs_info info;
+    if (ext4_parse_sb(&sb, &info) < 0) return false;
+
+    return (info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS) != 0;
+}
+
+#define F2FS_SUPER_OFFSET 1024
+#define F2FS_FEATURE_OFFSET 2180
+#define F2FS_FEATURE_RO 0x4000
+static bool fs_mgr_is_read_only_f2fs(const std::string& dev) {
+    if (!fs_mgr_is_f2fs(dev)) return false;
+
+    android::base::unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC));
+    if (fd < 0) return false;
+
+    __le32 feat;
+    if ((TEMP_FAILURE_RETRY(lseek64(fd, F2FS_SUPER_OFFSET + F2FS_FEATURE_OFFSET, SEEK_SET)) < 0) ||
+        (TEMP_FAILURE_RETRY(read(fd, &feat, sizeof(feat))) < 0)) {
+        return false;
+    }
+
+    return (feat & cpu_to_le32(F2FS_FEATURE_RO)) != 0;
+}
+
+static bool fs_mgr_overlayfs_enabled(FstabEntry* entry) {
+    // readonly filesystem, can not be mount -o remount,rw
+    // for squashfs, erofs or if free space is (near) zero making such a remount
+    // virtually useless, or if there are shared blocks that prevent remount,rw
+    if (!fs_mgr_filesystem_has_space(entry->mount_point)) {
+        return true;
+    }
+
+    // blk_device needs to be setup so we can check superblock.
+    // If we fail here, because during init first stage and have doubts.
+    if (!fs_mgr_update_blk_device(entry)) {
+        return true;
+    }
+
+    // f2fs read-only mode doesn't support remount,rw
+    if (fs_mgr_is_read_only_f2fs(entry->blk_device)) {
+        return true;
+    }
+
+    // check if ext4 de-dupe
+    auto has_shared_blocks = fs_mgr_has_shared_blocks(entry->mount_point, entry->blk_device);
+    if (!has_shared_blocks && (entry->mount_point == "/system")) {
+        has_shared_blocks = fs_mgr_has_shared_blocks("/", entry->blk_device);
+    }
+    return has_shared_blocks;
+}
+
+static std::string fs_mgr_get_overlayfs_candidate(const std::string& mount_point) {
+    if (!fs_mgr_is_dir(mount_point)) return "";
+    const auto base = android::base::Basename(mount_point) + "/";
+    for (const auto& overlay_mount_point : OverlayMountPoints()) {
+        auto dir = overlay_mount_point + kOverlayTopDir + "/" + base;
+        auto upper = dir + kUpperName;
+        if (!fs_mgr_is_dir(upper)) continue;
+        auto work = dir + kWorkName;
+        if (!fs_mgr_is_dir(work)) continue;
+        if (!fs_mgr_rw_access(work)) continue;
+        return dir;
+    }
+    return "";
+}
+
+static inline bool KernelSupportsUserXattrs() {
+    struct utsname uts;
+    uname(&uts);
+
+    int major, minor;
+    if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) {
+        return false;
+    }
+    return major > 5 || (major == 5 && minor >= 15);
+}
+
+const std::string fs_mgr_mount_point(const std::string& mount_point) {
+    if ("/"s != mount_point) return mount_point;
+    return "/system";
+}
+
+// default options for mount_point, returns empty string for none available.
+static std::string fs_mgr_get_overlayfs_options(const FstabEntry& entry) {
+    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
+    auto candidate = fs_mgr_get_overlayfs_candidate(mount_point);
+    if (candidate.empty()) return "";
+    auto ret = kLowerdirOption + mount_point + "," + kUpperdirOption + candidate + kUpperName +
+               ",workdir=" + candidate + kWorkName;
+    if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kOverrideCredsRequired) {
+        ret += ",override_creds=off";
+    }
+    if (KernelSupportsUserXattrs()) {
+        ret += ",userxattr";
+    }
+    for (const auto& flag : android::base::Split(entry.fs_options, ",")) {
+        if (android::base::StartsWith(flag, "context=")) {
+            ret += "," + flag;
+        }
+    }
+    return ret;
+}
+
+bool AutoSetFsCreateCon::Set(const std::string& context) {
+    if (setfscreatecon(context.c_str())) {
+        PLOG(ERROR) << "setfscreatecon " << context;
+        return false;
+    }
+    ok_ = true;
+    return true;
+}
+
+bool AutoSetFsCreateCon::Restore() {
+    if (restored_ || !ok_) {
+        return true;
+    }
+    if (setfscreatecon(nullptr)) {
+        PLOG(ERROR) << "setfscreatecon null";
+        return false;
+    }
+    restored_ = true;
+    return true;
+}
+
+// Returns true if immediate unmount succeeded and the scratch mount point was
+// removed.
+bool fs_mgr_overlayfs_umount_scratch() {
+    if (umount(kScratchMountPoint) != 0) {
+        return false;
+    }
+    if (rmdir(kScratchMountPoint) != 0 && errno != ENOENT) {
+        PLOG(ERROR) << "rmdir " << kScratchMountPoint;
+    }
+    return true;
+}
+
+static bool fs_mgr_overlayfs_set_shared_mount(const std::string& mount_point, bool shared_flag) {
+    auto ret = mount(nullptr, mount_point.c_str(), nullptr, shared_flag ? MS_SHARED : MS_PRIVATE,
+                     nullptr);
+    if (ret) {
+        PERROR << "__mount(target=" << mount_point
+               << ",flag=" << (shared_flag ? "MS_SHARED" : "MS_PRIVATE") << ")=" << ret;
+        // If "/system" doesn't look like a mountpoint, retry with "/".
+        if (errno == EINVAL && mount_point == "/system") {
+            return fs_mgr_overlayfs_set_shared_mount("/", shared_flag);
+        }
+        return false;
+    }
+    return true;
+}
+
+static bool fs_mgr_overlayfs_move_mount(const std::string& source, const std::string& target) {
+    auto ret = mount(source.c_str(), target.c_str(), nullptr, MS_MOVE, nullptr);
+    if (ret) {
+        PERROR << "__mount(source=" << source << ",target=" << target << ",flag=MS_MOVE)=" << ret;
+        return false;
+    }
+    return true;
+}
+
+struct mount_info {
+    std::string mount_point;
+    bool shared_flag;
+};
+
+static std::vector<mount_info> ReadMountinfoFromFile(const std::string& path) {
+    std::vector<mount_info> info;
+
+    auto file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
+    if (!file) {
+        PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'";
+        return info;
+    }
+
+    ssize_t len;
+    size_t alloc_len = 0;
+    char* line = nullptr;
+    while ((len = getline(&line, &alloc_len, file.get())) != -1) {
+        /* if the last character is a newline, shorten the string by 1 byte */
+        if (line[len - 1] == '\n') {
+            line[len - 1] = '\0';
+        }
+
+        static constexpr char delim[] = " \t";
+        char* save_ptr;
+        if (!strtok_r(line, delim, &save_ptr)) {
+            LERROR << "Error parsing mount ID";
+            break;
+        }
+        if (!strtok_r(nullptr, delim, &save_ptr)) {
+            LERROR << "Error parsing parent ID";
+            break;
+        }
+        if (!strtok_r(nullptr, delim, &save_ptr)) {
+            LERROR << "Error parsing mount source";
+            break;
+        }
+        if (!strtok_r(nullptr, delim, &save_ptr)) {
+            LERROR << "Error parsing root";
+            break;
+        }
+
+        char* p;
+        if (!(p = strtok_r(nullptr, delim, &save_ptr))) {
+            LERROR << "Error parsing mount_point";
+            break;
+        }
+        mount_info entry = {p, false};
+
+        if (!strtok_r(nullptr, delim, &save_ptr)) {
+            LERROR << "Error parsing mount_flags";
+            break;
+        }
+
+        while ((p = strtok_r(nullptr, delim, &save_ptr))) {
+            if ((p[0] == '-') && (p[1] == '\0')) break;
+            if (android::base::StartsWith(p, "shared:")) entry.shared_flag = true;
+        }
+        if (!p) {
+            LERROR << "Error parsing fields";
+            break;
+        }
+        info.emplace_back(std::move(entry));
+    }
+
+    free(line);
+    if (info.empty()) {
+        LERROR << __FUNCTION__ << "(): failed to load mountinfo from : '" << path << "'";
+    }
+    return info;
+}
+
+static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
+    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
+    const auto options = fs_mgr_get_overlayfs_options(entry);
+    if (options.empty()) return false;
+
+    auto retval = true;
+
+    struct move_entry {
+        std::string mount_point;
+        std::string dir;
+        bool shared_flag;
+    };
+    std::vector<move_entry> move;
+    auto parent_private = false;
+    auto parent_made_private = false;
+    auto dev_private = false;
+    auto dev_made_private = false;
+    for (auto& entry : ReadMountinfoFromFile("/proc/self/mountinfo")) {
+        if ((entry.mount_point == mount_point) && !entry.shared_flag) {
+            parent_private = true;
+        }
+        if ((entry.mount_point == "/dev") && !entry.shared_flag) {
+            dev_private = true;
+        }
+
+        if (!android::base::StartsWith(entry.mount_point, mount_point + "/")) {
+            continue;
+        }
+        if (std::find_if(move.begin(), move.end(), [&entry](const auto& it) {
+                return android::base::StartsWith(entry.mount_point, it.mount_point + "/");
+            }) != move.end()) {
+            continue;
+        }
+
+        // use as the bound directory in /dev.
+        AutoSetFsCreateCon createcon;
+        auto new_context = fs_mgr_get_context(entry.mount_point);
+        if (new_context.empty() || !createcon.Set(new_context)) {
+            continue;
+        }
+        move_entry new_entry = {std::move(entry.mount_point), "/dev/TemporaryDir-XXXXXX",
+                                entry.shared_flag};
+        const auto target = mkdtemp(new_entry.dir.data());
+        if (!createcon.Restore()) {
+            return false;
+        }
+        if (!target) {
+            retval = false;
+            PERROR << "temporary directory for MS_BIND";
+            continue;
+        }
+
+        if (!parent_private && !parent_made_private) {
+            parent_made_private = fs_mgr_overlayfs_set_shared_mount(mount_point, false);
+        }
+        if (new_entry.shared_flag) {
+            new_entry.shared_flag = fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, false);
+        }
+        if (!fs_mgr_overlayfs_move_mount(new_entry.mount_point, new_entry.dir)) {
+            retval = false;
+            if (new_entry.shared_flag) {
+                fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, true);
+            }
+            continue;
+        }
+        move.emplace_back(std::move(new_entry));
+    }
+
+    // hijack __mount() report format to help triage
+    auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
+    const auto opt_list = android::base::Split(options, ",");
+    for (const auto& opt : opt_list) {
+        if (android::base::StartsWith(opt, kUpperdirOption)) {
+            report = report + "," + opt;
+            break;
+        }
+    }
+    report = report + ")=";
+
+    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
+                     options.c_str());
+    if (ret) {
+        retval = false;
+        PERROR << report << ret;
+    } else {
+        LINFO << report << ret;
+    }
+
+    // Move submounts back.
+    for (const auto& entry : move) {
+        if (!dev_private && !dev_made_private) {
+            dev_made_private = fs_mgr_overlayfs_set_shared_mount("/dev", false);
+        }
+
+        if (!fs_mgr_overlayfs_move_mount(entry.dir, entry.mount_point)) {
+            retval = false;
+        } else if (entry.shared_flag &&
+                   !fs_mgr_overlayfs_set_shared_mount(entry.mount_point, true)) {
+            retval = false;
+        }
+        rmdir(entry.dir.c_str());
+    }
+    if (dev_made_private) {
+        fs_mgr_overlayfs_set_shared_mount("/dev", true);
+    }
+    if (parent_made_private) {
+        fs_mgr_overlayfs_set_shared_mount(mount_point, true);
+    }
+
+    return retval;
+}
+
+// Mount kScratchMountPoint
+static bool MountScratch(const std::string& device_path, bool readonly = false) {
+    if (readonly) {
+        if (!fs_mgr_access(device_path)) {
+            LOG(ERROR) << "Path does not exist: " << device_path;
+            return false;
+        }
+    } else if (!fs_mgr_rw_access(device_path)) {
+        LOG(ERROR) << "Path does not exist or is not readwrite: " << device_path;
+        return false;
+    }
+
+    std::vector<const char*> filesystem_candidates;
+    if (fs_mgr_is_f2fs(device_path)) {
+        filesystem_candidates = {"f2fs", "ext4"};
+    } else if (fs_mgr_is_ext4(device_path)) {
+        filesystem_candidates = {"ext4", "f2fs"};
+    } else {
+        LOG(ERROR) << "Scratch partition is not f2fs or ext4";
+        return false;
+    }
+
+    AutoSetFsCreateCon createcon(kOverlayfsFileContext);
+    if (!createcon.Ok()) {
+        return false;
+    }
+    if (mkdir(kScratchMountPoint, 0755) && (errno != EEXIST)) {
+        PERROR << "create " << kScratchMountPoint;
+        return false;
+    }
+
+    FstabEntry entry;
+    entry.blk_device = device_path;
+    entry.mount_point = kScratchMountPoint;
+    entry.flags = MS_NOATIME | MS_RDONLY;
+    if (!readonly) {
+        entry.flags &= ~MS_RDONLY;
+        entry.flags |= MS_SYNCHRONOUS;
+        entry.fs_options = "nodiscard";
+        fs_mgr_set_blk_ro(device_path, false);
+    }
+    // check_fs requires apex runtime library
+    if (fs_mgr_overlayfs_already_mounted("/data", false)) {
+        entry.fs_mgr_flags.check = true;
+    }
+    bool mounted = false;
+    for (auto fs_type : filesystem_candidates) {
+        entry.fs_type = fs_type;
+        if (fs_mgr_do_mount_one(entry) == 0) {
+            mounted = true;
+            break;
+        }
+    }
+    if (!createcon.Restore()) {
+        return false;
+    }
+    if (!mounted) {
+        rmdir(kScratchMountPoint);
+        return false;
+    }
+    return true;
+}
+
+const std::string kMkF2fs("/system/bin/make_f2fs");
+const std::string kMkExt4("/system/bin/mke2fs");
+
+// Note: The scratch partition of DSU is managed by gsid, and should be initialized during
+// first-stage-mount. Just check if the DM device for DSU scratch partition is created or not.
+static std::string GetDsuScratchDevice() {
+    auto& dm = DeviceMapper::Instance();
+    std::string device;
+    if (dm.GetState(android::gsi::kDsuScratch) != DmDeviceState::INVALID &&
+        dm.GetDmDevicePathByName(android::gsi::kDsuScratch, &device)) {
+        return device;
+    }
+    return "";
+}
+
+// This returns the scratch device that was detected during early boot (first-
+// stage init). If the device was created later, for example during setup for
+// the adb remount command, it can return an empty string since it does not
+// query ImageManager. (Note that ImageManager in first-stage init will always
+// use device-mapper, since /data is not available to use loop devices.)
+static std::string GetBootScratchDevice() {
+    // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
+    if (fs_mgr_is_dsu_running()) {
+        return GetDsuScratchDevice();
+    }
+
+    auto& dm = DeviceMapper::Instance();
+
+    // If there is a scratch partition allocated in /data or on super, we
+    // automatically prioritize that over super_other or system_other.
+    // Some devices, for example, have a write-protected eMMC and the
+    // super partition cannot be used even if it exists.
+    std::string device;
+    auto partition_name = android::base::Basename(kScratchMountPoint);
+    if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
+        dm.GetDmDevicePathByName(partition_name, &device)) {
+        return device;
+    }
+
+    return "";
+}
+
+// NOTE: OverlayfsSetupAllowed() must be "stricter" than OverlayfsTeardownAllowed().
+// Setup is allowed only if teardown is also allowed.
+bool OverlayfsSetupAllowed(bool verbose = false) {
+    if (!kAllowOverlayfs) {
+        if (verbose) {
+            LOG(ERROR) << "Overlayfs remounts can only be used in debuggable builds";
+        }
+        return false;
+    }
+    // Check mandatory kernel patches.
+    if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) {
+        if (verbose) {
+            LOG(ERROR) << "Kernel does not support overlayfs";
+        }
+        return false;
+    }
+    // in recovery or fastbootd, not allowed!
+    if (fs_mgr_in_recovery()) {
+        if (verbose) {
+            LOG(ERROR) << "Unsupported overlayfs setup from recovery";
+        }
+        return false;
+    }
+    return true;
+}
+
+bool fs_mgr_wants_overlayfs(FstabEntry* entry) {
+    // Don't check entries that are managed by vold.
+    if (entry->fs_mgr_flags.vold_managed || entry->fs_mgr_flags.recovery_only) return false;
+
+    // *_other doesn't want overlayfs.
+    if (entry->fs_mgr_flags.slot_select_other) return false;
+
+    // Only concerned with readonly partitions.
+    if (!(entry->flags & MS_RDONLY)) return false;
+
+    // If unbindable, do not allow overlayfs as this could expose us to
+    // security issues.  On Android, this could also be used to turn off
+    // the ability to overlay an otherwise acceptable filesystem since
+    // /system and /vendor are never bound(sic) to.
+    if (entry->flags & MS_UNBINDABLE) return false;
+
+    if (!fs_mgr_overlayfs_enabled(entry)) return false;
+
+    return true;
+}
+
+Fstab fs_mgr_overlayfs_candidate_list(const Fstab& fstab) {
+    android::fs_mgr::Fstab mounts;
+    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
+        PLOG(ERROR) << "Failed to read /proc/mounts";
+        return {};
+    }
+
+    Fstab candidates;
+    for (const auto& entry : fstab) {
+        // Filter out partitions whose type doesn't match what's mounted.
+        // This avoids spammy behavior on devices which can mount different
+        // filesystems for each partition.
+        auto proc_mount_point = (entry.mount_point == "/system") ? "/" : entry.mount_point;
+        auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point);
+        if (!mounted || mounted->fs_type != entry.fs_type) {
+            continue;
+        }
+
+        FstabEntry new_entry = entry;
+        if (!fs_mgr_overlayfs_already_mounted(entry.mount_point) &&
+            !fs_mgr_wants_overlayfs(&new_entry)) {
+            continue;
+        }
+        auto new_mount_point = fs_mgr_mount_point(entry.mount_point);
+        auto duplicate_or_more_specific = false;
+        for (auto it = candidates.begin(); it != candidates.end();) {
+            auto it_mount_point = fs_mgr_mount_point(it->mount_point);
+            if ((it_mount_point == new_mount_point) ||
+                (android::base::StartsWith(new_mount_point, it_mount_point + "/"))) {
+                duplicate_or_more_specific = true;
+                break;
+            }
+            if (android::base::StartsWith(it_mount_point, new_mount_point + "/")) {
+                it = candidates.erase(it);
+            } else {
+                ++it;
+            }
+        }
+        if (!duplicate_or_more_specific) candidates.emplace_back(std::move(new_entry));
+    }
+    return candidates;
+}
+
+static void TryMountScratch() {
+    // Note we get the boot scratch device here, which means if scratch was
+    // just created through ImageManager, this could fail. In practice this
+    // should not happen because "remount" detects this scenario (by checking
+    // if verity is still disabled, i.e. no reboot occurred), and skips calling
+    // fs_mgr_overlayfs_mount_all().
+    auto scratch_device = GetBootScratchDevice();
+    if (!fs_mgr_rw_access(scratch_device)) {
+        return;
+    }
+    if (!WaitForFile(scratch_device, 10s)) {
+        return;
+    }
+    if (!MountScratch(scratch_device, true /* readonly */)) {
+        return;
+    }
+    auto has_overlayfs_dir = fs_mgr_access(std::string(kScratchMountPoint) + kOverlayTopDir);
+    fs_mgr_overlayfs_umount_scratch();
+    if (has_overlayfs_dir) {
+        MountScratch(scratch_device);
+    }
+}
+
+bool fs_mgr_overlayfs_mount_all(Fstab* fstab) {
+    if (!OverlayfsSetupAllowed()) {
+        return false;
+    }
+    auto ret = true;
+    auto scratch_can_be_mounted = true;
+    for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
+        if (fs_mgr_is_verity_enabled(entry)) continue;
+        auto mount_point = fs_mgr_mount_point(entry.mount_point);
+        if (fs_mgr_overlayfs_already_mounted(mount_point)) {
+            continue;
+        }
+        if (scratch_can_be_mounted) {
+            scratch_can_be_mounted = false;
+            TryMountScratch();
+        }
+        ret &= fs_mgr_overlayfs_mount(entry);
+    }
+    return ret;
+}
+
+bool fs_mgr_overlayfs_is_setup() {
+    if (!OverlayfsSetupAllowed()) {
+        return false;
+    }
+    if (fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) return true;
+    Fstab fstab;
+    if (!ReadDefaultFstab(&fstab)) {
+        return false;
+    }
+    for (const auto& entry : fs_mgr_overlayfs_candidate_list(fstab)) {
+        if (fs_mgr_is_verity_enabled(entry)) continue;
+        if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) return true;
+    }
+    return false;
+}
+
+bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only) {
+    Fstab fstab;
+    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
+        return false;
+    }
+    const auto lowerdir = kLowerdirOption + mount_point;
+    for (const auto& entry : fstab) {
+        if (overlay_only && "overlay" != entry.fs_type && "overlayfs" != entry.fs_type) continue;
+        if (mount_point != entry.mount_point) continue;
+        if (!overlay_only) return true;
+        const auto options = android::base::Split(entry.fs_options, ",");
+        for (const auto& opt : options) {
+            if (opt == lowerdir) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
diff --git a/fs_mgr/fs_mgr_overlayfs_mount.h b/fs_mgr/fs_mgr_overlayfs_mount.h
new file mode 100644
index 0000000..ae3ea84
--- /dev/null
+++ b/fs_mgr/fs_mgr_overlayfs_mount.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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 <fstab/fstab.h>
+
+bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only = true);
+bool fs_mgr_wants_overlayfs(android::fs_mgr::FstabEntry* entry);
+android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab);
+
+#if ALLOW_ADBD_DISABLE_VERITY
+constexpr bool kAllowOverlayfs = true;
+#else
+constexpr bool kAllowOverlayfs = false;
+#endif
+
+class AutoSetFsCreateCon final {
+  public:
+    AutoSetFsCreateCon() {}
+    AutoSetFsCreateCon(const std::string& context) { Set(context); }
+    ~AutoSetFsCreateCon() { Restore(); }
+
+    bool Ok() const { return ok_; }
+    bool Set(const std::string& context);
+    bool Restore();
+
+  private:
+    bool ok_ = false;
+    bool restored_ = false;
+};
+
+constexpr auto kScratchMountPoint = "/mnt/scratch";
+constexpr char kOverlayfsFileContext[] = "u:object_r:overlayfs_file:s0";
+
+constexpr auto kUpperName = "upper";
+constexpr auto kWorkName = "work";
+constexpr auto kOverlayTopDir = "/overlay";
+
+bool fs_mgr_is_dsu_running();
+bool fs_mgr_in_recovery();
+bool fs_mgr_access(const std::string& path);
+bool fs_mgr_rw_access(const std::string& path);
+bool fs_mgr_filesystem_has_space(const std::string& mount_point);
+const std::string fs_mgr_mount_point(const std::string& mount_point);
+bool fs_mgr_overlayfs_umount_scratch();
+std::vector<const std::string> OverlayMountPoints();
diff --git a/fs_mgr/fs_mgr_priv_overlayfs.h b/fs_mgr/fs_mgr_priv_overlayfs.h
deleted file mode 100644
index 2033701..0000000
--- a/fs_mgr/fs_mgr_priv_overlayfs.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2022 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 <fstab/fstab.h>
-
-bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only = true);
-bool fs_mgr_wants_overlayfs(android::fs_mgr::FstabEntry* entry);
-android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab);
-
-// If "mount_point" is non-null, set up exactly one overlay.
-// If "mount_point" is null, setup any overlays.
-//
-// If |want_reboot| is non-null, and a reboot is needed to apply overlays, then
-// it will be true on return. The caller is responsible for initializing it.
-bool fs_mgr_overlayfs_setup(const android::fs_mgr::Fstab& fstab, const char* mount_point = nullptr,
-                            bool* want_reboot = nullptr, bool just_disabled_verity = true);
-
-enum class OverlayfsTeardownResult {
-    Ok,
-    Busy,  // Indicates that overlays are still in use.
-    Error
-};
-OverlayfsTeardownResult fs_mgr_overlayfs_teardown(const char* mount_point = nullptr,
-                                                  bool* want_reboot = nullptr);
-
-namespace android {
-namespace fs_mgr {
-
-void CleanupOldScratchFiles();
-
-}  // namespace fs_mgr
-}  // namespace android
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 5a9f391..4c45828 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -43,7 +43,8 @@
 #include <libavb_user/libavb_user.h>
 #include <libgsi/libgsid.h>
 
-#include "fs_mgr_priv_overlayfs.h"
+#include "fs_mgr_overlayfs_control.h"
+#include "fs_mgr_overlayfs_mount.h"
 
 using namespace std::literals;
 using android::fs_mgr::Fstab;
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 43de6d8..bc4a7a6 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -85,13 +85,10 @@
 #define FS_MGR_DOMNT_FAILED (-1)
 #define FS_MGR_DOMNT_BUSY (-2)
 #define FS_MGR_DOMNT_SUCCESS 0
-int fs_mgr_do_mount(android::fs_mgr::Fstab* fstab, const char* n_name, char* n_blk_device,
-                    char* tmp_mount_point);
-int fs_mgr_do_mount(android::fs_mgr::Fstab* fstab, const char* n_name, char* n_blk_device,
-                    char* tmp_mount_point, bool need_cp, bool metadata_encrypted);
+int fs_mgr_do_mount(android::fs_mgr::Fstab* fstab, const std::string& n_name,
+                    const std::string& n_blk_device, int needs_checkpoint, bool needs_encrypt);
 int fs_mgr_do_mount_one(const android::fs_mgr::FstabEntry& entry,
                         const std::string& mount_point = "");
-int fs_mgr_do_tmpfs_mount(const char *n_name);
 bool fs_mgr_load_verity_state(int* mode);
 // Returns true if verity is enabled on this particular FstabEntry.
 bool fs_mgr_is_verity_enabled(const android::fs_mgr::FstabEntry& entry);
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index a914b53..9cb1546 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -101,6 +101,7 @@
 bool SkipMountWithConfig(const std::string& skip_config, Fstab* fstab, bool verbose);
 
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
+bool ReadFstabFromProcMounts(Fstab* fstab);
 bool ReadFstabFromDt(Fstab* fstab, bool verbose = true);
 bool ReadDefaultFstab(Fstab* fstab);
 bool SkipMountingPartitions(Fstab* fstab, bool verbose = false);
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 1e8c14f..3a9ed9b 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -106,6 +106,10 @@
     if (!GetDeviceUniquePath(name, &unique_path)) {
         LOG(ERROR) << "Failed to get unique path for device " << name;
     }
+    // Expect to have uevent generated if the unique path actually exists. This may not exist
+    // if the device was created but has never been activated before it gets deleted.
+    bool need_uevent = !unique_path.empty() && access(unique_path.c_str(), F_OK) == 0;
+
     struct dm_ioctl io;
     InitIo(&io, name);
 
@@ -116,7 +120,7 @@
 
     // Check to make sure appropriate uevent is generated so ueventd will
     // do the right thing and remove the corresponding device node and symlinks.
-    if ((io.flags & DM_UEVENT_GENERATED_FLAG) == 0) {
+    if (need_uevent && (io.flags & DM_UEVENT_GENERATED_FLAG) == 0) {
         LOG(ERROR) << "Didn't generate uevent for [" << name << "] removal";
         return false;
     }
diff --git a/fs_mgr/libdm/dm_table.cpp b/fs_mgr/libdm/dm_table.cpp
index efe03ab..b546995 100644
--- a/fs_mgr/libdm/dm_table.cpp
+++ b/fs_mgr/libdm/dm_table.cpp
@@ -38,11 +38,11 @@
 bool DmTable::valid() const {
     if (targets_.empty()) {
         LOG(ERROR) << "Device-mapper table must have at least one target.";
-        return "";
+        return false;
     }
     if (targets_[0]->start() != 0) {
         LOG(ERROR) << "Device-mapper table must start at logical sector 0.";
-        return "";
+        return false;
     }
     return true;
 }
diff --git a/fs_mgr/libfiemap/Android.bp b/fs_mgr/libfiemap/Android.bp
index 5deba65..ddda648 100644
--- a/fs_mgr/libfiemap/Android.bp
+++ b/fs_mgr/libfiemap/Android.bp
@@ -93,6 +93,9 @@
     test_options: {
         min_shipping_api_level: 29,
     },
+    header_libs: [
+        "libstorage_literals_headers",
+    ],
     require_root: true,
 }
 
diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp
index 275388e..06e210e 100644
--- a/fs_mgr/libfiemap/fiemap_writer.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -458,9 +458,34 @@
             return FiemapStatus::Error();
     }
 
-    if (fallocate(file_fd, 0, 0, file_size)) {
-        PLOG(ERROR) << "Failed to allocate space for file: " << file_path << " size: " << file_size;
-        return FiemapStatus::FromErrno(errno);
+    // F2FS can return EAGAIN and partially fallocate. Keep trying to fallocate,
+    // and if we don't make forward progress, return ENOSPC.
+    std::optional<off_t> prev_size;
+    while (true) {
+        if (fallocate(file_fd, 0, 0, file_size) == 0) {
+            break;
+        }
+        if (errno != EAGAIN) {
+            PLOG(ERROR) << "Failed to allocate space for file: " << file_path
+                        << " size: " << file_size;
+            return FiemapStatus::FromErrno(errno);
+        }
+
+        struct stat s;
+        if (fstat(file_fd, &s) < 0) {
+            PLOG(ERROR) << "Failed to fstat after fallocate failure: " << file_path;
+            return FiemapStatus::FromErrno(errno);
+        }
+        if (!prev_size) {
+            prev_size = {s.st_size};
+            continue;
+        }
+        if (*prev_size >= s.st_size) {
+            LOG(ERROR) << "Fallocate retry failed, got " << s.st_size << ", asked for "
+                       << file_size;
+            return FiemapStatus(FiemapStatus::ErrorCode::NO_SPACE);
+        }
+        LOG(INFO) << "Retrying fallocate, got " << s.st_size << ", asked for " << file_size;
     }
 
     if (need_explicit_writes) {
diff --git a/fs_mgr/libfiemap/fiemap_writer_test.cpp b/fs_mgr/libfiemap/fiemap_writer_test.cpp
index c65481b..bd97a78 100644
--- a/fs_mgr/libfiemap/fiemap_writer_test.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer_test.cpp
@@ -22,21 +22,25 @@
 #include <string.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
 #include <sys/vfs.h>
 #include <unistd.h>
 
 #include <string>
+#include <utility>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
+#include <fstab/fstab.h>
 #include <gtest/gtest.h>
 #include <libdm/loop_control.h>
 #include <libfiemap/fiemap_writer.h>
 #include <libfiemap/split_fiemap_writer.h>
 #include <libgsi/libgsi.h>
+#include <storage_literals/storage_literals.h>
 
 #include "utility.h"
 
@@ -46,6 +50,7 @@
 using namespace std;
 using namespace std::string_literals;
 using namespace android::fiemap;
+using namespace android::storage_literals;
 using unique_fd = android::base::unique_fd;
 using LoopDevice = android::dm::LoopDevice;
 
@@ -427,90 +432,123 @@
     ASSERT_FALSE(ptr->Write(buffer.get(), kSize));
 }
 
-class VerifyBlockWritesExt4 : public ::testing::Test {
+// Get max file size and free space.
+std::pair<uint64_t, uint64_t> GetBigFileLimit(const std::string& mount_point) {
+    struct statvfs fs;
+    if (statvfs(mount_point.c_str(), &fs) < 0) {
+        PLOG(ERROR) << "statfs failed";
+        return {0, 0};
+    }
+
+    auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1);
+    auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize;
+
+    LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free;
+
+    return {fs_limit, fs_free};
+}
+
+class FsTest : public ::testing::Test {
+  protected:
     // 2GB Filesystem and 4k block size by default
     static constexpr uint64_t block_size = 4096;
-    static constexpr uint64_t fs_size = 2147483648;
+    static constexpr uint64_t fs_size = 64 * 1024 * 1024;
 
-  protected:
-    void SetUp() override {
-        fs_path = std::string(getenv("TMPDIR")) + "/ext4_2G.img";
+    void SetUp() {
+        android::fs_mgr::Fstab fstab;
+        ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab));
+
+        ASSERT_EQ(access(tmpdir_.path, F_OK), 0);
+        fs_path_ = tmpdir_.path + "/fs_image"s;
+        mntpoint_ = tmpdir_.path + "/mnt_point"s;
+
+        auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data");
+        ASSERT_NE(entry, nullptr);
+        if (entry->fs_type == "ext4") {
+            SetUpExt4();
+        } else if (entry->fs_type == "f2fs") {
+            SetUpF2fs();
+        } else {
+            FAIL() << "Unrecognized fs_type: " << entry->fs_type;
+        }
+    }
+
+    void SetUpExt4() {
         uint64_t count = fs_size / block_size;
         std::string dd_cmd =
                 ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
                                               " count=%" PRIu64 " > /dev/null 2>&1",
-                                              fs_path.c_str(), block_size, count);
+                                              fs_path_.c_str(), block_size, count);
         std::string mkfs_cmd =
-                ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path.c_str());
+                ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path_.c_str());
         // create mount point
-        mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
-        ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+        ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0);
         // create file for the file system
         int ret = system(dd_cmd.c_str());
         ASSERT_EQ(ret, 0);
         // Get and attach a loop device to the filesystem we created
-        LoopDevice loop_dev(fs_path, 10s);
+        LoopDevice loop_dev(fs_path_, 10s);
         ASSERT_TRUE(loop_dev.valid());
         // create file system
         ret = system(mkfs_cmd.c_str());
         ASSERT_EQ(ret, 0);
 
         // mount the file system
-        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0);
+        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "ext4", 0, nullptr), 0);
     }
 
-    void TearDown() override {
-        umount(mntpoint.c_str());
-        rmdir(mntpoint.c_str());
-        unlink(fs_path.c_str());
-    }
-
-    std::string mntpoint;
-    std::string fs_path;
-};
-
-class VerifyBlockWritesF2fs : public ::testing::Test {
-    // 2GB Filesystem and 4k block size by default
-    static constexpr uint64_t block_size = 4096;
-    static constexpr uint64_t fs_size = 2147483648;
-
-  protected:
-    void SetUp() override {
-        fs_path = std::string(getenv("TMPDIR")) + "/f2fs_2G.img";
+    void SetUpF2fs() {
         uint64_t count = fs_size / block_size;
         std::string dd_cmd =
                 ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
                                               " count=%" PRIu64 " > /dev/null 2>&1",
-                                              fs_path.c_str(), block_size, count);
+                                              fs_path_.c_str(), block_size, count);
         std::string mkfs_cmd =
-                ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path.c_str());
+                ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path_.c_str());
         // create mount point
-        mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
-        ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+        ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0);
         // create file for the file system
         int ret = system(dd_cmd.c_str());
         ASSERT_EQ(ret, 0);
         // Get and attach a loop device to the filesystem we created
-        LoopDevice loop_dev(fs_path, 10s);
+        LoopDevice loop_dev(fs_path_, 10s);
         ASSERT_TRUE(loop_dev.valid());
         // create file system
         ret = system(mkfs_cmd.c_str());
         ASSERT_EQ(ret, 0);
 
         // mount the file system
-        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0);
+        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "f2fs", 0, nullptr), 0);
     }
 
     void TearDown() override {
-        umount(mntpoint.c_str());
-        rmdir(mntpoint.c_str());
-        unlink(fs_path.c_str());
+        umount(mntpoint_.c_str());
+        rmdir(mntpoint_.c_str());
+        unlink(fs_path_.c_str());
     }
 
-    std::string mntpoint;
-    std::string fs_path;
+    TemporaryDir tmpdir_;
+    std::string mntpoint_;
+    std::string fs_path_;
 };
 
+TEST_F(FsTest, LowSpaceError) {
+    auto limits = GetBigFileLimit(mntpoint_);
+    ASSERT_GE(limits.first, 0);
+
+    FiemapUniquePtr ptr;
+
+    auto test_file = mntpoint_ + "/big_file";
+    auto status = FiemapWriter::Open(test_file, limits.first, &ptr);
+    ASSERT_FALSE(status.is_ok());
+    ASSERT_EQ(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE);
+
+    // Also test for EFBIG.
+    status = FiemapWriter::Open(test_file, 16_TiB, &ptr);
+    ASSERT_FALSE(status.is_ok());
+    ASSERT_NE(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE);
+}
+
 bool DetermineBlockSize() {
     struct statfs s;
     if (statfs(gTestDir.c_str(), &s)) {
diff --git a/fs_mgr/libfiemap/include/libfiemap/fiemap_status.h b/fs_mgr/libfiemap/include/libfiemap/fiemap_status.h
index d7b2cf1..1365ba4 100644
--- a/fs_mgr/libfiemap/include/libfiemap/fiemap_status.h
+++ b/fs_mgr/libfiemap/include/libfiemap/fiemap_status.h
@@ -56,8 +56,7 @@
     // For logging and debugging only.
     std::string string() const;
 
-  protected:
-    FiemapStatus(ErrorCode code) : error_code_(code) {}
+    explicit FiemapStatus(ErrorCode code) : error_code_(code) {}
 
   private:
     ErrorCode error_code_;
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 3dd1f1a..8f35381 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -85,11 +85,9 @@
         "android/snapshot/snapshot.proto",
         "device_info.cpp",
         "snapshot.cpp",
-        "snapshot_reader.cpp",
         "snapshot_stats.cpp",
         "snapshot_stub.cpp",
         "snapshot_metadata_updater.cpp",
-        "snapshot_writer.cpp",
         "partition_cow_creator.cpp",
         "return.cpp",
         "utility.cpp",
@@ -163,8 +161,11 @@
         "libbrotli",
         "libz",
         "liblz4",
+        "libzstd",
     ],
-    export_include_dirs: ["include"],
+    header_libs: [
+        "libupdate_engine_headers",
+    ],
 }
 
 cc_library_static {
@@ -173,12 +174,16 @@
         "libsnapshot_cow_defaults",
     ],
     srcs: [
-        "libsnapshot_cow/cow_decompress.cpp",
-        "libsnapshot_cow/cow_reader.cpp",
-        "libsnapshot_cow/cow_writer.cpp",
-        "libsnapshot_cow/cow_format.cpp",
         "libsnapshot_cow/cow_compress.cpp",
+        "libsnapshot_cow/cow_decompress.cpp",
+        "libsnapshot_cow/cow_format.cpp",
+        "libsnapshot_cow/cow_reader.cpp",
+        "libsnapshot_cow/parser_v2.cpp",
+        "libsnapshot_cow/snapshot_reader.cpp",
+        "libsnapshot_cow/writer_base.cpp",
+        "libsnapshot_cow/writer_v2.cpp",
     ],
+    export_include_dirs: ["include"],
     host_supported: true,
     recovery_available: true,
     ramdisk_available: true,
@@ -221,9 +226,7 @@
     srcs: [
         "partition_cow_creator_test.cpp",
         "snapshot_metadata_updater_test.cpp",
-        "snapshot_reader_test.cpp",
         "snapshot_test.cpp",
-        "snapshot_writer_test.cpp",
     ],
     shared_libs: [
         "libbinder",
@@ -369,7 +372,8 @@
         "libsnapshot_cow_defaults",
     ],
     srcs: [
-        "libsnapshot_cow/cow_api_test.cpp",
+        "libsnapshot_cow/snapshot_reader_test.cpp",
+        "libsnapshot_cow/test_v2.cpp",
     ],
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
@@ -413,6 +417,7 @@
         "libbrotli",
         "libcrypto_static",
         "liblog",
+        "libgflags",
         "libsnapshot_cow",
         "libz",
     ],
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index ba75a8d..3a81f63 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -15,7 +15,9 @@
 #pragma once
 
 #include <stdint.h>
-#include <string>
+
+#include <optional>
+#include <string_view>
 
 namespace android {
 namespace snapshot {
@@ -26,8 +28,12 @@
 
 static constexpr uint32_t kCowVersionManifest = 2;
 
-static constexpr size_t BLOCK_SZ = 4096;
-static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+static constexpr uint32_t kMinCowVersion = 1;
+static constexpr uint32_t kMaxCowVersion = 2;
+
+// Normally, this should be kMaxCowVersion. When a new version is under testing
+// it may be the previous value of kMaxCowVersion.
+static constexpr uint32_t kDefaultCowVersion = 2;
 
 // This header appears as the first sequence of bytes in the COW. All fields
 // in the layout are little-endian encoded. The on-disk layout is:
@@ -53,13 +59,15 @@
 // between writing the last operation/data pair, or the footer itself. In this
 // case, the safest way to proceed is to assume the last operation is faulty.
 
-struct CowHeader {
+struct CowHeaderPrefix {
     uint64_t magic;
     uint16_t major_version;
     uint16_t minor_version;
+    uint16_t header_size;  // size of CowHeader.
+} __attribute__((packed));
 
-    // Size of this struct.
-    uint16_t header_size;
+struct CowHeader {
+    CowHeaderPrefix prefix;
 
     // Size of footer struct
     uint16_t footer_size;
@@ -89,7 +97,7 @@
     // the compression type of that data (see constants below).
     uint8_t compression;
 
-    // Length of Footer Data. Currently 64 for both checksums
+    // Length of Footer Data. Currently 64.
     uint16_t data_length;
 
     // The amount of file space used by Cow operations
@@ -99,14 +107,6 @@
     uint64_t num_ops;
 } __attribute__((packed));
 
-struct CowFooterData {
-    // SHA256 checksums of Footer op
-    uint8_t footer_checksum[32];
-
-    // SHA256 of the operation sequence.
-    uint8_t ops_checksum[32];
-} __attribute__((packed));
-
 // Cow operations are currently fixed-size entries, but this may change if
 // needed.
 struct CowOperation {
@@ -158,16 +158,24 @@
     kCowCompressNone = 0,
     kCowCompressGz = 1,
     kCowCompressBrotli = 2,
-    kCowCompressLz4 = 3
+    kCowCompressLz4 = 3,
+    kCowCompressZstd = 4,
 };
 
 static constexpr uint8_t kCowReadAheadNotStarted = 0;
 static constexpr uint8_t kCowReadAheadInProgress = 1;
 static constexpr uint8_t kCowReadAheadDone = 2;
 
+static inline uint64_t GetCowOpSourceInfoData(const CowOperation* op) {
+    return op->source;
+}
+static inline bool GetCowOpSourceInfoCompression(const CowOperation* op) {
+    return op->compression != kCowCompressNone;
+}
+
 struct CowFooter {
     CowFooterOperation op;
-    CowFooterData data;
+    uint8_t unused[64];
 } __attribute__((packed));
 
 struct ScratchMetadata {
@@ -196,5 +204,8 @@
 // Ops that have dependencies on old blocks, and must take care in their merge order
 bool IsOrderedOp(const CowOperation& op);
 
+// Convert compression name to internal value.
+std::optional<CowCompressionAlgorithm> CompressionAlgorithmFromString(std::string_view name);
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index e8e4d72..f4ce52f 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -24,47 +24,24 @@
 #include <android-base/unique_fd.h>
 #include <libsnapshot/cow_format.h>
 
+namespace chromeos_update_engine {
+class FileDescriptor;
+}  // namespace chromeos_update_engine
+
 namespace android {
 namespace snapshot {
 
 class ICowOpIter;
 
-// A ByteSink object handles requests for a buffer of a specific size. It
-// always owns the underlying buffer. It's designed to minimize potential
-// copying as we parse or decompress the COW.
-class IByteSink {
-  public:
-    virtual ~IByteSink() {}
-
-    // Called when the reader has data. The size of the request is given. The
-    // sink must return a valid pointer (or null on failure), and return the
-    // maximum number of bytes that can be written to the returned buffer.
-    //
-    // The returned buffer is owned by IByteSink, but must remain valid until
-    // the read operation has completed (or the entire buffer has been
-    // covered by calls to ReturnData).
-    //
-    // After calling GetBuffer(), all previous buffers returned are no longer
-    // valid.
-    //
-    // GetBuffer() is intended to be sequential. A returned size of N indicates
-    // that the output stream will advance by N bytes, and the ReturnData call
-    // indicates that those bytes have been fulfilled. Therefore, it is
-    // possible to have ReturnBuffer do nothing, if the implementation doesn't
-    // care about incremental writes.
-    virtual void* GetBuffer(size_t requested, size_t* actual) = 0;
-
-    // Called when a section returned by |GetBuffer| has been filled with data.
-    virtual bool ReturnData(void* buffer, size_t length) = 0;
-};
-
 // Interface for reading from a snapuserd COW.
 class ICowReader {
   public:
+    using FileDescriptor = chromeos_update_engine::FileDescriptor;
+
     virtual ~ICowReader() {}
 
     // Return the file header.
-    virtual bool GetHeader(CowHeader* header) = 0;
+    virtual CowHeader& GetHeader() = 0;
 
     // Return the file footer.
     virtual bool GetFooter(CowFooter* footer) = 0;
@@ -83,29 +60,55 @@
     virtual std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress) = 0;
 
     // Get decoded bytes from the data section, handling any decompression.
-    // All retrieved data is passed to the sink.
-    virtual bool ReadData(const CowOperation& op, IByteSink* sink) = 0;
+    //
+    // If ignore_bytes is non-zero, it specifies the initial number of bytes
+    // to skip writing to |buffer|.
+    //
+    // Returns the number of bytes written to |buffer|, or -1 on failure.
+    // errno is NOT set.
+    //
+    // Partial reads are not possible unless |buffer_size| is less than the
+    // operation block size.
+    //
+    // The operation pointer must derive from ICowOpIter::Get().
+    virtual ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
+                             size_t ignore_bytes = 0) = 0;
+
+    // Get the absolute source offset, in bytes, of a CowOperation. Returns
+    // false if the operation does not read from source partitions.
+    virtual bool GetSourceOffset(const CowOperation* op, uint64_t* source_offset) = 0;
 };
 
-// Iterate over a sequence of COW operations.
+static constexpr uint64_t GetBlockFromOffset(const CowHeader& header, uint64_t offset) {
+    return offset / header.block_size;
+}
+
+static constexpr uint64_t GetBlockRelativeOffset(const CowHeader& header, uint64_t offset) {
+    return offset % header.block_size;
+}
+
+// Iterate over a sequence of COW operations. The iterator is bidirectional.
 class ICowOpIter {
   public:
     virtual ~ICowOpIter() {}
 
-    // True if there are no more items to read forward, false otherwise.
-    virtual bool Done() = 0;
+    // Returns true if the iterator is at the end of the operation list.
+    // If true, Get() and Next() must not be called.
+    virtual bool AtEnd() = 0;
 
     // Read the current operation.
-    virtual const CowOperation& Get() = 0;
+    virtual const CowOperation* Get() = 0;
 
     // Advance to the next item.
     virtual void Next() = 0;
 
+    // Returns true if the iterator is at the beginning of the operation list.
+    // If true, Prev() must not be called; Get() however will be valid if
+    // AtEnd() is not true.
+    virtual bool AtBegin() = 0;
+
     // Advance to the previous item.
     virtual void Prev() = 0;
-
-    // True if there are no more items to read backwards, false otherwise
-    virtual bool RDone() = 0;
 };
 
 class CowReader final : public ICowReader {
@@ -124,12 +127,11 @@
     bool Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label = {});
 
     bool InitForMerge(android::base::unique_fd&& fd);
+
     bool VerifyMergeOps() override;
-
-    bool GetHeader(CowHeader* header) override;
     bool GetFooter(CowFooter* footer) override;
-
     bool GetLastLabel(uint64_t* label) override;
+    bool GetSourceOffset(const CowOperation* op, uint64_t* source_offset) override;
 
     // Create a CowOpIter object which contains footer_.num_ops
     // CowOperation objects. Get() returns a unique CowOperation object
@@ -139,8 +141,12 @@
     std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress = false) override;
     std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress = false) override;
 
-    bool ReadData(const CowOperation& op, IByteSink* sink) override;
+    ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
+                     size_t ignore_bytes = 0) override;
 
+    CowHeader& GetHeader() override { return header_; }
+
+    bool GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read);
     bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
 
     // Returns the total number of data ops that should be merged. This is the
@@ -162,6 +168,7 @@
     bool ParseOps(std::optional<uint64_t> label);
     bool PrepMergeOps();
     uint64_t FindNumCopyops();
+    uint8_t GetCompressionType(const CowOperation* op);
 
     android::base::unique_fd owned_fd_;
     android::base::borrowed_fd fd_;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index c7b83a8..d6194eb 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -1,16 +1,16 @@
-// Copyright (C) 2019 The Android Open Source Project
+// 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
+// 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
+//      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.
+// 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
 
@@ -61,30 +61,30 @@
 // will occur in the sequence they were added to the COW.
 class ICowWriter {
   public:
-    explicit ICowWriter(const CowOptions& options) : options_(options) {}
+    using FileDescriptor = chromeos_update_engine::FileDescriptor;
 
     virtual ~ICowWriter() {}
 
     // Encode an operation that copies the contents of |old_block| to the
     // location of |new_block|. 'num_blocks' is the number of contiguous
     // COPY operations from |old_block| to |new_block|.
-    bool AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1);
+    virtual bool AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) = 0;
 
     // Encode a sequence of raw blocks. |size| must be a multiple of the block size.
-    bool AddRawBlocks(uint64_t new_block_start, const void* data, size_t size);
+    virtual bool AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) = 0;
 
     // Add a sequence of xor'd blocks. |size| must be a multiple of the block size.
-    bool AddXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block,
-                      uint16_t offset);
+    virtual bool AddXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                              uint32_t old_block, uint16_t offset) = 0;
 
     // Encode a sequence of zeroed blocks. |size| must be a multiple of the block size.
-    bool AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks);
+    virtual bool AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) = 0;
 
     // Add a label to the op sequence.
-    bool AddLabel(uint64_t label);
+    virtual bool AddLabel(uint64_t label) = 0;
 
     // Add sequence data for op merging. Data is a list of the destination block numbers.
-    bool AddSequenceData(size_t num_ops, const uint32_t* data);
+    virtual bool AddSequenceData(size_t num_ops, const uint32_t* data) = 0;
 
     // Flush all pending writes. This must be called before closing the writer
     // to ensure that the correct headers and footers are written.
@@ -93,24 +93,19 @@
     // Return number of bytes the cow image occupies on disk.
     virtual uint64_t GetCowSize() = 0;
 
-    // Returns true if AddCopy() operations are supported.
-    virtual bool SupportsCopyOperation() const { return true; }
+    virtual uint32_t GetBlockSize() const = 0;
+    virtual std::optional<uint32_t> GetMaxBlocks() const = 0;
 
-    const CowOptions& options() { return options_; }
+    // Open an ICowReader for this writer. The reader will be a snapshot of the
+    // current operations in the writer; new writes after OpenReader() will not
+    // be reflected.
+    virtual std::unique_ptr<ICowReader> OpenReader() = 0;
 
-  protected:
-    virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) = 0;
-    virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) = 0;
-    virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
-                               uint32_t old_block, uint16_t offset) = 0;
-    virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) = 0;
-    virtual bool EmitLabel(uint64_t label) = 0;
-    virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) = 0;
-
-    bool ValidateNewBlock(uint64_t new_block);
-
-  protected:
-    CowOptions options_;
+    // Open a file descriptor. This allows reading and seeing through the cow
+    // as if it were a normal file. The optional source_device must be a valid
+    // path if the CowReader contains any copy or xor operations.
+    virtual std::unique_ptr<FileDescriptor> OpenFileDescriptor(
+            const std::optional<std::string>& source_device) = 0;
 };
 
 class CompressWorker {
@@ -149,98 +144,15 @@
                         std::vector<std::basic_string<uint8_t>>* compressed_data);
 };
 
-class CowWriter : public ICowWriter {
-  public:
-    explicit CowWriter(const CowOptions& options);
-    ~CowWriter();
+// Create an ICowWriter not backed by any file. This is useful for estimating
+// the final size of a cow file.
+std::unique_ptr<ICowWriter> CreateCowEstimator(uint32_t version, const CowOptions& options);
 
-    // Set up the writer.
-    // The file starts from the beginning.
-    //
-    // If fd is < 0, the CowWriter will be opened against /dev/null. This is for
-    // computing COW sizes without using storage space.
-    bool Initialize(android::base::unique_fd&& fd);
-    bool Initialize(android::base::borrowed_fd fd);
-    // Set up a writer, assuming that the given label is the last valid label.
-    // This will result in dropping any labels that occur after the given on, and will fail
-    // if the given label does not appear.
-    bool InitializeAppend(android::base::unique_fd&&, uint64_t label);
-    bool InitializeAppend(android::base::borrowed_fd fd, uint64_t label);
-
-    bool Finalize() override;
-
-    uint64_t GetCowSize() override;
-
-    uint32_t GetCowVersion() { return header_.major_version; }
-
-  protected:
-    virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
-    virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
-    virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
-                               uint32_t old_block, uint16_t offset) override;
-    virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
-    virtual bool EmitLabel(uint64_t label) override;
-    virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
-
-  private:
-    bool EmitCluster();
-    bool EmitClusterIfNeeded();
-    bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
-                    uint16_t offset, uint8_t type);
-    void SetupHeaders();
-    void SetupWriteOptions();
-    bool ParseOptions();
-    bool OpenForWrite();
-    bool OpenForAppend(uint64_t label);
-    bool GetDataPos(uint64_t* pos);
-    bool WriteRawData(const void* data, size_t size);
-    bool WriteOperation(const CowOperation& op, const void* data = nullptr, size_t size = 0);
-    void AddOperation(const CowOperation& op);
-    void InitPos();
-    void InitBatchWrites();
-    void InitWorkers();
-    bool FlushCluster();
-
-    bool CompressBlocks(size_t num_blocks, const void* data);
-    bool SetFd(android::base::borrowed_fd fd);
-    bool Sync();
-    bool Truncate(off_t length);
-    bool EnsureSpaceAvailable(const uint64_t bytes_needed) const;
-
-  private:
-    android::base::unique_fd owned_fd_;
-    android::base::borrowed_fd fd_;
-    CowHeader header_{};
-    CowFooter footer_{};
-    CowCompressionAlgorithm compression_ = kCowCompressNone;
-    uint64_t current_op_pos_ = 0;
-    uint64_t next_op_pos_ = 0;
-    uint64_t next_data_pos_ = 0;
-    uint64_t current_data_pos_ = 0;
-    ssize_t total_data_written_ = 0;
-    uint32_t cluster_size_ = 0;
-    uint32_t current_cluster_size_ = 0;
-    uint64_t current_data_size_ = 0;
-    bool is_dev_null_ = false;
-    bool merge_in_progress_ = false;
-    bool is_block_device_ = false;
-    uint64_t cow_image_size_ = INT64_MAX;
-
-    int num_compress_threads_ = 1;
-    std::vector<std::unique_ptr<CompressWorker>> compress_threads_;
-    std::vector<std::future<bool>> threads_;
-    std::vector<std::basic_string<uint8_t>> compressed_buf_;
-    std::vector<std::basic_string<uint8_t>>::iterator buf_iter_;
-
-    std::vector<std::unique_ptr<CowOperation>> opbuffer_vec_;
-    std::vector<std::unique_ptr<uint8_t[]>> databuffer_vec_;
-    std::unique_ptr<struct iovec[]> cowop_vec_;
-    int op_vec_index_ = 0;
-
-    std::unique_ptr<struct iovec[]> data_vec_;
-    int data_vec_index_ = 0;
-    bool batch_write_ = false;
-};
+// Create an ICowWriter of the given version and options. If a label is given,
+// the writer is opened in append mode.
+std::unique_ptr<ICowWriter> CreateCowWriter(uint32_t version, const CowOptions& options,
+                                            android::base::unique_fd&& fd,
+                                            std::optional<uint64_t> label = {});
 
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
new file mode 100644
index 0000000..c58c654
--- /dev/null
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
@@ -0,0 +1,45 @@
+//
+// Copyright (C) 2021 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 <gmock/gmock.h>
+#include <libsnapshot/cow_writer.h>
+
+namespace android::snapshot {
+
+class MockCowWriter : public ICowWriter {
+  public:
+    using FileDescriptor = chromeos_update_engine::FileDescriptor;
+
+    MOCK_METHOD(bool, Finalize, (), (override));
+
+    MOCK_METHOD(uint64_t, GetCowSize, (), (override));
+
+    MOCK_METHOD(bool, AddCopy, (uint64_t, uint64_t, uint64_t), (override));
+    MOCK_METHOD(bool, AddRawBlocks, (uint64_t, const void*, size_t), (override));
+    MOCK_METHOD(bool, AddXorBlocks, (uint32_t, const void*, size_t, uint32_t, uint16_t),
+                (override));
+    MOCK_METHOD(bool, AddZeroBlocks, (uint64_t, uint64_t), (override));
+    MOCK_METHOD(bool, AddLabel, (uint64_t), (override));
+    MOCK_METHOD(bool, AddSequenceData, (size_t, const uint32_t*), (override));
+    MOCK_METHOD(uint32_t, GetBlockSize, (), (override, const));
+    MOCK_METHOD(std::optional<uint32_t>, GetMaxBlocks, (), (override, const));
+
+    MOCK_METHOD(std::unique_ptr<ICowReader>, OpenReader, (), (override));
+    MOCK_METHOD(std::unique_ptr<FileDescriptor>, OpenFileDescriptor,
+                (const std::optional<std::string>&), (override));
+};
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index d458b87..ca45d2f 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -42,9 +42,9 @@
                 (const android::fs_mgr::CreateLogicalPartitionParams& params,
                  std::string* snapshot_path),
                 (override));
-    MOCK_METHOD(std::unique_ptr<ISnapshotWriter>, OpenSnapshotWriter,
+    MOCK_METHOD(std::unique_ptr<ICowWriter>, OpenSnapshotWriter,
                 (const android::fs_mgr::CreateLogicalPartitionParams& params,
-                 const std::optional<std::string>&),
+                 std::optional<uint64_t>),
                 (override));
     MOCK_METHOD(bool, UnmapUpdateSnapshot, (const std::string& target_partition_name), (override));
     MOCK_METHOD(bool, NeedSnapshotsInFirstStageMount, (), (override));
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
deleted file mode 100644
index 29828bc..0000000
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
+++ /dev/null
@@ -1,55 +0,0 @@
-//
-// Copyright (C) 2021 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 <gmock/gmock.h>
-#include <libsnapshot/snapshot_writer.h>
-
-namespace android::snapshot {
-
-class MockSnapshotWriter : public ISnapshotWriter {
-  public:
-    using FileDescriptor = ISnapshotWriter::FileDescriptor;
-
-    explicit MockSnapshotWriter(const CowOptions& options) : ISnapshotWriter(options) {}
-    MockSnapshotWriter() : ISnapshotWriter({}) {}
-
-    MOCK_METHOD(bool, Finalize, (), (override));
-
-    // Return number of bytes the cow image occupies on disk.
-    MOCK_METHOD(uint64_t, GetCowSize, (), (override));
-
-    // Returns true if AddCopy() operations are supported.
-    MOCK_METHOD(bool, SupportsCopyOperation, (), (const override));
-
-    MOCK_METHOD(bool, EmitCopy, (uint64_t, uint64_t, uint64_t), (override));
-    MOCK_METHOD(bool, EmitRawBlocks, (uint64_t, const void*, size_t), (override));
-    MOCK_METHOD(bool, EmitXorBlocks, (uint32_t, const void*, size_t, uint32_t, uint16_t),
-                (override));
-    MOCK_METHOD(bool, EmitZeroBlocks, (uint64_t, uint64_t), (override));
-    MOCK_METHOD(bool, EmitLabel, (uint64_t), (override));
-    MOCK_METHOD(bool, EmitSequenceData, (size_t, const uint32_t*), (override));
-
-    // Open the writer in write mode (no append).
-    MOCK_METHOD(bool, Initialize, (), (override));
-    MOCK_METHOD(bool, VerifyMergeOps, (), (override, const, noexcept));
-
-    // Open the writer in append mode, with the last label to resume
-    // from. See CowWriter::InitializeAppend.
-    MOCK_METHOD(bool, InitializeAppend, (uint64_t label), (override));
-
-    MOCK_METHOD(std::unique_ptr<FileDescriptor>, OpenReader, (), (override));
-};
-}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 9eb89b6..df532ee 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -33,12 +33,11 @@
 #include <libfiemap/image_manager.h>
 #include <liblp/builder.h>
 #include <liblp/liblp.h>
-#include <update_engine/update_metadata.pb.h>
-
 #include <libsnapshot/auto_device.h>
+#include <libsnapshot/cow_writer.h>
 #include <libsnapshot/return.h>
-#include <libsnapshot/snapshot_writer.h>
 #include <snapuserd/snapuserd_client.h>
+#include <update_engine/update_metadata.pb.h>
 
 #ifndef FRIEND_TEST
 #define FRIEND_TEST(test_set_name, individual_test) \
@@ -211,16 +210,13 @@
     virtual bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
                                    std::string* snapshot_path) = 0;
 
-    // Create an ISnapshotWriter to build a snapshot against a target partition. The partition name
+    // Create an ICowWriter to build a snapshot against a target partition. The partition name
     // must be suffixed. If a source partition exists, it must be specified as well. The source
     // partition will only be used if raw bytes are needed. The source partition should be an
     // absolute path to the device, not a partition name.
-    //
-    // After calling OpenSnapshotWriter, the caller must invoke Initialize or InitializeForAppend
-    // before invoking write operations.
-    virtual std::unique_ptr<ISnapshotWriter> OpenSnapshotWriter(
+    virtual std::unique_ptr<ICowWriter> OpenSnapshotWriter(
             const android::fs_mgr::CreateLogicalPartitionParams& params,
-            const std::optional<std::string>& source_device) = 0;
+            std::optional<uint64_t> label = {}) = 0;
 
     // Unmap a snapshot device or CowWriter that was previously opened with MapUpdateSnapshot,
     // OpenSnapshotWriter. All outstanding open descriptors, writers, or
@@ -362,9 +358,9 @@
     Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
                            std::string* snapshot_path) override;
-    std::unique_ptr<ISnapshotWriter> OpenSnapshotWriter(
+    std::unique_ptr<ICowWriter> OpenSnapshotWriter(
             const android::fs_mgr::CreateLogicalPartitionParams& params,
-            const std::optional<std::string>& source_device) override;
+            std::optional<uint64_t> label) override;
     bool UnmapUpdateSnapshot(const std::string& target_partition_name) override;
     bool NeedSnapshotsInFirstStageMount() override;
     bool CreateLogicalAndSnapshotPartitions(
@@ -693,14 +689,10 @@
     };
 
     // Helpers for OpenSnapshotWriter.
-    std::unique_ptr<ISnapshotWriter> OpenCompressedSnapshotWriter(
-            LockedFile* lock, const std::optional<std::string>& source_device,
-            const std::string& partition_name, const SnapshotStatus& status,
-            const SnapshotPaths& paths);
-    std::unique_ptr<ISnapshotWriter> OpenKernelSnapshotWriter(
-            LockedFile* lock, const std::optional<std::string>& source_device,
-            const std::string& partition_name, const SnapshotStatus& status,
-            const SnapshotPaths& paths);
+    std::unique_ptr<ICowWriter> OpenCompressedSnapshotWriter(LockedFile* lock,
+                                                             const SnapshotStatus& status,
+                                                             const SnapshotPaths& paths,
+                                                             std::optional<uint64_t> label);
 
     // Map the base device, COW devices, and snapshot device.
     bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params,
@@ -747,7 +739,7 @@
     // Initialize snapshots so that they can be mapped later.
     // Map the COW partition and zero-initialize the header.
     Return InitializeUpdateSnapshots(
-            LockedFile* lock, MetadataBuilder* target_metadata,
+            LockedFile* lock, uint32_t cow_version, MetadataBuilder* target_metadata,
             const LpMetadata* exported_target_metadata, const std::string& target_suffix,
             const std::map<std::string, SnapshotStatus>& all_snapshot_status);
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index 171c7c6..1c9b403 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -40,9 +40,9 @@
             const chromeos_update_engine::DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
                            std::string* snapshot_path) override;
-    std::unique_ptr<ISnapshotWriter> OpenSnapshotWriter(
+    std::unique_ptr<ICowWriter> OpenSnapshotWriter(
             const android::fs_mgr::CreateLogicalPartitionParams& params,
-            const std::optional<std::string>& source_device) override;
+            std::optional<uint64_t> label) override;
     bool UnmapUpdateSnapshot(const std::string& target_partition_name) override;
     bool NeedSnapshotsInFirstStageMount() override;
     bool CreateLogicalAndSnapshotPartitions(
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
deleted file mode 100644
index 0e3b1db..0000000
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (C) 2020 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 <optional>
-
-#include <android-base/unique_fd.h>
-
-#include <libsnapshot/cow_writer.h>
-
-namespace chromeos_update_engine {
-class FileDescriptor;
-}  // namespace chromeos_update_engine
-
-namespace android {
-namespace snapshot {
-
-class ISnapshotWriter : public ICowWriter {
-  public:
-    using FileDescriptor = chromeos_update_engine::FileDescriptor;
-
-    explicit ISnapshotWriter(const CowOptions& options);
-
-    // Set the source device. This is used for AddCopy() operations, if the
-    // underlying writer needs the original bytes (for example if backed by
-    // dm-snapshot or if writing directly to an unsnapshotted region). The
-    // device is only opened on the first operation that requires it.
-    void SetSourceDevice(const std::string& source_device);
-
-    // Open the writer in write mode (no append).
-    virtual bool Initialize() = 0;
-
-    // Open the writer in append mode, with the last label to resume
-    // from. See CowWriter::InitializeAppend.
-    virtual bool InitializeAppend(uint64_t label) = 0;
-
-    virtual std::unique_ptr<FileDescriptor> OpenReader() = 0;
-    virtual bool VerifyMergeOps() const noexcept = 0;
-
-  protected:
-    android::base::borrowed_fd GetSourceFd();
-
-    std::optional<std::string> source_device_;
-
-  private:
-    android::base::unique_fd source_fd_;
-};
-
-// Send writes to a COW or a raw device directly, based on a threshold.
-class CompressedSnapshotWriter final : public ISnapshotWriter {
-  public:
-    CompressedSnapshotWriter(const CowOptions& options);
-
-    // Sets the COW device; this is required.
-    bool SetCowDevice(android::base::unique_fd&& cow_device);
-
-    bool Initialize() override;
-    bool InitializeAppend(uint64_t label) override;
-    bool Finalize() override;
-    uint64_t GetCowSize() override;
-    std::unique_ptr<FileDescriptor> OpenReader() override;
-    bool VerifyMergeOps() const noexcept;
-
-  protected:
-    bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
-    bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
-    bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block,
-                       uint16_t offset) override;
-    bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
-    bool EmitLabel(uint64_t label) override;
-    bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
-
-  private:
-    std::unique_ptr<CowReader> OpenCowReader() const;
-    android::base::unique_fd cow_device_;
-
-    std::unique_ptr<CowWriter> cow_;
-};
-
-// Write directly to a dm-snapshot device.
-class OnlineKernelSnapshotWriter final : public ISnapshotWriter {
-  public:
-    OnlineKernelSnapshotWriter(const CowOptions& options);
-
-    // Set the device used for all writes.
-    void SetSnapshotDevice(android::base::unique_fd&& snapshot_fd, uint64_t cow_size);
-
-    bool Initialize() override { return true; }
-    bool InitializeAppend(uint64_t) override { return true; }
-
-    bool Finalize() override;
-    uint64_t GetCowSize() override { return cow_size_; }
-    std::unique_ptr<FileDescriptor> OpenReader() override;
-
-    // Online kernel snapshot writer doesn't care about merge sequences.
-    // So ignore.
-    bool VerifyMergeOps() const noexcept override { return true; }
-
-  protected:
-    bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
-    bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
-    bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block,
-                       uint16_t offset) override;
-    bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
-    bool EmitLabel(uint64_t label) override;
-    bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
-
-  private:
-    android::base::unique_fd snapshot_fd_;
-    uint64_t cow_size_ = 0;
-};
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 24c91a8..5e9f049 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -198,7 +198,7 @@
 // Expect space of |path| is multiple of 4K.
 bool WriteRandomData(const std::string& path, std::optional<size_t> expect_size = std::nullopt,
                      std::string* hash = nullptr);
-std::string HashSnapshot(ISnapshotWriter* writer);
+std::string HashSnapshot(ICowWriter::FileDescriptor* writer);
 
 std::string ToHexString(const uint8_t* buf, size_t len);
 
@@ -214,29 +214,6 @@
 // Get partition size from update package metadata.
 uint64_t GetSize(PartitionUpdate* partition_update);
 
-// Util class for test cases on low space scenario. These tests assumes image manager
-// uses /data as backup device.
-class LowSpaceUserdata {
-  public:
-    // Set the maximum free space allowed for this test. If /userdata has more space than the given
-    // number, a file is allocated to consume space.
-    AssertionResult Init(uint64_t max_free_space);
-
-    uint64_t free_space() const;
-    uint64_t available_space() const;
-    uint64_t bsize() const;
-
-  private:
-    AssertionResult ReadUserdataStats();
-
-    static constexpr const char* kUserDataDevice = "/data";
-    std::unique_ptr<TemporaryFile> big_file_;
-    bool initialized_ = false;
-    uint64_t free_space_ = 0;
-    uint64_t available_space_ = 0;
-    uint64_t bsize_ = 0;
-};
-
 bool IsVirtualAbEnabled();
 
 #define SKIP_IF_NON_VIRTUAL_AB()                                                        \
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
index 9b50986..a4a0ad6 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
@@ -29,9 +29,27 @@
 #include <libsnapshot/cow_writer.h>
 #include <lz4.h>
 #include <zlib.h>
+#include <zstd.h>
 
 namespace android {
 namespace snapshot {
+
+std::optional<CowCompressionAlgorithm> CompressionAlgorithmFromString(std::string_view name) {
+    if (name == "gz") {
+        return {kCowCompressGz};
+    } else if (name == "brotli") {
+        return {kCowCompressBrotli};
+    } else if (name == "lz4") {
+        return {kCowCompressLz4};
+    } else if (name == "zstd") {
+        return {kCowCompressZstd};
+    } else if (name == "none" || name.empty()) {
+        return {kCowCompressNone};
+    } else {
+        return {};
+    }
+}
+
 std::basic_string<uint8_t> CompressWorker::Compress(const void* data, size_t length) {
     return Compress(compression_, data, length);
 }
@@ -97,6 +115,23 @@
             }
             return buffer;
         }
+        case kCowCompressZstd: {
+            std::basic_string<uint8_t> buffer(ZSTD_compressBound(length), '\0');
+            const auto compressed_size =
+                    ZSTD_compress(buffer.data(), buffer.size(), data, length, 0);
+            if (compressed_size <= 0) {
+                LOG(ERROR) << "ZSTD compression failed " << compressed_size;
+                return {};
+            }
+            // Don't run compression if the compressed output is larger
+            if (compressed_size >= length) {
+                buffer.resize(length);
+                memcpy(buffer.data(), data, length);
+            } else {
+                buffer.resize(compressed_size);
+            }
+            return buffer;
+        }
         default:
             LOG(ERROR) << "unhandled compression type: " << compression;
             break;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
index 139a29f..da90cc0 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
@@ -16,124 +16,122 @@
 
 #include "cow_decompress.h"
 
+#include <array>
+#include <cstring>
 #include <utility>
+#include <vector>
 
 #include <android-base/logging.h>
 #include <brotli/decode.h>
 #include <lz4.h>
 #include <zlib.h>
+#include <zstd.h>
 
 namespace android {
 namespace snapshot {
 
-class NoDecompressor final : public IDecompressor {
-  public:
-    bool Decompress(size_t) override;
-};
+ssize_t IByteStream::ReadFully(void* buffer, size_t buffer_size) {
+    size_t stream_remaining = Size();
 
-bool NoDecompressor::Decompress(size_t) {
-    size_t stream_remaining = stream_->Size();
+    char* buffer_start = reinterpret_cast<char*>(buffer);
+    char* buffer_pos = buffer_start;
+    size_t buffer_remaining = buffer_size;
     while (stream_remaining) {
-        size_t buffer_size = stream_remaining;
-        uint8_t* buffer = reinterpret_cast<uint8_t*>(sink_->GetBuffer(buffer_size, &buffer_size));
-        if (!buffer) {
-            LOG(ERROR) << "Could not acquire buffer from sink";
-            return false;
+        const size_t to_read = std::min(buffer_remaining, stream_remaining);
+        const ssize_t actual_read = Read(buffer_pos, to_read);
+        if (actual_read < 0) {
+            return -1;
         }
+        if (!actual_read) {
+            LOG(ERROR) << "Stream ended prematurely";
+            return -1;
+        }
+        CHECK_LE(actual_read, to_read);
 
-        // Read until we can fill the buffer.
-        uint8_t* buffer_pos = buffer;
-        size_t bytes_to_read = std::min(buffer_size, stream_remaining);
-        while (bytes_to_read) {
-            size_t read;
-            if (!stream_->Read(buffer_pos, bytes_to_read, &read)) {
-                return false;
-            }
-            if (!read) {
-                LOG(ERROR) << "Stream ended prematurely";
-                return false;
-            }
-            if (!sink_->ReturnData(buffer_pos, read)) {
-                LOG(ERROR) << "Could not return buffer to sink";
-                return false;
-            }
-            buffer_pos += read;
-            bytes_to_read -= read;
-            stream_remaining -= read;
-        }
+        stream_remaining -= actual_read;
+        buffer_pos += actual_read;
+        buffer_remaining -= actual_read;
     }
-    return true;
+    return buffer_pos - buffer_start;
 }
 
-std::unique_ptr<IDecompressor> IDecompressor::Uncompressed() {
-    return std::unique_ptr<IDecompressor>(new NoDecompressor());
+std::unique_ptr<IDecompressor> IDecompressor::FromString(std::string_view compressor) {
+    if (compressor == "lz4") {
+        return IDecompressor::Lz4();
+    } else if (compressor == "brotli") {
+        return IDecompressor::Brotli();
+    } else if (compressor == "gz") {
+        return IDecompressor::Gz();
+    } else {
+        return nullptr;
+    }
 }
 
 // Read chunks of the COW and incrementally stream them to the decoder.
 class StreamDecompressor : public IDecompressor {
   public:
-    bool Decompress(size_t output_bytes) override;
+    ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
+                       size_t ignore_bytes) override;
 
     virtual bool Init() = 0;
-    virtual bool DecompressInput(const uint8_t* data, size_t length) = 0;
-    virtual bool Done() = 0;
+    virtual bool PartialDecompress(const uint8_t* data, size_t length) = 0;
+    bool OutputFull() const { return !ignore_bytes_ && !output_buffer_remaining_; }
 
   protected:
-    bool GetFreshBuffer();
-
-    size_t output_bytes_;
     size_t stream_remaining_;
     uint8_t* output_buffer_ = nullptr;
     size_t output_buffer_remaining_ = 0;
+    size_t ignore_bytes_ = 0;
+    bool decompressor_ended_ = false;
 };
 
 static constexpr size_t kChunkSize = 4096;
 
-bool StreamDecompressor::Decompress(size_t output_bytes) {
+ssize_t StreamDecompressor::Decompress(void* buffer, size_t buffer_size, size_t,
+                                       size_t ignore_bytes) {
     if (!Init()) {
         return false;
     }
 
     stream_remaining_ = stream_->Size();
-    output_bytes_ = output_bytes;
+    output_buffer_ = reinterpret_cast<uint8_t*>(buffer);
+    output_buffer_remaining_ = buffer_size;
+    ignore_bytes_ = ignore_bytes;
 
     uint8_t chunk[kChunkSize];
-    while (stream_remaining_) {
-        size_t read = std::min(stream_remaining_, sizeof(chunk));
-        if (!stream_->Read(chunk, read, &read)) {
-            return false;
+    while (stream_remaining_ && output_buffer_remaining_ && !decompressor_ended_) {
+        size_t max_read = std::min(stream_remaining_, sizeof(chunk));
+        ssize_t read = stream_->Read(chunk, max_read);
+        if (read < 0) {
+            return -1;
         }
         if (!read) {
             LOG(ERROR) << "Stream ended prematurely";
-            return false;
+            return -1;
         }
-        if (!DecompressInput(chunk, read)) {
-            return false;
+        if (!PartialDecompress(chunk, read)) {
+            return -1;
         }
-
         stream_remaining_ -= read;
+    }
 
-        if (stream_remaining_ && Done()) {
+    if (stream_remaining_) {
+        if (decompressor_ended_ && !OutputFull()) {
+            // If there's more input in the stream, but we haven't finished
+            // consuming ignored bytes or available output space yet, then
+            // something weird happened. Report it and fail.
             LOG(ERROR) << "Decompressor terminated early";
-            return false;
+            return -1;
+        }
+    } else {
+        if (!decompressor_ended_ && !OutputFull()) {
+            // The stream ended, but the decoder doesn't think so, and there are
+            // more bytes in the output buffer.
+            LOG(ERROR) << "Decompressor expected more bytes";
+            return -1;
         }
     }
-    if (!Done()) {
-        LOG(ERROR) << "Decompressor expected more bytes";
-        return false;
-    }
-    return true;
-}
-
-bool StreamDecompressor::GetFreshBuffer() {
-    size_t request_size = std::min(output_bytes_, kChunkSize);
-    output_buffer_ =
-            reinterpret_cast<uint8_t*>(sink_->GetBuffer(request_size, &output_buffer_remaining_));
-    if (!output_buffer_) {
-        LOG(ERROR) << "Could not acquire buffer from sink";
-        return false;
-    }
-    return true;
+    return buffer_size - output_buffer_remaining_;
 }
 
 class GzDecompressor final : public StreamDecompressor {
@@ -141,12 +139,10 @@
     ~GzDecompressor();
 
     bool Init() override;
-    bool DecompressInput(const uint8_t* data, size_t length) override;
-    bool Done() override { return ended_; }
+    bool PartialDecompress(const uint8_t* data, size_t length) override;
 
   private:
     z_stream z_ = {};
-    bool ended_ = false;
 };
 
 bool GzDecompressor::Init() {
@@ -161,23 +157,39 @@
     inflateEnd(&z_);
 }
 
-bool GzDecompressor::DecompressInput(const uint8_t* data, size_t length) {
+bool GzDecompressor::PartialDecompress(const uint8_t* data, size_t length) {
     z_.next_in = reinterpret_cast<Bytef*>(const_cast<uint8_t*>(data));
     z_.avail_in = length;
 
-    while (z_.avail_in) {
-        // If no more output buffer, grab a new buffer.
-        if (z_.avail_out == 0) {
-            if (!GetFreshBuffer()) {
-                return false;
-            }
-            z_.next_out = reinterpret_cast<Bytef*>(output_buffer_);
-            z_.avail_out = output_buffer_remaining_;
+    // If we're asked to ignore starting bytes, we sink those into the output
+    // repeatedly until there is nothing left to ignore.
+    while (ignore_bytes_ && z_.avail_in) {
+        std::array<Bytef, kChunkSize> ignore_buffer;
+        size_t max_ignore = std::min(ignore_bytes_, ignore_buffer.size());
+        z_.next_out = ignore_buffer.data();
+        z_.avail_out = max_ignore;
+
+        int rv = inflate(&z_, Z_NO_FLUSH);
+        if (rv != Z_OK && rv != Z_STREAM_END) {
+            LOG(ERROR) << "inflate returned error code " << rv;
+            return false;
         }
 
-        // Remember the position of the output buffer so we can call ReturnData.
-        auto avail_out = z_.avail_out;
+        size_t returned = max_ignore - z_.avail_out;
+        CHECK_LE(returned, ignore_bytes_);
 
+        ignore_bytes_ -= returned;
+
+        if (rv == Z_STREAM_END) {
+            decompressor_ended_ = true;
+            return true;
+        }
+    }
+
+    z_.next_out = reinterpret_cast<Bytef*>(output_buffer_);
+    z_.avail_out = output_buffer_remaining_;
+
+    while (z_.avail_in && z_.avail_out) {
         // Decompress.
         int rv = inflate(&z_, Z_NO_FLUSH);
         if (rv != Z_OK && rv != Z_STREAM_END) {
@@ -185,20 +197,14 @@
             return false;
         }
 
-        size_t returned = avail_out - z_.avail_out;
-        if (!sink_->ReturnData(output_buffer_, returned)) {
-            LOG(ERROR) << "Could not return buffer to sink";
-            return false;
-        }
+        size_t returned = output_buffer_remaining_ - z_.avail_out;
+        CHECK_LE(returned, output_buffer_remaining_);
+
         output_buffer_ += returned;
         output_buffer_remaining_ -= returned;
 
         if (rv == Z_STREAM_END) {
-            if (z_.avail_in) {
-                LOG(ERROR) << "Gz stream ended prematurely";
-                return false;
-            }
-            ended_ = true;
+            decompressor_ended_ = true;
             return true;
         }
     }
@@ -214,8 +220,7 @@
     ~BrotliDecompressor();
 
     bool Init() override;
-    bool DecompressInput(const uint8_t* data, size_t length) override;
-    bool Done() override { return BrotliDecoderIsFinished(decoder_); }
+    bool PartialDecompress(const uint8_t* data, size_t length) override;
 
   private:
     BrotliDecoderState* decoder_ = nullptr;
@@ -232,28 +237,41 @@
     }
 }
 
-bool BrotliDecompressor::DecompressInput(const uint8_t* data, size_t length) {
+bool BrotliDecompressor::PartialDecompress(const uint8_t* data, size_t length) {
     size_t available_in = length;
     const uint8_t* next_in = data;
 
-    bool needs_more_output = false;
-    while (available_in || needs_more_output) {
-        if (!output_buffer_remaining_ && !GetFreshBuffer()) {
+    while (available_in && ignore_bytes_ && !BrotliDecoderIsFinished(decoder_)) {
+        std::array<uint8_t, kChunkSize> ignore_buffer;
+        size_t max_ignore = std::min(ignore_bytes_, ignore_buffer.size());
+        size_t ignore_size = max_ignore;
+
+        uint8_t* ignore_buffer_ptr = ignore_buffer.data();
+        auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in, &ignore_size,
+                                               &ignore_buffer_ptr, nullptr);
+        if (r == BROTLI_DECODER_RESULT_ERROR) {
+            LOG(ERROR) << "brotli decode failed";
+            return false;
+        } else if (r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT && available_in) {
+            LOG(ERROR) << "brotli unexpected needs more input";
             return false;
         }
+        ignore_bytes_ -= max_ignore - ignore_size;
+    }
 
-        auto output_buffer = output_buffer_;
+    while (available_in && !BrotliDecoderIsFinished(decoder_)) {
         auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in,
                                                &output_buffer_remaining_, &output_buffer_, nullptr);
         if (r == BROTLI_DECODER_RESULT_ERROR) {
             LOG(ERROR) << "brotli decode failed";
             return false;
-        }
-        if (!sink_->ReturnData(output_buffer, output_buffer_ - output_buffer)) {
+        } else if (r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT && available_in) {
+            LOG(ERROR) << "brotli unexpected needs more input";
             return false;
         }
-        needs_more_output = (r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
     }
+
+    decompressor_ended_ = BrotliDecoderIsFinished(decoder_);
     return true;
 }
 
@@ -265,44 +283,101 @@
   public:
     ~Lz4Decompressor() override = default;
 
-    bool Decompress(const size_t output_size) override {
-        size_t actual_buffer_size = 0;
-        auto&& output_buffer = sink_->GetBuffer(output_size, &actual_buffer_size);
-        if (actual_buffer_size != output_size) {
-            LOG(ERROR) << "Failed to allocate buffer of size " << output_size << " only got "
-                       << actual_buffer_size << " bytes";
-            return false;
+    ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
+                       size_t ignore_bytes) override {
+        std::string input_buffer(stream_->Size(), '\0');
+        ssize_t streamed_in = stream_->ReadFully(input_buffer.data(), input_buffer.size());
+        if (streamed_in < 0) {
+            return -1;
         }
-        // If input size is same as output size, then input is uncompressed.
-        if (stream_->Size() == output_size) {
-            size_t bytes_read = 0;
-            stream_->Read(output_buffer, output_size, &bytes_read);
-            if (bytes_read != output_size) {
-                LOG(ERROR) << "Failed to read all input at once. Expected: " << output_size
-                           << " actual: " << bytes_read;
-                return false;
+        CHECK_EQ(streamed_in, stream_->Size());
+
+        char* decode_buffer = reinterpret_cast<char*>(buffer);
+        size_t decode_buffer_size = buffer_size;
+
+        // It's unclear if LZ4 can exactly satisfy a partial decode request, so
+        // if we get one, create a temporary buffer.
+        std::string temp;
+        if (buffer_size < decompressed_size) {
+            temp.resize(decompressed_size, '\0');
+            decode_buffer = temp.data();
+            decode_buffer_size = temp.size();
+        }
+
+        const int bytes_decompressed = LZ4_decompress_safe(input_buffer.data(), decode_buffer,
+                                                           input_buffer.size(), decode_buffer_size);
+        if (bytes_decompressed < 0) {
+            LOG(ERROR) << "Failed to decompress LZ4 block, code: " << bytes_decompressed;
+            return -1;
+        }
+        if (bytes_decompressed != decompressed_size) {
+            LOG(ERROR) << "Failed to decompress LZ4 block, expected output size: "
+                       << bytes_decompressed << ", actual: " << bytes_decompressed;
+            return -1;
+        }
+        CHECK_LE(bytes_decompressed, decode_buffer_size);
+
+        if (ignore_bytes > bytes_decompressed) {
+            LOG(ERROR) << "Ignoring more bytes than exist in stream (ignoring " << ignore_bytes
+                       << ", got " << bytes_decompressed << ")";
+            return -1;
+        }
+
+        if (temp.empty()) {
+            // LZ4's API has no way to sink out the first N bytes of decoding,
+            // so we read them all in and memmove() to drop the partial read.
+            if (ignore_bytes) {
+                memmove(decode_buffer, decode_buffer + ignore_bytes,
+                        bytes_decompressed - ignore_bytes);
             }
-            sink_->ReturnData(output_buffer, output_size);
-            return true;
+            return bytes_decompressed - ignore_bytes;
         }
+
+        size_t max_copy = std::min(bytes_decompressed - ignore_bytes, buffer_size);
+        memcpy(buffer, temp.data() + ignore_bytes, max_copy);
+        return max_copy;
+    }
+};
+
+class ZstdDecompressor final : public IDecompressor {
+  public:
+    ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
+                       size_t ignore_bytes = 0) override {
+        if (buffer_size < decompressed_size - ignore_bytes) {
+            LOG(INFO) << "buffer size " << buffer_size
+                      << " is not large enough to hold decompressed data. Decompressed size "
+                      << decompressed_size << ", ignore_bytes " << ignore_bytes;
+            return -1;
+        }
+        if (ignore_bytes == 0) {
+            if (!Decompress(buffer, decompressed_size)) {
+                return -1;
+            }
+            return decompressed_size;
+        }
+        std::vector<unsigned char> ignore_buf(decompressed_size);
+        if (!Decompress(buffer, decompressed_size)) {
+            return -1;
+        }
+        memcpy(buffer, ignore_buf.data() + ignore_bytes, buffer_size);
+        return decompressed_size;
+    }
+    bool Decompress(void* output_buffer, const size_t output_size) {
         std::string input_buffer;
         input_buffer.resize(stream_->Size());
-        size_t bytes_read = 0;
-        stream_->Read(input_buffer.data(), input_buffer.size(), &bytes_read);
+        size_t bytes_read = stream_->Read(input_buffer.data(), input_buffer.size());
         if (bytes_read != input_buffer.size()) {
             LOG(ERROR) << "Failed to read all input at once. Expected: " << input_buffer.size()
                        << " actual: " << bytes_read;
             return false;
         }
-        const int bytes_decompressed =
-                LZ4_decompress_safe(input_buffer.data(), static_cast<char*>(output_buffer),
-                                    input_buffer.size(), output_size);
+        const auto bytes_decompressed = ZSTD_decompress(output_buffer, output_size,
+                                                        input_buffer.data(), input_buffer.size());
         if (bytes_decompressed != output_size) {
-            LOG(ERROR) << "Failed to decompress LZ4 block, expected output size: " << output_size
+            LOG(ERROR) << "Failed to decompress ZSTD block, expected output size: " << output_size
                        << ", actual: " << bytes_decompressed;
             return false;
         }
-        sink_->ReturnData(output_buffer, output_size);
         return true;
     }
 };
@@ -311,5 +386,9 @@
     return std::make_unique<Lz4Decompressor>();
 }
 
+std::unique_ptr<IDecompressor> IDecompressor::Zstd() {
+    return std::make_unique<ZstdDecompressor>();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
index 7f74eda..52b3d76 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
@@ -26,11 +26,16 @@
     virtual ~IByteStream() {}
 
     // Read up to |length| bytes, storing the number of bytes read in the out-
-    // parameter. If the end of the stream is reached, 0 is returned.
-    virtual bool Read(void* buffer, size_t length, size_t* read) = 0;
+    // parameter. If the end of the stream is reached, 0 is returned. On error,
+    // -1 is returned. errno is NOT set.
+    virtual ssize_t Read(void* buffer, size_t length) = 0;
 
     // Size of the stream.
     virtual size_t Size() const = 0;
+
+    // Helper for Read(). Read the entire stream into |buffer|, up to |length|
+    // bytes.
+    ssize_t ReadFully(void* buffer, size_t length);
 };
 
 class IDecompressor {
@@ -42,16 +47,24 @@
     static std::unique_ptr<IDecompressor> Gz();
     static std::unique_ptr<IDecompressor> Brotli();
     static std::unique_ptr<IDecompressor> Lz4();
+    static std::unique_ptr<IDecompressor> Zstd();
 
-    // |output_bytes| is the expected total number of bytes to sink.
-    virtual bool Decompress(size_t output_bytes) = 0;
+    static std::unique_ptr<IDecompressor> FromString(std::string_view compressor);
+
+    // Decompress at most |buffer_size| bytes, ignoring the first |ignore_bytes|
+    // of the decoded stream. |buffer_size| must be at least one byte.
+    // |decompressed_size| is the expected total size if the entire stream were
+    // decompressed.
+    //
+    // Returns the number of bytes written to |buffer|, or -1 on error. errno
+    // is NOT set.
+    virtual ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
+                               size_t ignore_bytes = 0) = 0;
 
     void set_stream(IByteStream* stream) { stream_ = stream; }
-    void set_sink(IByteSink* sink) { sink_ = sink; }
 
   protected:
     IByteStream* stream_ = nullptr;
-    IByteSink* sink_ = nullptr;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index 94c4109..ff3ccec 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -19,10 +19,13 @@
 #include <unistd.h>
 
 #include <android-base/logging.h>
+#include "writer_v2.h"
 
 namespace android {
 namespace snapshot {
 
+using android::base::unique_fd;
+
 std::ostream& operator<<(std::ostream& os, CowOperation const& op) {
     os << "CowOperation(type:";
     if (op.type == kCowCopyOp)
@@ -57,8 +60,6 @@
     os << "data_length:" << op.data_length << ",\t";
     os << "new_block:" << op.new_block << ",\t";
     os << "source:" << op.source;
-    if (op.type == kCowXorOp)
-        os << " (block:" << op.source / BLOCK_SZ << " offset:" << op.source % BLOCK_SZ << ")";
     os << ")";
     return os;
 }
@@ -105,5 +106,27 @@
     }
 }
 
+std::unique_ptr<ICowWriter> CreateCowWriter(uint32_t version, const CowOptions& options,
+                                            unique_fd&& fd, std::optional<uint64_t> label) {
+    std::unique_ptr<CowWriterBase> base;
+    switch (version) {
+        case 1:
+        case 2:
+            base = std::make_unique<CowWriterV2>(options, std::move(fd));
+            break;
+        default:
+            LOG(ERROR) << "Cannot create unknown cow version: " << version;
+            return nullptr;
+    }
+    if (!base->Initialize(label)) {
+        return nullptr;
+    }
+    return base;
+}
+
+std::unique_ptr<ICowWriter> CreateCowEstimator(uint32_t version, const CowOptions& options) {
+    return CreateCowWriter(version, options, unique_fd{-1}, std::nullopt);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 45be191..489669a 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -30,6 +30,7 @@
 #include <zlib.h>
 
 #include "cow_decompress.h"
+#include "parser_v2.h"
 
 namespace android {
 namespace snapshot {
@@ -42,15 +43,6 @@
       reader_flag_(reader_flag),
       is_merge_(is_merge) {}
 
-static void SHA256(const void*, size_t, uint8_t[]) {
-#if 0
-    SHA256_CTX c;
-    SHA256_Init(&c);
-    SHA256_Update(&c, data, length);
-    SHA256_Final(out, &c);
-#endif
-}
-
 std::unique_ptr<CowReader> CowReader::CloneCowReader() {
     auto cow = std::make_unique<CowReader>();
     cow->owned_fd_.reset();
@@ -62,7 +54,6 @@
     cow->merge_op_start_ = merge_op_start_;
     cow->num_total_data_ops_ = num_total_data_ops_;
     cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
-    cow->has_seq_ops_ = has_seq_ops_;
     cow->data_loc_ = data_loc_;
     cow->block_pos_index_ = block_pos_index_;
     cow->is_merge_ = is_merge_;
@@ -100,217 +91,26 @@
 bool CowReader::Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label) {
     fd_ = fd;
 
-    auto pos = lseek(fd_.get(), 0, SEEK_END);
-    if (pos < 0) {
-        PLOG(ERROR) << "lseek end failed";
-        return false;
-    }
-    fd_size_ = pos;
-
-    if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek header failed";
-        return false;
-    }
-    if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) {
-        PLOG(ERROR) << "read header failed";
+    if (!ReadCowHeader(fd, &header_)) {
         return false;
     }
 
-    if (header_.magic != kCowMagicNumber) {
-        LOG(ERROR) << "Header Magic corrupted. Magic: " << header_.magic
-                   << "Expected: " << kCowMagicNumber;
-        return false;
-    }
-    if (header_.footer_size != sizeof(CowFooter)) {
-        LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
-                   << sizeof(CowFooter);
-        return false;
-    }
-    if (header_.op_size != sizeof(CowOperation)) {
-        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
-                   << sizeof(CowOperation);
-        return false;
-    }
-    if (header_.cluster_ops == 1) {
-        LOG(ERROR) << "Clusters must contain at least two operations to function.";
-        return false;
-    }
-    if (header_.op_size != sizeof(CowOperation)) {
-        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
-                   << sizeof(CowOperation);
-        return false;
-    }
-    if (header_.cluster_ops == 1) {
-        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+    CowParserV2 parser;
+    if (!parser.Parse(fd, header_, label)) {
         return false;
     }
 
-    if ((header_.major_version > kCowVersionMajor) || (header_.minor_version != kCowVersionMinor)) {
-        LOG(ERROR) << "Header version mismatch";
-        LOG(ERROR) << "Major version: " << header_.major_version
-                   << "Expected: " << kCowVersionMajor;
-        LOG(ERROR) << "Minor version: " << header_.minor_version
-                   << "Expected: " << kCowVersionMinor;
-        return false;
-    }
+    footer_ = parser.footer();
+    fd_size_ = parser.fd_size();
+    last_label_ = parser.last_label();
+    ops_ = std::move(parser.ops());
+    data_loc_ = parser.data_loc();
 
-    if (!ParseOps(label)) {
-        return false;
-    }
     // If we're resuming a write, we're not ready to merge
     if (label.has_value()) return true;
     return PrepMergeOps();
 }
 
-bool CowReader::ParseOps(std::optional<uint64_t> label) {
-    uint64_t pos;
-    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
-
-    // Skip the scratch space
-    if (header_.major_version >= 2 && (header_.buffer_size > 0)) {
-        LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
-        size_t init_offset = header_.header_size + header_.buffer_size;
-        pos = lseek(fd_.get(), init_offset, SEEK_SET);
-        if (pos != init_offset) {
-            PLOG(ERROR) << "lseek ops failed";
-            return false;
-        }
-    } else {
-        pos = lseek(fd_.get(), header_.header_size, SEEK_SET);
-        if (pos != header_.header_size) {
-            PLOG(ERROR) << "lseek ops failed";
-            return false;
-        }
-        // Reading a v1 version of COW which doesn't have buffer_size.
-        header_.buffer_size = 0;
-    }
-    uint64_t data_pos = 0;
-
-    if (header_.cluster_ops) {
-        data_pos = pos + header_.cluster_ops * sizeof(CowOperation);
-    } else {
-        data_pos = pos + sizeof(CowOperation);
-    }
-
-    auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
-    uint64_t current_op_num = 0;
-    uint64_t cluster_ops = header_.cluster_ops ?: 1;
-    bool done = false;
-
-    // Alternating op clusters and data
-    while (!done) {
-        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
-        if (to_add == 0) break;
-        ops_buffer->resize(current_op_num + to_add);
-        if (!android::base::ReadFully(fd_, &ops_buffer->data()[current_op_num],
-                                      to_add * sizeof(CowOperation))) {
-            PLOG(ERROR) << "read op failed";
-            return false;
-        }
-        // Parse current cluster to find start of next cluster
-        while (current_op_num < ops_buffer->size()) {
-            auto& current_op = ops_buffer->data()[current_op_num];
-            current_op_num++;
-            if (current_op.type == kCowXorOp) {
-                data_loc->insert({current_op.new_block, data_pos});
-            }
-            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
-            data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
-
-            if (current_op.type == kCowClusterOp) {
-                break;
-            } else if (current_op.type == kCowLabelOp) {
-                last_label_ = {current_op.source};
-
-                // If we reach the requested label, stop reading.
-                if (label && label.value() == current_op.source) {
-                    done = true;
-                    break;
-                }
-            } else if (current_op.type == kCowFooterOp) {
-                footer_.emplace();
-                CowFooter* footer = &footer_.value();
-                memcpy(&footer_->op, &current_op, sizeof(footer->op));
-                off_t offs = lseek(fd_.get(), pos, SEEK_SET);
-                if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
-                    PLOG(ERROR) << "lseek next op failed " << offs;
-                    return false;
-                }
-                if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
-                    LOG(ERROR) << "Could not read COW footer";
-                    return false;
-                }
-
-                // Drop the footer from the op stream.
-                current_op_num--;
-                done = true;
-                break;
-            } else if (current_op.type == kCowSequenceOp) {
-                has_seq_ops_ = true;
-            }
-        }
-
-        // Position for next cluster read
-        off_t offs = lseek(fd_.get(), pos, SEEK_SET);
-        if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
-            PLOG(ERROR) << "lseek next op failed " << offs;
-            return false;
-        }
-        ops_buffer->resize(current_op_num);
-    }
-
-    LOG(DEBUG) << "COW file read complete. Total ops: " << ops_buffer->size();
-    // To successfully parse a COW file, we need either:
-    //  (1) a label to read up to, and for that label to be found, or
-    //  (2) a valid footer.
-    if (label) {
-        if (!last_label_) {
-            LOG(ERROR) << "Did not find label " << label.value()
-                       << " while reading COW (no labels found)";
-            return false;
-        }
-        if (last_label_.value() != label.value()) {
-            LOG(ERROR) << "Did not find label " << label.value()
-                       << ", last label=" << last_label_.value();
-            return false;
-        }
-    } else if (!footer_) {
-        LOG(ERROR) << "No COW footer found";
-        return false;
-    }
-
-    uint8_t csum[32];
-    memset(csum, 0, sizeof(uint8_t) * 32);
-
-    if (footer_) {
-        if (ops_buffer->size() != footer_->op.num_ops) {
-            LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
-                       << ops_buffer->size();
-            return false;
-        }
-        if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
-            LOG(ERROR) << "ops size does not match ";
-            return false;
-        }
-        SHA256(&footer_->op, sizeof(footer_->op), footer_->data.footer_checksum);
-        if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
-            LOG(ERROR) << "ops checksum does not match";
-            return false;
-        }
-        SHA256(ops_buffer->data(), footer_->op.ops_size, csum);
-        if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
-            LOG(ERROR) << "ops checksum does not match";
-            return false;
-        }
-    }
-
-    ops_ = ops_buffer;
-    ops_->shrink_to_fit();
-    data_loc_ = data_loc;
-
-    return true;
-}
-
 //
 // This sets up the data needed for MergeOpIter. MergeOpIter presents
 // data in the order we intend to merge in.
@@ -430,7 +230,7 @@
             size_t seq_len = current_op.data_length / sizeof(uint32_t);
 
             merge_op_blocks->resize(merge_op_blocks->size() + seq_len);
-            if (!GetRawBytes(current_op.source, &merge_op_blocks->data()[num_seqs],
+            if (!GetRawBytes(&current_op, &merge_op_blocks->data()[num_seqs],
                              current_op.data_length, &read)) {
                 PLOG(ERROR) << "Failed to read sequence op!";
                 return false;
@@ -445,7 +245,8 @@
             continue;
         }
 
-        if (!has_seq_ops_ && IsOrderedOp(current_op)) {
+        // Sequence ops must be the first ops in the stream.
+        if (seq_ops_set.empty() && IsOrderedOp(current_op)) {
             merge_op_blocks->emplace_back(current_op.new_block);
         } else if (seq_ops_set.count(current_op.new_block) == 0) {
             other_ops.push_back(current_op.new_block);
@@ -508,47 +309,38 @@
 
 bool CowReader::VerifyMergeOps() {
     auto itr = GetMergeOpIter(true);
-    std::unordered_map<uint64_t, CowOperation> overwritten_blocks;
-    while (!itr->Done()) {
-        CowOperation op = itr->Get();
-        uint64_t block;
-        bool offset;
-        if (op.type == kCowCopyOp) {
-            block = op.source;
-            offset = false;
-        } else if (op.type == kCowXorOp) {
-            block = op.source / BLOCK_SZ;
-            offset = (op.source % BLOCK_SZ) != 0;
-        } else {
+    std::unordered_map<uint64_t, const CowOperation*> overwritten_blocks;
+    while (!itr->AtEnd()) {
+        const auto& op = itr->Get();
+        uint64_t offset;
+        if (!GetSourceOffset(op, &offset)) {
             itr->Next();
             continue;
         }
 
-        CowOperation* overwrite = nullptr;
+        uint64_t block = GetBlockFromOffset(header_, offset);
+        bool misaligned = (GetBlockRelativeOffset(header_, offset) != 0);
+
+        const CowOperation* overwrite = nullptr;
         if (overwritten_blocks.count(block)) {
-            overwrite = &overwritten_blocks[block];
+            overwrite = overwritten_blocks[block];
             LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
                        << op << "\noverwritten by previously merged op:\n"
                        << *overwrite;
         }
-        if (offset && overwritten_blocks.count(block + 1)) {
-            overwrite = &overwritten_blocks[block + 1];
+        if (misaligned && overwritten_blocks.count(block + 1)) {
+            overwrite = overwritten_blocks[block + 1];
             LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
                        << op << "\noverwritten by previously merged op:\n"
                        << *overwrite;
         }
         if (overwrite != nullptr) return false;
-        overwritten_blocks[op.new_block] = op;
+        overwritten_blocks[op->new_block] = op;
         itr->Next();
     }
     return true;
 }
 
-bool CowReader::GetHeader(CowHeader* header) {
-    *header = header_;
-    return true;
-}
-
 bool CowReader::GetFooter(CowFooter* footer) {
     if (!footer_) return false;
     *footer = footer_.value();
@@ -565,12 +357,12 @@
   public:
     CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops, uint64_t start);
 
-    bool Done() override;
-    const CowOperation& Get() override;
+    bool AtEnd() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
-    bool RDone() override;
+    bool AtBegin() override;
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -582,27 +374,27 @@
     op_iter_ = ops_->begin() + start;
 }
 
-bool CowOpIter::RDone() {
+bool CowOpIter::AtBegin() {
     return op_iter_ == ops_->begin();
 }
 
 void CowOpIter::Prev() {
-    CHECK(!RDone());
+    CHECK(!AtBegin());
     op_iter_--;
 }
 
-bool CowOpIter::Done() {
+bool CowOpIter::AtEnd() {
     return op_iter_ == ops_->end();
 }
 
 void CowOpIter::Next() {
-    CHECK(!Done());
+    CHECK(!AtEnd());
     op_iter_++;
 }
 
-const CowOperation& CowOpIter::Get() {
-    CHECK(!Done());
-    return (*op_iter_);
+const CowOperation* CowOpIter::Get() {
+    CHECK(!AtEnd());
+    return &(*op_iter_);
 }
 
 class CowRevMergeOpIter final : public ICowOpIter {
@@ -610,12 +402,12 @@
     explicit CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
                                std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
-    bool Done() override;
-    const CowOperation& Get() override;
+    bool AtEnd() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
-    bool RDone() override;
+    bool AtBegin() override;
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -629,12 +421,12 @@
     explicit CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
                             std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
-    bool Done() override;
-    const CowOperation& Get() override;
+    bool AtEnd() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
-    bool RDone() override;
+    bool AtBegin() override;
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -651,27 +443,27 @@
     block_iter_ = cow_op_index_vec_->begin() + start;
 }
 
-bool CowMergeOpIter::RDone() {
+bool CowMergeOpIter::AtBegin() {
     return block_iter_ == cow_op_index_vec_->begin();
 }
 
 void CowMergeOpIter::Prev() {
-    CHECK(!RDone());
+    CHECK(!AtBegin());
     block_iter_--;
 }
 
-bool CowMergeOpIter::Done() {
+bool CowMergeOpIter::AtEnd() {
     return block_iter_ == cow_op_index_vec_->end();
 }
 
 void CowMergeOpIter::Next() {
-    CHECK(!Done());
+    CHECK(!AtEnd());
     block_iter_++;
 }
 
-const CowOperation& CowMergeOpIter::Get() {
-    CHECK(!Done());
-    return ops_->data()[*block_iter_];
+const CowOperation* CowMergeOpIter::Get() {
+    CHECK(!AtEnd());
+    return &ops_->data()[*block_iter_];
 }
 
 CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
@@ -683,27 +475,27 @@
     block_riter_ = cow_op_index_vec_->rbegin();
 }
 
-bool CowRevMergeOpIter::RDone() {
+bool CowRevMergeOpIter::AtBegin() {
     return block_riter_ == cow_op_index_vec_->rbegin();
 }
 
 void CowRevMergeOpIter::Prev() {
-    CHECK(!RDone());
+    CHECK(!AtBegin());
     block_riter_--;
 }
 
-bool CowRevMergeOpIter::Done() {
+bool CowRevMergeOpIter::AtEnd() {
     return block_riter_ == cow_op_index_vec_->rend() - start_;
 }
 
 void CowRevMergeOpIter::Next() {
-    CHECK(!Done());
+    CHECK(!AtEnd());
     block_riter_++;
 }
 
-const CowOperation& CowRevMergeOpIter::Get() {
-    CHECK(!Done());
-    return ops_->data()[*block_riter_];
+const CowOperation* CowRevMergeOpIter::Get() {
+    CHECK(!AtEnd());
+    return &ops_->data()[*block_riter_];
 }
 
 std::unique_ptr<ICowOpIter> CowReader::GetOpIter(bool merge_progress) {
@@ -720,10 +512,22 @@
                                             ignore_progress ? 0 : merge_op_start_);
 }
 
+bool CowReader::GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read) {
+    switch (op->type) {
+        case kCowSequenceOp:
+        case kCowReplaceOp:
+        case kCowXorOp:
+            return GetRawBytes(GetCowOpSourceInfoData(op), buffer, len, read);
+        default:
+            LOG(ERROR) << "Cannot get raw bytes of non-data op: " << *op;
+            return false;
+    }
+}
+
 bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
     // Validate the offset, taking care to acknowledge possible overflow of offset+len.
-    if (offset < header_.header_size || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
-        offset + len > fd_size_ - sizeof(CowFooter)) {
+    if (offset < header_.prefix.header_size || offset >= fd_size_ - sizeof(CowFooter) ||
+        len >= fd_size_ || offset + len > fd_size_ - sizeof(CowFooter)) {
         LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
         return false;
     }
@@ -747,18 +551,18 @@
         remaining_ = data_length_;
     }
 
-    bool Read(void* buffer, size_t length, size_t* read) override {
+    ssize_t Read(void* buffer, size_t length) override {
         size_t to_read = std::min(length, remaining_);
         if (!to_read) {
-            *read = 0;
-            return true;
+            return 0;
         }
-        if (!reader_->GetRawBytes(offset_, buffer, to_read, read)) {
-            return false;
+        size_t read;
+        if (!reader_->GetRawBytes(offset_, buffer, to_read, &read)) {
+            return -1;
         }
-        offset_ += *read;
-        remaining_ -= *read;
-        return true;
+        offset_ += read;
+        remaining_ -= read;
+        return read;
     }
 
     size_t Size() const override { return data_length_; }
@@ -770,11 +574,15 @@
     size_t remaining_;
 };
 
-bool CowReader::ReadData(const CowOperation& op, IByteSink* sink) {
+uint8_t CowReader::GetCompressionType(const CowOperation* op) {
+    return op->compression;
+}
+
+ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
+                            size_t ignore_bytes) {
     std::unique_ptr<IDecompressor> decompressor;
-    switch (op.compression) {
+    switch (GetCompressionType(op)) {
         case kCowCompressNone:
-            decompressor = IDecompressor::Uncompressed();
             break;
         case kCowCompressGz:
             decompressor = IDecompressor::Gz();
@@ -782,24 +590,49 @@
         case kCowCompressBrotli:
             decompressor = IDecompressor::Brotli();
             break;
+        case kCowCompressZstd:
+            if (header_.block_size != op->data_length) {
+                decompressor = IDecompressor::Zstd();
+            }
+            break;
         case kCowCompressLz4:
-            decompressor = IDecompressor::Lz4();
+            if (header_.block_size != op->data_length) {
+                decompressor = IDecompressor::Lz4();
+            }
             break;
         default:
-            LOG(ERROR) << "Unknown compression type: " << op.compression;
-            return false;
+            LOG(ERROR) << "Unknown compression type: " << GetCompressionType(op);
+            return -1;
     }
 
     uint64_t offset;
-    if (op.type == kCowXorOp) {
-        offset = data_loc_->at(op.new_block);
+    if (op->type == kCowXorOp) {
+        offset = data_loc_->at(op->new_block);
     } else {
-        offset = op.source;
+        offset = GetCowOpSourceInfoData(op);
     }
-    CowDataStream stream(this, offset, op.data_length);
+
+    if (!decompressor) {
+        CowDataStream stream(this, offset + ignore_bytes, op->data_length - ignore_bytes);
+        return stream.ReadFully(buffer, buffer_size);
+    }
+
+    CowDataStream stream(this, offset, op->data_length);
     decompressor->set_stream(&stream);
-    decompressor->set_sink(sink);
-    return decompressor->Decompress(header_.block_size);
+    return decompressor->Decompress(buffer, buffer_size, header_.block_size, ignore_bytes);
+}
+
+bool CowReader::GetSourceOffset(const CowOperation* op, uint64_t* source_offset) {
+    switch (op->type) {
+        case kCowCopyOp:
+            *source_offset = GetCowOpSourceInfoData(op) * header_.block_size;
+            return true;
+        case kCowXorOp:
+            *source_offset = GetCowOpSourceInfoData(op);
+            return true;
+        default:
+            return false;
+    }
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 167ff8c..a6dee4f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -16,6 +16,7 @@
 #include <stdio.h>
 #include <unistd.h>
 
+#include <chrono>
 #include <iomanip>
 #include <iostream>
 #include <string>
@@ -23,11 +24,26 @@
 
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
+#include <gflags/gflags.h>
 #include <libsnapshot/cow_reader.h>
+#include "parser_v2.h"
+
+DEFINE_bool(silent, false, "Run silently");
+DEFINE_bool(decompress, false, "Attempt to decompress data ops");
+DEFINE_bool(show_bad_data, false, "If an op fails to decompress, show its daw data");
+DEFINE_bool(show_ops, false, "Print all opcode information");
+DEFINE_string(order, "", "If show_ops is true, change the order (either merge or reverse-merge)");
+DEFINE_bool(show_merged, false,
+            "If show_ops is true, and order is merge or reverse-merge, include merged ops");
+DEFINE_bool(verify_merge_sequence, false, "Verify merge order sequencing");
+DEFINE_bool(show_merge_sequence, false, "Show merge order sequence");
+DEFINE_bool(show_raw_ops, false, "Show raw ops directly from the underlying parser");
 
 namespace android {
 namespace snapshot {
 
+using android::base::borrowed_fd;
+
 void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
               unsigned int, const char* message) {
     if (severity == android::base::ERROR) {
@@ -37,69 +53,57 @@
     }
 }
 
-static void usage(void) {
-    LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
-    LOG(ERROR) << "\t -s Run Silent";
-    LOG(ERROR) << "\t -d Attempt to decompress";
-    LOG(ERROR) << "\t -b Show data for failed decompress";
-    LOG(ERROR) << "\t -l Show ops";
-    LOG(ERROR) << "\t -m Show ops in reverse merge order";
-    LOG(ERROR) << "\t -n Show ops in merge order";
-    LOG(ERROR) << "\t -a Include merged ops in any merge order listing";
-    LOG(ERROR) << "\t -o Shows sequence op block order";
-    LOG(ERROR) << "\t -v Verifies merge order has no conflicts\n";
-}
-
-enum OpIter { Normal, RevMerge, Merge };
-
-struct Options {
-    bool silent;
-    bool decompress;
-    bool show_ops;
-    bool show_bad;
-    bool show_seq;
-    bool verify_sequence;
-    OpIter iter_type;
-    bool include_merged;
-};
-
-// Sink that always appends to the end of a string.
-class StringSink : public IByteSink {
-  public:
-    void* GetBuffer(size_t requested, size_t* actual) override {
-        size_t old_size = stream_.size();
-        stream_.resize(old_size + requested, '\0');
-        *actual = requested;
-        return stream_.data() + old_size;
-    }
-    bool ReturnData(void*, size_t) override { return true; }
-    void Reset() { stream_.clear(); }
-
-    std::string& stream() { return stream_; }
-
-  private:
-    std::string stream_;
-};
-
-static void ShowBad(CowReader& reader, const struct CowOperation& op) {
+static void ShowBad(CowReader& reader, const struct CowOperation* op) {
     size_t count;
-    auto buffer = std::make_unique<uint8_t[]>(op.data_length);
+    auto buffer = std::make_unique<uint8_t[]>(op->data_length);
 
-    if (!reader.GetRawBytes(op.source, buffer.get(), op.data_length, &count)) {
+    if (!reader.GetRawBytes(op, buffer.get(), op->data_length, &count)) {
         std::cerr << "Failed to read at all!\n";
     } else {
         std::cout << "The Block data is:\n";
-        for (int i = 0; i < op.data_length; i++) {
+        for (int i = 0; i < op->data_length; i++) {
             std::cout << std::hex << (int)buffer[i];
         }
         std::cout << std::dec << "\n\n";
-        if (op.data_length >= sizeof(CowOperation)) {
+        if (op->data_length >= sizeof(CowOperation)) {
             std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n";
         }
     }
 }
 
-static bool Inspect(const std::string& path, Options opt) {
+static bool ShowRawOpStreamV2(borrowed_fd fd, const CowHeader& header) {
+    CowParserV2 parser;
+    if (!parser.Parse(fd, header)) {
+        LOG(ERROR) << "v2 parser failed";
+        return false;
+    }
+    for (const auto& op : *parser.ops()) {
+        std::cout << op << "\n";
+        if (auto iter = parser.data_loc()->find(op.new_block); iter != parser.data_loc()->end()) {
+            std::cout << "    data loc: " << iter->second << "\n";
+        }
+    }
+    return true;
+}
+
+static bool ShowRawOpStream(borrowed_fd fd) {
+    CowHeader header;
+    if (!ReadCowHeader(fd, &header)) {
+        LOG(ERROR) << "parse header failed";
+        return false;
+    }
+
+    switch (header.prefix.major_version) {
+        case 1:
+        case 2:
+            return ShowRawOpStreamV2(fd, header);
+        default:
+            LOG(ERROR) << "unknown COW version: " << header.prefix.major_version;
+            return false;
+    }
+}
+
+static bool Inspect(const std::string& path) {
     android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
     if (fd < 0) {
         PLOG(ERROR) << "open failed: " << path;
@@ -107,37 +111,41 @@
     }
 
     CowReader reader;
+
+    auto start_time = std::chrono::steady_clock::now();
     if (!reader.Parse(fd)) {
         LOG(ERROR) << "parse failed: " << path;
         return false;
     }
+    std::chrono::duration<double> parse_time = std::chrono::steady_clock::now() - start_time;
 
-    CowHeader header;
-    if (!reader.GetHeader(&header)) {
-        LOG(ERROR) << "could not get header: " << path;
-        return false;
-    }
+    const CowHeader& header = reader.GetHeader();
     CowFooter footer;
     bool has_footer = false;
     if (reader.GetFooter(&footer)) has_footer = true;
 
-    if (!opt.silent) {
-        std::cout << "Major version: " << header.major_version << "\n";
-        std::cout << "Minor version: " << header.minor_version << "\n";
-        std::cout << "Header size: " << header.header_size << "\n";
+    if (!FLAGS_silent) {
+        std::cout << "Version: " << header.prefix.major_version << "."
+                  << header.prefix.minor_version << "\n";
+        std::cout << "Header size: " << header.prefix.header_size << "\n";
         std::cout << "Footer size: " << header.footer_size << "\n";
         std::cout << "Block size: " << header.block_size << "\n";
-        std::cout << "Num merge ops: " << header.num_merge_ops << "\n";
-        std::cout << "RA buffer size: " << header.buffer_size << "\n";
-        std::cout << "\n";
+        std::cout << "Merge ops: " << header.num_merge_ops << "\n";
+        std::cout << "Readahead buffer: " << header.buffer_size << " bytes\n";
         if (has_footer) {
-            std::cout << "Total Ops size: " << footer.op.ops_size << "\n";
-            std::cout << "Number of Ops: " << footer.op.num_ops << "\n";
-            std::cout << "\n";
+            std::cout << "Footer: ops usage: " << footer.op.ops_size << " bytes\n";
+            std::cout << "Footer: op count: " << footer.op.num_ops << "\n";
+        } else {
+            std::cout << "Footer: none\n";
         }
     }
 
-    if (opt.verify_sequence) {
+    if (!FLAGS_silent) {
+        std::cout << "Parse time: " << (parse_time.count() * 1000) << "ms\n";
+    }
+
+    if (FLAGS_verify_merge_sequence) {
+        std::cout << "\n";
         if (reader.VerifyMergeOps()) {
             std::cout << "\nMerge sequence is consistent.\n";
         } else {
@@ -146,41 +154,57 @@
     }
 
     std::unique_ptr<ICowOpIter> iter;
-    if (opt.iter_type == Normal) {
+    if (FLAGS_order.empty()) {
         iter = reader.GetOpIter();
-    } else if (opt.iter_type == RevMerge) {
-        iter = reader.GetRevMergeOpIter(opt.include_merged);
-    } else if (opt.iter_type == Merge) {
-        iter = reader.GetMergeOpIter(opt.include_merged);
+    } else if (FLAGS_order == "reverse-merge") {
+        iter = reader.GetRevMergeOpIter(FLAGS_show_merged);
+    } else if (FLAGS_order == "merge") {
+        iter = reader.GetMergeOpIter(FLAGS_show_merged);
     }
-    StringSink sink;
+
+    std::string buffer(header.block_size, '\0');
+
+    if (!FLAGS_silent && FLAGS_show_raw_ops) {
+        std::cout << "\n";
+        std::cout << "Listing raw op stream:\n";
+        std::cout << "----------------------\n";
+        if (!ShowRawOpStream(fd)) {
+            return false;
+        }
+    }
+
+    if (!FLAGS_silent && FLAGS_show_ops) {
+        std::cout << "\n";
+        std::cout << "Listing op stream:\n";
+        std::cout << "------------------\n";
+    }
+
     bool success = true;
     uint64_t xor_ops = 0, copy_ops = 0, replace_ops = 0, zero_ops = 0;
-    while (!iter->Done()) {
-        const CowOperation& op = iter->Get();
+    while (!iter->AtEnd()) {
+        const CowOperation* op = iter->Get();
 
-        if (!opt.silent && opt.show_ops) std::cout << op << "\n";
+        if (!FLAGS_silent && FLAGS_show_ops) std::cout << *op << "\n";
 
-        if (opt.decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
-            if (!reader.ReadData(op, &sink)) {
-                std::cerr << "Failed to decompress for :" << op << "\n";
+        if (FLAGS_decompress && op->type == kCowReplaceOp && op->compression != kCowCompressNone) {
+            if (reader.ReadData(op, buffer.data(), buffer.size()) < 0) {
+                std::cerr << "Failed to decompress for :" << *op << "\n";
                 success = false;
-                if (opt.show_bad) ShowBad(reader, op);
+                if (FLAGS_show_bad_data) ShowBad(reader, op);
             }
-            sink.Reset();
         }
 
-        if (op.type == kCowSequenceOp && opt.show_seq) {
+        if (op->type == kCowSequenceOp && FLAGS_show_merge_sequence) {
             size_t read;
             std::vector<uint32_t> merge_op_blocks;
-            size_t seq_len = op.data_length / sizeof(uint32_t);
+            size_t seq_len = op->data_length / sizeof(uint32_t);
             merge_op_blocks.resize(seq_len);
-            if (!reader.GetRawBytes(op.source, merge_op_blocks.data(), op.data_length, &read)) {
+            if (!reader.GetRawBytes(op, merge_op_blocks.data(), op->data_length, &read)) {
                 PLOG(ERROR) << "Failed to read sequence op!";
                 return false;
             }
-            if (!opt.silent) {
-                std::cout << "Sequence for " << op << " is :\n";
+            if (!FLAGS_silent) {
+                std::cout << "Sequence for " << *op << " is :\n";
                 for (size_t i = 0; i < seq_len; i++) {
                     std::cout << std::setfill('0') << std::setw(6) << merge_op_blocks[i] << ", ";
                     if ((i + 1) % 10 == 0 || i + 1 == seq_len) std::cout << "\n";
@@ -188,24 +212,26 @@
             }
         }
 
-        if (op.type == kCowCopyOp) {
+        if (op->type == kCowCopyOp) {
             copy_ops++;
-        } else if (op.type == kCowReplaceOp) {
+        } else if (op->type == kCowReplaceOp) {
             replace_ops++;
-        } else if (op.type == kCowZeroOp) {
+        } else if (op->type == kCowZeroOp) {
             zero_ops++;
-        } else if (op.type == kCowXorOp) {
+        } else if (op->type == kCowXorOp) {
             xor_ops++;
         }
 
         iter->Next();
     }
 
-    if (!opt.silent) {
+    if (!FLAGS_silent) {
         auto total_ops = replace_ops + zero_ops + copy_ops + xor_ops;
-        std::cout << "Total-data-ops: " << total_ops << "Replace-ops: " << replace_ops
-                  << " Zero-ops: " << zero_ops << " Copy-ops: " << copy_ops
-                  << " Xor_ops: " << xor_ops << std::endl;
+        std::cout << "Data ops: " << total_ops << "\n";
+        std::cout << "Replace ops: " << replace_ops << "\n";
+        std::cout << "Zero ops: " << zero_ops << "\n";
+        std::cout << "Copy ops: " << copy_ops << "\n";
+        std::cout << "Xor ops: " << xor_ops << "\n";
     }
 
     return success;
@@ -215,55 +241,20 @@
 }  // namespace android
 
 int main(int argc, char** argv) {
-    int ch;
-    struct android::snapshot::Options opt;
-    opt.silent = false;
-    opt.decompress = false;
-    opt.show_bad = false;
-    opt.iter_type = android::snapshot::Normal;
-    opt.verify_sequence = false;
-    opt.include_merged = false;
-    while ((ch = getopt(argc, argv, "sdbmnolva")) != -1) {
-        switch (ch) {
-            case 's':
-                opt.silent = true;
-                break;
-            case 'd':
-                opt.decompress = true;
-                break;
-            case 'b':
-                opt.show_bad = true;
-                break;
-            case 'm':
-                opt.iter_type = android::snapshot::RevMerge;
-                break;
-            case 'n':
-                opt.iter_type = android::snapshot::Merge;
-                break;
-            case 'o':
-                opt.show_seq = true;
-                break;
-            case 'l':
-                opt.show_ops = true;
-                break;
-            case 'v':
-                opt.verify_sequence = true;
-                break;
-            case 'a':
-                opt.include_merged = true;
-                break;
-            default:
-                android::snapshot::usage();
-        }
-    }
-    android::base::InitLogging(argv, android::snapshot::MyLogger);
+    gflags::ParseCommandLineFlags(&argc, &argv, true);
 
-    if (argc < optind + 1) {
-        android::snapshot::usage();
+    if (argc < 2) {
+        gflags::ShowUsageWithFlags(argv[0]);
+        return 1;
+    }
+    if (FLAGS_order != "" && FLAGS_order != "merge" && FLAGS_order != "reverse-merge") {
+        std::cerr << "Order must either be \"merge\" or \"reverse-merge\".\n";
         return 1;
     }
 
-    if (!android::snapshot::Inspect(argv[optind], opt)) {
+    android::base::InitLogging(argv, android::snapshot::MyLogger);
+
+    if (!android::snapshot::Inspect(argv[1])) {
         return 1;
     }
     return 0;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
new file mode 100644
index 0000000..fdb5c59
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
@@ -0,0 +1,238 @@
+// Copyright (C) 2023 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 "parser_v2.h"
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::borrowed_fd;
+
+bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header) {
+    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+
+    memset(header, 0, sizeof(*header));
+
+    if (!android::base::ReadFully(fd, &header->prefix, sizeof(header->prefix))) {
+        return false;
+    }
+    if (header->prefix.magic != kCowMagicNumber) {
+        LOG(ERROR) << "Header Magic corrupted. Magic: " << header->prefix.magic
+                   << "Expected: " << kCowMagicNumber;
+        return false;
+    }
+    if (header->prefix.header_size > sizeof(CowHeader)) {
+        LOG(ERROR) << "Unknown CowHeader size (got " << header->prefix.header_size
+                   << " bytes, expected at most " << sizeof(CowHeader) << " bytes)";
+        return false;
+    }
+
+    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+    return android::base::ReadFully(fd, header, header->prefix.header_size);
+}
+
+bool CowParserV2::Parse(borrowed_fd fd, const CowHeader& header, std::optional<uint64_t> label) {
+    auto pos = lseek(fd.get(), 0, SEEK_END);
+    if (pos < 0) {
+        PLOG(ERROR) << "lseek end failed";
+        return false;
+    }
+    fd_size_ = pos;
+    header_ = header;
+
+    if (header_.footer_size != sizeof(CowFooter)) {
+        LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
+                   << sizeof(CowFooter);
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
+
+    if ((header_.prefix.major_version > kCowVersionMajor) ||
+        (header_.prefix.minor_version != kCowVersionMinor)) {
+        LOG(ERROR) << "Header version mismatch, "
+                   << "major version: " << header_.prefix.major_version
+                   << ", expected: " << kCowVersionMajor
+                   << ", minor version: " << header_.prefix.minor_version
+                   << ", expected: " << kCowVersionMinor;
+        return false;
+    }
+
+    return ParseOps(fd, label);
+}
+
+bool CowParserV2::ParseOps(borrowed_fd fd, std::optional<uint64_t> label) {
+    uint64_t pos;
+    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
+
+    // Skip the scratch space
+    if (header_.prefix.major_version >= 2 && (header_.buffer_size > 0)) {
+        LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
+        size_t init_offset = header_.prefix.header_size + header_.buffer_size;
+        pos = lseek(fd.get(), init_offset, SEEK_SET);
+        if (pos != init_offset) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+    } else {
+        pos = lseek(fd.get(), header_.prefix.header_size, SEEK_SET);
+        if (pos != header_.prefix.header_size) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+        // Reading a v1 version of COW which doesn't have buffer_size.
+        header_.buffer_size = 0;
+    }
+    uint64_t data_pos = 0;
+
+    if (header_.cluster_ops) {
+        data_pos = pos + header_.cluster_ops * sizeof(CowOperation);
+    } else {
+        data_pos = pos + sizeof(CowOperation);
+    }
+
+    auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
+    uint64_t current_op_num = 0;
+    uint64_t cluster_ops = header_.cluster_ops ?: 1;
+    bool done = false;
+
+    // Alternating op clusters and data
+    while (!done) {
+        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
+        if (to_add == 0) break;
+        ops_buffer->resize(current_op_num + to_add);
+        if (!android::base::ReadFully(fd, &ops_buffer->data()[current_op_num],
+                                      to_add * sizeof(CowOperation))) {
+            PLOG(ERROR) << "read op failed";
+            return false;
+        }
+        // Parse current cluster to find start of next cluster
+        while (current_op_num < ops_buffer->size()) {
+            auto& current_op = ops_buffer->data()[current_op_num];
+            current_op_num++;
+            if (current_op.type == kCowXorOp) {
+                data_loc->insert({current_op.new_block, data_pos});
+            }
+            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
+            data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
+
+            if (current_op.type == kCowClusterOp) {
+                break;
+            } else if (current_op.type == kCowLabelOp) {
+                last_label_ = {current_op.source};
+
+                // If we reach the requested label, stop reading.
+                if (label && label.value() == current_op.source) {
+                    done = true;
+                    break;
+                }
+            } else if (current_op.type == kCowFooterOp) {
+                footer_.emplace();
+                CowFooter* footer = &footer_.value();
+                memcpy(&footer_->op, &current_op, sizeof(footer->op));
+                off_t offs = lseek(fd.get(), pos, SEEK_SET);
+                if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
+                    PLOG(ERROR) << "lseek next op failed " << offs;
+                    return false;
+                }
+                if (!android::base::ReadFully(fd, &footer->unused, sizeof(footer->unused))) {
+                    LOG(ERROR) << "Could not read COW footer";
+                    return false;
+                }
+
+                // Drop the footer from the op stream.
+                current_op_num--;
+                done = true;
+                break;
+            }
+        }
+
+        // Position for next cluster read
+        off_t offs = lseek(fd.get(), pos, SEEK_SET);
+        if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
+            PLOG(ERROR) << "lseek next op failed " << offs;
+            return false;
+        }
+        ops_buffer->resize(current_op_num);
+    }
+
+    LOG(DEBUG) << "COW file read complete. Total ops: " << ops_buffer->size();
+    // To successfully parse a COW file, we need either:
+    //  (1) a label to read up to, and for that label to be found, or
+    //  (2) a valid footer.
+    if (label) {
+        if (!last_label_) {
+            LOG(ERROR) << "Did not find label " << label.value()
+                       << " while reading COW (no labels found)";
+            return false;
+        }
+        if (last_label_.value() != label.value()) {
+            LOG(ERROR) << "Did not find label " << label.value()
+                       << ", last label=" << last_label_.value();
+            return false;
+        }
+    } else if (!footer_) {
+        LOG(ERROR) << "No COW footer found";
+        return false;
+    }
+
+    uint8_t csum[32];
+    memset(csum, 0, sizeof(uint8_t) * 32);
+
+    if (footer_) {
+        if (ops_buffer->size() != footer_->op.num_ops) {
+            LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
+                       << ops_buffer->size();
+            return false;
+        }
+        if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
+            LOG(ERROR) << "ops size does not match ";
+            return false;
+        }
+    }
+
+    ops_ = ops_buffer;
+    ops_->shrink_to_fit();
+    data_loc_ = data_loc;
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
new file mode 100644
index 0000000..afcf687
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 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 <memory>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_format.h>
+
+namespace android {
+namespace snapshot {
+
+class CowParserV2 {
+  public:
+    bool Parse(android::base::borrowed_fd fd, const CowHeader& header,
+               std::optional<uint64_t> label = {});
+
+    const CowHeader& header() const { return header_; }
+    const std::optional<CowFooter>& footer() const { return footer_; }
+    std::shared_ptr<std::vector<CowOperation>> ops() { return ops_; }
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc() const { return data_loc_; }
+    uint64_t fd_size() const { return fd_size_; }
+    const std::optional<uint64_t>& last_label() const { return last_label_; }
+
+  private:
+    bool ParseOps(android::base::borrowed_fd fd, std::optional<uint64_t> label);
+
+    CowHeader header_ = {};
+    std::optional<CowFooter> footer_;
+    std::shared_ptr<std::vector<CowOperation>> ops_;
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+    uint64_t fd_size_;
+    std::optional<uint64_t> last_label_;
+};
+
+bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header);
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
new file mode 100644
index 0000000..a3e40d9
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
@@ -0,0 +1,262 @@
+//
+// Copyright (C) 2020 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 "snapshot_reader.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::borrowed_fd;
+
+CompressedSnapshotReader::CompressedSnapshotReader(std::unique_ptr<ICowReader>&& cow,
+                                                   const std::optional<std::string>& source_device,
+                                                   std::optional<uint64_t> block_dev_size)
+    : cow_(std::move(cow)),
+      block_size_(cow_->GetHeader().block_size),
+      source_device_(source_device),
+      block_device_size_(block_dev_size.value_or(0)) {
+    const auto& header = cow_->GetHeader();
+    block_size_ = header.block_size;
+
+    // Populate the operation map.
+    op_iter_ = cow_->GetOpIter(false);
+    while (!op_iter_->AtEnd()) {
+        const CowOperation* op = op_iter_->Get();
+        if (IsMetadataOp(*op)) {
+            op_iter_->Next();
+            continue;
+        }
+        if (op->new_block >= ops_.size()) {
+            ops_.resize(op->new_block + 1, nullptr);
+        }
+        ops_[op->new_block] = op;
+        op_iter_->Next();
+    }
+}
+
+// Not supported.
+bool CompressedSnapshotReader::Open(const char*, int, mode_t) {
+    errno = EINVAL;
+    return false;
+}
+
+bool CompressedSnapshotReader::Open(const char*, int) {
+    errno = EINVAL;
+    return false;
+}
+
+ssize_t CompressedSnapshotReader::Write(const void*, size_t) {
+    errno = EINVAL;
+    return false;
+}
+
+bool CompressedSnapshotReader::BlkIoctl(int, uint64_t, uint64_t, int*) {
+    errno = EINVAL;
+    return false;
+}
+
+borrowed_fd CompressedSnapshotReader::GetSourceFd() {
+    if (source_fd_ < 0) {
+        if (!source_device_) {
+            LOG(ERROR) << "CompressedSnapshotReader needs source device, but none was set";
+            errno = EINVAL;
+            return {-1};
+        }
+        source_fd_.reset(open(source_device_->c_str(), O_RDONLY | O_CLOEXEC));
+        if (source_fd_ < 0) {
+            PLOG(ERROR) << "open " << *source_device_;
+            return {-1};
+        }
+    }
+    return source_fd_;
+}
+
+ssize_t CompressedSnapshotReader::Read(void* buf, size_t count) {
+    // Find the start and end chunks, inclusive.
+    uint64_t start_chunk = offset_ / block_size_;
+    uint64_t end_chunk = (offset_ + count - 1) / block_size_;
+
+    // Chop off the first N bytes if the position is not block-aligned.
+    size_t start_offset = offset_ % block_size_;
+
+    uint8_t* buf_pos = reinterpret_cast<uint8_t*>(buf);
+    size_t buf_remaining = count;
+
+    size_t initial_bytes = std::min(block_size_ - start_offset, buf_remaining);
+    ssize_t rv = ReadBlock(start_chunk, start_offset, buf_pos, initial_bytes);
+    if (rv < 0) {
+        return -1;
+    }
+    offset_ += rv;
+    buf_pos += rv;
+    buf_remaining -= rv;
+
+    for (uint64_t chunk = start_chunk + 1; chunk < end_chunk; chunk++) {
+        ssize_t rv = ReadBlock(chunk, 0, buf_pos, buf_remaining);
+        if (rv < 0) {
+            return -1;
+        }
+        offset_ += rv;
+        buf_pos += rv;
+        buf_remaining -= rv;
+    }
+
+    if (buf_remaining) {
+        ssize_t rv = ReadBlock(end_chunk, 0, buf_pos, buf_remaining);
+        if (rv < 0) {
+            return -1;
+        }
+        offset_ += rv;
+        buf_pos += rv;
+        buf_remaining -= rv;
+    }
+
+    CHECK_EQ(buf_pos - reinterpret_cast<uint8_t*>(buf), count);
+    CHECK_EQ(buf_remaining, 0);
+
+    errno = 0;
+    return count;
+}
+
+ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, size_t start_offset, void* buffer,
+                                            size_t buffer_size) {
+    size_t bytes_to_read = std::min(static_cast<size_t>(block_size_), buffer_size);
+
+    // The offset is relative to the chunk; we should be reading no more than
+    // one chunk.
+    CHECK(start_offset + bytes_to_read <= block_size_);
+
+    const CowOperation* op = nullptr;
+    if (chunk < ops_.size()) {
+        op = ops_[chunk];
+    }
+
+    if (!op || op->type == kCowCopyOp) {
+        borrowed_fd fd = GetSourceFd();
+        if (fd < 0) {
+            // GetSourceFd sets errno.
+            return -1;
+        }
+
+        if (op) {
+            uint64_t source_offset;
+            if (!cow_->GetSourceOffset(op, &source_offset)) {
+                LOG(ERROR) << "GetSourceOffset failed in CompressedSnapshotReader for op: " << *op;
+                return false;
+            }
+            chunk = GetBlockFromOffset(cow_->GetHeader(), source_offset);
+        }
+
+        off64_t offset = (chunk * block_size_) + start_offset;
+        if (!android::base::ReadFullyAtOffset(fd, buffer, bytes_to_read, offset)) {
+            PLOG(ERROR) << "read " << *source_device_;
+            // ReadFullyAtOffset sets errno.
+            return -1;
+        }
+    } else if (op->type == kCowZeroOp) {
+        memset(buffer, 0, bytes_to_read);
+    } else if (op->type == kCowReplaceOp) {
+        if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
+            LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
+            errno = EIO;
+            return -1;
+        }
+    } else if (op->type == kCowXorOp) {
+        borrowed_fd fd = GetSourceFd();
+        if (fd < 0) {
+            // GetSourceFd sets errno.
+            return -1;
+        }
+
+        uint64_t source_offset;
+        if (!cow_->GetSourceOffset(op, &source_offset)) {
+            LOG(ERROR) << "GetSourceOffset failed in CompressedSnapshotReader for op: " << *op;
+            return false;
+        }
+        off64_t offset = source_offset + start_offset;
+
+        std::string data(bytes_to_read, '\0');
+        if (!android::base::ReadFullyAtOffset(fd, data.data(), data.size(), offset)) {
+            PLOG(ERROR) << "read " << *source_device_;
+            // ReadFullyAtOffset sets errno.
+            return -1;
+        }
+
+        if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
+            LOG(ERROR) << "CompressedSnapshotReader failed to read xor op";
+            errno = EIO;
+            return -1;
+        }
+
+        for (size_t i = 0; i < bytes_to_read; i++) {
+            ((char*)buffer)[i] ^= data[i];
+        }
+    } else {
+        LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type);
+        errno = EINVAL;
+        return -1;
+    }
+
+    // MemoryByteSink doesn't do anything in ReturnBuffer, so don't bother calling it.
+    return bytes_to_read;
+}
+
+off64_t CompressedSnapshotReader::Seek(off64_t offset, int whence) {
+    switch (whence) {
+        case SEEK_SET:
+            offset_ = offset;
+            break;
+        case SEEK_END:
+            offset_ = static_cast<off64_t>(block_device_size_) + offset;
+            break;
+        case SEEK_CUR:
+            offset_ += offset;
+            break;
+        default:
+            LOG(ERROR) << "Unrecognized seek whence: " << whence;
+            errno = EINVAL;
+            return -1;
+    }
+    return offset_;
+}
+
+uint64_t CompressedSnapshotReader::BlockDevSize() {
+    return block_device_size_;
+}
+
+bool CompressedSnapshotReader::Close() {
+    cow_ = nullptr;
+    source_fd_ = {};
+    return true;
+}
+
+bool CompressedSnapshotReader::IsSettingErrno() {
+    return true;
+}
+
+bool CompressedSnapshotReader::IsOpen() {
+    return cow_ != nullptr;
+}
+
+bool CompressedSnapshotReader::Flush() {
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_reader.h b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.h
similarity index 64%
rename from fs_mgr/libsnapshot/snapshot_reader.h
rename to fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.h
index a3e7e22..3de63ed 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.h
@@ -26,18 +26,16 @@
 namespace android {
 namespace snapshot {
 
-class ReadOnlyFileDescriptor : public chromeos_update_engine::FileDescriptor {
+class CompressedSnapshotReader : public chromeos_update_engine::FileDescriptor {
   public:
+    CompressedSnapshotReader(std::unique_ptr<ICowReader>&& cow,
+                             const std::optional<std::string>& source_device,
+                             std::optional<uint64_t> block_dev_size);
+
     bool Open(const char* path, int flags, mode_t mode) override;
     bool Open(const char* path, int flags) override;
     ssize_t Write(const void* buf, size_t count) override;
     bool BlkIoctl(int request, uint64_t start, uint64_t length, int* result) override;
-};
-
-class ReadFdFileDescriptor : public ReadOnlyFileDescriptor {
-  public:
-    explicit ReadFdFileDescriptor(android::base::unique_fd&& fd);
-
     ssize_t Read(void* buf, size_t count) override;
     off64_t Seek(off64_t offset, int whence) override;
     uint64_t BlockDevSize() override;
@@ -47,29 +45,10 @@
     bool Flush() override;
 
   private:
-    android::base::unique_fd fd_;
-};
-
-class CompressedSnapshotReader : public ReadOnlyFileDescriptor {
-  public:
-    bool SetCow(std::unique_ptr<CowReader>&& cow);
-    void SetSourceDevice(const std::string& source_device);
-    void SetBlockDeviceSize(uint64_t block_device_size);
-
-    ssize_t Read(void* buf, size_t count) override;
-    off64_t Seek(off64_t offset, int whence) override;
-    uint64_t BlockDevSize() override;
-    bool Close() override;
-    bool IsSettingErrno() override;
-    bool IsOpen() override;
-    bool Flush() override;
-
-  private:
-    ssize_t ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
-                      const std::optional<uint64_t>& max_bytes = {});
+    ssize_t ReadBlock(uint64_t chunk, size_t start_offset, void* buffer, size_t size);
     android::base::borrowed_fd GetSourceFd();
 
-    std::unique_ptr<CowReader> cow_;
+    std::unique_ptr<ICowReader> cow_;
     std::unique_ptr<ICowOpIter> op_iter_;
     uint32_t block_size_ = 0;
 
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader_test.cpp
similarity index 88%
rename from fs_mgr/libsnapshot/snapshot_reader_test.cpp
rename to fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader_test.cpp
index 9adc655..10cb06d 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader_test.cpp
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <libsnapshot/snapshot.h>
-
 #include <unordered_set>
 
 #include <android-base/file.h>
@@ -61,7 +59,7 @@
         ASSERT_EQ(fsync(base_->fd), 0);
     }
 
-    void WriteCow(ISnapshotWriter* writer) {
+    void WriteCow(ICowWriter* writer) {
         std::string new_block = MakeNewBlockString();
         std::string xor_block = MakeXorBlockString();
 
@@ -72,8 +70,8 @@
         ASSERT_TRUE(writer->Finalize());
     }
 
-    void TestBlockReads(ISnapshotWriter* writer) {
-        auto reader = writer->OpenReader();
+    void TestBlockReads(ICowWriter* writer) {
+        auto reader = writer->OpenFileDescriptor(base_->path);
         ASSERT_NE(reader, nullptr);
 
         // Test that unchanged blocks are not modified.
@@ -117,8 +115,8 @@
         ASSERT_EQ(two_blocks, zeroes);
     }
 
-    void TestByteReads(ISnapshotWriter* writer) {
-        auto reader = writer->OpenReader();
+    void TestByteReads(ICowWriter* writer) {
+        auto reader = writer->OpenFileDescriptor(base_->path);
         ASSERT_NE(reader, nullptr);
 
         std::string blob(kBlockSize * 3, 'x');
@@ -140,9 +138,21 @@
         ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset);
         ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value));
         ASSERT_EQ(value, MakeNewBlockString()[1000]);
+
+        // Test a sequence of one byte reads.
+        offset = 5 * kBlockSize + 10;
+        std::string expected = MakeNewBlockString().substr(10, 20);
+        ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset);
+
+        std::string got;
+        while (got.size() < expected.size()) {
+            ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value));
+            got.push_back(value);
+        }
+        ASSERT_EQ(got, expected);
     }
 
-    void TestReads(ISnapshotWriter* writer) {
+    void TestReads(ICowWriter* writer) {
         ASSERT_NO_FATAL_FAILURE(TestBlockReads(writer));
         ASSERT_NO_FATAL_FAILURE(TestByteReads(writer));
     }
@@ -174,10 +184,7 @@
     unique_fd cow_fd(dup(cow_->fd));
     ASSERT_GE(cow_fd, 0);
 
-    auto writer = std::make_unique<CompressedSnapshotWriter>(options);
-    writer->SetSourceDevice(base_->path);
-    ASSERT_TRUE(writer->SetCowDevice(std::move(cow_fd)));
-    ASSERT_TRUE(writer->Initialize());
+    auto writer = CreateCowWriter(kDefaultCowVersion, options, std::move(cow_fd));
     ASSERT_NO_FATAL_FAILURE(WriteCow(writer.get()));
     ASSERT_NO_FATAL_FAILURE(TestReads(writer.get()));
 }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
similarity index 68%
rename from fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
rename to fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
index 862ce55..31b9a58 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
@@ -24,7 +24,10 @@
 #include <gtest/gtest.h>
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
+#include "cow_decompress.h"
+#include "writer_v2.h"
 
+using android::base::unique_fd;
 using testing::AssertionFailure;
 using testing::AssertionResult;
 using testing::AssertionSuccess;
@@ -41,57 +44,47 @@
 
     virtual void TearDown() override { cow_ = nullptr; }
 
+    unique_fd GetCowFd() { return unique_fd{dup(cow_->fd)}; }
+
     std::unique_ptr<TemporaryFile> cow_;
 };
 
-// Sink that always appends to the end of a string.
-class StringSink : public IByteSink {
-  public:
-    void* GetBuffer(size_t requested, size_t* actual) override {
-        size_t old_size = stream_.size();
-        stream_.resize(old_size + requested, '\0');
-        *actual = requested;
-        return stream_.data() + old_size;
-    }
-    bool ReturnData(void*, size_t) override { return true; }
-    void Reset() { stream_.clear(); }
-
-    std::string& stream() { return stream_; }
-
-  private:
-    std::string stream_;
-};
+// Helper to check read sizes.
+static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) {
+    return reader.ReadData(op, buffer, size) == size;
+}
 
 TEST_F(CowTest, CopyContiguous) {
     CowOptions options;
     options.cluster_ops = 0;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     ASSERT_TRUE(writer.AddCopy(10, 1000, 100));
     ASSERT_TRUE(writer.Finalize());
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
     CowReader reader;
-    CowHeader header;
-    CowFooter footer;
     ASSERT_TRUE(reader.Parse(cow_->fd));
-    ASSERT_TRUE(reader.GetHeader(&header));
-    ASSERT_TRUE(reader.GetFooter(&footer));
-    ASSERT_EQ(header.magic, kCowMagicNumber);
-    ASSERT_EQ(header.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.minor_version, kCowVersionMinor);
+
+    const auto& header = reader.GetHeader();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
+
+    CowFooter footer;
+    ASSERT_TRUE(reader.GetFooter(&footer));
     ASSERT_EQ(footer.op.num_ops, 100);
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
+    ASSERT_FALSE(iter->AtEnd());
 
     size_t i = 0;
-    while (!iter->Done()) {
-        auto op = &iter->Get();
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
         ASSERT_EQ(op->type, kCowCopyOp);
         ASSERT_EQ(op->compression, kCowCompressNone);
         ASSERT_EQ(op->data_length, 0);
@@ -107,9 +100,9 @@
 TEST_F(CowTest, ReadWrite) {
     CowOptions options;
     options.cluster_ops = 0;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
@@ -122,21 +115,22 @@
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
     CowReader reader;
-    CowHeader header;
-    CowFooter footer;
     ASSERT_TRUE(reader.Parse(cow_->fd));
-    ASSERT_TRUE(reader.GetHeader(&header));
-    ASSERT_TRUE(reader.GetFooter(&footer));
-    ASSERT_EQ(header.magic, kCowMagicNumber);
-    ASSERT_EQ(header.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.minor_version, kCowVersionMinor);
+
+    const auto& header = reader.GetHeader();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
+
+    CowFooter footer;
+    ASSERT_TRUE(reader.GetFooter(&footer));
     ASSERT_EQ(footer.op.num_ops, 4);
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
 
     ASSERT_EQ(op->type, kCowCopyOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -144,22 +138,22 @@
     ASSERT_EQ(op->new_block, 10);
     ASSERT_EQ(op->source, 20);
 
-    StringSink sink;
+    std::string sink(data.size(), '\0');
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_FALSE(GetCowOpSourceInfoCompression(op));
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
     ASSERT_EQ(op->type, kCowZeroOp);
@@ -169,8 +163,8 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowZeroOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -179,15 +173,15 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, ReadWriteXor) {
     CowOptions options;
     options.cluster_ops = 0;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
@@ -200,21 +194,22 @@
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
     CowReader reader;
-    CowHeader header;
-    CowFooter footer;
     ASSERT_TRUE(reader.Parse(cow_->fd));
-    ASSERT_TRUE(reader.GetHeader(&header));
-    ASSERT_TRUE(reader.GetFooter(&footer));
-    ASSERT_EQ(header.magic, kCowMagicNumber);
-    ASSERT_EQ(header.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.minor_version, kCowVersionMinor);
+
+    const auto& header = reader.GetHeader();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
+
+    CowFooter footer;
+    ASSERT_TRUE(reader.GetFooter(&footer));
     ASSERT_EQ(footer.op.num_ops, 4);
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
 
     ASSERT_EQ(op->type, kCowCopyOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -222,23 +217,23 @@
     ASSERT_EQ(op->new_block, 10);
     ASSERT_EQ(op->source, 20);
 
-    StringSink sink;
+    std::string sink(data.size(), '\0');
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowXorOp);
-    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_FALSE(GetCowOpSourceInfoCompression(op));
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_EQ(op->source, 98314);  // 4096 * 24 + 10
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data);
+    ASSERT_EQ(GetCowOpSourceInfoData(op), 98314);  // 4096 * 24 + 10
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
     ASSERT_EQ(op->type, kCowZeroOp);
@@ -248,8 +243,8 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowZeroOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -258,16 +253,16 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, CompressGz) {
     CowOptions options;
     options.cluster_ops = 0;
     options.compression = "gz";
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
@@ -282,32 +277,32 @@
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
 
-    StringSink sink;
+    std::string sink(data.size(), '\0');
 
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_EQ(op->compression, kCowCompressGz);
+    ASSERT_TRUE(GetCowOpSourceInfoCompression(op));
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
-class CompressionRWTest : public CowTest, public testing::WithParamInterface<const char*> {};
+class CompressionTest : public CowTest, public testing::WithParamInterface<const char*> {};
 
-TEST_P(CompressionRWTest, ThreadedBatchWrites) {
+TEST_P(CompressionTest, ThreadedBatchWrites) {
     CowOptions options;
     options.compression = GetParam();
     options.num_compress_threads = 2;
 
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     std::string xor_data = "This is test data-1. Testing xor";
     xor_data.resize(options.block_size, '\0');
@@ -337,36 +332,37 @@
     ASSERT_NE(iter, nullptr);
 
     int total_blocks = 0;
-    while (!iter->Done()) {
-        auto op = &iter->Get();
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
 
         if (op->type == kCowXorOp) {
             total_blocks += 1;
-            StringSink sink;
+            std::string sink(xor_data.size(), '\0');
             ASSERT_EQ(op->new_block, 50);
-            ASSERT_EQ(op->source, 98314);  // 4096 * 24 + 10
-            ASSERT_TRUE(reader.ReadData(*op, &sink));
-            ASSERT_EQ(sink.stream(), xor_data);
+            ASSERT_EQ(GetCowOpSourceInfoData(op), 98314);  // 4096 * 24 + 10
+            ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+            ASSERT_EQ(sink, xor_data);
         }
 
         if (op->type == kCowReplaceOp) {
             total_blocks += 1;
             if (op->new_block == 100) {
-                StringSink sink;
-                ASSERT_TRUE(reader.ReadData(*op, &sink));
                 data.resize(options.block_size);
-                ASSERT_EQ(sink.stream(), data);
+                std::string sink(data.size(), '\0');
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+                ASSERT_EQ(sink.size(), data.size());
+                ASSERT_EQ(sink, data);
             }
             if (op->new_block == 6000) {
-                StringSink sink;
-                ASSERT_TRUE(reader.ReadData(*op, &sink));
                 data2.resize(options.block_size);
-                ASSERT_EQ(sink.stream(), data2);
+                std::string sink(data2.size(), '\0');
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+                ASSERT_EQ(sink, data2);
             }
             if (op->new_block == 9000) {
-                StringSink sink;
-                ASSERT_TRUE(reader.ReadData(*op, &sink));
-                ASSERT_EQ(sink.stream(), data3);
+                std::string sink(data3.size(), '\0');
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+                ASSERT_EQ(sink, data3);
             }
         }
 
@@ -376,15 +372,15 @@
     ASSERT_EQ(total_blocks, expected_blocks);
 }
 
-TEST_P(CompressionRWTest, NoBatchWrites) {
+TEST_P(CompressionTest, NoBatchWrites) {
     CowOptions options;
     options.compression = GetParam();
     options.num_compress_threads = 1;
     options.cluster_ops = 0;
 
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     std::string data = "Testing replace ops without batch writes";
     data.resize(options.block_size * 1024, '\0');
@@ -410,27 +406,27 @@
     ASSERT_NE(iter, nullptr);
 
     int total_blocks = 0;
-    while (!iter->Done()) {
-        auto op = &iter->Get();
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
 
         if (op->type == kCowReplaceOp) {
             total_blocks += 1;
             if (op->new_block == 50) {
-                StringSink sink;
-                ASSERT_TRUE(reader.ReadData(*op, &sink));
                 data.resize(options.block_size);
-                ASSERT_EQ(sink.stream(), data);
+                std::string sink(data.size(), '\0');
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+                ASSERT_EQ(sink, data);
             }
             if (op->new_block == 3000) {
-                StringSink sink;
-                ASSERT_TRUE(reader.ReadData(*op, &sink));
                 data2.resize(options.block_size);
-                ASSERT_EQ(sink.stream(), data2);
+                std::string sink(data2.size(), '\0');
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+                ASSERT_EQ(sink, data2);
             }
             if (op->new_block == 5000) {
-                StringSink sink;
-                ASSERT_TRUE(reader.ReadData(*op, &sink));
-                ASSERT_EQ(sink.stream(), data3);
+                std::string sink(data3.size(), '\0');
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+                ASSERT_EQ(sink, data3);
             }
         }
 
@@ -440,15 +436,74 @@
     ASSERT_EQ(total_blocks, expected_blocks);
 }
 
-INSTANTIATE_TEST_SUITE_P(CowApi, CompressionRWTest, testing::Values("none", "gz", "brotli", "lz4"));
+template <typename T>
+class HorribleStream : public IByteStream {
+  public:
+    HorribleStream(const std::basic_string<T>& input) : input_(input) {}
+
+    ssize_t Read(void* buffer, size_t length) override {
+        if (pos_ >= input_.size()) {
+            return 0;
+        }
+        if (length) {
+            *reinterpret_cast<char*>(buffer) = input_[pos_];
+        }
+        pos_++;
+        return 1;
+    }
+    size_t Size() const override { return input_.size(); }
+
+  private:
+    std::basic_string<T> input_;
+    size_t pos_ = 0;
+};
+
+TEST(HorribleStream, ReadFully) {
+    std::string expected = "this is some data";
+
+    HorribleStream<char> stream(expected);
+
+    std::string buffer(expected.size(), '\0');
+    ASSERT_TRUE(stream.ReadFully(buffer.data(), buffer.size()));
+    ASSERT_EQ(buffer, expected);
+}
+
+TEST_P(CompressionTest, HorribleStream) {
+    if (strcmp(GetParam(), "none") == 0) {
+        GTEST_SKIP();
+    }
+
+    auto algorithm = CompressionAlgorithmFromString(GetParam());
+    ASSERT_TRUE(algorithm.has_value());
+
+    std::string expected = "The quick brown fox jumps over the lazy dog.";
+    expected.resize(4096, '\0');
+
+    auto result = CompressWorker::Compress(*algorithm, expected.data(), expected.size());
+    ASSERT_FALSE(result.empty());
+
+    HorribleStream<uint8_t> stream(result);
+    auto decomp = IDecompressor::FromString(GetParam());
+    ASSERT_NE(decomp, nullptr);
+    decomp->set_stream(&stream);
+
+    expected = expected.substr(10, 500);
+
+    std::string buffer(expected.size(), '\0');
+    ASSERT_EQ(decomp->Decompress(buffer.data(), 500, 4096, 10), 500);
+    ASSERT_EQ(buffer, expected);
+}
+
+INSTANTIATE_TEST_SUITE_P(AllCompressors, CompressionTest,
+                         testing::Values("none", "gz", "brotli", "lz4"));
 
 TEST_F(CowTest, ClusterCompressGz) {
     CowOptions options;
     options.compression = "gz";
     options.cluster_ops = 2;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
@@ -467,52 +522,53 @@
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
 
-    StringSink sink;
+    std::string sink(data.size(), '\0');
 
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_EQ(op->compression, kCowCompressGz);
+    ASSERT_TRUE(GetCowOpSourceInfoCompression(op));
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
-    sink.Reset();
-    ASSERT_EQ(op->compression, kCowCompressGz);
+    sink = {};
+    sink.resize(data2.size(), '\0');
+    ASSERT_TRUE(GetCowOpSourceInfoCompression(op));
     ASSERT_EQ(op->data_length, 41);  // compressed!
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data2);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data2);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, CompressTwoBlocks) {
     CowOptions options;
     options.compression = "gz";
     options.cluster_ops = 0;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size * 2, '\0');
@@ -527,68 +583,28 @@
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
+    ASSERT_FALSE(iter->AtEnd());
     iter->Next();
-    ASSERT_FALSE(iter->Done());
+    ASSERT_FALSE(iter->AtEnd());
 
-    StringSink sink;
+    std::string sink(options.block_size, '\0');
 
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_EQ(op->compression, kCowCompressGz);
+    ASSERT_TRUE(GetCowOpSourceInfoCompression(op));
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
 }
 
-// Only return 1-byte buffers, to stress test the partial read logic in
-// CowReader.
-class HorribleStringSink : public StringSink {
-  public:
-    void* GetBuffer(size_t, size_t* actual) override { return StringSink::GetBuffer(1, actual); }
-};
-
-class CompressionTest : public CowTest, public testing::WithParamInterface<const char*> {};
-
-TEST_P(CompressionTest, HorribleSink) {
-    CowOptions options;
-    options.compression = GetParam();
-    options.cluster_ops = 0;
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
-
-    std::string data = "This is some data, believe it";
-    data.resize(options.block_size, '\0');
-
-    ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size()));
-    ASSERT_TRUE(writer.Finalize());
-
-    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
-
-    CowReader reader;
-    ASSERT_TRUE(reader.Parse(cow_->fd));
-
-    auto iter = reader.GetOpIter();
-    ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-
-    HorribleStringSink sink;
-    auto op = &iter->Get();
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data);
-}
-
-INSTANTIATE_TEST_SUITE_P(CowApi, CompressionTest, testing::Values("none", "gz", "brotli"));
-
 TEST_F(CowTest, GetSize) {
     CowOptions options;
     options.cluster_ops = 0;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
     if (ftruncate(cow_->fd, 0) < 0) {
         perror("Fails to set temp file size");
         FAIL();
     }
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
@@ -609,8 +625,8 @@
 TEST_F(CowTest, AppendLabelSmall) {
     CowOptions options;
     options.cluster_ops = 0;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
@@ -620,8 +636,8 @@
 
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 3));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize({3}));
 
     std::string data2 = "More data!";
     data2.resize(options.block_size, '\0');
@@ -641,42 +657,43 @@
     ASSERT_TRUE(reader.GetLastLabel(&label));
     ASSERT_EQ(label, 3);
 
-    StringSink sink;
+    std::string sink(data.size(), '\0');
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
 
     iter->Next();
-    sink.Reset();
+    sink = {};
+    sink.resize(data2.size(), '\0');
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 3);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data2);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data2);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, AppendLabelMissing) {
     CowOptions options;
     options.cluster_ops = 0;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     ASSERT_TRUE(writer->AddLabel(0));
     std::string data = "This is some data, believe it";
@@ -688,9 +705,9 @@
 
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_FALSE(writer->InitializeAppend(cow_->fd, 1));
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 0));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_FALSE(writer->Initialize({1}));
+    ASSERT_TRUE(writer->Initialize({0}));
 
     ASSERT_TRUE(writer->AddZeroBlocks(51, 1));
     ASSERT_TRUE(writer->Finalize());
@@ -705,32 +722,30 @@
     CowReader reader;
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
-    StringSink sink;
-
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, AppendExtendedCorrupted) {
     CowOptions options;
     options.cluster_ops = 0;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     ASSERT_TRUE(writer->AddLabel(5));
 
@@ -752,8 +767,8 @@
 
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 5));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize({5}));
 
     ASSERT_TRUE(writer->Finalize());
 
@@ -765,25 +780,23 @@
     CowReader reader;
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
-    StringSink sink;
-
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, AppendbyLabel) {
     CowOptions options;
     options.cluster_ops = 0;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size * 2, '\0');
@@ -801,9 +814,9 @@
 
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_FALSE(writer->InitializeAppend(cow_->fd, 12));
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 5));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_FALSE(writer->Initialize({12}));
+    ASSERT_TRUE(writer->Initialize({5}));
 
     // This should drop label 6
     ASSERT_TRUE(writer->Finalize());
@@ -816,62 +829,62 @@
     CowReader reader;
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
-    StringSink sink;
+    std::string sink(options.block_size, '\0');
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data.substr(0, options.block_size));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data.substr(0, options.block_size));
 
     iter->Next();
-    sink.Reset();
+    sink = {};
+    sink.resize(options.block_size, '\0');
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data.substr(options.block_size, 2 * options.block_size));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data.substr(options.block_size, 2 * options.block_size));
 
     iter->Next();
-    sink.Reset();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 4);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
     iter->Next();
 
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, ClusterTest) {
     CowOptions options;
     options.cluster_ops = 4;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
@@ -897,87 +910,86 @@
     CowReader reader;
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
-    StringSink sink;
+    std::string sink(data.size(), '\0');
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data.substr(0, options.block_size));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data.substr(0, options.block_size));
 
     iter->Next();
-    sink.Reset();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 4);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowCopyOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 6);
 
     iter->Next();
 
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, ClusterAppendTest) {
     CowOptions options;
     options.cluster_ops = 3;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     ASSERT_TRUE(writer->AddLabel(50));
     ASSERT_TRUE(writer->Finalize());  // Adds a cluster op, should be dropped on append
 
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 50));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize({50}));
 
     std::string data2 = "More data!";
     data2.resize(options.block_size, '\0');
@@ -997,40 +1009,40 @@
     ASSERT_TRUE(reader.GetLastLabel(&label));
     ASSERT_EQ(label, 50);
 
-    StringSink sink;
+    std::string sink(data2.size(), '\0');
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 50);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(reader.ReadData(*op, &sink));
-    ASSERT_EQ(sink.stream(), data2);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data2);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
 
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, AppendAfterFinalize) {
     CowOptions options;
     options.cluster_ops = 0;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
@@ -1050,29 +1062,28 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 }
 
-AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) {
-    data.resize(writer->options().block_size, '\0');
+AssertionResult WriteDataBlock(ICowWriter* writer, uint64_t new_block, std::string data) {
+    data.resize(writer->GetBlockSize(), '\0');
     if (!writer->AddRawBlocks(new_block, data.data(), data.size())) {
         return AssertionFailure() << "Failed to add raw block";
     }
     return AssertionSuccess();
 }
 
-AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation* op,
                                  const std::string& data) {
-    CowHeader header;
-    reader->GetHeader(&header);
+    const auto& header = reader->GetHeader();
 
     std::string cmp = data;
     cmp.resize(header.block_size, '\0');
 
-    StringSink sink;
-    if (!reader->ReadData(op, &sink)) {
+    std::string sink(cmp.size(), '\0');
+    if (!reader->ReadData(op, sink.data(), sink.size())) {
         return AssertionFailure() << "Failed to read data block";
     }
-    if (cmp != sink.stream()) {
+    if (cmp != sink) {
         return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got "
-                                  << sink.stream();
+                                  << sink;
     }
 
     return AssertionSuccess();
@@ -1081,8 +1092,8 @@
 TEST_F(CowTest, ResumeMidCluster) {
     CowOptions options;
     options.cluster_ops = 7;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
@@ -1092,8 +1103,8 @@
     ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize({1}));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
@@ -1111,18 +1122,18 @@
     size_t max_in_cluster = 0;
     size_t num_in_cluster = 0;
     size_t num_clusters = 0;
-    while (!iter->Done()) {
+    while (!iter->AtEnd()) {
         const auto& op = iter->Get();
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1138,8 +1149,8 @@
     CowOptions options;
     int cluster_ops = 5;
     options.cluster_ops = cluster_ops;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
@@ -1153,8 +1164,8 @@
     ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize({1}));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
@@ -1172,18 +1183,18 @@
     size_t max_in_cluster = 0;
     size_t num_in_cluster = 0;
     size_t num_clusters = 0;
-    while (!iter->Done()) {
+    while (!iter->AtEnd()) {
         const auto& op = iter->Get();
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1198,8 +1209,8 @@
 TEST_F(CowTest, DeleteMidCluster) {
     CowOptions options;
     options.cluster_ops = 7;
-    auto writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
 
     ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
     ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
@@ -1211,8 +1222,8 @@
     ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize({1}));
     ASSERT_TRUE(writer->Finalize());
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
@@ -1224,17 +1235,17 @@
     size_t max_in_cluster = 0;
     size_t num_in_cluster = 0;
     size_t num_clusters = 0;
-    while (!iter->Done()) {
+    while (!iter->AtEnd()) {
         const auto& op = iter->Get();
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1248,14 +1259,14 @@
 
 TEST_F(CowTest, BigSeqOp) {
     CowOptions options;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
     const int seq_len = std::numeric_limits<uint16_t>::max() / sizeof(uint32_t) + 1;
     uint32_t sequence[seq_len];
     for (int i = 0; i < seq_len; i++) {
         sequence[i] = i + 1;
     }
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     ASSERT_TRUE(writer.AddSequenceData(seq_len, sequence));
     ASSERT_TRUE(writer.AddZeroBlocks(1, seq_len));
@@ -1268,26 +1279,26 @@
     auto iter = reader.GetRevMergeOpIter();
 
     for (int i = 0; i < seq_len; i++) {
-        ASSERT_TRUE(!iter->Done());
+        ASSERT_TRUE(!iter->AtEnd());
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, seq_len - i);
+        ASSERT_EQ(op->new_block, seq_len - i);
 
         iter->Next();
     }
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, MissingSeqOp) {
     CowOptions options;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
     const int seq_len = 10;
     uint32_t sequence[seq_len];
     for (int i = 0; i < seq_len; i++) {
         sequence[i] = i + 1;
     }
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     ASSERT_TRUE(writer.AddSequenceData(seq_len, sequence));
     ASSERT_TRUE(writer.AddZeroBlocks(1, seq_len - 1));
@@ -1301,14 +1312,14 @@
 
 TEST_F(CowTest, ResumeSeqOp) {
     CowOptions options;
-    auto writer = std::make_unique<CowWriter>(options);
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
     const int seq_len = 10;
     uint32_t sequence[seq_len];
     for (int i = 0; i < seq_len; i++) {
         sequence[i] = i + 1;
     }
 
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    ASSERT_TRUE(writer->Initialize());
 
     ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
     ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len / 2));
@@ -1319,10 +1330,10 @@
     auto reader = std::make_unique<CowReader>();
     ASSERT_TRUE(reader->Parse(cow_->fd, 1));
     auto itr = reader->GetRevMergeOpIter();
-    ASSERT_TRUE(itr->Done());
+    ASSERT_TRUE(itr->AtEnd());
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize({1}));
     ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, seq_len / 2));
     ASSERT_TRUE(writer->Finalize());
 
@@ -1334,27 +1345,27 @@
     auto iter = reader->GetRevMergeOpIter();
 
     uint64_t expected_block = 10;
-    while (!iter->Done() && expected_block > 0) {
-        ASSERT_FALSE(iter->Done());
+    while (!iter->AtEnd() && expected_block > 0) {
+        ASSERT_FALSE(iter->AtEnd());
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, expected_block);
+        ASSERT_EQ(op->new_block, expected_block);
 
         iter->Next();
         expected_block--;
     }
     ASSERT_EQ(expected_block, 0);
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, RevMergeOpItrTest) {
     CowOptions options;
     options.cluster_ops = 5;
     options.num_merge_ops = 1;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
     uint32_t sequence[] = {2, 10, 6, 7, 3, 5};
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     ASSERT_TRUE(writer.AddSequenceData(6, sequence));
     ASSERT_TRUE(writer.AddCopy(6, 13));
@@ -1385,25 +1396,25 @@
     auto iter = reader.GetRevMergeOpIter();
     auto expected_new_block = revMergeOpSequence.begin();
 
-    while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) {
+    while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) {
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, *expected_new_block);
+        ASSERT_EQ(op->new_block, *expected_new_block);
 
         iter->Next();
         expected_new_block++;
     }
     ASSERT_EQ(expected_new_block, revMergeOpSequence.end());
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, LegacyRevMergeOpItrTest) {
     CowOptions options;
     options.cluster_ops = 5;
     options.num_merge_ops = 1;
-    CowWriter writer(options);
+    CowWriterV2 writer(options, GetCowFd());
 
-    ASSERT_TRUE(writer.Initialize(cow_->fd));
+    ASSERT_TRUE(writer.Initialize());
 
     ASSERT_TRUE(writer.AddCopy(2, 11));
     ASSERT_TRUE(writer.AddCopy(10, 12));
@@ -1434,16 +1445,16 @@
     auto iter = reader.GetRevMergeOpIter();
     auto expected_new_block = revMergeOpSequence.begin();
 
-    while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) {
+    while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) {
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, *expected_new_block);
+        ASSERT_EQ(op->new_block, *expected_new_block);
 
         iter->Next();
         expected_new_block++;
     }
     ASSERT_EQ(expected_new_block, revMergeOpSequence.end());
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, InvalidMergeOrderTest) {
@@ -1452,10 +1463,10 @@
     options.num_merge_ops = 1;
     std::string data = "This is some data, believe it";
     data.resize(options.block_size, '\0');
-    auto writer = std::make_unique<CowWriter>(options);
+    auto writer = std::make_unique<CowWriterV2>(options, GetCowFd());
     CowReader reader;
 
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    ASSERT_TRUE(writer->Initialize());
 
     ASSERT_TRUE(writer->AddCopy(3, 2));
     ASSERT_TRUE(writer->AddCopy(2, 1));
@@ -1464,14 +1475,14 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
     ASSERT_TRUE(reader.VerifyMergeOps());
 
-    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(writer->Initialize({1}));
     ASSERT_TRUE(writer->AddCopy(4, 2));
     ASSERT_TRUE(writer->Finalize());
     ASSERT_TRUE(reader.Parse(cow_->fd));
     ASSERT_FALSE(reader.VerifyMergeOps());
 
-    writer = std::make_unique<CowWriter>(options);
-    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    writer = std::make_unique<CowWriterV2>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize());
     ASSERT_TRUE(writer->AddCopy(2, 1));
     ASSERT_TRUE(writer->AddXorBlocks(3, &data, data.size(), 1, 1));
     ASSERT_TRUE(writer->Finalize());
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.cpp
new file mode 100644
index 0000000..ff34c59
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.cpp
@@ -0,0 +1,195 @@
+// Copyright (C) 2023 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 "writer_base.h"
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include "snapshot_reader.h"
+
+// The info messages here are spammy, but as useful for update_engine. Disable
+// them when running on the host.
+#ifdef __ANDROID__
+#define LOG_INFO LOG(INFO)
+#else
+#define LOG_INFO LOG(VERBOSE)
+#endif
+
+namespace android {
+namespace snapshot {
+
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+
+namespace {
+std::string GetFdPath(borrowed_fd fd) {
+    const auto fd_path = "/proc/self/fd/" + std::to_string(fd.get());
+    std::string file_path(512, '\0');
+    const auto err = readlink(fd_path.c_str(), file_path.data(), file_path.size());
+    if (err <= 0) {
+        PLOG(ERROR) << "Failed to determine path for fd " << fd.get();
+        file_path.clear();
+    } else {
+        file_path.resize(err);
+    }
+    return file_path;
+}
+}  // namespace
+
+CowWriterBase::CowWriterBase(const CowOptions& options, unique_fd&& fd)
+    : options_(options), fd_(std::move(fd)) {}
+
+bool CowWriterBase::InitFd() {
+    if (fd_.get() < 0) {
+        fd_.reset(open("/dev/null", O_RDWR | O_CLOEXEC));
+        if (fd_ < 0) {
+            PLOG(ERROR) << "open /dev/null failed";
+            return false;
+        }
+        is_dev_null_ = true;
+        return true;
+    }
+
+    struct stat stat {};
+    if (fstat(fd_.get(), &stat) < 0) {
+        PLOG(ERROR) << "fstat failed";
+        return false;
+    }
+    const auto file_path = GetFdPath(fd_);
+    is_block_device_ = S_ISBLK(stat.st_mode);
+    if (is_block_device_) {
+        uint64_t size_in_bytes = 0;
+        if (ioctl(fd_.get(), BLKGETSIZE64, &size_in_bytes)) {
+            PLOG(ERROR) << "Failed to get total size for: " << fd_.get();
+            return false;
+        }
+        cow_image_size_ = size_in_bytes;
+        LOG_INFO << "COW image " << file_path << " has size " << size_in_bytes;
+    } else {
+        LOG_INFO << "COW image " << file_path
+                 << " is not a block device, assuming unlimited space.";
+    }
+    return true;
+}
+
+bool CowWriterBase::AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
+    CHECK(num_blocks != 0);
+
+    for (size_t i = 0; i < num_blocks; i++) {
+        if (!ValidateNewBlock(new_block + i)) {
+            return false;
+        }
+    }
+
+    return EmitCopy(new_block, old_block, num_blocks);
+}
+
+bool CowWriterBase::AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
+    if (size % options_.block_size != 0) {
+        LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of "
+                   << options_.block_size;
+        return false;
+    }
+
+    uint64_t num_blocks = size / options_.block_size;
+    uint64_t last_block = new_block_start + num_blocks - 1;
+    if (!ValidateNewBlock(last_block)) {
+        return false;
+    }
+    return EmitRawBlocks(new_block_start, data, size);
+}
+
+bool CowWriterBase::AddXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                                 uint32_t old_block, uint16_t offset) {
+    if (size % options_.block_size != 0) {
+        LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of "
+                   << options_.block_size;
+        return false;
+    }
+
+    uint64_t num_blocks = size / options_.block_size;
+    uint64_t last_block = new_block_start + num_blocks - 1;
+    if (!ValidateNewBlock(last_block)) {
+        return false;
+    }
+    if (offset >= options_.block_size) {
+        LOG(ERROR) << "AddXorBlocks: offset " << offset << " is not less than "
+                   << options_.block_size;
+    }
+    return EmitXorBlocks(new_block_start, data, size, old_block, offset);
+}
+
+bool CowWriterBase::AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
+    uint64_t last_block = new_block_start + num_blocks - 1;
+    if (!ValidateNewBlock(last_block)) {
+        return false;
+    }
+    return EmitZeroBlocks(new_block_start, num_blocks);
+}
+
+bool CowWriterBase::AddLabel(uint64_t label) {
+    return EmitLabel(label);
+}
+
+bool CowWriterBase::AddSequenceData(size_t num_ops, const uint32_t* data) {
+    return EmitSequenceData(num_ops, data);
+}
+
+bool CowWriterBase::ValidateNewBlock(uint64_t new_block) {
+    if (options_.max_blocks && new_block >= options_.max_blocks.value()) {
+        LOG(ERROR) << "New block " << new_block << " exceeds maximum block count "
+                   << options_.max_blocks.value();
+        return false;
+    }
+    return true;
+}
+
+std::unique_ptr<ICowReader> CowWriterBase::OpenReader() {
+    unique_fd cow_fd(fcntl(fd_.get(), F_DUPFD | F_DUPFD_CLOEXEC, 0));
+    if (cow_fd < 0) {
+        PLOG(ERROR) << "CowWriterV2::OpenReander: dup COW device";
+        return nullptr;
+    }
+
+    auto cow = std::make_unique<CowReader>();
+    if (!cow->Parse(std::move(cow_fd))) {
+        LOG(ERROR) << "CowWriterV2::OpenReader: unable to read COW";
+        return nullptr;
+    }
+    return cow;
+}
+
+std::unique_ptr<chromeos_update_engine::FileDescriptor> CowWriterBase::OpenFileDescriptor(
+        const std::optional<std::string>& source_device) {
+    auto reader = OpenReader();
+    if (!reader) {
+        return nullptr;
+    }
+
+    std::optional<uint64_t> block_dev_size;
+    if (options_.max_blocks) {
+        block_dev_size = {*options_.max_blocks * options_.block_size};
+    }
+
+    return std::make_unique<CompressedSnapshotReader>(std::move(reader), source_device,
+                                                      block_dev_size);
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
new file mode 100644
index 0000000..c8b4772
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2023 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 <libsnapshot/cow_writer.h>
+
+namespace android {
+namespace snapshot {
+
+class CowWriterBase : public ICowWriter {
+  public:
+    CowWriterBase(const CowOptions& options, android::base::unique_fd&& fd);
+    virtual ~CowWriterBase() {}
+
+    // Set up the writer.
+    // The file starts from the beginning.
+    //
+    // If fd is < 0, the CowWriter will be opened against /dev/null. This is for
+    // computing COW sizes without using storage space.
+    //
+    // If a label is given, any operations after the given label will be dropped.
+    // If the given label is not found, Initialize will fail.
+    virtual bool Initialize(std::optional<uint64_t> label = {}) = 0;
+
+    bool AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
+    bool AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
+    bool AddXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block,
+                      uint16_t offset) override;
+    bool AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
+    bool AddLabel(uint64_t label) override;
+    bool AddSequenceData(size_t num_ops, const uint32_t* data) override;
+    uint32_t GetBlockSize() const override { return options_.block_size; }
+    std::optional<uint32_t> GetMaxBlocks() const override { return options_.max_blocks; }
+    std::unique_ptr<ICowReader> OpenReader() override;
+    std::unique_ptr<FileDescriptor> OpenFileDescriptor(
+            const std::optional<std::string>& source_device) override;
+
+    const CowOptions& options() const { return options_; }
+
+  protected:
+    virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) = 0;
+    virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) = 0;
+    virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                               uint32_t old_block, uint16_t offset) = 0;
+    virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) = 0;
+    virtual bool EmitLabel(uint64_t label) = 0;
+    virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) = 0;
+
+    bool InitFd();
+    bool ValidateNewBlock(uint64_t new_block);
+
+    CowOptions options_;
+    CowHeader header_{};
+
+    android::base::unique_fd fd_;
+    bool is_dev_null_ = false;
+    bool is_block_device_ = false;
+    uint64_t cow_image_size_ = INT64_MAX;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
similarity index 70%
rename from fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
rename to fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
index 56b48f0..c549969 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
@@ -14,6 +14,8 @@
 // limitations under the License.
 //
 
+#include "writer_v2.h"
+
 #include <sys/types.h>
 #include <sys/uio.h>
 #include <unistd.h>
@@ -37,107 +39,30 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 
+#include "parser_v2.h"
+
+// The info messages here are spammy, but as useful for update_engine. Disable
+// them when running on the host.
+#ifdef __ANDROID__
+#define LOG_INFO LOG(INFO)
+#else
+#define LOG_INFO LOG(VERBOSE)
+#endif
+
 namespace android {
 namespace snapshot {
 
-namespace {
-std::string GetFdPath(int fd) {
-    const auto fd_path = "/proc/self/fd/" + std::to_string(fd);
-    std::string file_path(512, '\0');
-    const auto err = readlink(fd_path.c_str(), file_path.data(), file_path.size());
-    if (err <= 0) {
-        PLOG(ERROR) << "Failed to determine path for fd " << fd;
-        file_path.clear();
-    } else {
-        file_path.resize(err);
-    }
-    return file_path;
-}
-}  // namespace
-
 static_assert(sizeof(off_t) == sizeof(uint64_t));
 
-using android::base::borrowed_fd;
 using android::base::unique_fd;
 
-bool ICowWriter::AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
-    CHECK(num_blocks != 0);
-
-    for (size_t i = 0; i < num_blocks; i++) {
-        if (!ValidateNewBlock(new_block + i)) {
-            return false;
-        }
-    }
-
-    return EmitCopy(new_block, old_block, num_blocks);
-}
-
-bool ICowWriter::AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
-    if (size % options_.block_size != 0) {
-        LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of "
-                   << options_.block_size;
-        return false;
-    }
-
-    uint64_t num_blocks = size / options_.block_size;
-    uint64_t last_block = new_block_start + num_blocks - 1;
-    if (!ValidateNewBlock(last_block)) {
-        return false;
-    }
-    return EmitRawBlocks(new_block_start, data, size);
-}
-
-bool ICowWriter::AddXorBlocks(uint32_t new_block_start, const void* data, size_t size,
-                              uint32_t old_block, uint16_t offset) {
-    if (size % options_.block_size != 0) {
-        LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of "
-                   << options_.block_size;
-        return false;
-    }
-
-    uint64_t num_blocks = size / options_.block_size;
-    uint64_t last_block = new_block_start + num_blocks - 1;
-    if (!ValidateNewBlock(last_block)) {
-        return false;
-    }
-    if (offset >= options_.block_size) {
-        LOG(ERROR) << "AddXorBlocks: offset " << offset << " is not less than "
-                   << options_.block_size;
-    }
-    return EmitXorBlocks(new_block_start, data, size, old_block, offset);
-}
-
-bool ICowWriter::AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
-    uint64_t last_block = new_block_start + num_blocks - 1;
-    if (!ValidateNewBlock(last_block)) {
-        return false;
-    }
-    return EmitZeroBlocks(new_block_start, num_blocks);
-}
-
-bool ICowWriter::AddLabel(uint64_t label) {
-    return EmitLabel(label);
-}
-
-bool ICowWriter::AddSequenceData(size_t num_ops, const uint32_t* data) {
-    return EmitSequenceData(num_ops, data);
-}
-
-bool ICowWriter::ValidateNewBlock(uint64_t new_block) {
-    if (options_.max_blocks && new_block >= options_.max_blocks.value()) {
-        LOG(ERROR) << "New block " << new_block << " exceeds maximum block count "
-                   << options_.max_blocks.value();
-        return false;
-    }
-    return true;
-}
-
-CowWriter::CowWriter(const CowOptions& options) : ICowWriter(options), fd_(-1) {
+CowWriterV2::CowWriterV2(const CowOptions& options, unique_fd&& fd)
+    : CowWriterBase(options, std::move(fd)) {
     SetupHeaders();
     SetupWriteOptions();
 }
 
-CowWriter::~CowWriter() {
+CowWriterV2::~CowWriterV2() {
     for (size_t i = 0; i < compress_threads_.size(); i++) {
         CompressWorker* worker = compress_threads_[i].get();
         if (worker) {
@@ -156,7 +81,7 @@
     compress_threads_.clear();
 }
 
-void CowWriter::SetupWriteOptions() {
+void CowWriterV2::SetupWriteOptions() {
     num_compress_threads_ = options_.num_compress_threads;
 
     if (!num_compress_threads_) {
@@ -176,12 +101,12 @@
     }
 }
 
-void CowWriter::SetupHeaders() {
+void CowWriterV2::SetupHeaders() {
     header_ = {};
-    header_.magic = kCowMagicNumber;
-    header_.major_version = kCowVersionMajor;
-    header_.minor_version = kCowVersionMinor;
-    header_.header_size = sizeof(CowHeader);
+    header_.prefix.magic = kCowMagicNumber;
+    header_.prefix.major_version = kCowVersionMajor;
+    header_.prefix.minor_version = kCowVersionMinor;
+    header_.prefix.header_size = sizeof(CowHeader);
     header_.footer_size = sizeof(CowFooter);
     header_.op_size = sizeof(CowOperation);
     header_.block_size = options_.block_size;
@@ -193,19 +118,14 @@
     footer_.op.type = kCowFooterOp;
 }
 
-bool CowWriter::ParseOptions() {
-    if (options_.compression == "gz") {
-        compression_ = kCowCompressGz;
-    } else if (options_.compression == "brotli") {
-        compression_ = kCowCompressBrotli;
-    } else if (options_.compression == "lz4") {
-        compression_ = kCowCompressLz4;
-    } else if (options_.compression == "none") {
-        compression_ = kCowCompressNone;
-    } else if (!options_.compression.empty()) {
+bool CowWriterV2::ParseOptions() {
+    auto algorithm = CompressionAlgorithmFromString(options_.compression);
+    if (!algorithm) {
         LOG(ERROR) << "unrecognized compression: " << options_.compression;
         return false;
     }
+    compression_ = *algorithm;
+
     if (options_.cluster_ops == 1) {
         LOG(ERROR) << "Clusters must contain at least two operations to function.";
         return false;
@@ -213,42 +133,7 @@
     return true;
 }
 
-bool CowWriter::SetFd(android::base::borrowed_fd fd) {
-    if (fd.get() < 0) {
-        owned_fd_.reset(open("/dev/null", O_RDWR | O_CLOEXEC));
-        if (owned_fd_ < 0) {
-            PLOG(ERROR) << "open /dev/null failed";
-            return false;
-        }
-        fd_ = owned_fd_;
-        is_dev_null_ = true;
-    } else {
-        fd_ = fd;
-
-        struct stat stat {};
-        if (fstat(fd.get(), &stat) < 0) {
-            PLOG(ERROR) << "fstat failed";
-            return false;
-        }
-        const auto file_path = GetFdPath(fd.get());
-        is_block_device_ = S_ISBLK(stat.st_mode);
-        if (is_block_device_) {
-            uint64_t size_in_bytes = 0;
-            if (ioctl(fd.get(), BLKGETSIZE64, &size_in_bytes)) {
-                PLOG(ERROR) << "Failed to get total size for: " << fd.get();
-                return false;
-            }
-            cow_image_size_ = size_in_bytes;
-            LOG(INFO) << "COW image " << file_path << " has size " << size_in_bytes;
-        } else {
-            LOG(INFO) << "COW image " << file_path
-                      << " is not a block device, assuming unlimited space.";
-        }
-    }
-    return true;
-}
-
-void CowWriter::InitBatchWrites() {
+void CowWriterV2::InitBatchWrites() {
     if (batch_write_) {
         cowop_vec_ = std::make_unique<struct iovec[]>(header_.cluster_ops);
         data_vec_ = std::make_unique<struct iovec[]>(header_.cluster_ops);
@@ -271,12 +156,12 @@
     }
 
     std::string batch_write = batch_write_ ? "enabled" : "disabled";
-    LOG(INFO) << "Batch writes: " << batch_write;
+    LOG_INFO << "Batch writes: " << batch_write;
 }
 
-void CowWriter::InitWorkers() {
+void CowWriterV2::InitWorkers() {
     if (num_compress_threads_ <= 1) {
-        LOG(INFO) << "Not creating new threads for compression.";
+        LOG_INFO << "Not creating new threads for compression.";
         return;
     }
     for (int i = 0; i < num_compress_threads_; i++) {
@@ -285,47 +170,30 @@
         compress_threads_.push_back(std::move(wt));
     }
 
-    LOG(INFO) << num_compress_threads_ << " thread used for compression";
+    LOG_INFO << num_compress_threads_ << " thread used for compression";
 }
 
-bool CowWriter::Initialize(unique_fd&& fd) {
-    owned_fd_ = std::move(fd);
-    return Initialize(borrowed_fd{owned_fd_});
-}
-
-bool CowWriter::Initialize(borrowed_fd fd) {
-    if (!SetFd(fd) || !ParseOptions()) {
+bool CowWriterV2::Initialize(std::optional<uint64_t> label) {
+    if (!InitFd() || !ParseOptions()) {
         return false;
     }
-
-    if (!OpenForWrite()) {
-        return false;
+    if (!label) {
+        if (!OpenForWrite()) {
+            return false;
+        }
+    } else {
+        if (!OpenForAppend(*label)) {
+            return false;
+        }
     }
 
-    InitWorkers();
+    if (!compress_threads_.size()) {
+        InitWorkers();
+    }
     return true;
 }
 
-bool CowWriter::InitializeAppend(android::base::unique_fd&& fd, uint64_t label) {
-    owned_fd_ = std::move(fd);
-    return InitializeAppend(android::base::borrowed_fd{owned_fd_}, label);
-}
-
-bool CowWriter::InitializeAppend(android::base::borrowed_fd fd, uint64_t label) {
-    if (!SetFd(fd) || !ParseOptions()) {
-        return false;
-    }
-
-    bool ret = OpenForAppend(label);
-
-    if (ret && !compress_threads_.size()) {
-        InitWorkers();
-    }
-
-    return ret;
-}
-
-void CowWriter::InitPos() {
+void CowWriterV2::InitPos() {
     next_op_pos_ = sizeof(header_) + header_.buffer_size;
     cluster_size_ = header_.cluster_ops * sizeof(CowOperation);
     if (header_.cluster_ops) {
@@ -337,7 +205,7 @@
     current_data_size_ = 0;
 }
 
-bool CowWriter::OpenForWrite() {
+bool CowWriterV2::OpenForWrite() {
     // This limitation is tied to the data field size in CowOperation.
     if (header_.block_size > std::numeric_limits<uint16_t>::max()) {
         LOG(ERROR) << "Block size is too large";
@@ -385,11 +253,18 @@
     return true;
 }
 
-bool CowWriter::OpenForAppend(uint64_t label) {
-    auto reader = std::make_unique<CowReader>();
-    std::queue<CowOperation> toAdd;
+bool CowWriterV2::OpenForAppend(uint64_t label) {
+    if (!ReadCowHeader(fd_, &header_)) {
+        return false;
+    }
 
-    if (!reader->Parse(fd_, {label}) || !reader->GetHeader(&header_)) {
+    CowParserV2 parser;
+    if (!parser.Parse(fd_, header_, {label})) {
+        return false;
+    }
+    if (header_.prefix.major_version > 2) {
+        LOG(ERROR) << "CowWriterV2 tried to open incompatible version "
+                   << header_.prefix.major_version;
         return false;
     }
 
@@ -400,16 +275,10 @@
     footer_.op.num_ops = 0;
     InitPos();
 
-    auto iter = reader->GetOpIter();
-
-    while (!iter->Done()) {
-        AddOperation(iter->Get());
-        iter->Next();
+    for (const auto& op : *parser.ops()) {
+        AddOperation(op);
     }
 
-    // Free reader so we own the descriptor position again.
-    reader = nullptr;
-
     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
         PLOG(ERROR) << "lseek failed";
         return false;
@@ -420,7 +289,7 @@
     return EmitClusterIfNeeded();
 }
 
-bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
+bool CowWriterV2::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
     CHECK(!merge_in_progress_);
 
     for (size_t i = 0; i < num_blocks; i++) {
@@ -436,16 +305,16 @@
     return true;
 }
 
-bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
+bool CowWriterV2::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
     return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp);
 }
 
-bool CowWriter::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
-                              uint32_t old_block, uint16_t offset) {
+bool CowWriterV2::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                                uint32_t old_block, uint16_t offset) {
     return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp);
 }
 
-bool CowWriter::CompressBlocks(size_t num_blocks, const void* data) {
+bool CowWriterV2::CompressBlocks(size_t num_blocks, const void* data) {
     size_t num_threads = (num_blocks == 1) ? 1 : num_compress_threads_;
     size_t num_blocks_per_thread = num_blocks / num_threads;
     const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
@@ -479,8 +348,8 @@
     return true;
 }
 
-bool CowWriter::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
-                           uint64_t old_block, uint16_t offset, uint8_t type) {
+bool CowWriterV2::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
+                             uint64_t old_block, uint16_t offset, uint8_t type) {
     CHECK(!merge_in_progress_);
     const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
 
@@ -554,7 +423,7 @@
     return true;
 }
 
-bool CowWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
+bool CowWriterV2::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
     CHECK(!merge_in_progress_);
     for (uint64_t i = 0; i < num_blocks; i++) {
         CowOperation op = {};
@@ -566,7 +435,7 @@
     return true;
 }
 
-bool CowWriter::EmitLabel(uint64_t label) {
+bool CowWriterV2::EmitLabel(uint64_t label) {
     CHECK(!merge_in_progress_);
     CowOperation op = {};
     op.type = kCowLabelOp;
@@ -574,7 +443,7 @@
     return WriteOperation(op) && Sync();
 }
 
-bool CowWriter::EmitSequenceData(size_t num_ops, const uint32_t* data) {
+bool CowWriterV2::EmitSequenceData(size_t num_ops, const uint32_t* data) {
     CHECK(!merge_in_progress_);
     size_t to_add = 0;
     size_t max_ops = (header_.block_size * 2) / sizeof(uint32_t);
@@ -594,7 +463,7 @@
     return true;
 }
 
-bool CowWriter::EmitCluster() {
+bool CowWriterV2::EmitCluster() {
     CowOperation op = {};
     op.type = kCowClusterOp;
     // Next cluster starts after remainder of current cluster and the next data block.
@@ -602,7 +471,7 @@
     return WriteOperation(op);
 }
 
-bool CowWriter::EmitClusterIfNeeded() {
+bool CowWriterV2::EmitClusterIfNeeded() {
     // If there isn't room for another op and the cluster end op, end the current cluster
     if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) {
         if (!EmitCluster()) return false;
@@ -610,18 +479,7 @@
     return true;
 }
 
-// TODO: Fix compilation issues when linking libcrypto library
-// when snapuserd is compiled as part of ramdisk.
-static void SHA256(const void*, size_t, uint8_t[]) {
-#if 0
-    SHA256_CTX c;
-    SHA256_Init(&c);
-    SHA256_Update(&c, data, length);
-    SHA256_Final(out, &c);
-#endif
-}
-
-bool CowWriter::Finalize() {
+bool CowWriterV2::Finalize() {
     if (!FlushCluster()) {
         LOG(ERROR) << "Finalize: FlushCluster() failed";
         return false;
@@ -661,10 +519,8 @@
         PLOG(ERROR) << "Failed to seek to footer position.";
         return false;
     }
-    memset(&footer_.data.ops_checksum, 0, sizeof(uint8_t) * 32);
-    memset(&footer_.data.footer_checksum, 0, sizeof(uint8_t) * 32);
+    memset(&footer_.unused, 0, sizeof(footer_.unused));
 
-    SHA256(&footer_.op, sizeof(footer_.op), footer_.data.footer_checksum);
     // Write out footer at end of file
     if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&footer_),
                                    sizeof(footer_))) {
@@ -697,7 +553,7 @@
     return Sync();
 }
 
-uint64_t CowWriter::GetCowSize() {
+uint64_t CowWriterV2::GetCowSize() {
     if (current_data_size_ > 0) {
         return next_data_pos_ + sizeof(footer_);
     } else {
@@ -705,7 +561,7 @@
     }
 }
 
-bool CowWriter::GetDataPos(uint64_t* pos) {
+bool CowWriterV2::GetDataPos(uint64_t* pos) {
     off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
     if (offs < 0) {
         PLOG(ERROR) << "lseek failed";
@@ -715,7 +571,7 @@
     return true;
 }
 
-bool CowWriter::EnsureSpaceAvailable(const uint64_t bytes_needed) const {
+bool CowWriterV2::EnsureSpaceAvailable(const uint64_t bytes_needed) const {
     if (bytes_needed > cow_image_size_) {
         LOG(ERROR) << "No space left on COW device. Required: " << bytes_needed
                    << ", available: " << cow_image_size_;
@@ -725,7 +581,7 @@
     return true;
 }
 
-bool CowWriter::FlushCluster() {
+bool CowWriterV2::FlushCluster() {
     ssize_t ret;
 
     if (op_vec_index_) {
@@ -754,7 +610,7 @@
     return true;
 }
 
-bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t size) {
+bool CowWriterV2::WriteOperation(const CowOperation& op, const void* data, size_t size) {
     if (!EnsureSpaceAvailable(next_op_pos_ + sizeof(op))) {
         return false;
     }
@@ -802,7 +658,7 @@
     return EmitClusterIfNeeded();
 }
 
-void CowWriter::AddOperation(const CowOperation& op) {
+void CowWriterV2::AddOperation(const CowOperation& op) {
     footer_.op.num_ops++;
 
     if (op.type == kCowClusterOp) {
@@ -817,14 +673,14 @@
     next_op_pos_ += sizeof(CowOperation) + GetNextOpOffset(op, header_.cluster_ops);
 }
 
-bool CowWriter::WriteRawData(const void* data, const size_t size) {
+bool CowWriterV2::WriteRawData(const void* data, const size_t size) {
     if (!android::base::WriteFullyAtOffset(fd_, data, size, next_data_pos_)) {
         return false;
     }
     return true;
 }
 
-bool CowWriter::Sync() {
+bool CowWriterV2::Sync() {
     if (is_dev_null_) {
         return true;
     }
@@ -835,7 +691,7 @@
     return true;
 }
 
-bool CowWriter::Truncate(off_t length) {
+bool CowWriterV2::Truncate(off_t length) {
     if (is_dev_null_ || is_block_device_) {
         return true;
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
new file mode 100644
index 0000000..809ae57
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2023 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 "writer_base.h"
+
+namespace android {
+namespace snapshot {
+
+class CowWriterV2 : public CowWriterBase {
+  public:
+    explicit CowWriterV2(const CowOptions& options, android::base::unique_fd&& fd);
+    ~CowWriterV2() override;
+
+    bool Initialize(std::optional<uint64_t> label = {}) override;
+    bool Finalize() override;
+    uint64_t GetCowSize() override;
+
+  protected:
+    virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
+    virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
+    virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                               uint32_t old_block, uint16_t offset) override;
+    virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
+    virtual bool EmitLabel(uint64_t label) override;
+    virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
+
+  private:
+    bool EmitCluster();
+    bool EmitClusterIfNeeded();
+    bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
+                    uint16_t offset, uint8_t type);
+    void SetupHeaders();
+    void SetupWriteOptions();
+    bool ParseOptions();
+    bool OpenForWrite();
+    bool OpenForAppend(uint64_t label);
+    bool GetDataPos(uint64_t* pos);
+    bool WriteRawData(const void* data, size_t size);
+    bool WriteOperation(const CowOperation& op, const void* data = nullptr, size_t size = 0);
+    void AddOperation(const CowOperation& op);
+    void InitPos();
+    void InitBatchWrites();
+    void InitWorkers();
+    bool FlushCluster();
+
+    bool CompressBlocks(size_t num_blocks, const void* data);
+    bool Sync();
+    bool Truncate(off_t length);
+    bool EnsureSpaceAvailable(const uint64_t bytes_needed) const;
+
+  private:
+    CowFooter footer_{};
+    CowCompressionAlgorithm compression_ = kCowCompressNone;
+    uint64_t current_op_pos_ = 0;
+    uint64_t next_op_pos_ = 0;
+    uint64_t next_data_pos_ = 0;
+    uint64_t current_data_pos_ = 0;
+    ssize_t total_data_written_ = 0;
+    uint32_t cluster_size_ = 0;
+    uint32_t current_cluster_size_ = 0;
+    uint64_t current_data_size_ = 0;
+    bool merge_in_progress_ = false;
+
+    int num_compress_threads_ = 1;
+    std::vector<std::unique_ptr<CompressWorker>> compress_threads_;
+    std::vector<std::future<bool>> threads_;
+    std::vector<std::basic_string<uint8_t>> compressed_buf_;
+    std::vector<std::basic_string<uint8_t>>::iterator buf_iter_;
+
+    std::vector<std::unique_ptr<CowOperation>> opbuffer_vec_;
+    std::vector<std::unique_ptr<uint8_t[]>> databuffer_vec_;
+    std::unique_ptr<struct iovec[]> cowop_vec_;
+    int op_vec_index_ = 0;
+
+    std::unique_ptr<struct iovec[]> data_vec_;
+    int data_vec_index_ = 0;
+    bool batch_write_ = false;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 64637c2..fbea79b 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -45,9 +45,9 @@
 #include <android/snapshot/snapshot.pb.h>
 #include <libsnapshot/snapshot_stats.h>
 #include "device_info.h"
+#include "libsnapshot_cow/parser_v2.h"
 #include "partition_cow_creator.h"
 #include "snapshot_metadata_updater.h"
-#include "snapshot_reader.h"
 #include "utility.h"
 
 namespace android {
@@ -3133,6 +3133,7 @@
     for (auto&& [name, status] : all_snapshot_status) {
         sum += status.cow_file_size();
     }
+    LOG(INFO) << "Calculated needed COW space: " << sum << " bytes";
     return Return::NoSpace(sum);
 }
 
@@ -3201,39 +3202,20 @@
     CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
           target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
 
-    std::map<std::string, SnapshotStatus> all_snapshot_status;
-
-    // In case of error, automatically delete devices that are created along the way.
-    // Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for
-    // these devices.
-    AutoDeviceList created_devices;
-
     const auto& dap_metadata = manifest.dynamic_partition_metadata();
-    CowOptions options;
-    CowWriter writer(options);
-    bool cow_format_support = true;
-    if (dap_metadata.cow_version() < writer.GetCowVersion()) {
-        cow_format_support = false;
-    }
-
-    LOG(INFO) << " dap_metadata.cow_version(): " << dap_metadata.cow_version()
-              << " writer.GetCowVersion(): " << writer.GetCowVersion();
-
-    // Deduce supported features.
-    bool userspace_snapshots = CanUseUserspaceSnapshots();
-    bool legacy_compression = GetLegacyCompressionEnabledProperty();
 
     std::string vabc_disable_reason;
     if (!dap_metadata.vabc_enabled()) {
         vabc_disable_reason = "not enabled metadata";
     } else if (device_->IsRecovery()) {
         vabc_disable_reason = "recovery";
-    } else if (!cow_format_support) {
-        vabc_disable_reason = "cow format not supported";
     } else if (!KernelSupportsCompressedSnapshots()) {
         vabc_disable_reason = "kernel missing userspace block device support";
     }
 
+    // Deduce supported features.
+    bool userspace_snapshots = CanUseUserspaceSnapshots();
+    bool legacy_compression = GetLegacyCompressionEnabledProperty();
     if (!vabc_disable_reason.empty()) {
         if (userspace_snapshots) {
             LOG(INFO) << "Userspace snapshots disabled: " << vabc_disable_reason;
@@ -3245,6 +3227,16 @@
         legacy_compression = false;
     }
 
+    if (legacy_compression || userspace_snapshots) {
+        if (dap_metadata.cow_version() < kMinCowVersion ||
+            dap_metadata.cow_version() > kMaxCowVersion) {
+            LOG(ERROR) << "Manifest cow version is out of bounds (got: "
+                       << dap_metadata.cow_version() << ", min: " << kMinCowVersion
+                       << ", max: " << kMaxCowVersion << ")";
+            return Return::Error();
+        }
+    }
+
     const bool using_snapuserd = userspace_snapshots || legacy_compression;
     if (!using_snapuserd) {
         LOG(INFO) << "Using legacy Virtual A/B (dm-snapshot)";
@@ -3277,9 +3269,17 @@
         cow_creator.batched_writes = dap_metadata.vabc_feature_set().batch_writes();
     }
 
+    // In case of error, automatically delete devices that are created along the way.
+    // Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for
+    // these devices.
+    AutoDeviceList created_devices;
+    std::map<std::string, SnapshotStatus> all_snapshot_status;
     auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
                                              &all_snapshot_status);
-    if (!ret.is_ok()) return ret;
+    if (!ret.is_ok()) {
+        LOG(ERROR) << "CreateUpdateSnapshotsInternal failed: " << ret.string();
+        return ret;
+    }
 
     auto exported_target_metadata = target_metadata->Export();
     if (exported_target_metadata == nullptr) {
@@ -3287,7 +3287,7 @@
         return Return::Error();
     }
 
-    ret = InitializeUpdateSnapshots(lock.get(), target_metadata.get(),
+    ret = InitializeUpdateSnapshots(lock.get(), dap_metadata.cow_version(), target_metadata.get(),
                                     exported_target_metadata.get(), target_suffix,
                                     all_snapshot_status);
     if (!ret.is_ok()) return ret;
@@ -3496,7 +3496,10 @@
         // Create the backing COW image if necessary.
         if (snapshot_status.cow_file_size() > 0) {
             auto ret = CreateCowImage(lock, name);
-            if (!ret.is_ok()) return AddRequiredSpace(ret, *all_snapshot_status);
+            if (!ret.is_ok()) {
+                LOG(ERROR) << "CreateCowImage failed: " << ret.string();
+                return AddRequiredSpace(ret, *all_snapshot_status);
+            }
         }
 
         LOG(INFO) << "Successfully created snapshot for " << name;
@@ -3506,7 +3509,7 @@
 }
 
 Return SnapshotManager::InitializeUpdateSnapshots(
-        LockedFile* lock, MetadataBuilder* target_metadata,
+        LockedFile* lock, uint32_t cow_version, MetadataBuilder* target_metadata,
         const LpMetadata* exported_target_metadata, const std::string& target_suffix,
         const std::map<std::string, SnapshotStatus>& all_snapshot_status) {
     CHECK(lock);
@@ -3554,8 +3557,8 @@
             }
             options.compression = it->second.compression_algorithm();
 
-            CowWriter writer(options);
-            if (!writer.Initialize(fd) || !writer.Finalize()) {
+            auto writer = CreateCowWriter(cow_version, options, std::move(fd));
+            if (!writer->Finalize()) {
                 LOG(ERROR) << "Could not initialize COW device for " << target_partition->name();
                 return Return::Error();
             }
@@ -3605,12 +3608,12 @@
     return true;
 }
 
-std::unique_ptr<ISnapshotWriter> SnapshotManager::OpenSnapshotWriter(
+std::unique_ptr<ICowWriter> SnapshotManager::OpenSnapshotWriter(
         const android::fs_mgr::CreateLogicalPartitionParams& params,
-        const std::optional<std::string>& source_device) {
+        std::optional<uint64_t> label) {
 #if defined(LIBSNAPSHOT_NO_COW_WRITE)
     (void)params;
-    (void)source_device;
+    (void)label;
 
     LOG(ERROR) << "Snapshots cannot be written in first-stage init or recovery";
     return nullptr;
@@ -3644,20 +3647,19 @@
         return nullptr;
     }
 
-    if (status.using_snapuserd()) {
-        return OpenCompressedSnapshotWriter(lock.get(), source_device, params.GetPartitionName(),
-                                            status, paths);
+    if (!status.using_snapuserd()) {
+        LOG(ERROR) << "Can only create snapshot writers with userspace or compressed snapshots";
+        return nullptr;
     }
-    return OpenKernelSnapshotWriter(lock.get(), source_device, params.GetPartitionName(), status,
-                                    paths);
+
+    return OpenCompressedSnapshotWriter(lock.get(), status, paths, label);
 #endif
 }
 
 #if !defined(LIBSNAPSHOT_NO_COW_WRITE)
-std::unique_ptr<ISnapshotWriter> SnapshotManager::OpenCompressedSnapshotWriter(
-        LockedFile* lock, const std::optional<std::string>& source_device,
-        [[maybe_unused]] const std::string& partition_name, const SnapshotStatus& status,
-        const SnapshotPaths& paths) {
+std::unique_ptr<ICowWriter> SnapshotManager::OpenCompressedSnapshotWriter(
+        LockedFile* lock, const SnapshotStatus& status, const SnapshotPaths& paths,
+        std::optional<uint64_t> label) {
     CHECK(lock);
 
     CowOptions cow_options;
@@ -3674,11 +3676,6 @@
     // never creates this scenario.
     CHECK(status.snapshot_size() == status.device_size());
 
-    auto writer = std::make_unique<CompressedSnapshotWriter>(cow_options);
-    if (source_device) {
-        writer->SetSourceDevice(*source_device);
-    }
-
     std::string cow_path;
     if (!GetMappedImageDevicePath(paths.cow_device_name, &cow_path)) {
         LOG(ERROR) << "Could not determine path for " << paths.cow_device_name;
@@ -3690,40 +3687,14 @@
         PLOG(ERROR) << "OpenCompressedSnapshotWriter: open " << cow_path;
         return nullptr;
     }
-    if (!writer->SetCowDevice(std::move(cow_fd))) {
-        LOG(ERROR) << "Could not create COW writer from " << cow_path;
+
+    CowHeader header;
+    if (!ReadCowHeader(cow_fd, &header)) {
+        LOG(ERROR) << "OpenCompressedSnapshotWriter: read header failed";
         return nullptr;
     }
 
-    return writer;
-}
-
-std::unique_ptr<ISnapshotWriter> SnapshotManager::OpenKernelSnapshotWriter(
-        LockedFile* lock, const std::optional<std::string>& source_device,
-        [[maybe_unused]] const std::string& partition_name, const SnapshotStatus& status,
-        const SnapshotPaths& paths) {
-    CHECK(lock);
-
-    CowOptions cow_options;
-    cow_options.max_blocks = {status.device_size() / cow_options.block_size};
-
-    auto writer = std::make_unique<OnlineKernelSnapshotWriter>(cow_options);
-
-    std::string path = paths.snapshot_device.empty() ? paths.target_device : paths.snapshot_device;
-    unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC));
-    if (fd < 0) {
-        PLOG(ERROR) << "open failed: " << path;
-        return nullptr;
-    }
-
-    if (source_device) {
-        writer->SetSourceDevice(*source_device);
-    }
-
-    uint64_t cow_size = status.cow_partition_size() + status.cow_file_size();
-    writer->SetSnapshotDevice(std::move(fd), cow_size);
-
-    return writer;
+    return CreateCowWriter(header.prefix.major_version, cow_options, std::move(cow_fd), label);
 }
 #endif  // !defined(LIBSNAPSHOT_NO_COW_WRITE)
 
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
deleted file mode 100644
index 6546c2a..0000000
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ /dev/null
@@ -1,355 +0,0 @@
-//
-// Copyright (C) 2020 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 "snapshot_reader.h"
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <ext4_utils/ext4_utils.h>
-
-namespace android {
-namespace snapshot {
-
-using android::base::borrowed_fd;
-
-// Not supported.
-bool ReadOnlyFileDescriptor::Open(const char*, int, mode_t) {
-    errno = EINVAL;
-    return false;
-}
-
-bool ReadOnlyFileDescriptor::Open(const char*, int) {
-    errno = EINVAL;
-    return false;
-}
-
-ssize_t ReadOnlyFileDescriptor::Write(const void*, size_t) {
-    errno = EINVAL;
-    return false;
-}
-
-bool ReadOnlyFileDescriptor::BlkIoctl(int, uint64_t, uint64_t, int*) {
-    errno = EINVAL;
-    return false;
-}
-
-ReadFdFileDescriptor::ReadFdFileDescriptor(android::base::unique_fd&& fd) : fd_(std::move(fd)) {}
-
-ssize_t ReadFdFileDescriptor::Read(void* buf, size_t count) {
-    return read(fd_.get(), buf, count);
-}
-
-off64_t ReadFdFileDescriptor::Seek(off64_t offset, int whence) {
-    return lseek(fd_.get(), offset, whence);
-}
-
-uint64_t ReadFdFileDescriptor::BlockDevSize() {
-    return get_block_device_size(fd_.get());
-}
-
-bool ReadFdFileDescriptor::Close() {
-    fd_ = {};
-    return true;
-}
-
-bool ReadFdFileDescriptor::IsSettingErrno() {
-    return true;
-}
-
-bool ReadFdFileDescriptor::IsOpen() {
-    return fd_ >= 0;
-}
-
-bool ReadFdFileDescriptor::Flush() {
-    return true;
-}
-
-bool CompressedSnapshotReader::SetCow(std::unique_ptr<CowReader>&& cow) {
-    cow_ = std::move(cow);
-
-    CowHeader header;
-    if (!cow_->GetHeader(&header)) {
-        return false;
-    }
-    block_size_ = header.block_size;
-
-    // Populate the operation map.
-    op_iter_ = cow_->GetOpIter();
-    while (!op_iter_->Done()) {
-        const CowOperation* op = &op_iter_->Get();
-        if (IsMetadataOp(*op)) {
-            op_iter_->Next();
-            continue;
-        }
-        if (op->new_block >= ops_.size()) {
-            ops_.resize(op->new_block + 1, nullptr);
-        }
-        ops_[op->new_block] = op;
-        op_iter_->Next();
-    }
-
-    return true;
-}
-
-void CompressedSnapshotReader::SetSourceDevice(const std::string& source_device) {
-    source_device_ = {source_device};
-}
-
-void CompressedSnapshotReader::SetBlockDeviceSize(uint64_t block_device_size) {
-    block_device_size_ = block_device_size;
-}
-
-borrowed_fd CompressedSnapshotReader::GetSourceFd() {
-    if (source_fd_ < 0) {
-        if (!source_device_) {
-            LOG(ERROR) << "CompressedSnapshotReader needs source device, but none was set";
-            errno = EINVAL;
-            return {-1};
-        }
-        source_fd_.reset(open(source_device_->c_str(), O_RDONLY | O_CLOEXEC));
-        if (source_fd_ < 0) {
-            PLOG(ERROR) << "open " << *source_device_;
-            return {-1};
-        }
-    }
-    return source_fd_;
-}
-
-class MemoryByteSink : public IByteSink {
-  public:
-    MemoryByteSink(void* buf, size_t count) {
-        buf_ = reinterpret_cast<uint8_t*>(buf);
-        pos_ = buf_;
-        end_ = buf_ + count;
-    }
-
-    void* GetBuffer(size_t requested, size_t* actual) override {
-        *actual = std::min(remaining(), requested);
-        if (!*actual) {
-            return nullptr;
-        }
-
-        uint8_t* start = pos_;
-        pos_ += *actual;
-        return start;
-    }
-
-    bool ReturnData(void*, size_t) override { return true; }
-
-    uint8_t* buf() const { return buf_; }
-    uint8_t* pos() const { return pos_; }
-    size_t remaining() const { return end_ - pos_; }
-
-  private:
-    uint8_t* buf_;
-    uint8_t* pos_;
-    uint8_t* end_;
-};
-
-ssize_t CompressedSnapshotReader::Read(void* buf, size_t count) {
-    // Find the start and end chunks, inclusive.
-    uint64_t start_chunk = offset_ / block_size_;
-    uint64_t end_chunk = (offset_ + count - 1) / block_size_;
-
-    // Chop off the first N bytes if the position is not block-aligned.
-    size_t start_offset = offset_ % block_size_;
-
-    MemoryByteSink sink(buf, count);
-
-    size_t initial_bytes = std::min(block_size_ - start_offset, sink.remaining());
-    ssize_t rv = ReadBlock(start_chunk, &sink, start_offset, initial_bytes);
-    if (rv < 0) {
-        return -1;
-    }
-    offset_ += rv;
-
-    for (uint64_t chunk = start_chunk + 1; chunk < end_chunk; chunk++) {
-        ssize_t rv = ReadBlock(chunk, &sink, 0);
-        if (rv < 0) {
-            return -1;
-        }
-        offset_ += rv;
-    }
-
-    if (sink.remaining()) {
-        ssize_t rv = ReadBlock(end_chunk, &sink, 0, {sink.remaining()});
-        if (rv < 0) {
-            return -1;
-        }
-        offset_ += rv;
-    }
-
-    errno = 0;
-
-    DCHECK(sink.pos() - sink.buf() == count);
-    return count;
-}
-
-// Discard the first N bytes of a sink request, or any excess bytes.
-class PartialSink : public MemoryByteSink {
-  public:
-    PartialSink(void* buffer, size_t size, size_t ignore_start)
-        : MemoryByteSink(buffer, size), ignore_start_(ignore_start) {}
-
-    void* GetBuffer(size_t requested, size_t* actual) override {
-        // Throw away the first N bytes if needed.
-        if (ignore_start_) {
-            *actual = std::min({requested, ignore_start_, sizeof(discard_)});
-            ignore_start_ -= *actual;
-            return discard_;
-        }
-        // Throw away any excess bytes if needed.
-        if (remaining() == 0) {
-            *actual = std::min(requested, sizeof(discard_));
-            return discard_;
-        }
-        return MemoryByteSink::GetBuffer(requested, actual);
-    }
-
-  private:
-    size_t ignore_start_;
-    char discard_[BLOCK_SZ];
-};
-
-ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
-                                            const std::optional<uint64_t>& max_bytes) {
-    size_t bytes_to_read = block_size_;
-    if (max_bytes) {
-        bytes_to_read = *max_bytes;
-    }
-
-    // The offset is relative to the chunk; we should be reading no more than
-    // one chunk.
-    CHECK(start_offset + bytes_to_read <= block_size_);
-
-    const CowOperation* op = nullptr;
-    if (chunk < ops_.size()) {
-        op = ops_[chunk];
-    }
-
-    size_t actual;
-    void* buffer = sink->GetBuffer(bytes_to_read, &actual);
-    if (!buffer || actual < bytes_to_read) {
-        // This should never happen unless we calculated the read size wrong
-        // somewhere. MemoryByteSink always fulfills the entire requested
-        // region unless there's not enough buffer remaining.
-        LOG(ERROR) << "Asked for buffer of size " << bytes_to_read << ", got " << actual;
-        errno = EINVAL;
-        return -1;
-    }
-
-    if (!op || op->type == kCowCopyOp) {
-        borrowed_fd fd = GetSourceFd();
-        if (fd < 0) {
-            // GetSourceFd sets errno.
-            return -1;
-        }
-
-        if (op) {
-            chunk = op->source;
-        }
-
-        off64_t offset = (chunk * block_size_) + start_offset;
-        if (!android::base::ReadFullyAtOffset(fd, buffer, bytes_to_read, offset)) {
-            PLOG(ERROR) << "read " << *source_device_;
-            // ReadFullyAtOffset sets errno.
-            return -1;
-        }
-    } else if (op->type == kCowZeroOp) {
-        memset(buffer, 0, bytes_to_read);
-    } else if (op->type == kCowReplaceOp) {
-        PartialSink partial_sink(buffer, bytes_to_read, start_offset);
-        if (!cow_->ReadData(*op, &partial_sink)) {
-            LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
-            errno = EIO;
-            return -1;
-        }
-    } else if (op->type == kCowXorOp) {
-        borrowed_fd fd = GetSourceFd();
-        if (fd < 0) {
-            // GetSourceFd sets errno.
-            return -1;
-        }
-
-        off64_t offset = op->source + start_offset;
-        char data[BLOCK_SZ];
-        if (!android::base::ReadFullyAtOffset(fd, &data, bytes_to_read, offset)) {
-            PLOG(ERROR) << "read " << *source_device_;
-            // ReadFullyAtOffset sets errno.
-            return -1;
-        }
-        PartialSink partial_sink(buffer, bytes_to_read, start_offset);
-        if (!cow_->ReadData(*op, &partial_sink)) {
-            LOG(ERROR) << "CompressedSnapshotReader failed to read xor op";
-            errno = EIO;
-            return -1;
-        }
-        for (size_t i = 0; i < bytes_to_read; i++) {
-            ((char*)buffer)[i] ^= data[i];
-        }
-    } else {
-        LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type);
-        errno = EINVAL;
-        return -1;
-    }
-
-    // MemoryByteSink doesn't do anything in ReturnBuffer, so don't bother calling it.
-    return bytes_to_read;
-}
-
-off64_t CompressedSnapshotReader::Seek(off64_t offset, int whence) {
-    switch (whence) {
-        case SEEK_SET:
-            offset_ = offset;
-            break;
-        case SEEK_END:
-            offset_ = static_cast<off64_t>(block_device_size_) + offset;
-            break;
-        case SEEK_CUR:
-            offset_ += offset;
-            break;
-        default:
-            LOG(ERROR) << "Unrecognized seek whence: " << whence;
-            errno = EINVAL;
-            return -1;
-    }
-    return offset_;
-}
-
-uint64_t CompressedSnapshotReader::BlockDevSize() {
-    return block_device_size_;
-}
-
-bool CompressedSnapshotReader::Close() {
-    cow_ = nullptr;
-    source_fd_ = {};
-    return true;
-}
-
-bool CompressedSnapshotReader::IsSettingErrno() {
-    return true;
-}
-
-bool CompressedSnapshotReader::IsOpen() {
-    return cow_ != nullptr;
-}
-
-bool CompressedSnapshotReader::Flush() {
-    return true;
-}
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 84e2226..9354102 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -154,8 +154,8 @@
     return &snapshot_merge_stats;
 }
 
-std::unique_ptr<ISnapshotWriter> SnapshotManagerStub::OpenSnapshotWriter(
-        const CreateLogicalPartitionParams&, const std::optional<std::string>&) {
+std::unique_ptr<ICowWriter> SnapshotManagerStub::OpenSnapshotWriter(
+        const CreateLogicalPartitionParams&, std::optional<uint64_t>) {
     LOG(ERROR) << __FUNCTION__ << " should never be called.";
     return nullptr;
 }
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 460d49d..0a85489 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -19,6 +19,7 @@
 #include <signal.h>
 #include <sys/file.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
 
 #include <chrono>
@@ -196,6 +197,17 @@
         return true;
     }
 
+    bool ShouldSkipLegacyMerging() {
+        if (!GetLegacyCompressionEnabledProperty() || !snapuserd_required_) {
+            return false;
+        }
+        int api_level = android::base::GetIntProperty("ro.board.api_level", -1);
+        if (api_level == -1) {
+            api_level = android::base::GetIntProperty("ro.product.first_api_level", -1);
+        }
+        return api_level != __ANDROID_API_S__;
+    }
+
     void InitializeState() {
         ASSERT_TRUE(sm->EnsureImageManager());
         image_manager_ = sm->image_manager();
@@ -333,7 +345,7 @@
     }
 
     AssertionResult MapUpdateSnapshot(const std::string& name,
-                                      std::unique_ptr<ISnapshotWriter>* writer) {
+                                      std::unique_ptr<ICowWriter>* writer) {
         TestPartitionOpener opener(fake_super);
         CreateLogicalPartitionParams params{
                 .block_device = fake_super,
@@ -343,14 +355,10 @@
                 .partition_opener = &opener,
         };
 
-        auto old_partition = "/dev/block/mapper/" + GetOtherPartitionName(name);
-        auto result = sm->OpenSnapshotWriter(params, {old_partition});
+        auto result = sm->OpenSnapshotWriter(params, {});
         if (!result) {
             return AssertionFailure() << "Cannot open snapshot for writing: " << name;
         }
-        if (!result->Initialize()) {
-            return AssertionFailure() << "Cannot initialize snapshot for writing: " << name;
-        }
 
         if (writer) {
             *writer = std::move(result);
@@ -428,7 +436,7 @@
 
     // Prepare A/B slot for a partition named "test_partition".
     AssertionResult PrepareOneSnapshot(uint64_t device_size,
-                                       std::unique_ptr<ISnapshotWriter>* writer = nullptr) {
+                                       std::unique_ptr<ICowWriter>* writer = nullptr) {
         lock_ = nullptr;
 
         DeltaArchiveManifest manifest;
@@ -631,21 +639,38 @@
 TEST_F(SnapshotTest, Merge) {
     ASSERT_TRUE(AcquireLock());
 
-    static const uint64_t kDeviceSize = 1024 * 1024;
-
-    std::unique_ptr<ISnapshotWriter> writer;
-    ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
-
-    bool userspace_snapshots = sm->UpdateUsesUserSnapshots(lock_.get());
-
-    // Release the lock.
-    lock_ = nullptr;
+    static constexpr uint64_t kDeviceSize = 1024 * 1024;
+    static constexpr uint32_t kBlockSize = 4096;
 
     std::string test_string = "This is a test string.";
-    test_string.resize(writer->options().block_size);
-    ASSERT_TRUE(writer->AddRawBlocks(0, test_string.data(), test_string.size()));
-    ASSERT_TRUE(writer->Finalize());
-    writer = nullptr;
+    test_string.resize(kBlockSize);
+
+    bool userspace_snapshots = false;
+    if (snapuserd_required_) {
+        std::unique_ptr<ICowWriter> writer;
+        ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
+
+        userspace_snapshots = sm->UpdateUsesUserSnapshots(lock_.get());
+
+        // Release the lock.
+        lock_ = nullptr;
+
+        ASSERT_TRUE(writer->AddRawBlocks(0, test_string.data(), test_string.size()));
+        ASSERT_TRUE(writer->Finalize());
+        writer = nullptr;
+    } else {
+        ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
+
+        // Release the lock.
+        lock_ = nullptr;
+
+        std::string path;
+        ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b", &path));
+
+        unique_fd fd(open(path.c_str(), O_WRONLY));
+        ASSERT_GE(fd, 0);
+        ASSERT_TRUE(android::base::WriteFully(fd, test_string.data(), test_string.size()));
+    }
 
     // Done updating.
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
@@ -654,6 +679,10 @@
 
     test_device->set_slot_suffix("_b");
     ASSERT_TRUE(sm->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(sm->InitiateMerge());
 
     // The device should have been switched to a snapshot-merge target.
@@ -761,6 +790,10 @@
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(init->InitiateMerge());
 
     // Now, reflash super. Note that we haven't called ProcessUpdateState, so the
@@ -1123,7 +1156,7 @@
 
     AssertionResult MapOneUpdateSnapshot(const std::string& name) {
         if (snapuserd_required_) {
-            std::unique_ptr<ISnapshotWriter> writer;
+            std::unique_ptr<ICowWriter> writer;
             return MapUpdateSnapshot(name, &writer);
         } else {
             std::string path;
@@ -1144,7 +1177,7 @@
     AssertionResult WriteSnapshotAndHash(PartitionUpdate* partition) {
         std::string name = partition->partition_name() + "_b";
         if (snapuserd_required_) {
-            std::unique_ptr<ISnapshotWriter> writer;
+            std::unique_ptr<ICowWriter> writer;
             auto res = MapUpdateSnapshot(name, &writer);
             if (!res) {
                 return res;
@@ -1183,13 +1216,13 @@
         SHA256_CTX ctx;
         SHA256_Init(&ctx);
 
-        if (!writer->options().max_blocks) {
+        if (!writer->GetMaxBlocks()) {
             LOG(ERROR) << "CowWriter must specify maximum number of blocks";
             return false;
         }
-        const auto num_blocks = writer->options().max_blocks.value();
+        const auto num_blocks = writer->GetMaxBlocks().value();
 
-        const auto block_size = writer->options().block_size;
+        const auto block_size = writer->GetBlockSize();
         std::string block(block_size, '\0');
         for (uint64_t i = 0; i < num_blocks; i++) {
             if (!ReadFully(rand, block.data(), block.size())) {
@@ -1213,17 +1246,17 @@
     // It doesn't really matter the order, we just want copies that reference
     // blocks that won't exist if the partition shrinks.
     AssertionResult ShiftAllSnapshotBlocks(const std::string& name, uint64_t old_size) {
-        std::unique_ptr<ISnapshotWriter> writer;
+        std::unique_ptr<ICowWriter> writer;
         if (auto res = MapUpdateSnapshot(name, &writer); !res) {
             return res;
         }
-        if (!writer->options().max_blocks || !*writer->options().max_blocks) {
+        if (!writer->GetMaxBlocks() || !*writer->GetMaxBlocks()) {
             return AssertionFailure() << "No max blocks set for " << name << " writer";
         }
 
-        uint64_t src_block = (old_size / writer->options().block_size) - 1;
+        uint64_t src_block = (old_size / writer->GetBlockSize()) - 1;
         uint64_t dst_block = 0;
-        uint64_t max_blocks = *writer->options().max_blocks;
+        uint64_t max_blocks = *writer->GetMaxBlocks();
         while (dst_block < max_blocks && dst_block < src_block) {
             if (!writer->AddCopy(dst_block, src_block)) {
                 return AssertionFailure() << "Unable to add copy for " << name << " for blocks "
@@ -1236,7 +1269,13 @@
             return AssertionFailure() << "Unable to finalize writer for " << name;
         }
 
-        auto hash = HashSnapshot(writer.get());
+        auto old_partition = "/dev/block/mapper/" + GetOtherPartitionName(name);
+        auto reader = writer->OpenFileDescriptor(old_partition);
+        if (!reader) {
+            return AssertionFailure() << "Could not open file descriptor for " << name;
+        }
+
+        auto hash = HashSnapshot(reader.get());
         if (hash.empty()) {
             return AssertionFailure() << "Unable to hash snapshot writer for " << name;
         }
@@ -1344,6 +1383,10 @@
     }
 
     // Initiate the merge and wait for it to be completed.
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
@@ -1387,7 +1430,7 @@
     for (auto* partition : partitions) {
         AddOperation(partition);
 
-        std::unique_ptr<ISnapshotWriter> writer;
+        std::unique_ptr<ICowWriter> writer;
         auto res = MapUpdateSnapshot(partition->partition_name() + "_b", &writer);
         ASSERT_TRUE(res);
         ASSERT_TRUE(writer->AddZeroBlocks(0, 1));
@@ -1407,6 +1450,10 @@
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
 
     // Initiate the merge and wait for it to be completed.
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
 }
@@ -1476,6 +1523,10 @@
     }
 
     // Initiate the merge and wait for it to be completed.
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
@@ -1584,6 +1635,10 @@
             });
 
     // Initiate the merge and wait for it to be completed.
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
@@ -1786,6 +1841,10 @@
 
     // Initiate the merge and wait for it to be completed.
     auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(new_sm->InitiateMerge());
     ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState());
 
@@ -1924,6 +1983,10 @@
     ASSERT_GE(fd, 0);
 
     // COW cannot be removed due to open fd, so expect a soft failure.
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(UpdateState::MergeNeedsReboot, init->ProcessUpdateState());
 
@@ -2027,6 +2090,10 @@
 
     // Initiate the merge and then immediately stop it to simulate a reboot.
     auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(new_sm->InitiateMerge());
     ASSERT_TRUE(UnmapAll());
 
@@ -2059,6 +2126,10 @@
 
     // Initiate the merge and then immediately stop it to simulate a reboot.
     auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(new_sm->InitiateMerge());
     ASSERT_TRUE(UnmapAll());
 
@@ -2136,6 +2207,10 @@
 
 // Test update package that requests data wipe.
 TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) {
+    if (ShouldSkipLegacyMerging()) {
+        GTEST_SKIP() << "Skipping legacy merge in test";
+    }
+
     AddOperationForPartitions();
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
@@ -2175,6 +2250,10 @@
 
 // Test update package that requests data wipe.
 TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) {
+    if (ShouldSkipLegacyMerging()) {
+        GTEST_SKIP() << "Skipping legacy merge in test";
+    }
+
     AddOperationForPartitions();
 
     // Execute the update.
@@ -2312,32 +2391,6 @@
             << "FinishedSnapshotWrites should detect overflow of CoW device.";
 }
 
-TEST_F(SnapshotUpdateTest, LowSpace) {
-    static constexpr auto kMaxFree = 10_MiB;
-    auto userdata = std::make_unique<LowSpaceUserdata>();
-    ASSERT_TRUE(userdata->Init(kMaxFree));
-
-    // Grow all partitions to 10_MiB, total 30_MiB. This requires 30 MiB of CoW space. After
-    // using the empty space in super (< 1 MiB), it uses 30 MiB of /userdata space.
-    constexpr uint64_t partition_size = 10_MiB;
-    SetSize(sys_, partition_size);
-    SetSize(vnd_, partition_size);
-    SetSize(prd_, partition_size);
-    sys_->set_estimate_cow_size(partition_size);
-    vnd_->set_estimate_cow_size(partition_size);
-    prd_->set_estimate_cow_size(partition_size);
-
-    AddOperationForPartitions();
-
-    // Execute the update.
-    ASSERT_TRUE(sm->BeginUpdate());
-    auto res = sm->CreateUpdateSnapshots(manifest_);
-    ASSERT_FALSE(res);
-    ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code());
-    ASSERT_GE(res.required_size(), 14_MiB);
-    ASSERT_LT(res.required_size(), 40_MiB);
-}
-
 TEST_F(SnapshotUpdateTest, AddPartition) {
     group_->add_partition_names("dlkm");
 
@@ -2397,6 +2450,10 @@
     }
 
     // Initiate the merge and wait for it to be completed.
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
 
@@ -2583,6 +2640,11 @@
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState());
 
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
+
     // Simulate a reboot that tries the merge again, with the non-failing dm.
     ASSERT_TRUE(UnmapAll());
     init = NewManagerForFirstStageMount("_b");
@@ -2591,6 +2653,24 @@
     ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
 }
 
+TEST_F(SnapshotUpdateTest, BadCowVersion) {
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "VABC only";
+    }
+
+    ASSERT_TRUE(sm->BeginUpdate());
+
+    auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
+    dynamic_partition_metadata->set_cow_version(kMinCowVersion - 1);
+    ASSERT_FALSE(sm->CreateUpdateSnapshots(manifest_));
+
+    dynamic_partition_metadata->set_cow_version(kMaxCowVersion + 1);
+    ASSERT_FALSE(sm->CreateUpdateSnapshots(manifest_));
+
+    dynamic_partition_metadata->set_cow_version(kMaxCowVersion);
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+}
+
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
                              public WithParamInterface<std::tuple<uint32_t, bool>> {
   public:
@@ -2699,50 +2779,6 @@
                                     "Merge"s;
                          });
 
-class ImageManagerTest : public SnapshotTest {
-  protected:
-    void SetUp() override {
-        SKIP_IF_NON_VIRTUAL_AB();
-        SnapshotTest::SetUp();
-    }
-    void TearDown() override {
-        RETURN_IF_NON_VIRTUAL_AB();
-        CleanUp();
-    }
-    void CleanUp() {
-        if (!image_manager_) {
-            return;
-        }
-        EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
-                    image_manager_->DeleteBackingImage(kImageName));
-    }
-
-    static constexpr const char* kImageName = "my_image";
-};
-
-TEST_F(ImageManagerTest, CreateImageNoSpace) {
-    bool at_least_one_failure = false;
-    for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) {
-        auto userdata = std::make_unique<LowSpaceUserdata>();
-        ASSERT_TRUE(userdata->Init(size));
-
-        uint64_t to_allocate = userdata->free_space() + userdata->bsize();
-
-        auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
-                                                      IImageManager::CREATE_IMAGE_DEFAULT);
-        if (!res) {
-            at_least_one_failure = true;
-        } else {
-            ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
-        }
-
-        CleanUp();
-    }
-
-    ASSERT_TRUE(at_least_one_failure)
-            << "We should have failed to allocate at least one over-sized image";
-}
-
 bool Mkdir(const std::string& path) {
     if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
         std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
deleted file mode 100644
index 82a7fd7..0000000
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ /dev/null
@@ -1,243 +0,0 @@
-//
-// Copyright (C) 2020 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 <libsnapshot/snapshot_writer.h>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <payload_consumer/file_descriptor.h>
-#include "snapshot_reader.h"
-
-namespace android {
-namespace snapshot {
-
-using android::base::borrowed_fd;
-using android::base::unique_fd;
-using chromeos_update_engine::FileDescriptor;
-
-ISnapshotWriter::ISnapshotWriter(const CowOptions& options) : ICowWriter(options) {}
-
-void ISnapshotWriter::SetSourceDevice(const std::string& source_device) {
-    source_device_ = {source_device};
-}
-
-borrowed_fd ISnapshotWriter::GetSourceFd() {
-    if (!source_device_) {
-        LOG(ERROR) << "Attempted to read from source device but none was set";
-        return borrowed_fd{-1};
-    }
-
-    if (source_fd_ < 0) {
-        source_fd_.reset(open(source_device_->c_str(), O_RDONLY | O_CLOEXEC));
-        if (source_fd_ < 0) {
-            PLOG(ERROR) << "open " << *source_device_;
-            return borrowed_fd{-1};
-        }
-    }
-    return source_fd_;
-}
-
-CompressedSnapshotWriter::CompressedSnapshotWriter(const CowOptions& options)
-    : ISnapshotWriter(options) {}
-
-bool CompressedSnapshotWriter::SetCowDevice(android::base::unique_fd&& cow_device) {
-    cow_device_ = std::move(cow_device);
-    cow_ = std::make_unique<CowWriter>(options_);
-    return true;
-}
-
-bool CompressedSnapshotWriter::Finalize() {
-    return cow_->Finalize();
-}
-
-uint64_t CompressedSnapshotWriter::GetCowSize() {
-    return cow_->GetCowSize();
-}
-
-std::unique_ptr<CowReader> CompressedSnapshotWriter::OpenCowReader() const {
-    unique_fd cow_fd(dup(cow_device_.get()));
-    if (cow_fd < 0) {
-        PLOG(ERROR) << "dup COW device";
-        return nullptr;
-    }
-
-    auto cow = std::make_unique<CowReader>();
-    if (!cow->Parse(std::move(cow_fd))) {
-        LOG(ERROR) << "Unable to read COW";
-        return nullptr;
-    }
-    return cow;
-}
-
-bool CompressedSnapshotWriter::VerifyMergeOps() const noexcept {
-    auto cow_reader = OpenCowReader();
-    if (cow_reader == nullptr) {
-        LOG(ERROR) << "Couldn't open CowReader";
-        return false;
-    }
-    return cow_reader->VerifyMergeOps();
-}
-
-std::unique_ptr<FileDescriptor> CompressedSnapshotWriter::OpenReader() {
-    auto cow = OpenCowReader();
-    if (cow == nullptr) {
-        return nullptr;
-    }
-
-    auto reader = std::make_unique<CompressedSnapshotReader>();
-    if (!reader->SetCow(std::move(cow))) {
-        LOG(ERROR) << "Unable to initialize COW reader";
-        return nullptr;
-    }
-    if (source_device_) {
-        reader->SetSourceDevice(*source_device_);
-    }
-
-    const auto& cow_options = options();
-    if (cow_options.max_blocks) {
-        reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
-    }
-
-    return reader;
-}
-
-bool CompressedSnapshotWriter::EmitCopy(uint64_t new_block, uint64_t old_block,
-                                        uint64_t num_blocks) {
-    return cow_->AddCopy(new_block, old_block, num_blocks);
-}
-
-bool CompressedSnapshotWriter::EmitRawBlocks(uint64_t new_block_start, const void* data,
-                                             size_t size) {
-    return cow_->AddRawBlocks(new_block_start, data, size);
-}
-
-bool CompressedSnapshotWriter::EmitXorBlocks(uint32_t new_block_start, const void* data,
-                                             size_t size, uint32_t old_block, uint16_t offset) {
-    return cow_->AddXorBlocks(new_block_start, data, size, old_block, offset);
-}
-
-bool CompressedSnapshotWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
-    return cow_->AddZeroBlocks(new_block_start, num_blocks);
-}
-
-bool CompressedSnapshotWriter::EmitLabel(uint64_t label) {
-    return cow_->AddLabel(label);
-}
-
-bool CompressedSnapshotWriter::EmitSequenceData(size_t num_ops, const uint32_t* data) {
-    return cow_->AddSequenceData(num_ops, data);
-}
-
-bool CompressedSnapshotWriter::Initialize() {
-    return cow_->Initialize(cow_device_);
-}
-
-bool CompressedSnapshotWriter::InitializeAppend(uint64_t label) {
-    return cow_->InitializeAppend(cow_device_, label);
-}
-
-OnlineKernelSnapshotWriter::OnlineKernelSnapshotWriter(const CowOptions& options)
-    : ISnapshotWriter(options) {}
-
-void OnlineKernelSnapshotWriter::SetSnapshotDevice(android::base::unique_fd&& snapshot_fd,
-                                                   uint64_t cow_size) {
-    snapshot_fd_ = std::move(snapshot_fd);
-    cow_size_ = cow_size;
-}
-
-bool OnlineKernelSnapshotWriter::Finalize() {
-    if (fsync(snapshot_fd_.get()) < 0) {
-        PLOG(ERROR) << "fsync";
-        return false;
-    }
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitRawBlocks(uint64_t new_block_start, const void* data,
-                                               size_t size) {
-    uint64_t offset = new_block_start * options_.block_size;
-    if (lseek(snapshot_fd_.get(), offset, SEEK_SET) < 0) {
-        PLOG(ERROR) << "EmitRawBlocks lseek to offset " << offset;
-        return false;
-    }
-    if (!android::base::WriteFully(snapshot_fd_, data, size)) {
-        PLOG(ERROR) << "EmitRawBlocks write";
-        return false;
-    }
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitXorBlocks(uint32_t, const void*, size_t, uint32_t, uint16_t) {
-    LOG(ERROR) << "EmitXorBlocks not implemented.";
-    return false;
-}
-
-bool OnlineKernelSnapshotWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
-    std::string zeroes(options_.block_size, 0);
-    for (uint64_t i = 0; i < num_blocks; i++) {
-        if (!EmitRawBlocks(new_block_start + i, zeroes.data(), zeroes.size())) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitCopy(uint64_t new_block, uint64_t old_block,
-                                          uint64_t num_blocks) {
-    auto source_fd = GetSourceFd();
-    if (source_fd < 0) {
-        return false;
-    }
-
-    CHECK(num_blocks != 0);
-
-    for (size_t i = 0; i < num_blocks; i++) {
-        std::string buffer(options_.block_size, 0);
-        uint64_t offset = (old_block + i) * options_.block_size;
-        if (!android::base::ReadFullyAtOffset(source_fd, buffer.data(), buffer.size(), offset)) {
-            PLOG(ERROR) << "EmitCopy read";
-            return false;
-        }
-        if (!EmitRawBlocks(new_block + i, buffer.data(), buffer.size())) {
-            PLOG(ERROR) << "EmitRawBlocks failed";
-            return false;
-        }
-    }
-
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitLabel(uint64_t) {
-    // Not Needed
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitSequenceData(size_t, const uint32_t*) {
-    // Not Needed
-    return true;
-}
-
-std::unique_ptr<FileDescriptor> OnlineKernelSnapshotWriter::OpenReader() {
-    unique_fd fd(dup(snapshot_fd_.get()));
-    if (fd < 0) {
-        PLOG(ERROR) << "dup2 failed in OpenReader";
-        return nullptr;
-    }
-    return std::make_unique<ReadFdFileDescriptor>(std::move(fd));
-}
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_writer_test.cpp b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
deleted file mode 100644
index a03632b..0000000
--- a/fs_mgr/libsnapshot/snapshot_writer_test.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-// Copyright (C) 2021 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 <libsnapshot/snapshot.h>
-
-#include <unordered_set>
-
-#include <android-base/file.h>
-#include <gtest/gtest.h>
-#include <libsnapshot/snapshot_writer.h>
-#include <payload_consumer/file_descriptor.h>
-
-namespace android::snapshot {
-class CompressedSnapshotWriterTest : public ::testing::Test {
-  public:
-    static constexpr size_t BLOCK_SIZE = 4096;
-};
-
-TEST_F(CompressedSnapshotWriterTest, ReadAfterWrite) {
-    TemporaryFile cow_device_file{};
-    android::snapshot::CowOptions options{.block_size = BLOCK_SIZE};
-    android::snapshot::CompressedSnapshotWriter snapshot_writer{options};
-    ASSERT_TRUE(snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd}));
-    ASSERT_TRUE(snapshot_writer.Initialize());
-    std::vector<unsigned char> buffer;
-    buffer.resize(BLOCK_SIZE);
-    std::fill(buffer.begin(), buffer.end(), 123);
-
-    ASSERT_TRUE(snapshot_writer.AddRawBlocks(0, buffer.data(), buffer.size()));
-    ASSERT_TRUE(snapshot_writer.Finalize());
-    auto cow_reader = snapshot_writer.OpenReader();
-    ASSERT_NE(cow_reader, nullptr);
-    ASSERT_TRUE(snapshot_writer.AddRawBlocks(1, buffer.data(), buffer.size()));
-    ASSERT_TRUE(snapshot_writer.AddRawBlocks(2, buffer.data(), buffer.size()));
-    ASSERT_TRUE(snapshot_writer.Finalize());
-    // After wrigin some data, if we call OpenReader() again, writes should
-    // be visible to the newly opened reader. update_engine relies on this
-    // behavior for verity writes.
-    cow_reader = snapshot_writer.OpenReader();
-    ASSERT_NE(cow_reader, nullptr);
-    std::vector<unsigned char> read_back;
-    read_back.resize(buffer.size());
-    cow_reader->Seek(BLOCK_SIZE, SEEK_SET);
-    const auto bytes_read = cow_reader->Read(read_back.data(), read_back.size());
-    ASSERT_EQ((size_t)(bytes_read), BLOCK_SIZE);
-    ASSERT_EQ(read_back, buffer);
-}
-
-}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index ad3f83c..38eb719 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -133,7 +133,6 @@
 
     // Write the "new" system partition.
     auto system_target_name = "system" + target_slot;
-    auto source_device = "/dev/block/mapper/" + system_source_name;
     CreateLogicalPartitionParams clpp = {
             .block_device = fs_mgr_get_super_partition_name(target_slot_number),
             .metadata_slot = {target_slot_number},
@@ -141,15 +140,11 @@
             .partition_opener = &opener,
             .timeout_ms = 10s,
     };
-    auto writer = sm->OpenSnapshotWriter(clpp, {source_device});
+    auto writer = sm->OpenSnapshotWriter(clpp, std::nullopt);
     if (!writer) {
         std::cerr << "Could not open snapshot writer.\n";
         return false;
     }
-    if (!writer->Initialize()) {
-        std::cerr << "Could not initialize snapshot for writing.\n";
-        return false;
-    }
 
     for (uint64_t block = 0; block < system_source_size / 4096; block++) {
         if (!writer->AddCopy(block, block)) {
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 9261482..9fe567a 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -62,6 +62,7 @@
         "dm-snapshot-merge/snapuserd_worker.cpp",
         "dm-snapshot-merge/snapuserd_readahead.cpp",
         "snapuserd_buffer.cpp",
+        "user-space-merge/handler_manager.cpp",
         "user-space-merge/snapuserd_core.cpp",
         "user-space-merge/snapuserd_dm_user.cpp",
         "user-space-merge/snapuserd_merge.cpp",
@@ -111,6 +112,7 @@
         "liblz4",
         "libext4_utils",
         "liburing",
+        "libzstd",
     ],
 
     header_libs: [
@@ -163,7 +165,7 @@
 }
 
 cc_test {
-    name: "cow_snapuserd_test",
+    name: "snapuserd_test_legacy",
     defaults: [
         "fs_mgr_defaults",
         "libsnapshot_cow_defaults",
@@ -215,16 +217,17 @@
     ],
     static_libs: [
         "libbrotli",
+        "libcutils_sockets",
+        "libdm",
+        "libext4_utils",
+        "libfs_mgr",
+        "libgflags",
         "libgtest",
         "libsnapshot_cow",
         "libsnapshot_snapuserd",
-        "libcutils_sockets",
-        "libz",
-        "libfs_mgr",
-        "libdm",
-        "libext4_utils",
+        "libsnapuserd",
         "liburing",
-        "libgflags",
+        "libz",
     ],
     include_dirs: ["bionic/libc/kernel"],
     header_libs: [
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index 3c4ab2e..737c480 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
@@ -122,6 +122,7 @@
     void SimulateDaemonRestart();
     void StartMerge();
 
+    std::unique_ptr<ICowWriter> CreateCowDeviceInternal();
     void CreateCowDevice();
     void CreateCowDeviceOrderedOps();
     void CreateCowDeviceOrderedOpsInverted();
@@ -164,6 +165,7 @@
 
   private:
     void InitMetadata();
+    std::unique_ptr<ICowWriter> CreateCowDeviceInternal();
     void CreateCowDevice();
     void CreateCowPartialFilledArea();
 
@@ -258,6 +260,19 @@
     }
 }
 
+std::unique_ptr<ICowWriter> CowSnapuserdTest::CreateCowDeviceInternal() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+
+    unique_fd fd(cow_system_->fd);
+    cow_system_->fd = -1;
+
+    return CreateCowWriter(kDefaultCowVersion, options, std::move(fd));
+}
+
 void CowSnapuserdTest::ReadLastBlock() {
     unique_fd rnd_fd;
     total_base_size_ = BLOCK_SZ * 2;
@@ -280,9 +295,6 @@
     base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
     ASSERT_TRUE(base_loop_->valid());
 
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
-
     std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(total_base_size_);
     loff_t offset = 0;
 
@@ -294,16 +306,13 @@
         offset += BLOCK_SZ;
     }
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+    ASSERT_TRUE(writer->AddRawBlocks(0, random_buffer_1_.get(), BLOCK_SZ));
+    ASSERT_TRUE(writer->AddRawBlocks(1, (char*)random_buffer_1_.get() + BLOCK_SZ, BLOCK_SZ));
 
-    ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1_.get(), BLOCK_SZ));
-    ASSERT_TRUE(writer.AddRawBlocks(1, (char*)random_buffer_1_.get() + BLOCK_SZ, BLOCK_SZ));
-
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
 
     SetDeviceControlName();
 
@@ -381,22 +390,16 @@
 }
 
 void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t x = num_blocks;
     size_t blk_src_copy = 0;
 
     // Create overlapping copy operations
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
+        ASSERT_TRUE(writer->AddCopy(blk_src_copy, blk_src_copy + 1));
         x -= 1;
         if (x == 1) {
             break;
@@ -405,7 +408,7 @@
     }
 
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
 
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
@@ -433,22 +436,16 @@
 }
 
 void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t x = num_blocks;
     size_t blk_src_copy = num_blocks - 1;
 
     // Create overlapping copy operations
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
+        ASSERT_TRUE(writer->AddCopy(blk_src_copy + 1, blk_src_copy));
         x -= 1;
         if (x == 0) {
             ASSERT_EQ(blk_src_copy, 0);
@@ -458,7 +455,7 @@
     }
 
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
 
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
@@ -468,10 +465,11 @@
               true);
 
     // Merged operations
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), writer->GetBlockSize(),
+                                               0),
               true);
     ASSERT_EQ(android::base::ReadFullyAtOffset(
-                      base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
+                      base_fd_, (char*)orig_buffer_.get() + writer->GetBlockSize(), size_, 0),
               true);
 }
 
@@ -479,8 +477,8 @@
     unique_fd rnd_fd;
     loff_t offset = 0;
 
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
@@ -495,13 +493,7 @@
         offset += 1_MiB;
     }
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t blk_end_copy = num_blocks * 3;
     size_t source_blk = num_blocks - 1;
     size_t blk_src_copy = blk_end_copy - 1;
@@ -509,7 +501,7 @@
 
     size_t x = num_blocks;
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
         x -= 1;
         if (x == 0) {
             break;
@@ -519,12 +511,12 @@
     }
 
     for (size_t i = num_blocks; i > 0; i--) {
-        ASSERT_TRUE(writer.AddXorBlocks(num_blocks + i - 1,
-                                        &random_buffer_1_.get()[options.block_size * (i - 1)],
-                                        options.block_size, 2 * num_blocks + i - 1, xor_offset));
+        ASSERT_TRUE(writer->AddXorBlocks(
+                num_blocks + i - 1, &random_buffer_1_.get()[writer->GetBlockSize() * (i - 1)],
+                writer->GetBlockSize(), 2 * num_blocks + i - 1, xor_offset));
     }
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
     // Read the entire base device
@@ -542,8 +534,8 @@
     unique_fd rnd_fd;
     loff_t offset = 0;
 
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
@@ -559,20 +551,14 @@
     }
     memset(random_buffer_1_.get(), 0, size_);
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t x = num_blocks;
     size_t source_blk = 0;
     size_t blk_src_copy = 2 * num_blocks;
     uint16_t xor_offset = 5;
 
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
 
         x -= 1;
         if (x == 0) {
@@ -582,10 +568,10 @@
         blk_src_copy += 1;
     }
 
-    ASSERT_TRUE(writer.AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
-                                    xor_offset));
+    ASSERT_TRUE(writer->AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
+                                     xor_offset));
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
     // Read the entire base device
@@ -603,8 +589,8 @@
     unique_fd rnd_fd;
     loff_t offset = 0;
 
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
@@ -619,13 +605,7 @@
         offset += 1_MiB;
     }
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t blk_end_copy = num_blocks * 2;
     size_t source_blk = num_blocks - 1;
     size_t blk_src_copy = blk_end_copy - 1;
@@ -639,11 +619,11 @@
     for (int i = 0; i < num_blocks; i++) {
         sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
     }
-    ASSERT_TRUE(writer.AddSequenceData(2 * num_blocks, sequence));
+    ASSERT_TRUE(writer->AddSequenceData(2 * num_blocks, sequence));
 
     size_t x = num_blocks;
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
         x -= 1;
         if (x == 0) {
             break;
@@ -655,24 +635,24 @@
     source_blk = num_blocks;
     blk_src_copy = blk_end_copy;
 
-    ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+    ASSERT_TRUE(writer->AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
 
     size_t blk_zero_copy_start = source_blk + num_blocks;
     size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
 
-    ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
+    ASSERT_TRUE(writer->AddZeroBlocks(blk_zero_copy_start, num_blocks));
 
     size_t blk_random2_replace_start = blk_zero_copy_end;
 
-    ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
+    ASSERT_TRUE(writer->AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
 
     size_t blk_xor_start = blk_random2_replace_start + num_blocks;
     size_t xor_offset = BLOCK_SZ / 2;
-    ASSERT_TRUE(writer.AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
-                                    xor_offset));
+    ASSERT_TRUE(writer->AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
+                                     xor_offset));
 
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
     std::string zero_buffer(size_, 0);
@@ -902,29 +882,36 @@
     ASSERT_TRUE(Merge());
 }
 
-void CowSnapuserdMetadataTest::CreateCowPartialFilledArea() {
+std::unique_ptr<ICowWriter> CowSnapuserdMetadataTest::CreateCowDeviceInternal() {
     std::string path = android::base::GetExecutableDirectory();
     cow_system_ = std::make_unique<TemporaryFile>(path);
 
     CowOptions options;
     options.compression = "gz";
-    CowWriter writer(options);
 
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+    unique_fd fd(cow_system_->fd);
+    cow_system_->fd = -1;
+
+    return CreateCowWriter(kDefaultCowVersion, options, std::move(fd));
+}
+
+void CowSnapuserdMetadataTest::CreateCowPartialFilledArea() {
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
     // Area 0 is completely filled with 256 exceptions
     for (int i = 0; i < 256; i++) {
-        ASSERT_TRUE(writer.AddCopy(i, 256 + i));
+        ASSERT_TRUE(writer->AddCopy(i, 256 + i));
     }
 
     // Area 1 is partially filled with 2 copy ops and 10 zero ops
-    ASSERT_TRUE(writer.AddCopy(500, 1000));
-    ASSERT_TRUE(writer.AddCopy(501, 1001));
+    ASSERT_TRUE(writer->AddCopy(500, 1000));
+    ASSERT_TRUE(writer->AddCopy(501, 1001));
 
-    ASSERT_TRUE(writer.AddZeroBlocks(300, 10));
+    ASSERT_TRUE(writer->AddZeroBlocks(300, 10));
 
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
 }
 
 void CowSnapuserdMetadataTest::ValidatePartialFilledArea() {
@@ -956,8 +943,8 @@
     unique_fd rnd_fd;
     loff_t offset = 0;
 
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
@@ -972,50 +959,44 @@
         offset += 1_MiB;
     }
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
 
     // Overlapping region. This has to be split
     // into two batch operations
-    ASSERT_TRUE(writer.AddCopy(23, 20));
-    ASSERT_TRUE(writer.AddCopy(22, 19));
-    ASSERT_TRUE(writer.AddCopy(21, 18));
-    ASSERT_TRUE(writer.AddCopy(20, 17));
-    ASSERT_TRUE(writer.AddCopy(19, 16));
-    ASSERT_TRUE(writer.AddCopy(18, 15));
+    ASSERT_TRUE(writer->AddCopy(23, 20));
+    ASSERT_TRUE(writer->AddCopy(22, 19));
+    ASSERT_TRUE(writer->AddCopy(21, 18));
+    ASSERT_TRUE(writer->AddCopy(20, 17));
+    ASSERT_TRUE(writer->AddCopy(19, 16));
+    ASSERT_TRUE(writer->AddCopy(18, 15));
 
     // Contiguous region but blocks in ascending order
     // Daemon has to ensure that these blocks are merged
     // in a batch
-    ASSERT_TRUE(writer.AddCopy(50, 75));
-    ASSERT_TRUE(writer.AddCopy(51, 76));
-    ASSERT_TRUE(writer.AddCopy(52, 77));
-    ASSERT_TRUE(writer.AddCopy(53, 78));
+    ASSERT_TRUE(writer->AddCopy(50, 75));
+    ASSERT_TRUE(writer->AddCopy(51, 76));
+    ASSERT_TRUE(writer->AddCopy(52, 77));
+    ASSERT_TRUE(writer->AddCopy(53, 78));
 
     // Dis-contiguous region
-    ASSERT_TRUE(writer.AddCopy(110, 130));
-    ASSERT_TRUE(writer.AddCopy(105, 125));
-    ASSERT_TRUE(writer.AddCopy(100, 120));
+    ASSERT_TRUE(writer->AddCopy(110, 130));
+    ASSERT_TRUE(writer->AddCopy(105, 125));
+    ASSERT_TRUE(writer->AddCopy(100, 120));
 
     // Overlap
-    ASSERT_TRUE(writer.AddCopy(25, 30));
-    ASSERT_TRUE(writer.AddCopy(30, 31));
+    ASSERT_TRUE(writer->AddCopy(25, 30));
+    ASSERT_TRUE(writer->AddCopy(30, 31));
 
     size_t source_blk = num_blocks;
 
-    ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+    ASSERT_TRUE(writer->AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
 
     size_t blk_zero_copy_start = source_blk + num_blocks;
 
-    ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
+    ASSERT_TRUE(writer->AddZeroBlocks(blk_zero_copy_start, num_blocks));
 
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
 }
 
 void CowSnapuserdMetadataTest::InitMetadata() {
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 8926030..978a7f2 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -347,7 +347,6 @@
  */
 bool Snapuserd::ReadMetadata() {
     reader_ = std::make_unique<CowReader>();
-    CowHeader header;
     CowOptions options;
     bool metadata_found = false;
     int replace_ops = 0, zero_ops = 0, copy_ops = 0;
@@ -359,11 +358,7 @@
         return false;
     }
 
-    if (!reader_->GetHeader(&header)) {
-        SNAP_LOG(ERROR) << "Failed to get header";
-        return false;
-    }
-
+    const auto& header = reader_->GetHeader();
     if (!(header.block_size == BLOCK_SZ)) {
         SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
         return false;
@@ -395,8 +390,8 @@
     // this memset will ensure that metadata read is completed.
     memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
 
-    while (!cowop_rm_iter->Done()) {
-        const CowOperation* cow_op = &cowop_rm_iter->Get();
+    while (!cowop_rm_iter->AtEnd()) {
+        const CowOperation* cow_op = cowop_rm_iter->Get();
         struct disk_exception* de =
                 reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
 
@@ -442,7 +437,7 @@
                                                  sizeof(struct disk_exception));
             memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
 
-            if (cowop_rm_iter->Done()) {
+            if (cowop_rm_iter->AtEnd()) {
                 vec_.push_back(std::move(de_ptr));
             }
         }
@@ -462,9 +457,9 @@
                     << " Number of replace/zero ops completed in this area: " << num_ops
                     << " Pending copy ops for this area: " << pending_ordered_ops;
 
-    while (!cowop_rm_iter->Done()) {
+    while (!cowop_rm_iter->AtEnd()) {
         do {
-            const CowOperation* cow_op = &cowop_rm_iter->Get();
+            const CowOperation* cow_op = cowop_rm_iter->Get();
 
             // We have two cases specific cases:
             //
@@ -513,11 +508,9 @@
             // the merge of operations are done based on the ops present
             // in the file.
             //===========================================================
-            uint64_t block_source = cow_op->source;
-            uint64_t block_offset = 0;
+            uint64_t block_source = GetCowOpSourceInfoData(cow_op);
             if (prev_id.has_value()) {
-                if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source) ||
-                    (block_offset > 0 && source_blocks.count(block_source + 1))) {
+                if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source)) {
                     break;
                 }
             }
@@ -525,13 +518,10 @@
             pending_ordered_ops -= 1;
             vec.push_back(cow_op);
             dest_blocks.insert(block_source);
-            if (block_offset > 0) {
-                dest_blocks.insert(block_source + 1);
-            }
             source_blocks.insert(cow_op->new_block);
             prev_id = cow_op->new_block;
             cowop_rm_iter->Next();
-        } while (!cowop_rm_iter->Done() && pending_ordered_ops);
+        } while (!cowop_rm_iter->AtEnd() && pending_ordered_ops);
 
         data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
         SNAP_LOG(DEBUG) << "Batch Merge copy-ops of size: " << vec.size()
@@ -574,7 +564,7 @@
                                                      sizeof(struct disk_exception));
                 memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
 
-                if (cowop_rm_iter->Done()) {
+                if (cowop_rm_iter->AtEnd()) {
                     vec_.push_back(std::move(de_ptr));
                     SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
                 }
@@ -636,11 +626,10 @@
 }
 
 bool Snapuserd::MmapMetadata() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
-    if (header.major_version >= 2 && header.buffer_size > 0) {
-        total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+    if (header.prefix.major_version >= 2 && header.buffer_size > 0) {
+        total_mapped_addr_length_ = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE;
         read_ahead_feature_ = true;
     } else {
         // mmap the first 4k page - older COW format
@@ -832,10 +821,9 @@
 }
 
 uint64_t Snapuserd::GetBufferMetadataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
-    size_t size = header.header_size + sizeof(BufferState);
+    size_t size = header.prefix.header_size + sizeof(BufferState);
     return size;
 }
 
@@ -848,37 +836,33 @@
  *
  */
 size_t Snapuserd::GetBufferMetadataSize() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     size_t metadata_bytes = (header.buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ;
     return metadata_bytes;
 }
 
 size_t Snapuserd::GetBufferDataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
-    return (header.header_size + GetBufferMetadataSize());
+    return (header.prefix.header_size + GetBufferMetadataSize());
 }
 
 /*
  * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
  */
 size_t Snapuserd::GetBufferDataSize() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     size_t size = header.buffer_size - GetBufferMetadataSize();
     return size;
 }
 
 struct BufferState* Snapuserd::GetBufferState() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     struct BufferState* ra_state =
-            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.prefix.header_size);
     return ra_state;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index 01123f8..d5fbe91 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -172,17 +172,12 @@
 }
 
 void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
-    uint64_t source_block = cow_op->source;
-    uint64_t source_offset = 0;
-    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
-        (source_offset > 0 && source_blocks_.count(source_block + 1))) {
+    uint64_t source_block = GetCowOpSourceInfoData(cow_op);
+    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block)) {
         overlap_ = true;
     }
 
     dest_blocks_.insert(source_block);
-    if (source_offset > 0) {
-        dest_blocks_.insert(source_block + 1);
-    }
     source_blocks_.insert(cow_op->new_block);
 }
 
@@ -196,7 +191,7 @@
         // Get the first block with offset
         const CowOperation* cow_op = GetRAOpIter();
         CHECK_NE(cow_op, nullptr);
-        *source_offset = cow_op->source;
+        *source_offset = GetCowOpSourceInfoData(cow_op);
         if (cow_op->type == kCowCopyOp) {
             *source_offset *= BLOCK_SZ;
         }
@@ -215,7 +210,7 @@
         while (!RAIterDone() && num_ops) {
             const CowOperation* op = GetRAOpIter();
             CHECK_NE(op, nullptr);
-            uint64_t next_offset = op->source;
+            uint64_t next_offset = GetCowOpSourceInfoData(op);
             if (op->type == kCowCopyOp) {
                 next_offset *= BLOCK_SZ;
             }
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 965af40..559dcc7 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -95,11 +95,17 @@
 // internal COW format and if the block is compressed,
 // it will be de-compressed.
 bool WorkerThread::ProcessReplaceOp(const CowOperation* cow_op) {
-    if (!reader_->ReadData(*cow_op, &bufsink_)) {
-        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (!buffer) {
+        SNAP_LOG(ERROR) << "No space in buffer sink";
         return false;
     }
-
+    ssize_t rv = reader_->ReadData(cow_op, buffer, BLOCK_SZ);
+    if (rv != BLOCK_SZ) {
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block
+                        << ", return = " << rv;
+        return false;
+    }
     return true;
 }
 
@@ -109,12 +115,13 @@
         SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
         return false;
     }
-    SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
-                    << " Source: " << cow_op->source;
-    uint64_t offset = cow_op->source;
-    if (cow_op->type == kCowCopyOp) {
-        offset *= BLOCK_SZ;
+    uint64_t offset;
+    if (!reader_->GetSourceOffset(cow_op, &offset)) {
+        SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get source offset";
+        return false;
     }
+    SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
+                    << " Source: " << offset;
     if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
         SNAP_PLOG(ERROR) << "Copy op failed. Read from backing store: " << backing_store_device_
                          << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ;
@@ -502,7 +509,7 @@
                 if (read_ahead_buffer_map.find(cow_op->new_block) == read_ahead_buffer_map.end()) {
                     SNAP_LOG(ERROR)
                             << " Block: " << cow_op->new_block << " not found in read-ahead cache"
-                            << " Source: " << cow_op->source;
+                            << " Op: " << *cow_op;
                     return -1;
                 }
                 // If this is a final block merged in the read-ahead buffer
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
index 2e4cac6..a6b6a7f 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
@@ -25,16 +25,15 @@
 namespace android {
 namespace snapshot {
 
-class BufferSink : public IByteSink {
+class BufferSink final {
   public:
     void Initialize(size_t size);
     void* GetBufPtr() { return buffer_.get(); }
     void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
     void* GetPayloadBuffer(size_t size);
-    void* GetBuffer(size_t requested, size_t* actual) override;
+    void* GetBuffer(size_t requested, size_t* actual);
     void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
     struct dm_user_header* GetHeaderPtr();
-    bool ReturnData(void*, size_t) override { return true; }
     void ResetBufferOffset() { buffer_offset_ = 0; }
     void* GetPayloadBufPtr();
 
@@ -44,12 +43,12 @@
     size_t buffer_size_;
 };
 
-class XorSink : public IByteSink {
+class XorSink final {
   public:
     void Initialize(BufferSink* sink, size_t size);
     void Reset();
-    void* GetBuffer(size_t requested, size_t* actual) override;
-    bool ReturnData(void* buffer, size_t len) override;
+    void* GetBuffer(size_t requested, size_t* actual);
+    bool ReturnData(void* buffer, size_t len);
 
   private:
     BufferSink* bufsink_;
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
index c592257..46952c4 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
@@ -41,6 +41,9 @@
  */
 static constexpr uint32_t SECTOR_SHIFT = 9;
 
+static constexpr size_t BLOCK_SZ = 4096;
+static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+
 typedef __u64 sector_t;
 typedef sector_t chunk_t;
 
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index ae20e1f..36dad33 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -114,7 +114,7 @@
             return false;
         }
         auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
-        if (!handler || !user_server_.StartHandler(handler)) {
+        if (!handler || !user_server_.StartHandler(parts[0])) {
             return false;
         }
     }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
new file mode 100644
index 0000000..bdba5c0
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -0,0 +1,374 @@
+// Copyright (C) 2023 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 "handler_manager.h"
+
+#include <sys/eventfd.h>
+
+#include <android-base/logging.h>
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+static constexpr uint8_t kMaxMergeThreads = 2;
+
+HandlerThread::HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd)
+    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
+
+void HandlerThread::FreeResources() {
+    // Each worker thread holds a reference to snapuserd.
+    // Clear them so that all the resources
+    // held by snapuserd is released
+    if (snapuserd_) {
+        snapuserd_->FreeResources();
+        snapuserd_ = nullptr;
+    }
+}
+
+SnapshotHandlerManager::SnapshotHandlerManager() {
+    monitor_merge_event_fd_.reset(eventfd(0, EFD_CLOEXEC));
+    if (monitor_merge_event_fd_ == -1) {
+        PLOG(FATAL) << "monitor_merge_event_fd_: failed to create eventfd";
+    }
+}
+
+std::shared_ptr<HandlerThread> SnapshotHandlerManager::AddHandler(
+        const std::string& misc_name, const std::string& cow_device_path,
+        const std::string& backing_device, const std::string& base_path_merge,
+        int num_worker_threads, bool use_iouring, bool perform_verification) {
+    auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
+                                                       base_path_merge, num_worker_threads,
+                                                       use_iouring, perform_verification);
+    if (!snapuserd->InitCowDevice()) {
+        LOG(ERROR) << "Failed to initialize Snapuserd";
+        return nullptr;
+    }
+
+    if (!snapuserd->InitializeWorkers()) {
+        LOG(ERROR) << "Failed to initialize workers";
+        return nullptr;
+    }
+
+    auto handler = std::make_shared<HandlerThread>(snapuserd);
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        if (FindHandler(&lock, misc_name) != dm_users_.end()) {
+            LOG(ERROR) << "Handler already exists: " << misc_name;
+            return nullptr;
+        }
+        dm_users_.push_back(handler);
+    }
+    return handler;
+}
+
+bool SnapshotHandlerManager::StartHandler(const std::string& misc_name) {
+    std::lock_guard<std::mutex> lock(lock_);
+    auto iter = FindHandler(&lock, misc_name);
+    if (iter == dm_users_.end()) {
+        LOG(ERROR) << "Could not find handler: " << misc_name;
+        return false;
+    }
+    if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Tried to re-attach control device: " << misc_name;
+        return false;
+    }
+    if (!StartHandler(*iter)) {
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotHandlerManager::StartHandler(const std::shared_ptr<HandlerThread>& handler) {
+    if (handler->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Handler already attached";
+        return false;
+    }
+
+    handler->snapuserd()->AttachControlDevice();
+
+    handler->thread() = std::thread(std::bind(&SnapshotHandlerManager::RunThread, this, handler));
+    return true;
+}
+
+bool SnapshotHandlerManager::DeleteHandler(const std::string& misc_name) {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        auto iter = FindHandler(&lock, misc_name);
+        if (iter == dm_users_.end()) {
+            // After merge is completed, we swap dm-user table with
+            // the underlying dm-linear base device. Hence, worker
+            // threads would have terminted and was removed from
+            // the list.
+            LOG(DEBUG) << "Could not find handler: " << misc_name;
+            return true;
+        }
+
+        if (!(*iter)->ThreadTerminated()) {
+            (*iter)->snapuserd()->NotifyIOTerminated();
+        }
+    }
+    if (!RemoveAndJoinHandler(misc_name)) {
+        return false;
+    }
+    return true;
+}
+
+void SnapshotHandlerManager::RunThread(std::shared_ptr<HandlerThread> handler) {
+    LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
+
+    if (!handler->snapuserd()->Start()) {
+        LOG(ERROR) << " Failed to launch all worker threads";
+    }
+
+    handler->snapuserd()->CloseFds();
+    bool merge_completed = handler->snapuserd()->CheckMergeCompletionStatus();
+    handler->snapuserd()->UnmapBufferRegion();
+
+    auto misc_name = handler->misc_name();
+    LOG(INFO) << "Handler thread about to exit: " << misc_name;
+
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        if (merge_completed) {
+            num_partitions_merge_complete_ += 1;
+            active_merge_threads_ -= 1;
+            WakeupMonitorMergeThread();
+        }
+        handler->SetThreadTerminated();
+        auto iter = FindHandler(&lock, handler->misc_name());
+        if (iter == dm_users_.end()) {
+            // RemoveAndJoinHandler() already removed us from the list, and is
+            // now waiting on a join(), so just return. Additionally, release
+            // all the resources held by snapuserd object which are shared
+            // by worker threads. This should be done when the last reference
+            // of "handler" is released; but we will explicitly release here
+            // to make sure snapuserd object is freed as it is the biggest
+            // consumer of memory in the daemon.
+            handler->FreeResources();
+            LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
+            return;
+        }
+
+        LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name;
+
+        if (handler->snapuserd()->IsAttached()) {
+            handler->thread().detach();
+        }
+
+        // Important: free resources within the lock. This ensures that if
+        // WaitForDelete() is called, the handler is either in the list, or
+        // it's not and its resources are guaranteed to be freed.
+        handler->FreeResources();
+        dm_users_.erase(iter);
+    }
+}
+
+bool SnapshotHandlerManager::InitiateMerge(const std::string& misc_name) {
+    std::lock_guard<std::mutex> lock(lock_);
+    auto iter = FindHandler(&lock, misc_name);
+    if (iter == dm_users_.end()) {
+        LOG(ERROR) << "Could not find handler: " << misc_name;
+        return false;
+    }
+
+    return StartMerge(&lock, *iter);
+}
+
+bool SnapshotHandlerManager::StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
+                                        const std::shared_ptr<HandlerThread>& handler) {
+    CHECK(proof_of_lock);
+
+    if (!handler->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
+        return false;
+    }
+
+    handler->snapuserd()->MonitorMerge();
+
+    if (!is_merge_monitor_started_) {
+        std::thread(&SnapshotHandlerManager::MonitorMerge, this).detach();
+        is_merge_monitor_started_ = true;
+    }
+
+    merge_handlers_.push(handler);
+    WakeupMonitorMergeThread();
+    return true;
+}
+
+void SnapshotHandlerManager::WakeupMonitorMergeThread() {
+    uint64_t notify = 1;
+    ssize_t rc = TEMP_FAILURE_RETRY(write(monitor_merge_event_fd_.get(), &notify, sizeof(notify)));
+    if (rc < 0) {
+        PLOG(FATAL) << "failed to notify monitor merge thread";
+    }
+}
+
+void SnapshotHandlerManager::MonitorMerge() {
+    while (!stop_monitor_merge_thread_) {
+        uint64_t testVal;
+        ssize_t ret =
+                TEMP_FAILURE_RETRY(read(monitor_merge_event_fd_.get(), &testVal, sizeof(testVal)));
+        if (ret == -1) {
+            PLOG(FATAL) << "Failed to read from eventfd";
+        } else if (ret == 0) {
+            LOG(FATAL) << "Hit EOF on eventfd";
+        }
+
+        LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_;
+        {
+            std::lock_guard<std::mutex> lock(lock_);
+            while (active_merge_threads_ < kMaxMergeThreads && merge_handlers_.size() > 0) {
+                auto handler = merge_handlers_.front();
+                merge_handlers_.pop();
+
+                if (!handler->snapuserd()) {
+                    LOG(INFO) << "MonitorMerge: skipping deleted handler: " << handler->misc_name();
+                    continue;
+                }
+
+                LOG(INFO) << "Starting merge for partition: "
+                          << handler->snapuserd()->GetMiscName();
+                handler->snapuserd()->InitiateMerge();
+                active_merge_threads_ += 1;
+            }
+        }
+    }
+
+    LOG(INFO) << "Exiting MonitorMerge: size: " << merge_handlers_.size();
+}
+
+std::string SnapshotHandlerManager::GetMergeStatus(const std::string& misc_name) {
+    std::lock_guard<std::mutex> lock(lock_);
+    auto iter = FindHandler(&lock, misc_name);
+    if (iter == dm_users_.end()) {
+        LOG(ERROR) << "Could not find handler: " << misc_name;
+        return {};
+    }
+
+    return (*iter)->snapuserd()->GetMergeStatus();
+}
+
+double SnapshotHandlerManager::GetMergePercentage() {
+    std::lock_guard<std::mutex> lock(lock_);
+
+    double percentage = 0.0;
+    int n = 0;
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        auto& th = (*iter)->thread();
+        if (th.joinable()) {
+            // Merge percentage by individual partitions wherein merge is still
+            // in-progress
+            percentage += (*iter)->snapuserd()->GetMergePercentage();
+            n += 1;
+        }
+    }
+
+    // Calculate final merge including those partitions where merge was already
+    // completed - num_partitions_merge_complete_ will track them when each
+    // thread exists in RunThread.
+    int total_partitions = n + num_partitions_merge_complete_;
+
+    if (total_partitions) {
+        percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
+    }
+
+    LOG(DEBUG) << "Merge %: " << percentage
+               << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
+               << " total_partitions: " << total_partitions << " n: " << n;
+    return percentage;
+}
+
+bool SnapshotHandlerManager::GetVerificationStatus() {
+    std::lock_guard<std::mutex> lock(lock_);
+
+    bool status = true;
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        auto& th = (*iter)->thread();
+        if (th.joinable() && status) {
+            status = (*iter)->snapuserd()->CheckPartitionVerification() && status;
+        } else {
+            // return immediately if there is a failure
+            return false;
+        }
+    }
+
+    return status;
+}
+
+bool SnapshotHandlerManager::RemoveAndJoinHandler(const std::string& misc_name) {
+    std::shared_ptr<HandlerThread> handler;
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+
+        auto iter = FindHandler(&lock, misc_name);
+        if (iter == dm_users_.end()) {
+            // Client already deleted.
+            return true;
+        }
+        handler = std::move(*iter);
+        dm_users_.erase(iter);
+    }
+
+    auto& th = handler->thread();
+    if (th.joinable()) {
+        th.join();
+    }
+    return true;
+}
+
+void SnapshotHandlerManager::TerminateMergeThreads() {
+    std::lock_guard<std::mutex> guard(lock_);
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        if (!(*iter)->ThreadTerminated()) {
+            (*iter)->snapuserd()->NotifyIOTerminated();
+        }
+    }
+}
+
+void SnapshotHandlerManager::JoinAllThreads() {
+    // Acquire the thread list within the lock.
+    std::vector<std::shared_ptr<HandlerThread>> dm_users;
+    {
+        std::lock_guard<std::mutex> guard(lock_);
+        dm_users = std::move(dm_users_);
+    }
+
+    for (auto& client : dm_users) {
+        auto& th = client->thread();
+
+        if (th.joinable()) th.join();
+    }
+
+    stop_monitor_merge_thread_ = true;
+    WakeupMonitorMergeThread();
+}
+
+auto SnapshotHandlerManager::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                         const std::string& misc_name) -> HandlerList::iterator {
+    CHECK(proof_of_lock);
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        if ((*iter)->misc_name() == misc_name) {
+            return iter;
+        }
+    }
+    return dm_users_.end();
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
new file mode 100644
index 0000000..b7ddac1
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
@@ -0,0 +1,131 @@
+// Copyright (C) 2023 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 <queue>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace snapshot {
+
+class SnapshotHandler;
+
+class HandlerThread {
+  public:
+    explicit HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd);
+
+    void FreeResources();
+    const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
+    std::thread& thread() { return thread_; }
+
+    const std::string& misc_name() const { return misc_name_; }
+    bool ThreadTerminated() { return thread_terminated_; }
+    void SetThreadTerminated() { thread_terminated_ = true; }
+
+  private:
+    std::thread thread_;
+    std::shared_ptr<SnapshotHandler> snapuserd_;
+    std::string misc_name_;
+    bool thread_terminated_ = false;
+};
+
+class ISnapshotHandlerManager {
+  public:
+    virtual ~ISnapshotHandlerManager() {}
+
+    // Add a new snapshot handler but do not start serving requests yet.
+    virtual std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
+                                                      const std::string& cow_device_path,
+                                                      const std::string& backing_device,
+                                                      const std::string& base_path_merge,
+                                                      int num_worker_threads, bool use_iouring,
+                                                      bool perform_verification) = 0;
+
+    // Start serving requests on a snapshot handler.
+    virtual bool StartHandler(const std::string& misc_name) = 0;
+
+    // Stop serving requests on a snapshot handler and remove it.
+    virtual bool DeleteHandler(const std::string& misc_name) = 0;
+
+    // Begin merging blocks on the given snapshot handler.
+    virtual bool InitiateMerge(const std::string& misc_name) = 0;
+
+    // Return a string containing a status code indicating the merge status
+    // on the handler. Returns empty on error.
+    virtual std::string GetMergeStatus(const std::string& misc_name) = 0;
+
+    // Wait until all handlers have terminated.
+    virtual void JoinAllThreads() = 0;
+
+    // Stop any in-progress merge threads.
+    virtual void TerminateMergeThreads() = 0;
+
+    // Returns the merge progress across all merging snapshot handlers.
+    virtual double GetMergePercentage() = 0;
+
+    // Returns whether all snapshots have verified.
+    virtual bool GetVerificationStatus() = 0;
+};
+
+class SnapshotHandlerManager final : public ISnapshotHandlerManager {
+  public:
+    SnapshotHandlerManager();
+    std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
+                                              const std::string& cow_device_path,
+                                              const std::string& backing_device,
+                                              const std::string& base_path_merge,
+                                              int num_worker_threads, bool use_iouring,
+                                              bool perform_verification) override;
+    bool StartHandler(const std::string& misc_name) override;
+    bool DeleteHandler(const std::string& misc_name) override;
+    bool InitiateMerge(const std::string& misc_name) override;
+    std::string GetMergeStatus(const std::string& misc_name) override;
+    void JoinAllThreads() override;
+    void TerminateMergeThreads() override;
+    double GetMergePercentage() override;
+    bool GetVerificationStatus() override;
+
+  private:
+    bool StartHandler(const std::shared_ptr<HandlerThread>& handler);
+    void RunThread(std::shared_ptr<HandlerThread> handler);
+    bool StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
+                    const std::shared_ptr<HandlerThread>& handler);
+    void MonitorMerge();
+    void WakeupMonitorMergeThread();
+    bool RemoveAndJoinHandler(const std::string& misc_name);
+
+    // Find a HandlerThread within a lock.
+    using HandlerList = std::vector<std::shared_ptr<HandlerThread>>;
+    HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                      const std::string& misc_name);
+
+    std::mutex lock_;
+    HandlerList dm_users_;
+
+    bool is_merge_monitor_started_ = false;
+    bool stop_monitor_merge_thread_ = false;
+    int active_merge_threads_ = 0;
+    int num_partitions_merge_complete_ = 0;
+    std::queue<std::shared_ptr<HandlerThread>> merge_handlers_;
+    android::base::unique_fd monitor_merge_event_fd_;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 2c201ff..8e1212b 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -98,8 +98,7 @@
         }
     } else {
         reader_->UpdateMergeOpsCompleted(num_merge_ops);
-        CowHeader header;
-        reader_->GetHeader(&header);
+        const auto& header = reader_->GetHeader();
 
         if (lseek(cow_fd_.get(), 0, SEEK_SET) < 0) {
             SNAP_PLOG(ERROR) << "lseek failed";
@@ -154,7 +153,6 @@
 
 bool SnapshotHandler::ReadMetadata() {
     reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE, true);
-    CowHeader header;
     CowOptions options;
 
     SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
@@ -164,11 +162,7 @@
         return false;
     }
 
-    if (!reader_->GetHeader(&header)) {
-        SNAP_LOG(ERROR) << "Failed to get header";
-        return false;
-    }
-
+    const auto& header = reader_->GetHeader();
     if (!(header.block_size == BLOCK_SZ)) {
         SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
         return false;
@@ -191,8 +185,8 @@
 
     size_t copy_ops = 0, replace_ops = 0, zero_ops = 0, xor_ops = 0;
 
-    while (!cowop_iter->Done()) {
-        const CowOperation* cow_op = &cowop_iter->Get();
+    while (!cowop_iter->AtEnd()) {
+        const CowOperation* cow_op = cowop_iter->Get();
 
         if (cow_op->type == kCowCopyOp) {
             copy_ops += 1;
@@ -244,12 +238,11 @@
 }
 
 bool SnapshotHandler::MmapMetadata() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
-    total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+    total_mapped_addr_length_ = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE;
 
-    if (header.major_version >= 2 && header.buffer_size > 0) {
+    if (header.prefix.major_version >= 2 && header.buffer_size > 0) {
         scratch_space_ = true;
     }
 
@@ -367,10 +360,9 @@
 }
 
 uint64_t SnapshotHandler::GetBufferMetadataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
-    return (header.header_size + sizeof(BufferState));
+    return (header.prefix.header_size + sizeof(BufferState));
 }
 
 /*
@@ -383,8 +375,7 @@
  *
  */
 size_t SnapshotHandler::GetBufferMetadataSize() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
     size_t buffer_size = header.buffer_size;
 
     // If there is no scratch space, then just use the
@@ -397,18 +388,16 @@
 }
 
 size_t SnapshotHandler::GetBufferDataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
-    return (header.header_size + GetBufferMetadataSize());
+    return (header.prefix.header_size + GetBufferMetadataSize());
 }
 
 /*
  * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
  */
 size_t SnapshotHandler::GetBufferDataSize() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
     size_t buffer_size = header.buffer_size;
 
     // If there is no scratch space, then just use the
@@ -421,11 +410,10 @@
 }
 
 struct BufferState* SnapshotHandler::GetBufferState() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     struct BufferState* ra_state =
-            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.prefix.header_size);
     return ra_state;
 }
 
@@ -433,11 +421,6 @@
     struct utsname uts;
     unsigned int major, minor;
 
-    if (android::base::GetBoolProperty("snapuserd.test.io_uring.force_disable", false)) {
-        SNAP_LOG(INFO) << "io_uring disabled for testing";
-        return false;
-    }
-
     if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
         SNAP_LOG(ERROR) << "Could not parse the kernel version from uname. "
                         << " io_uring not supported";
@@ -465,5 +448,9 @@
     return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
 }
 
+bool SnapshotHandler::CheckPartitionVerification() {
+    return update_verify_->CheckPartitionVerification();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index 777aa07..c984a61 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -46,6 +46,8 @@
 #include <snapuserd/snapuserd_buffer.h>
 #include <snapuserd/snapuserd_kernel.h>
 #include <storage_literals/storage_literals.h>
+#include "snapuserd_readahead.h"
+#include "snapuserd_verify.h"
 
 namespace android {
 namespace snapshot {
@@ -96,112 +98,6 @@
         : merge_state_(state), num_ios_in_progress(n_ios) {}
 };
 
-class ReadAhead {
-  public:
-    ReadAhead(const std::string& cow_device, const std::string& backing_device,
-              const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd);
-    bool RunThread();
-
-  private:
-    void InitializeRAIter();
-    bool RAIterDone();
-    void RAIterNext();
-    void RAResetIter(uint64_t num_blocks);
-    const CowOperation* GetRAOpIter();
-
-    void InitializeBuffer();
-    bool InitReader();
-    bool InitializeFds();
-
-    void CloseFds() { backing_store_fd_ = {}; }
-
-    bool ReadAheadIOStart();
-    int PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops,
-                             std::vector<uint64_t>& blocks,
-                             std::vector<const CowOperation*>& xor_op_vec);
-    bool ReconstructDataFromCow();
-    void CheckOverlap(const CowOperation* cow_op);
-
-    bool ReadAheadAsyncIO();
-    bool ReapIoCompletions(int pending_ios_to_complete);
-    bool ReadXorData(size_t block_index, size_t xor_op_index,
-                     std::vector<const CowOperation*>& xor_op_vec);
-    void ProcessXorData(size_t& block_xor_index, size_t& xor_index,
-                        std::vector<const CowOperation*>& xor_op_vec, void* buffer,
-                        loff_t& buffer_offset);
-    void UpdateScratchMetadata();
-
-    bool ReadAheadSyncIO();
-    bool InitializeIouring();
-    void FinalizeIouring();
-
-    void* read_ahead_buffer_;
-    void* metadata_buffer_;
-
-    std::unique_ptr<ICowOpIter> cowop_iter_;
-
-    std::string cow_device_;
-    std::string backing_store_device_;
-    std::string misc_name_;
-
-    unique_fd cow_fd_;
-    unique_fd backing_store_fd_;
-
-    std::shared_ptr<SnapshotHandler> snapuserd_;
-    std::unique_ptr<CowReader> reader_;
-
-    std::unordered_set<uint64_t> dest_blocks_;
-    std::unordered_set<uint64_t> source_blocks_;
-    bool overlap_;
-    std::vector<uint64_t> blocks_;
-    int total_blocks_merged_ = 0;
-    std::unique_ptr<uint8_t[]> ra_temp_buffer_;
-    std::unique_ptr<uint8_t[]> ra_temp_meta_buffer_;
-    BufferSink bufsink_;
-
-    uint64_t total_ra_blocks_completed_ = 0;
-    bool read_ahead_async_ = false;
-    // Queue depth of 8 seems optimal. We don't want
-    // to have a huge depth as it may put more memory pressure
-    // on the kernel worker threads given that we use
-    // IOSQE_ASYNC flag - ASYNC flags can potentially
-    // result in EINTR; Since we don't restart
-    // syscalls and fallback to synchronous I/O, we
-    // don't want huge queue depth
-    int queue_depth_ = 8;
-    std::unique_ptr<struct io_uring> ring_;
-};
-
-class UpdateVerify {
-  public:
-    UpdateVerify(const std::string& misc_name);
-    void VerifyUpdatePartition();
-    bool CheckPartitionVerification();
-
-  private:
-    enum class UpdateVerifyState {
-        VERIFY_UNKNOWN,
-        VERIFY_FAILED,
-        VERIFY_SUCCESS,
-    };
-
-    std::string misc_name_;
-    UpdateVerifyState state_;
-    std::mutex m_lock_;
-    std::condition_variable m_cv_;
-
-    int kMinThreadsToVerify = 1;
-    int kMaxThreadsToVerify = 4;
-    uint64_t kThresholdSize = 512_MiB;
-    uint64_t kBlockSizeVerify = 1_MiB;
-
-    bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
-    void UpdatePartitionVerificationState(UpdateVerifyState state);
-    bool VerifyPartition(const std::string& partition_name, const std::string& dm_block_device);
-    bool VerifyBlocks(const std::string& partition_name, const std::string& dm_block_device,
-                      off_t offset, int skip_blocks, uint64_t dev_sz);
-};
-
 class Worker {
   public:
     Worker(const std::string& cow_device, const std::string& backing_device,
@@ -223,8 +119,7 @@
     }
 
     // Functions interacting with dm-user
-    bool ReadDmUserHeader();
-    bool WriteDmUserPayload(size_t size, bool header_response);
+    bool WriteDmUserPayload(size_t size);
     bool DmuserReadRequest();
 
     // IO Path
@@ -234,11 +129,11 @@
     bool ReadDataFromBaseDevice(sector_t sector, size_t read_size);
     bool ReadFromSourceDevice(const CowOperation* cow_op);
 
-    bool ReadAlignedSector(sector_t sector, size_t sz, bool header_response);
+    bool ReadAlignedSector(sector_t sector, size_t sz);
     bool ReadUnalignedSector(sector_t sector, size_t size);
     int ReadUnalignedSector(sector_t sector, size_t size,
                             std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
-    bool RespondIOError(bool header_response);
+    void RespondIOError();
 
     // Processing COW operations
     bool ProcessCowOp(const CowOperation* cow_op);
@@ -280,6 +175,7 @@
     unique_fd backing_store_fd_;
     unique_fd base_path_merge_fd_;
     unique_fd ctrl_fd_;
+    bool header_response_ = false;
 
     std::unique_ptr<ICowOpIter> cowop_iter_;
     size_t ra_block_index_ = 0;
@@ -383,7 +279,7 @@
     MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer);
 
     bool IsIouringSupported();
-    bool CheckPartitionVerification() { return update_verify_->CheckPartitionVerification(); }
+    bool CheckPartitionVerification();
 
   private:
     bool ReadMetadata();
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
index 0d0f711..2b9d14e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -76,11 +76,15 @@
 // internal COW format and if the block is compressed,
 // it will be de-compressed.
 bool Worker::ProcessReplaceOp(const CowOperation* cow_op) {
-    if (!reader_->ReadData(*cow_op, &bufsink_)) {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (!buffer) {
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed to allocate buffer";
+        return false;
+    }
+    if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
         SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
         return false;
     }
-
     return true;
 }
 
@@ -90,12 +94,13 @@
         SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
         return false;
     }
-    SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
-                    << " Source: " << cow_op->source;
-    uint64_t offset = cow_op->source;
-    if (cow_op->type == kCowCopyOp) {
-        offset *= BLOCK_SZ;
+    uint64_t offset;
+    if (!reader_->GetSourceOffset(cow_op, &offset)) {
+        SNAP_LOG(ERROR) << "ReadFromSourceDevice: Failed to get source offset";
+        return false;
     }
+    SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
+                    << " Op: " << *cow_op;
     if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
         std::string op;
         if (cow_op->type == kCowCopyOp)
@@ -125,12 +130,26 @@
     if (!ReadFromSourceDevice(cow_op)) {
         return false;
     }
+
     xorsink_.Reset();
-    if (!reader_->ReadData(*cow_op, &xorsink_)) {
-        SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block;
+
+    size_t actual = 0;
+    void* buffer = xorsink_.GetBuffer(BLOCK_SZ, &actual);
+    if (!buffer || actual < BLOCK_SZ) {
+        SNAP_LOG(ERROR) << "ProcessXorOp failed to get buffer of " << BLOCK_SZ << " size, got "
+                        << actual;
         return false;
     }
-
+    ssize_t size = reader_->ReadData(cow_op, buffer, BLOCK_SZ);
+    if (size != BLOCK_SZ) {
+        SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block
+                        << ", return value: " << size;
+        return false;
+    }
+    if (!xorsink_.ReturnData(buffer, size)) {
+        SNAP_LOG(ERROR) << "ProcessXorOp failed to return data";
+        return false;
+    }
     return true;
 }
 
@@ -271,26 +290,11 @@
     return true;
 }
 
-// Read Header from dm-user misc device. This gives
-// us the sector number for which IO is issued by dm-snapshot device
-bool Worker::ReadDmUserHeader() {
-    if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
-        if (errno != ENOTBLK) {
-            SNAP_PLOG(ERROR) << "Control-read failed";
-        }
-
-        SNAP_PLOG(DEBUG) << "ReadDmUserHeader failed....";
-        return false;
-    }
-
-    return true;
-}
-
 // Send the payload/data back to dm-user misc device.
-bool Worker::WriteDmUserPayload(size_t size, bool header_response) {
+bool Worker::WriteDmUserPayload(size_t size) {
     size_t payload_size = size;
     void* buf = bufsink_.GetPayloadBufPtr();
-    if (header_response) {
+    if (header_response_) {
         payload_size += sizeof(struct dm_user_header);
         buf = bufsink_.GetBufPtr();
     }
@@ -300,6 +304,9 @@
         return false;
     }
 
+    // After the first header is sent in response to a request, we cannot
+    // send any additional headers.
+    header_response_ = false;
     return true;
 }
 
@@ -322,20 +329,16 @@
     return true;
 }
 
-bool Worker::ReadAlignedSector(sector_t sector, size_t sz, bool header_response) {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+bool Worker::ReadAlignedSector(sector_t sector, size_t sz) {
     size_t remaining_size = sz;
     std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
-    bool io_error = false;
     int ret = 0;
 
     do {
         // Process 1MB payload at a time
         size_t read_size = std::min(PAYLOAD_BUFFER_SZ, remaining_size);
 
-        header->type = DM_USER_RESP_SUCCESS;
         size_t total_bytes_read = 0;
-        io_error = false;
         bufsink_.ResetBufferOffset();
 
         while (read_size) {
@@ -353,7 +356,7 @@
                 // device.
                 if (!ReadDataFromBaseDevice(sector, size)) {
                     SNAP_LOG(ERROR) << "ReadDataFromBaseDevice failed";
-                    header->type = DM_USER_RESP_ERROR;
+                    return false;
                 }
 
                 ret = size;
@@ -362,20 +365,10 @@
                 // process it.
                 if (!ProcessCowOp(it->second)) {
                     SNAP_LOG(ERROR) << "ProcessCowOp failed";
-                    header->type = DM_USER_RESP_ERROR;
-                }
-
-                ret = BLOCK_SZ;
-            }
-
-            // Just return the header if it is an error
-            if (header->type == DM_USER_RESP_ERROR) {
-                if (!RespondIOError(header_response)) {
                     return false;
                 }
 
-                io_error = true;
-                break;
+                ret = BLOCK_SZ;
             }
 
             read_size -= ret;
@@ -384,18 +377,14 @@
             bufsink_.UpdateBufferOffset(ret);
         }
 
-        if (!io_error) {
-            if (!WriteDmUserPayload(total_bytes_read, header_response)) {
-                return false;
-            }
-
-            SNAP_LOG(DEBUG) << "WriteDmUserPayload success total_bytes_read: " << total_bytes_read
-                            << " header-response: " << header_response
-                            << " remaining_size: " << remaining_size;
-            header_response = false;
-            remaining_size -= total_bytes_read;
+        if (!WriteDmUserPayload(total_bytes_read)) {
+            return false;
         }
-    } while (remaining_size > 0 && !io_error);
+
+        SNAP_LOG(DEBUG) << "WriteDmUserPayload success total_bytes_read: " << total_bytes_read
+                        << " remaining_size: " << remaining_size;
+        remaining_size -= total_bytes_read;
+    } while (remaining_size > 0);
 
     return true;
 }
@@ -436,8 +425,6 @@
 }
 
 bool Worker::ReadUnalignedSector(sector_t sector, size_t size) {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
-    header->type = DM_USER_RESP_SUCCESS;
     bufsink_.ResetBufferOffset();
     std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
 
@@ -465,7 +452,6 @@
     // to any COW ops. In that case, we just need to read from the base
     // device.
     bool merge_complete = false;
-    bool header_response = true;
     if (it == chunk_vec.end()) {
         if (chunk_vec.size() > 0) {
             // I/O request beyond the last mapped sector
@@ -484,7 +470,7 @@
             --it;
         }
     } else {
-        return ReadAlignedSector(sector, size, header_response);
+        return ReadAlignedSector(sector, size);
     }
 
     loff_t requested_offset = sector << SECTOR_SHIFT;
@@ -518,7 +504,7 @@
         if (ret < 0) {
             SNAP_LOG(ERROR) << "ReadUnalignedSector failed for sector: " << sector
                             << " size: " << size << " it->sector: " << it->first;
-            return RespondIOError(header_response);
+            return false;
         }
 
         remaining_size -= ret;
@@ -526,14 +512,13 @@
         sector += (ret >> SECTOR_SHIFT);
 
         // Send the data back
-        if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+        if (!WriteDmUserPayload(total_bytes_read)) {
             return false;
         }
 
-        header_response = false;
         // If we still have pending data to be processed, this will be aligned I/O
         if (remaining_size) {
-            return ReadAlignedSector(sector, remaining_size, header_response);
+            return ReadAlignedSector(sector, remaining_size);
         }
     } else {
         // This is all about handling I/O request to be routed to base device
@@ -547,21 +532,21 @@
         CHECK(diff_size <= BLOCK_SZ);
         if (remaining_size < diff_size) {
             if (!ReadDataFromBaseDevice(sector, remaining_size)) {
-                return RespondIOError(header_response);
+                return false;
             }
             total_bytes_read += remaining_size;
 
-            if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+            if (!WriteDmUserPayload(total_bytes_read)) {
                 return false;
             }
         } else {
             if (!ReadDataFromBaseDevice(sector, diff_size)) {
-                return RespondIOError(header_response);
+                return false;
             }
 
             total_bytes_read += diff_size;
 
-            if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+            if (!WriteDmUserPayload(total_bytes_read)) {
                 return false;
             }
 
@@ -569,17 +554,16 @@
             size_t num_sectors_read = (diff_size >> SECTOR_SHIFT);
             sector += num_sectors_read;
             CHECK(IsBlockAligned(sector << SECTOR_SHIFT));
-            header_response = false;
 
             // If we still have pending data to be processed, this will be aligned I/O
-            return ReadAlignedSector(sector, remaining_size, header_response);
+            return ReadAlignedSector(sector, remaining_size);
         }
     }
 
     return true;
 }
 
-bool Worker::RespondIOError(bool header_response) {
+void Worker::RespondIOError() {
     struct dm_user_header* header = bufsink_.GetHeaderPtr();
     header->type = DM_USER_RESP_ERROR;
     // This is an issue with the dm-user interface. There
@@ -591,15 +575,9 @@
     // this back to dm-user.
     //
     // TODO: Fix the interface
-    CHECK(header_response);
+    CHECK(header_response_);
 
-    if (!WriteDmUserPayload(0, header_response)) {
-        return false;
-    }
-
-    // There is no need to process further as we have already seen
-    // an I/O error
-    return true;
+    WriteDmUserPayload(0);
 }
 
 bool Worker::DmuserReadRequest() {
@@ -610,13 +588,19 @@
         return ReadUnalignedSector(header->sector, header->len);
     }
 
-    return ReadAlignedSector(header->sector, header->len, true);
+    return ReadAlignedSector(header->sector, header->len);
 }
 
 bool Worker::ProcessIORequest() {
+    // Read Header from dm-user misc device. This gives
+    // us the sector number for which IO is issued by dm-snapshot device
     struct dm_user_header* header = bufsink_.GetHeaderPtr();
+    if (!android::base::ReadFully(ctrl_fd_, header, sizeof(*header))) {
+        if (errno != ENOTBLK) {
+            SNAP_PLOG(ERROR) << "Control-read failed";
+        }
 
-    if (!ReadDmUserHeader()) {
+        SNAP_PLOG(DEBUG) << "ReadDmUserHeader failed....";
         return false;
     }
 
@@ -626,24 +610,34 @@
     SNAP_LOG(DEBUG) << "Daemon: msg->type: " << std::dec << header->type;
     SNAP_LOG(DEBUG) << "Daemon: msg->flags: " << std::dec << header->flags;
 
-    switch (header->type) {
-        case DM_USER_REQ_MAP_READ: {
-            if (!DmuserReadRequest()) {
-                return false;
-            }
-            break;
-        }
+    // Use the same header buffer as the response header.
+    int request_type = header->type;
+    header->type = DM_USER_RESP_SUCCESS;
+    header_response_ = true;
 
-        case DM_USER_REQ_MAP_WRITE: {
+    bool ok;
+    switch (request_type) {
+        case DM_USER_REQ_MAP_READ:
+            ok = DmuserReadRequest();
+            break;
+
+        case DM_USER_REQ_MAP_WRITE:
             // TODO: We should not get any write request
             // to dm-user as we mount all partitions
             // as read-only. Need to verify how are TRIM commands
             // handled during mount.
-            return false;
-        }
+            ok = false;
+            break;
+
+        default:
+            ok = false;
+            break;
     }
 
-    return true;
+    if (!ok && header->type != DM_USER_RESP_ERROR) {
+        RespondIOError();
+    }
+    return ok;
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index d57f434..ce95b76 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -30,8 +30,8 @@
     bool checkOrderedOp = (replace_zero_vec == nullptr);
 
     do {
-        if (!cowop_iter_->Done() && num_ops) {
-            const CowOperation* cow_op = &cowop_iter_->Get();
+        if (!cowop_iter_->AtEnd() && num_ops) {
+            const CowOperation* cow_op = cowop_iter_->Get();
             if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
                 break;
             }
@@ -45,8 +45,8 @@
             num_ops -= 1;
             nr_consecutive = 1;
 
-            while (!cowop_iter_->Done() && num_ops) {
-                const CowOperation* op = &cowop_iter_->Get();
+            while (!cowop_iter_->AtEnd() && num_ops) {
+                const CowOperation* op = cowop_iter_->Get();
                 if (checkOrderedOp && !IsOrderedOp(*op)) {
                     break;
                 }
@@ -85,7 +85,7 @@
 
     SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
 
-    while (!cowop_iter_->Done()) {
+    while (!cowop_iter_->AtEnd()) {
         int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
         std::vector<const CowOperation*> replace_zero_vec;
         uint64_t source_offset;
@@ -93,7 +93,7 @@
         int linear_blocks = PrepareMerge(&source_offset, &num_ops, &replace_zero_vec);
         if (linear_blocks == 0) {
             // Merge complete
-            CHECK(cowop_iter_->Done());
+            CHECK(cowop_iter_->AtEnd());
             break;
         }
 
@@ -180,8 +180,8 @@
 
     SNAP_LOG(INFO) << "MergeOrderedOpsAsync started....";
 
-    while (!cowop_iter_->Done()) {
-        const CowOperation* cow_op = &cowop_iter_->Get();
+    while (!cowop_iter_->AtEnd()) {
+        const CowOperation* cow_op = cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -361,8 +361,8 @@
 
     SNAP_LOG(INFO) << "MergeOrderedOps started....";
 
-    while (!cowop_iter_->Done()) {
-        const CowOperation* cow_op = &cowop_iter_->Get();
+    while (!cowop_iter_->AtEnd()) {
+        const CowOperation* cow_op = cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -443,7 +443,7 @@
     if (!MergeOrderedOpsAsync()) {
         SNAP_LOG(ERROR) << "MergeOrderedOpsAsync failed - Falling back to synchronous I/O";
         // Reset the iter so that we retry the merge
-        while (blocks_merged_in_group_ && !cowop_iter_->RDone()) {
+        while (blocks_merged_in_group_ && !cowop_iter_->AtBegin()) {
             cowop_iter_->Prev();
             blocks_merged_in_group_ -= 1;
         }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index fbe57d2..399f7b8 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "snapuserd_readahead.h"
+
 #include "snapuserd_core.h"
 
 namespace android {
@@ -32,14 +34,17 @@
 }
 
 void ReadAhead::CheckOverlap(const CowOperation* cow_op) {
-    uint64_t source_block = cow_op->source;
-    uint64_t source_offset = 0;
-    if (cow_op->type == kCowXorOp) {
-        source_block /= BLOCK_SZ;
-        source_offset = cow_op->source % BLOCK_SZ;
+    uint64_t source_offset;
+    if (!reader_->GetSourceOffset(cow_op, &source_offset)) {
+        SNAP_LOG(ERROR) << "ReadAhead operation has no source offset: " << *cow_op;
+        return;
     }
+
+    uint64_t source_block = GetBlockFromOffset(header_, source_offset);
+    bool misaligned = (GetBlockRelativeOffset(header_, source_offset) != 0);
+
     if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
-        (source_offset > 0 && source_blocks_.count(source_block + 1))) {
+        (misaligned && source_blocks_.count(source_block + 1))) {
         overlap_ = true;
     }
 
@@ -64,11 +69,12 @@
 
     // Get the first block with offset
     const CowOperation* cow_op = GetRAOpIter();
-    *source_offset = cow_op->source;
 
-    if (cow_op->type == kCowCopyOp) {
-        *source_offset *= BLOCK_SZ;
-    } else if (cow_op->type == kCowXorOp) {
+    if (!reader_->GetSourceOffset(cow_op, source_offset)) {
+        SNAP_LOG(ERROR) << "PrepareNextReadAhead operation has no source offset: " << *cow_op;
+        return nr_consecutive;
+    }
+    if (cow_op->type == kCowXorOp) {
         xor_op_vec.push_back(cow_op);
     }
 
@@ -86,10 +92,10 @@
      */
     while (!RAIterDone() && num_ops) {
         const CowOperation* op = GetRAOpIter();
-        uint64_t next_offset = op->source;
-
-        if (cow_op->type == kCowCopyOp) {
-            next_offset *= BLOCK_SZ;
+        uint64_t next_offset;
+        if (!reader_->GetSourceOffset(op, &next_offset)) {
+            SNAP_LOG(ERROR) << "PrepareNextReadAhead operation has no source offset: " << *cow_op;
+            break;
         }
 
         // Check for consecutive blocks
@@ -114,6 +120,24 @@
     return nr_consecutive;
 }
 
+class [[nodiscard]] AutoNotifyReadAheadFailed {
+  public:
+    AutoNotifyReadAheadFailed(std::shared_ptr<SnapshotHandler> snapuserd) : snapuserd_(snapuserd) {}
+
+    ~AutoNotifyReadAheadFailed() {
+        if (cancelled_) {
+            return;
+        }
+        snapuserd_->ReadAheadIOFailed();
+    }
+
+    void Cancel() { cancelled_ = true; }
+
+  private:
+    std::shared_ptr<SnapshotHandler> snapuserd_;
+    bool cancelled_ = false;
+};
+
 bool ReadAhead::ReconstructDataFromCow() {
     std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
     loff_t metadata_offset = 0;
@@ -145,6 +169,8 @@
         metadata_offset += sizeof(struct ScratchMetadata);
     }
 
+    AutoNotifyReadAheadFailed notify_read_ahead_failed(snapuserd_);
+
     // We are done re-constructing the mapping; however, we need to make sure
     // all the COW operations to-be merged are present in the re-constructed
     // mapping.
@@ -162,7 +188,6 @@
         if (!(num_ops == 0)) {
             SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
                             << " Pending ops: " << num_ops;
-            snapuserd_->ReadAheadIOFailed();
             return false;
         }
 
@@ -175,11 +200,11 @@
 
     if (!snapuserd_->ReadAheadIOCompleted(true)) {
         SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
-        snapuserd_->ReadAheadIOFailed();
         return false;
     }
 
     SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+    notify_read_ahead_failed.Cancel();
     return true;
 }
 
@@ -467,9 +492,16 @@
         if (xor_op_index < xor_op_vec.size()) {
             const CowOperation* xor_op = xor_op_vec[xor_op_index];
             if (xor_op->new_block == new_block) {
-                if (!reader_->ReadData(*xor_op, &bufsink_)) {
+                void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+                if (!buffer) {
+                    SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer for block: "
+                                    << xor_op->new_block;
+                    return false;
+                }
+                if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
                     SNAP_LOG(ERROR)
-                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
+                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block
+                            << ", return value: " << rv;
                     return false;
                 }
 
@@ -492,6 +524,8 @@
     blocks_.clear();
     std::vector<const CowOperation*> xor_op_vec;
 
+    AutoNotifyReadAheadFailed notify_read_ahead_failed(snapuserd_);
+
     bufsink_.ResetBufferOffset();
 
     // Number of ops to be merged in this window. This is a fixed size
@@ -518,8 +552,6 @@
                              << " offset :" << source_offset % BLOCK_SZ
                              << " buffer_offset : " << buffer_offset << " io_size : " << io_size
                              << " buf-addr : " << read_ahead_buffer_;
-
-            snapuserd_->ReadAheadIOFailed();
             return false;
         }
 
@@ -530,6 +562,7 @@
 
     // Done with merging ordered ops
     if (RAIterDone() && total_blocks_merged_ == 0) {
+        notify_read_ahead_failed.Cancel();
         return true;
     }
 
@@ -560,20 +593,25 @@
             // Check if this block is an XOR op
             if (xor_op->new_block == new_block) {
                 // Read the xor'ed data from COW
-                if (!reader_->ReadData(*xor_op, &bufsink)) {
+                void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+                if (!buffer) {
+                    SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer";
+                    return false;
+                }
+                if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
                     SNAP_LOG(ERROR)
-                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
-                    snapuserd_->ReadAheadIOFailed();
+                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block
+                            << ", return value: " << rv;
                     return false;
                 }
                 // Pointer to the data read from base device
-                uint8_t* buffer = reinterpret_cast<uint8_t*>(bufptr);
+                uint8_t* read_buffer = reinterpret_cast<uint8_t*>(bufptr);
                 // Get the xor'ed data read from COW device
                 uint8_t* xor_data = reinterpret_cast<uint8_t*>(bufsink.GetPayloadBufPtr());
 
                 // Retrieve the original data
                 for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) {
-                    buffer[byte_offset] ^= xor_data[byte_offset];
+                    read_buffer[byte_offset] ^= xor_data[byte_offset];
                 }
 
                 // Move to next XOR op
@@ -604,6 +642,7 @@
     bm->new_block = 0;
     bm->file_offset = 0;
 
+    notify_read_ahead_failed.Cancel();
     return true;
 }
 
@@ -768,6 +807,7 @@
     if (!reader_->InitForMerge(std::move(cow_fd_))) {
         return false;
     }
+    header_ = reader_->GetHeader();
     return true;
 }
 
@@ -776,7 +816,7 @@
 }
 
 bool ReadAhead::RAIterDone() {
-    if (cowop_iter_->Done()) {
+    if (cowop_iter_->AtEnd()) {
         return true;
     }
 
@@ -794,15 +834,14 @@
 }
 
 void ReadAhead::RAResetIter(uint64_t num_blocks) {
-    while (num_blocks && !cowop_iter_->RDone()) {
+    while (num_blocks && !cowop_iter_->AtBegin()) {
         cowop_iter_->Prev();
         num_blocks -= 1;
     }
 }
 
 const CowOperation* ReadAhead::GetRAOpIter() {
-    const CowOperation* cow_op = &cowop_iter_->Get();
-    return cow_op;
+    return cowop_iter_->Get();
 }
 
 void ReadAhead::InitializeBuffer() {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h
new file mode 100644
index 0000000..d3ba126
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_reader.h>
+#include <liburing.h>
+#include <snapuserd/snapuserd_buffer.h>
+
+namespace android {
+namespace snapshot {
+
+class SnapshotHandler;
+
+class ReadAhead {
+  public:
+    ReadAhead(const std::string& cow_device, const std::string& backing_device,
+              const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd);
+    bool RunThread();
+
+  private:
+    void InitializeRAIter();
+    bool RAIterDone();
+    void RAIterNext();
+    void RAResetIter(uint64_t num_blocks);
+    const CowOperation* GetRAOpIter();
+
+    void InitializeBuffer();
+    bool InitReader();
+    bool InitializeFds();
+
+    void CloseFds() { backing_store_fd_ = {}; }
+
+    bool ReadAheadIOStart();
+    int PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops,
+                             std::vector<uint64_t>& blocks,
+                             std::vector<const CowOperation*>& xor_op_vec);
+    bool ReconstructDataFromCow();
+    void CheckOverlap(const CowOperation* cow_op);
+
+    bool ReadAheadAsyncIO();
+    bool ReapIoCompletions(int pending_ios_to_complete);
+    bool ReadXorData(size_t block_index, size_t xor_op_index,
+                     std::vector<const CowOperation*>& xor_op_vec);
+    void ProcessXorData(size_t& block_xor_index, size_t& xor_index,
+                        std::vector<const CowOperation*>& xor_op_vec, void* buffer,
+                        loff_t& buffer_offset);
+    void UpdateScratchMetadata();
+
+    bool ReadAheadSyncIO();
+    bool InitializeIouring();
+    void FinalizeIouring();
+
+    void* read_ahead_buffer_;
+    void* metadata_buffer_;
+
+    std::unique_ptr<ICowOpIter> cowop_iter_;
+
+    std::string cow_device_;
+    std::string backing_store_device_;
+    std::string misc_name_;
+
+    android::base::unique_fd cow_fd_;
+    android::base::unique_fd backing_store_fd_;
+
+    std::shared_ptr<SnapshotHandler> snapuserd_;
+    std::unique_ptr<CowReader> reader_;
+    CowHeader header_;
+
+    std::unordered_set<uint64_t> dest_blocks_;
+    std::unordered_set<uint64_t> source_blocks_;
+    bool overlap_;
+    std::vector<uint64_t> blocks_;
+    int total_blocks_merged_ = 0;
+    std::unique_ptr<uint8_t[]> ra_temp_buffer_;
+    std::unique_ptr<uint8_t[]> ra_temp_meta_buffer_;
+    BufferSink bufsink_;
+
+    uint64_t total_ra_blocks_completed_ = 0;
+    bool read_ahead_async_ = false;
+    // Queue depth of 8 seems optimal. We don't want
+    // to have a huge depth as it may put more memory pressure
+    // on the kernel worker threads given that we use
+    // IOSQE_ASYNC flag - ASYNC flags can potentially
+    // result in EINTR; Since we don't restart
+    // syscalls and fallback to synchronous I/O, we
+    // don't want huge queue depth
+    int queue_depth_ = 8;
+    std::unique_ptr<struct io_uring> ring_;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index b7ce210..c953f1a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -45,28 +45,9 @@
 using android::base::borrowed_fd;
 using android::base::unique_fd;
 
-DaemonOps UserSnapshotServer::Resolveop(std::string& input) {
-    if (input == "init") return DaemonOps::INIT;
-    if (input == "start") return DaemonOps::START;
-    if (input == "stop") return DaemonOps::STOP;
-    if (input == "query") return DaemonOps::QUERY;
-    if (input == "delete") return DaemonOps::DELETE;
-    if (input == "detach") return DaemonOps::DETACH;
-    if (input == "supports") return DaemonOps::SUPPORTS;
-    if (input == "initiate_merge") return DaemonOps::INITIATE;
-    if (input == "merge_percent") return DaemonOps::PERCENTAGE;
-    if (input == "getstatus") return DaemonOps::GETSTATUS;
-    if (input == "update-verify") return DaemonOps::UPDATE_VERIFY;
-
-    return DaemonOps::INVALID;
-}
-
 UserSnapshotServer::UserSnapshotServer() {
-    monitor_merge_event_fd_.reset(eventfd(0, EFD_CLOEXEC));
-    if (monitor_merge_event_fd_ == -1) {
-        PLOG(FATAL) << "monitor_merge_event_fd_: failed to create eventfd";
-    }
     terminating_ = false;
+    handlers_ = std::make_unique<SnapshotHandlerManager>();
 }
 
 UserSnapshotServer::~UserSnapshotServer() {
@@ -99,12 +80,9 @@
 
 void UserSnapshotServer::ShutdownThreads() {
     terminating_ = true;
-    JoinAllThreads();
+    handlers_->JoinAllThreads();
 }
 
-HandlerThread::HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd)
-    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
-
 bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
     ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
     if (ret < 0) {
@@ -135,226 +113,118 @@
 
     std::vector<std::string> out;
     Parsemsg(str, delim, out);
-    DaemonOps op = Resolveop(out[0]);
 
-    switch (op) {
-        case DaemonOps::INIT: {
-            // Message format:
-            // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
-            //
-            // Reads the metadata and send the number of sectors
-            if (out.size() != 5) {
-                LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-
-            auto handler = AddHandler(out[1], out[2], out[3], out[4]);
-            if (!handler) {
-                return Sendmsg(fd, "fail");
-            }
-
-            auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
-            return Sendmsg(fd, retval);
-        }
-        case DaemonOps::START: {
-            // Message format:
-            // start,<misc_name>
-            //
-            // Start the new thread which binds to dm-user misc device
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-
-            std::lock_guard<std::mutex> lock(lock_);
-            auto iter = FindHandler(&lock, out[1]);
-            if (iter == dm_users_.end()) {
-                LOG(ERROR) << "Could not find handler: " << out[1];
-                return Sendmsg(fd, "fail");
-            }
-            if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) {
-                LOG(ERROR) << "Tried to re-attach control device: " << out[1];
-                return Sendmsg(fd, "fail");
-            }
-            if (!StartHandler(*iter)) {
-                return Sendmsg(fd, "fail");
-            }
-            return Sendmsg(fd, "success");
-        }
-        case DaemonOps::STOP: {
-            // Message format: stop
-            //
-            // Stop all the threads gracefully and then shutdown the
-            // main thread
-            SetTerminating();
-            ShutdownThreads();
-            return true;
-        }
-        case DaemonOps::QUERY: {
-            // Message format: query
-            //
-            // As part of transition, Second stage daemon will be
-            // created before terminating the first stage daemon. Hence,
-            // for a brief period client may have to distiguish between
-            // first stage daemon and second stage daemon.
-            //
-            // Second stage daemon is marked as active and hence will
-            // be ready to receive control message.
-            return Sendmsg(fd, GetDaemonStatus());
-        }
-        case DaemonOps::DELETE: {
-            // Message format:
-            // delete,<misc_name>
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-            {
-                std::lock_guard<std::mutex> lock(lock_);
-                auto iter = FindHandler(&lock, out[1]);
-                if (iter == dm_users_.end()) {
-                    // After merge is completed, we swap dm-user table with
-                    // the underlying dm-linear base device. Hence, worker
-                    // threads would have terminted and was removed from
-                    // the list.
-                    LOG(DEBUG) << "Could not find handler: " << out[1];
-                    return Sendmsg(fd, "success");
-                }
-
-                if (!(*iter)->ThreadTerminated()) {
-                    (*iter)->snapuserd()->NotifyIOTerminated();
-                }
-            }
-            if (!RemoveAndJoinHandler(out[1])) {
-                return Sendmsg(fd, "fail");
-            }
-            return Sendmsg(fd, "success");
-        }
-        case DaemonOps::DETACH: {
-            std::lock_guard<std::mutex> lock(lock_);
-            TerminateMergeThreads(&lock);
-            terminating_ = true;
-            return true;
-        }
-        case DaemonOps::SUPPORTS: {
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-            if (out[1] == "second_stage_socket_handoff") {
-                return Sendmsg(fd, "success");
-            }
+    const auto& cmd = out[0];
+    if (cmd == "init") {
+        // Message format:
+        // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
+        //
+        // Reads the metadata and send the number of sectors
+        if (out.size() != 5) {
+            LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
             return Sendmsg(fd, "fail");
         }
-        case DaemonOps::INITIATE: {
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-            if (out[0] == "initiate_merge") {
-                std::lock_guard<std::mutex> lock(lock_);
-                auto iter = FindHandler(&lock, out[1]);
-                if (iter == dm_users_.end()) {
-                    LOG(ERROR) << "Could not find handler: " << out[1];
-                    return Sendmsg(fd, "fail");
-                }
 
-                if (!StartMerge(&lock, *iter)) {
-                    return Sendmsg(fd, "fail");
-                }
-
-                return Sendmsg(fd, "success");
-            }
+        auto handler = AddHandler(out[1], out[2], out[3], out[4]);
+        if (!handler) {
             return Sendmsg(fd, "fail");
         }
-        case DaemonOps::PERCENTAGE: {
-            std::lock_guard<std::mutex> lock(lock_);
-            double percentage = GetMergePercentage(&lock);
 
-            return Sendmsg(fd, std::to_string(percentage));
+        auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
+        return Sendmsg(fd, retval);
+    } else if (cmd == "start") {
+        // Message format:
+        // start,<misc_name>
+        //
+        // Start the new thread which binds to dm-user misc device
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
+            return Sendmsg(fd, "fail");
         }
-        case DaemonOps::GETSTATUS: {
-            // Message format:
-            // getstatus,<misc_name>
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
-                return Sendmsg(fd, "snapshot-merge-failed");
-            }
-            {
-                std::lock_guard<std::mutex> lock(lock_);
-                auto iter = FindHandler(&lock, out[1]);
-                if (iter == dm_users_.end()) {
-                    LOG(ERROR) << "Could not find handler: " << out[1];
-                    return Sendmsg(fd, "snapshot-merge-failed");
-                }
 
-                std::string merge_status = GetMergeStatus(*iter);
-                return Sendmsg(fd, merge_status);
-            }
+        if (!handlers_->StartHandler(out[1])) {
+            return Sendmsg(fd, "fail");
         }
-        case DaemonOps::UPDATE_VERIFY: {
-            std::lock_guard<std::mutex> lock(lock_);
-            if (!UpdateVerification(&lock)) {
-                return Sendmsg(fd, "fail");
-            }
-
+        return Sendmsg(fd, "success");
+    } else if (cmd == "stop") {
+        // Message format: stop
+        //
+        // Stop all the threads gracefully and then shutdown the
+        // main thread
+        SetTerminating();
+        ShutdownThreads();
+        return true;
+    } else if (cmd == "query") {
+        // Message format: query
+        //
+        // As part of transition, Second stage daemon will be
+        // created before terminating the first stage daemon. Hence,
+        // for a brief period client may have to distiguish between
+        // first stage daemon and second stage daemon.
+        //
+        // Second stage daemon is marked as active and hence will
+        // be ready to receive control message.
+        return Sendmsg(fd, GetDaemonStatus());
+    } else if (cmd == "delete") {
+        // Message format:
+        // delete,<misc_name>
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+            return Sendmsg(fd, "fail");
+        }
+        if (!handlers_->DeleteHandler(out[1])) {
+            return Sendmsg(fd, "fail");
+        }
+        return Sendmsg(fd, "success");
+    } else if (cmd == "detach") {
+        handlers_->TerminateMergeThreads();
+        terminating_ = true;
+        return true;
+    } else if (cmd == "supports") {
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
+            return Sendmsg(fd, "fail");
+        }
+        if (out[1] == "second_stage_socket_handoff") {
             return Sendmsg(fd, "success");
         }
-        default: {
-            LOG(ERROR) << "Received unknown message type from client";
-            Sendmsg(fd, "fail");
-            return false;
+        return Sendmsg(fd, "fail");
+    } else if (cmd == "initiate_merge") {
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
+            return Sendmsg(fd, "fail");
         }
-    }
-}
-
-void UserSnapshotServer::RunThread(std::shared_ptr<HandlerThread> handler) {
-    LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
-
-    if (!handler->snapuserd()->Start()) {
-        LOG(ERROR) << " Failed to launch all worker threads";
-    }
-
-    handler->snapuserd()->CloseFds();
-    bool merge_completed = handler->snapuserd()->CheckMergeCompletionStatus();
-    handler->snapuserd()->UnmapBufferRegion();
-
-    auto misc_name = handler->misc_name();
-    LOG(INFO) << "Handler thread about to exit: " << misc_name;
-
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        if (merge_completed) {
-            num_partitions_merge_complete_ += 1;
-            active_merge_threads_ -= 1;
-            WakeupMonitorMergeThread();
+        if (out[0] == "initiate_merge") {
+            if (!handlers_->InitiateMerge(out[1])) {
+                return Sendmsg(fd, "fail");
+            }
+            return Sendmsg(fd, "success");
         }
-        handler->SetThreadTerminated();
-        auto iter = FindHandler(&lock, handler->misc_name());
-        if (iter == dm_users_.end()) {
-            // RemoveAndJoinHandler() already removed us from the list, and is
-            // now waiting on a join(), so just return. Additionally, release
-            // all the resources held by snapuserd object which are shared
-            // by worker threads. This should be done when the last reference
-            // of "handler" is released; but we will explicitly release here
-            // to make sure snapuserd object is freed as it is the biggest
-            // consumer of memory in the daemon.
-            handler->FreeResources();
-            LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
-            return;
+        return Sendmsg(fd, "fail");
+    } else if (cmd == "merge_percent") {
+        double percentage = handlers_->GetMergePercentage();
+        return Sendmsg(fd, std::to_string(percentage));
+    } else if (cmd == "getstatus") {
+        // Message format:
+        // getstatus,<misc_name>
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+            return Sendmsg(fd, "snapshot-merge-failed");
         }
-
-        LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name;
-
-        if (handler->snapuserd()->IsAttached()) {
-            handler->thread().detach();
+        auto status = handlers_->GetMergeStatus(out[1]);
+        if (status.empty()) {
+            return Sendmsg(fd, "snapshot-merge-failed");
         }
-
-        // Important: free resources within the lock. This ensures that if
-        // WaitForDelete() is called, the handler is either in the list, or
-        // it's not and its resources are guaranteed to be freed.
-        handler->FreeResources();
-        dm_users_.erase(iter);
+        return Sendmsg(fd, status);
+    } else if (cmd == "update-verify") {
+        if (!handlers_->GetVerificationStatus()) {
+            return Sendmsg(fd, "fail");
+        }
+        return Sendmsg(fd, "success");
+    } else {
+        LOG(ERROR) << "Received unknown message type from client";
+        Sendmsg(fd, "fail");
+        return false;
     }
 }
 
@@ -423,28 +293,10 @@
         }
     }
 
-    JoinAllThreads();
+    handlers_->JoinAllThreads();
     return true;
 }
 
-void UserSnapshotServer::JoinAllThreads() {
-    // Acquire the thread list within the lock.
-    std::vector<std::shared_ptr<HandlerThread>> dm_users;
-    {
-        std::lock_guard<std::mutex> guard(lock_);
-        dm_users = std::move(dm_users_);
-    }
-
-    for (auto& client : dm_users) {
-        auto& th = client->thread();
-
-        if (th.joinable()) th.join();
-    }
-
-    stop_monitor_merge_thread_ = true;
-    WakeupMonitorMergeThread();
-}
-
 void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
     struct pollfd p = {};
     p.fd = fd.get();
@@ -506,185 +358,13 @@
         perform_verification = false;
     }
 
-    auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
-                                                       base_path_merge, num_worker_threads,
-                                                       io_uring_enabled_, perform_verification);
-    if (!snapuserd->InitCowDevice()) {
-        LOG(ERROR) << "Failed to initialize Snapuserd";
-        return nullptr;
-    }
-
-    if (!snapuserd->InitializeWorkers()) {
-        LOG(ERROR) << "Failed to initialize workers";
-        return nullptr;
-    }
-
-    auto handler = std::make_shared<HandlerThread>(snapuserd);
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        if (FindHandler(&lock, misc_name) != dm_users_.end()) {
-            LOG(ERROR) << "Handler already exists: " << misc_name;
-            return nullptr;
-        }
-        dm_users_.push_back(handler);
-    }
-    return handler;
-}
-
-bool UserSnapshotServer::StartHandler(const std::shared_ptr<HandlerThread>& handler) {
-    if (handler->snapuserd()->IsAttached()) {
-        LOG(ERROR) << "Handler already attached";
-        return false;
-    }
-
-    handler->snapuserd()->AttachControlDevice();
-
-    handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler));
-    return true;
-}
-
-bool UserSnapshotServer::StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
-                                    const std::shared_ptr<HandlerThread>& handler) {
-    CHECK(proof_of_lock);
-
-    if (!handler->snapuserd()->IsAttached()) {
-        LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
-        return false;
-    }
-
-    handler->snapuserd()->MonitorMerge();
-
-    if (!is_merge_monitor_started_.has_value()) {
-        std::thread(&UserSnapshotServer::MonitorMerge, this).detach();
-        is_merge_monitor_started_ = true;
-    }
-
-    merge_handlers_.push(handler);
-    WakeupMonitorMergeThread();
-    return true;
-}
-
-auto UserSnapshotServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                     const std::string& misc_name) -> HandlerList::iterator {
-    CHECK(proof_of_lock);
-
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        if ((*iter)->misc_name() == misc_name) {
-            return iter;
-        }
-    }
-    return dm_users_.end();
-}
-
-void UserSnapshotServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
-    CHECK(proof_of_lock);
-
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        if (!(*iter)->ThreadTerminated()) {
-            (*iter)->snapuserd()->NotifyIOTerminated();
-        }
-    }
-}
-
-std::string UserSnapshotServer::GetMergeStatus(const std::shared_ptr<HandlerThread>& handler) {
-    return handler->snapuserd()->GetMergeStatus();
-}
-
-double UserSnapshotServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
-    CHECK(proof_of_lock);
-    double percentage = 0.0;
-    int n = 0;
-
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        auto& th = (*iter)->thread();
-        if (th.joinable()) {
-            // Merge percentage by individual partitions wherein merge is still
-            // in-progress
-            percentage += (*iter)->snapuserd()->GetMergePercentage();
-            n += 1;
-        }
-    }
-
-    // Calculate final merge including those partitions where merge was already
-    // completed - num_partitions_merge_complete_ will track them when each
-    // thread exists in RunThread.
-    int total_partitions = n + num_partitions_merge_complete_;
-
-    if (total_partitions) {
-        percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
-    }
-
-    LOG(DEBUG) << "Merge %: " << percentage
-               << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
-               << " total_partitions: " << total_partitions << " n: " << n;
-    return percentage;
-}
-
-bool UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) {
-    std::shared_ptr<HandlerThread> handler;
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-
-        auto iter = FindHandler(&lock, misc_name);
-        if (iter == dm_users_.end()) {
-            // Client already deleted.
-            return true;
-        }
-        handler = std::move(*iter);
-        dm_users_.erase(iter);
-    }
-
-    auto& th = handler->thread();
-    if (th.joinable()) {
-        th.join();
-    }
-    return true;
-}
-
-void UserSnapshotServer::WakeupMonitorMergeThread() {
-    uint64_t notify = 1;
-    ssize_t rc = TEMP_FAILURE_RETRY(write(monitor_merge_event_fd_.get(), &notify, sizeof(notify)));
-    if (rc < 0) {
-        PLOG(FATAL) << "failed to notify monitor merge thread";
-    }
-}
-
-void UserSnapshotServer::MonitorMerge() {
-    while (!stop_monitor_merge_thread_) {
-        uint64_t testVal;
-        ssize_t ret =
-                TEMP_FAILURE_RETRY(read(monitor_merge_event_fd_.get(), &testVal, sizeof(testVal)));
-        if (ret == -1) {
-            PLOG(FATAL) << "Failed to read from eventfd";
-        } else if (ret == 0) {
-            LOG(FATAL) << "Hit EOF on eventfd";
-        }
-
-        LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_;
-        {
-            std::lock_guard<std::mutex> lock(lock_);
-            while (active_merge_threads_ < kMaxMergeThreads && merge_handlers_.size() > 0) {
-                auto handler = merge_handlers_.front();
-                merge_handlers_.pop();
-
-                if (!handler->snapuserd()) {
-                    LOG(INFO) << "MonitorMerge: skipping deleted handler: " << handler->misc_name();
-                    continue;
-                }
-
-                LOG(INFO) << "Starting merge for partition: "
-                          << handler->snapuserd()->GetMiscName();
-                handler->snapuserd()->InitiateMerge();
-                active_merge_threads_ += 1;
-            }
-        }
-    }
-
-    LOG(INFO) << "Exiting MonitorMerge: size: " << merge_handlers_.size();
+    return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge,
+                                 num_worker_threads, io_uring_enabled_, perform_verification);
 }
 
 bool UserSnapshotServer::WaitForSocket() {
-    auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
+    auto scope_guard =
+            android::base::make_scope_guard([this]() -> void { handlers_->JoinAllThreads(); });
 
     auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
 
@@ -781,21 +461,8 @@
     return true;
 }
 
-bool UserSnapshotServer::UpdateVerification(std::lock_guard<std::mutex>* proof_of_lock) {
-    CHECK(proof_of_lock);
-
-    bool status = true;
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        auto& th = (*iter)->thread();
-        if (th.joinable() && status) {
-            status = (*iter)->snapuserd()->CheckPartitionVerification() && status;
-        } else {
-            // return immediately if there is a failure
-            return false;
-        }
-    }
-
-    return status;
+bool UserSnapshotServer::StartHandler(const std::string& misc_name) {
+    return handlers_->StartHandler(misc_name);
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index 12c3903..988c01a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -31,6 +31,7 @@
 #include <vector>
 
 #include <android-base/unique_fd.h>
+#include "handler_manager.h"
 #include "snapuserd_core.h"
 
 namespace android {
@@ -39,48 +40,6 @@
 static constexpr uint32_t kMaxPacketSize = 512;
 static constexpr uint8_t kMaxMergeThreads = 2;
 
-enum class DaemonOps {
-    INIT,
-    START,
-    QUERY,
-    STOP,
-    DELETE,
-    DETACH,
-    SUPPORTS,
-    INITIATE,
-    PERCENTAGE,
-    GETSTATUS,
-    UPDATE_VERIFY,
-    INVALID,
-};
-
-class HandlerThread {
-  public:
-    explicit HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd);
-
-    void FreeResources() {
-        // Each worker thread holds a reference to snapuserd.
-        // Clear them so that all the resources
-        // held by snapuserd is released
-        if (snapuserd_) {
-            snapuserd_->FreeResources();
-            snapuserd_ = nullptr;
-        }
-    }
-    const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
-    std::thread& thread() { return thread_; }
-
-    const std::string& misc_name() const { return misc_name_; }
-    bool ThreadTerminated() { return thread_terminated_; }
-    void SetThreadTerminated() { thread_terminated_ = true; }
-
-  private:
-    std::thread thread_;
-    std::shared_ptr<SnapshotHandler> snapuserd_;
-    std::string misc_name_;
-    bool thread_terminated_ = false;
-};
-
 class UserSnapshotServer {
   private:
     android::base::unique_fd sockfd_;
@@ -88,21 +47,12 @@
     volatile bool received_socket_signal_ = false;
     std::vector<struct pollfd> watched_fds_;
     bool is_socket_present_ = false;
-    int num_partitions_merge_complete_ = 0;
-    int active_merge_threads_ = 0;
-    bool stop_monitor_merge_thread_ = false;
     bool is_server_running_ = false;
     bool io_uring_enabled_ = false;
-    std::optional<bool> is_merge_monitor_started_;
-
-    android::base::unique_fd monitor_merge_event_fd_;
+    std::unique_ptr<ISnapshotHandlerManager> handlers_;
 
     std::mutex lock_;
 
-    using HandlerList = std::vector<std::shared_ptr<HandlerThread>>;
-    HandlerList dm_users_;
-    std::queue<std::shared_ptr<HandlerThread>> merge_handlers_;
-
     void AddWatchedFd(android::base::borrowed_fd fd, int events);
     void AcceptClient();
     bool HandleClient(android::base::borrowed_fd fd, int revents);
@@ -111,28 +61,14 @@
     bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
 
     void ShutdownThreads();
-    bool RemoveAndJoinHandler(const std::string& control_device);
-    DaemonOps Resolveop(std::string& input);
     std::string GetDaemonStatus();
     void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
 
     bool IsTerminating() { return terminating_; }
 
-    void RunThread(std::shared_ptr<HandlerThread> handler);
-    void MonitorMerge();
-
     void JoinAllThreads();
     bool StartWithSocket(bool start_listening);
 
-    // Find a HandlerThread within a lock.
-    HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                      const std::string& misc_name);
-
-    double GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock);
-    void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
-
-    bool UpdateVerification(std::lock_guard<std::mutex>* proof_of_lock);
-
   public:
     UserSnapshotServer();
     ~UserSnapshotServer();
@@ -147,12 +83,8 @@
                                               const std::string& cow_device_path,
                                               const std::string& backing_device,
                                               const std::string& base_path_merge);
-    bool StartHandler(const std::shared_ptr<HandlerThread>& handler);
-    bool StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
-                    const std::shared_ptr<HandlerThread>& handler);
-    std::string GetMergeStatus(const std::shared_ptr<HandlerThread>& handler);
+    bool StartHandler(const std::string& misc_name);
 
-    void WakeupMonitorMergeThread();
     void SetTerminating() { terminating_ = true; }
     void ReceivedSocketSignal() { received_socket_signal_ = true; }
     void SetServerRunning() { is_server_running_ = true; }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index 1421403..efe0c14 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -37,9 +37,9 @@
 #include <libdm/dm.h>
 #include <libdm/loop_control.h>
 #include <libsnapshot/cow_writer.h>
-#include <snapuserd/snapuserd_client.h>
 #include <storage_literals/storage_literals.h>
 
+#include "handler_manager.h"
 #include "snapuserd_core.h"
 
 DEFINE_string(force_config, "", "Force testing mode with iouring disabled");
@@ -54,8 +54,6 @@
 using namespace android::dm;
 using namespace std;
 
-static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
-
 class Tempdevice {
   public:
     Tempdevice(const std::string& name, const DmTable& table)
@@ -68,15 +66,15 @@
     }
     ~Tempdevice() {
         if (valid_) {
-            dm_.DeleteDevice(name_);
+            dm_.DeleteDeviceIfExists(name_);
         }
     }
     bool Destroy() {
         if (!valid_) {
-            return false;
+            return true;
         }
         valid_ = false;
-        return dm_.DeleteDevice(name_);
+        return dm_.DeleteDeviceIfExists(name_);
     }
     const std::string& path() const { return path_; }
     const std::string& name() const { return name_; }
@@ -127,6 +125,7 @@
 
     void SimulateDaemonRestart();
 
+    std::unique_ptr<ICowWriter> CreateCowDeviceInternal();
     void CreateCowDevice();
     void CreateCowDeviceOrderedOps();
     void CreateCowDeviceOrderedOpsInverted();
@@ -138,7 +137,6 @@
     void SetDeviceControlName();
     void InitDaemon();
     void CreateDmUserDevice();
-    void StartSnapuserdDaemon();
 
     unique_ptr<LoopDevice> base_loop_;
     unique_ptr<Tempdevice> dmuser_dev_;
@@ -148,9 +146,9 @@
 
     unique_fd base_fd_;
     std::unique_ptr<TemporaryFile> cow_system_;
-    std::unique_ptr<SnapuserdClient> client_;
     std::unique_ptr<uint8_t[]> orig_buffer_;
     std::unique_ptr<uint8_t[]> merged_buffer_;
+    SnapshotHandlerManager handlers_;
     bool setup_ok_ = false;
     bool merge_ok_ = false;
     size_t size_ = 100_MiB;
@@ -180,9 +178,9 @@
     ASSERT_TRUE(dmuser_dev_->Destroy());
 
     auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
-    ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
+    ASSERT_TRUE(handlers_.DeleteHandler(system_device_ctrl_name_));
     ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
-    ASSERT_TRUE(client_->DetachSnapuserd());
+    handlers_.TerminateMergeThreads();
 }
 
 bool SnapuserdTest::SetupDefault() {
@@ -217,8 +215,6 @@
 bool SnapuserdTest::SetupDaemon() {
     SetDeviceControlName();
 
-    StartSnapuserdDaemon();
-
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -228,20 +224,6 @@
     return setup_ok_;
 }
 
-void SnapuserdTest::StartSnapuserdDaemon() {
-    pid_t pid = fork();
-    ASSERT_GE(pid, 0);
-    if (pid == 0) {
-        std::string arg0 = "/system/bin/snapuserd";
-        std::string arg1 = "-socket="s + kSnapuserdSocketTest;
-        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
-        ASSERT_GE(execv(arg0.c_str(), argv), 0);
-    } else {
-        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
-        ASSERT_NE(client_, nullptr);
-    }
-}
-
 void SnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
 
@@ -296,23 +278,30 @@
     ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
 }
 
-void SnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
+std::unique_ptr<ICowWriter> SnapuserdTest::CreateCowDeviceInternal() {
     std::string path = android::base::GetExecutableDirectory();
     cow_system_ = std::make_unique<TemporaryFile>(path);
 
     CowOptions options;
     options.compression = "gz";
-    CowWriter writer(options);
 
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+    unique_fd fd(cow_system_->fd);
+    cow_system_->fd = -1;
 
-    size_t num_blocks = size_ / options.block_size;
+    return CreateCowWriter(kDefaultCowVersion, options, std::move(fd));
+}
+
+void SnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
+
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t x = num_blocks;
     size_t blk_src_copy = 0;
 
     // Create overlapping copy operations
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
+        ASSERT_TRUE(writer->AddCopy(blk_src_copy, blk_src_copy + 1));
         x -= 1;
         if (x == 1) {
             break;
@@ -321,7 +310,7 @@
     }
 
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
 
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
@@ -349,22 +338,16 @@
 }
 
 void SnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t x = num_blocks;
     size_t blk_src_copy = num_blocks - 1;
 
     // Create overlapping copy operations
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
+        ASSERT_TRUE(writer->AddCopy(blk_src_copy + 1, blk_src_copy));
         x -= 1;
         if (x == 0) {
             ASSERT_EQ(blk_src_copy, 0);
@@ -374,7 +357,7 @@
     }
 
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
 
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
@@ -384,10 +367,11 @@
               true);
 
     // Merged operations
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), writer->GetBlockSize(),
+                                               0),
               true);
     ASSERT_EQ(android::base::ReadFullyAtOffset(
-                      base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
+                      base_fd_, (char*)orig_buffer_.get() + writer->GetBlockSize(), size_, 0),
               true);
 }
 
@@ -395,8 +379,8 @@
     unique_fd rnd_fd;
     loff_t offset = 0;
 
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
@@ -411,13 +395,7 @@
         offset += 1_MiB;
     }
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t blk_end_copy = num_blocks * 3;
     size_t source_blk = num_blocks - 1;
     size_t blk_src_copy = blk_end_copy - 1;
@@ -425,7 +403,7 @@
 
     size_t x = num_blocks;
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
         x -= 1;
         if (x == 0) {
             break;
@@ -435,12 +413,12 @@
     }
 
     for (size_t i = num_blocks; i > 0; i--) {
-        ASSERT_TRUE(writer.AddXorBlocks(num_blocks + i - 1,
-                                        &random_buffer_1_.get()[options.block_size * (i - 1)],
-                                        options.block_size, 2 * num_blocks + i - 1, xor_offset));
+        ASSERT_TRUE(writer->AddXorBlocks(
+                num_blocks + i - 1, &random_buffer_1_.get()[writer->GetBlockSize() * (i - 1)],
+                writer->GetBlockSize(), 2 * num_blocks + i - 1, xor_offset));
     }
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
     // Read the entire base device
@@ -458,8 +436,8 @@
     unique_fd rnd_fd;
     loff_t offset = 0;
 
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
@@ -475,20 +453,14 @@
     }
     memset(random_buffer_1_.get(), 0, size_);
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t x = num_blocks;
     size_t source_blk = 0;
     size_t blk_src_copy = 2 * num_blocks;
     uint16_t xor_offset = 5;
 
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
 
         x -= 1;
         if (x == 0) {
@@ -498,10 +470,10 @@
         blk_src_copy += 1;
     }
 
-    ASSERT_TRUE(writer.AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
-                                    xor_offset));
+    ASSERT_TRUE(writer->AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
+                                     xor_offset));
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
     // Read the entire base device
@@ -519,8 +491,8 @@
     unique_fd rnd_fd;
     loff_t offset = 0;
 
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
@@ -535,13 +507,7 @@
         offset += 1_MiB;
     }
 
-    CowOptions options;
-    options.compression = "gz";
-    CowWriter writer(options);
-
-    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
-
-    size_t num_blocks = size_ / options.block_size;
+    size_t num_blocks = size_ / writer->GetBlockSize();
     size_t blk_end_copy = num_blocks * 2;
     size_t source_blk = num_blocks - 1;
     size_t blk_src_copy = blk_end_copy - 1;
@@ -555,11 +521,11 @@
     for (int i = 0; i < num_blocks; i++) {
         sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
     }
-    ASSERT_TRUE(writer.AddSequenceData(2 * num_blocks, sequence));
+    ASSERT_TRUE(writer->AddSequenceData(2 * num_blocks, sequence));
 
     size_t x = num_blocks;
     while (1) {
-        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
         x -= 1;
         if (x == 0) {
             break;
@@ -571,24 +537,24 @@
     source_blk = num_blocks;
     blk_src_copy = blk_end_copy;
 
-    ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+    ASSERT_TRUE(writer->AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
 
     size_t blk_zero_copy_start = source_blk + num_blocks;
     size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
 
-    ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
+    ASSERT_TRUE(writer->AddZeroBlocks(blk_zero_copy_start, num_blocks));
 
     size_t blk_random2_replace_start = blk_zero_copy_end;
 
-    ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
+    ASSERT_TRUE(writer->AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
 
     size_t blk_xor_start = blk_random2_replace_start + num_blocks;
     size_t xor_offset = BLOCK_SZ / 2;
-    ASSERT_TRUE(writer.AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
-                                    xor_offset));
+    ASSERT_TRUE(writer->AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
+                                     xor_offset));
 
     // Flush operations
-    ASSERT_TRUE(writer.Finalize());
+    ASSERT_TRUE(writer->Finalize());
     // Construct the buffer required for validation
     orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
     std::string zero_buffer(size_, 0);
@@ -606,9 +572,17 @@
 }
 
 void SnapuserdTest::InitCowDevice() {
-    uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
-                                                  base_loop_->device(), base_loop_->device());
-    ASSERT_NE(num_sectors, 0);
+    bool use_iouring = true;
+    if (FLAGS_force_config == "iouring_disabled") {
+        use_iouring = false;
+    }
+
+    auto handler =
+            handlers_.AddHandler(system_device_ctrl_name_, cow_system_->path, base_loop_->device(),
+                                 base_loop_->device(), 1, use_iouring, false);
+    ASSERT_NE(handler, nullptr);
+    ASSERT_NE(handler->snapuserd(), nullptr);
+    ASSERT_NE(handler->snapuserd()->GetNumSectors(), 0);
 }
 
 void SnapuserdTest::SetDeviceControlName() {
@@ -646,13 +620,12 @@
 }
 
 void SnapuserdTest::InitDaemon() {
-    bool ok = client_->AttachDmUser(system_device_ctrl_name_);
-    ASSERT_TRUE(ok);
+    ASSERT_TRUE(handlers_.StartHandler(system_device_ctrl_name_));
 }
 
 void SnapuserdTest::CheckMergeCompletion() {
     while (true) {
-        double percentage = client_->GetMergePercent();
+        double percentage = handlers_.GetMergePercentage();
         if ((int)percentage == 100) {
             break;
         }
@@ -667,8 +640,6 @@
 
     SetDeviceControlName();
 
-    StartSnapuserdDaemon();
-
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -684,8 +655,7 @@
 }
 
 void SnapuserdTest::StartMerge() {
-    bool ok = client_->InitiateMerge(system_device_ctrl_name_);
-    ASSERT_TRUE(ok);
+    ASSERT_TRUE(handlers_.InitiateMerge(system_device_ctrl_name_));
 }
 
 void SnapuserdTest::ValidateMerge() {
@@ -699,7 +669,6 @@
     Shutdown();
     std::this_thread::sleep_for(500ms);
     SetDeviceControlName();
-    StartSnapuserdDaemon();
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -859,20 +828,5 @@
 
     gflags::ParseCommandLineFlags(&argc, &argv, false);
 
-    android::base::SetProperty("ctl.stop", "snapuserd");
-
-    if (FLAGS_force_config == "iouring_disabled") {
-        if (!android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1")) {
-            return testing::AssertionFailure()
-                   << "Failed to disable property: snapuserd.test.io_uring.disabled";
-        }
-    }
-
-    int ret = RUN_ALL_TESTS();
-
-    if (FLAGS_force_config == "iouring_disabled") {
-        android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
-    }
-
-    return ret;
+    return RUN_ALL_TESTS();
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp
index 18c1dfc..6817340 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-#include "snapuserd_core.h"
+#include "snapuserd_verify.h"
 
 #include <android-base/chrono_utils.h>
 #include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 
+#include "snapuserd_core.h"
+
 namespace android {
 namespace snapshot {
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h
new file mode 100644
index 0000000..d07d2f8
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 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 <sys/types.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <string>
+
+#include <snapuserd/snapuserd_kernel.h>
+#include <storage_literals/storage_literals.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android::storage_literals;
+
+class UpdateVerify {
+  public:
+    UpdateVerify(const std::string& misc_name);
+    void VerifyUpdatePartition();
+    bool CheckPartitionVerification();
+
+  private:
+    enum class UpdateVerifyState {
+        VERIFY_UNKNOWN,
+        VERIFY_FAILED,
+        VERIFY_SUCCESS,
+    };
+
+    std::string misc_name_;
+    UpdateVerifyState state_;
+    std::mutex m_lock_;
+    std::condition_variable m_cv_;
+
+    int kMinThreadsToVerify = 1;
+    int kMaxThreadsToVerify = 4;
+    uint64_t kThresholdSize = 512_MiB;
+    uint64_t kBlockSizeVerify = 1_MiB;
+
+    bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
+    void UpdatePartitionVerificationState(UpdateVerifyState state);
+    bool VerifyPartition(const std::string& partition_name, const std::string& dm_block_device);
+    bool VerifyBlocks(const std::string& partition_name, const std::string& dm_block_device,
+                      off_t offset, int skip_blocks, uint64_t dev_sz);
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 9f1d676..2eac347 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -130,12 +130,7 @@
     return true;
 }
 
-std::string HashSnapshot(ISnapshotWriter* writer) {
-    auto reader = writer->OpenReader();
-    if (!reader) {
-        return {};
-    }
-
+std::string HashSnapshot(ICowWriter::FileDescriptor* reader) {
     SHA256_CTX ctx;
     SHA256_Init(&ctx);
 
@@ -214,68 +209,6 @@
     return partition_update->mutable_new_partition_info()->size();
 }
 
-AssertionResult LowSpaceUserdata::Init(uint64_t max_free_space) {
-    auto res = ReadUserdataStats();
-    if (!res) return res;
-
-    // Try to fill up the disk as much as possible until free_space_ <= max_free_space.
-    big_file_ = std::make_unique<TemporaryFile>();
-    if (big_file_->fd == -1) {
-        return AssertionFailure() << strerror(errno);
-    }
-    if (!android::base::StartsWith(big_file_->path, kUserDataDevice)) {
-        return AssertionFailure() << "Temp file allocated to " << big_file_->path << ", not in "
-                                  << kUserDataDevice;
-    }
-    uint64_t next_consume = std::min(std::max(available_space_, max_free_space) - max_free_space,
-                                     (uint64_t)std::numeric_limits<off_t>::max());
-    off_t allocated = 0;
-    while (next_consume > 0 && free_space_ > max_free_space) {
-        int status = fallocate(big_file_->fd, 0, allocated, next_consume);
-        if (status == -1 && errno == ENOSPC) {
-            next_consume /= 2;
-            continue;
-        }
-        if (status == -1) {
-            return AssertionFailure() << strerror(errno);
-        }
-        allocated += next_consume;
-
-        res = ReadUserdataStats();
-        if (!res) return res;
-    }
-
-    LOG(INFO) << allocated << " bytes allocated to " << big_file_->path;
-    initialized_ = true;
-    return AssertionSuccess();
-}
-
-AssertionResult LowSpaceUserdata::ReadUserdataStats() {
-    struct statvfs buf;
-    if (statvfs(kUserDataDevice, &buf) == -1) {
-        return AssertionFailure() << strerror(errno);
-    }
-    bsize_ = buf.f_bsize;
-    free_space_ = bsize_ * buf.f_bfree;
-    available_space_ = bsize_ * buf.f_bavail;
-    return AssertionSuccess();
-}
-
-uint64_t LowSpaceUserdata::free_space() const {
-    CHECK(initialized_);
-    return free_space_;
-}
-
-uint64_t LowSpaceUserdata::available_space() const {
-    CHECK(initialized_);
-    return available_space_;
-}
-
-uint64_t LowSpaceUserdata::bsize() const {
-    CHECK(initialized_);
-    return bsize_;
-}
-
 bool IsVirtualAbEnabled() {
     return android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
 }
diff --git a/fs_mgr/libstorage_literals/storage_literals/storage_literals.h b/fs_mgr/libstorage_literals/storage_literals/storage_literals.h
index ac0dfbd..bbeabd5 100644
--- a/fs_mgr/libstorage_literals/storage_literals/storage_literals.h
+++ b/fs_mgr/libstorage_literals/storage_literals/storage_literals.h
@@ -37,6 +37,7 @@
 using KiB = Size<10>;
 using MiB = Size<20>;
 using GiB = Size<30>;
+using TiB = Size<40>;
 
 constexpr B operator""_B(unsigned long long v) {  // NOLINT
     return B{v};
@@ -54,6 +55,10 @@
     return GiB{v};
 }
 
+constexpr TiB operator""_TiB(unsigned long long v) {  // NOLINT
+    return TiB{v};
+}
+
 template <typename Dest, typename Src>
 constexpr Dest size_cast(Src src) {
     if (Src::power < Dest::power) {
@@ -69,6 +74,7 @@
 static_assert(1_KiB == 1 << 10);
 static_assert(1_MiB == 1 << 20);
 static_assert(1_GiB == 1 << 30);
+static_assert(1_TiB == 1ULL << 40);
 static_assert(size_cast<KiB>(1_B).count() == 0);
 static_assert(size_cast<KiB>(1024_B).count() == 1);
 static_assert(size_cast<KiB>(1_MiB).count() == 1024);
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index e33681c..5f889ca 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -497,6 +497,7 @@
     EXPECT_EQ("none0", entry->mount_point);
     {
         FstabEntry::FsMgrFlags flags = {};
+        flags.file_encryption = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     EXPECT_EQ("", entry->metadata_key_dir);
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index 838f734..534fc1a 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -18,8 +18,8 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_binary {
-    name: "gatekeeperd",
+cc_defaults {
+    name: "gatekeeperd_defaults",
     cflags: [
         "-Wall",
         "-Wextra",
@@ -52,6 +52,16 @@
 
     static_libs: ["libscrypt_static"],
     include_dirs: ["external/scrypt/lib/crypto"],
+}
+
+cc_binary {
+    name: "gatekeeperd",
+    defaults: [
+        "gatekeeperd_defaults",
+    ],
+    srcs: [
+        "main.cpp",
+    ],
     init_rc: ["gatekeeperd.rc"],
 }
 
@@ -88,3 +98,20 @@
         "libbinder",
     ],
 }
+
+cc_fuzz {
+    name: "gatekeeperd_service_fuzzer",
+    defaults: [
+        "gatekeeperd_defaults",
+        "service_fuzzer_defaults"
+    ],
+    srcs: [
+        "fuzzer/GateKeeperServiceFuzzer.cpp",
+    ],
+    fuzz_config: {
+        cc: [
+            "subrahmanyaman@google.com",
+            "swillden@google.com",
+        ],
+    },
+}
\ No newline at end of file
diff --git a/gatekeeperd/fuzzer/GateKeeperServiceFuzzer.cpp b/gatekeeperd/fuzzer/GateKeeperServiceFuzzer.cpp
new file mode 100644
index 0000000..bc0d5fe
--- /dev/null
+++ b/gatekeeperd/fuzzer/GateKeeperServiceFuzzer.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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 <fuzzbinder/libbinder_driver.h>
+
+#include "gatekeeperd.h"
+
+using android::fuzzService;
+using android::GateKeeperProxy;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    auto gatekeeperService = new GateKeeperProxy();
+    fuzzService(gatekeeperService, FuzzedDataProvider(data, size));
+    return 0;
+}
\ No newline at end of file
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index d2fc651..bdfb7f6 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -13,11 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #define LOG_TAG "gatekeeperd"
 
-#include <android/service/gatekeeper/BnGateKeeperService.h>
-#include <gatekeeper/GateKeeperResponse.h>
+#include "gatekeeperd.h"
 
 #include <endian.h>
 #include <errno.h>
@@ -39,25 +37,18 @@
 #include <log/log.h>
 #include <utils/String16.h>
 
-#include <aidl/android/hardware/gatekeeper/IGatekeeper.h>
 #include <aidl/android/hardware/security/keymint/HardwareAuthToken.h>
 #include <aidl/android/security/authorization/IKeystoreAuthorization.h>
-#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
 #include <hidl/HidlSupport.h>
 
 using android::sp;
 using android::hardware::Return;
 using android::hardware::gatekeeper::V1_0::GatekeeperResponse;
 using android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;
-using android::hardware::gatekeeper::V1_0::IGatekeeper;
 
 using AidlGatekeeperEnrollResp = aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse;
 using AidlGatekeeperVerifyResp = aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse;
-using AidlIGatekeeper = aidl::android::hardware::gatekeeper::IGatekeeper;
 
-using ::android::binder::Status;
-using ::android::service::gatekeeper::BnGateKeeperService;
-using GKResponse = ::android::service::gatekeeper::GateKeeperResponse;
 using GKResponseCode = ::android::service::gatekeeper::ResponseCode;
 using ::aidl::android::hardware::security::keymint::HardwareAuthenticatorType;
 using ::aidl::android::hardware::security::keymint::HardwareAuthToken;
@@ -70,176 +61,184 @@
 static const String16 DUMP_PERMISSION("android.permission.DUMP");
 constexpr const char gatekeeperServiceName[] = "android.hardware.gatekeeper.IGatekeeper/default";
 
-class GateKeeperProxy : public BnGateKeeperService {
-  public:
-    GateKeeperProxy() {
-        clear_state_if_needed_done = false;
-        if (AServiceManager_isDeclared(gatekeeperServiceName)) {
-            ::ndk::SpAIBinder ks2Binder(AServiceManager_waitForService(gatekeeperServiceName));
-            aidl_hw_device = AidlIGatekeeper::fromBinder(ks2Binder);
-        }
-        if (!aidl_hw_device) {
-            hw_device = IGatekeeper::getService();
-        }
-        is_running_gsi = android::base::GetBoolProperty(android::gsi::kGsiBootedProp, false);
+GateKeeperProxy::GateKeeperProxy() {
+    clear_state_if_needed_done = false;
+    if (AServiceManager_isDeclared(gatekeeperServiceName)) {
+        ::ndk::SpAIBinder ks2Binder(AServiceManager_waitForService(gatekeeperServiceName));
+        aidl_hw_device = AidlIGatekeeper::fromBinder(ks2Binder);
+    }
+    if (!aidl_hw_device) {
+        hw_device = IGatekeeper::getService();
+    }
+    is_running_gsi = android::base::GetBoolProperty(android::gsi::kGsiBootedProp, false);
 
-        if (!aidl_hw_device && !hw_device) {
-            LOG(ERROR) << "Could not find Gatekeeper device, which makes me very sad.";
+    if (!aidl_hw_device && !hw_device) {
+        LOG(ERROR) << "Could not find Gatekeeper device, which makes me very sad.";
+    }
+}
+
+void GateKeeperProxy::store_sid(uint32_t userId, uint64_t sid) {
+    char filename[21];
+    snprintf(filename, sizeof(filename), "%u", userId);
+    int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
+    if (fd < 0) {
+        ALOGE("could not open file: %s: %s", filename, strerror(errno));
+        return;
+    }
+    write(fd, &sid, sizeof(sid));
+    close(fd);
+}
+
+void GateKeeperProxy::clear_state_if_needed() {
+    if (clear_state_if_needed_done) {
+        return;
+    }
+
+    if (mark_cold_boot() && !is_running_gsi) {
+        ALOGI("cold boot: clearing state");
+        if (aidl_hw_device) {
+            aidl_hw_device->deleteAllUsers();
+        } else if (hw_device) {
+            hw_device->deleteAllUsers([](const GatekeeperResponse&) {});
         }
     }
 
-    virtual ~GateKeeperProxy() {}
+    clear_state_if_needed_done = true;
+}
 
-    void store_sid(uint32_t userId, uint64_t sid) {
-        char filename[21];
-        snprintf(filename, sizeof(filename), "%u", userId);
+bool GateKeeperProxy::mark_cold_boot() {
+    const char* filename = ".coldboot";
+    if (access(filename, F_OK) == -1) {
         int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
         if (fd < 0) {
-            ALOGE("could not open file: %s: %s", filename, strerror(errno));
-            return;
+            ALOGE("could not open file: %s : %s", filename, strerror(errno));
+            return false;
         }
-        write(fd, &sid, sizeof(sid));
         close(fd);
+        return true;
+    }
+    return false;
+}
+
+void GateKeeperProxy::maybe_store_sid(uint32_t userId, uint64_t sid) {
+    char filename[21];
+    snprintf(filename, sizeof(filename), "%u", userId);
+    if (access(filename, F_OK) == -1) {
+        store_sid(userId, sid);
+    }
+}
+
+uint64_t GateKeeperProxy::read_sid(uint32_t userId) {
+    char filename[21];
+    uint64_t sid;
+    snprintf(filename, sizeof(filename), "%u", userId);
+    int fd = open(filename, O_RDONLY);
+    if (fd < 0) return 0;
+    read(fd, &sid, sizeof(sid));
+    close(fd);
+    return sid;
+}
+
+void GateKeeperProxy::clear_sid(uint32_t userId) {
+    char filename[21];
+    snprintf(filename, sizeof(filename), "%u", userId);
+    if (remove(filename) < 0 && errno != ENOENT) {
+        ALOGE("%s: could not remove file [%s], attempting 0 write", __func__, strerror(errno));
+        store_sid(userId, 0);
+    }
+}
+
+Status GateKeeperProxy::adjust_userId(uint32_t userId, uint32_t* hw_userId) {
+    static constexpr uint32_t kGsiOffset = 1000000;
+    if (userId >= kGsiOffset) {
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
     }
 
-    void clear_state_if_needed() {
-        if (clear_state_if_needed_done) {
-            return;
-        }
-
-        if (mark_cold_boot() && !is_running_gsi) {
-            ALOGI("cold boot: clearing state");
-            if (aidl_hw_device) {
-                aidl_hw_device->deleteAllUsers();
-            } else if (hw_device) {
-                hw_device->deleteAllUsers([](const GatekeeperResponse&) {});
-            }
-        }
-
-        clear_state_if_needed_done = true;
+    if ((aidl_hw_device == nullptr) && (hw_device == nullptr)) {
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
     }
 
-    bool mark_cold_boot() {
-        const char* filename = ".coldboot";
-        if (access(filename, F_OK) == -1) {
-            int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
-            if (fd < 0) {
-                ALOGE("could not open file: %s : %s", filename, strerror(errno));
-                return false;
-            }
-            close(fd);
-            return true;
-        }
-        return false;
+    if (is_running_gsi) {
+        *hw_userId = userId + kGsiOffset;
+        return Status::ok();
     }
-
-    void maybe_store_sid(uint32_t userId, uint64_t sid) {
-        char filename[21];
-        snprintf(filename, sizeof(filename), "%u", userId);
-        if (access(filename, F_OK) == -1) {
-            store_sid(userId, sid);
-        }
-    }
-
-    uint64_t read_sid(uint32_t userId) {
-        char filename[21];
-        uint64_t sid;
-        snprintf(filename, sizeof(filename), "%u", userId);
-        int fd = open(filename, O_RDONLY);
-        if (fd < 0) return 0;
-        read(fd, &sid, sizeof(sid));
-        close(fd);
-        return sid;
-    }
-
-    void clear_sid(uint32_t userId) {
-        char filename[21];
-        snprintf(filename, sizeof(filename), "%u", userId);
-        if (remove(filename) < 0 && errno != ENOENT) {
-            ALOGE("%s: could not remove file [%s], attempting 0 write", __func__, strerror(errno));
-            store_sid(userId, 0);
-        }
-    }
-
-    // This should only be called on userIds being passed to the GateKeeper HAL. It ensures that
-    // secure storage shared across a GSI image and a host image will not overlap.
-    uint32_t adjust_userId(uint32_t userId) {
-        static constexpr uint32_t kGsiOffset = 1000000;
-        CHECK(userId < kGsiOffset);
-        CHECK((aidl_hw_device != nullptr) || (hw_device != nullptr));
-        if (is_running_gsi) {
-            return userId + kGsiOffset;
-        }
-        return userId;
-    }
+    *hw_userId = userId;
+    return Status::ok();
+}
 
 #define GK_ERROR *gkResponse = GKResponse::error(), Status::ok()
 
-    Status enroll(int32_t userId, const std::optional<std::vector<uint8_t>>& currentPasswordHandle,
-                  const std::optional<std::vector<uint8_t>>& currentPassword,
-                  const std::vector<uint8_t>& desiredPassword, GKResponse* gkResponse) override {
-        IPCThreadState* ipc = IPCThreadState::self();
-        const int calling_pid = ipc->getCallingPid();
-        const int calling_uid = ipc->getCallingUid();
-        if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
-            return GK_ERROR;
-        }
+Status GateKeeperProxy::enroll(int32_t userId,
+                               const std::optional<std::vector<uint8_t>>& currentPasswordHandle,
+                               const std::optional<std::vector<uint8_t>>& currentPassword,
+                               const std::vector<uint8_t>& desiredPassword,
+                               GKResponse* gkResponse) {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int calling_pid = ipc->getCallingPid();
+    const int calling_uid = ipc->getCallingUid();
+    if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+        return GK_ERROR;
+    }
 
-        // Make sure to clear any state from before factory reset as soon as a credential is
-        // enrolled (which may happen during device setup).
-        clear_state_if_needed();
+    // Make sure to clear any state from before factory reset as soon as a credential is
+    // enrolled (which may happen during device setup).
+    clear_state_if_needed();
 
-        // need a desired password to enroll
-        if (desiredPassword.size() == 0) return GK_ERROR;
+    // need a desired password to enroll
+    if (desiredPassword.size() == 0) return GK_ERROR;
 
-        if (!aidl_hw_device && !hw_device) {
-            LOG(ERROR) << "has no HAL to talk to";
-            return GK_ERROR;
-        }
+    if (!aidl_hw_device && !hw_device) {
+        LOG(ERROR) << "has no HAL to talk to";
+        return GK_ERROR;
+    }
 
-        android::hardware::hidl_vec<uint8_t> curPwdHandle;
-        android::hardware::hidl_vec<uint8_t> curPwd;
+    android::hardware::hidl_vec<uint8_t> curPwdHandle;
+    android::hardware::hidl_vec<uint8_t> curPwd;
 
-        if (currentPasswordHandle && currentPassword) {
-            if (hw_device) {
-                // Hidl Implementations expects passwordHandle to be in
-                // gatekeeper::password_handle_t format.
-                if (currentPasswordHandle->size() != sizeof(gatekeeper::password_handle_t)) {
-                    LOG(INFO) << "Password handle has wrong length";
-                    return GK_ERROR;
-                }
-            }
-            curPwdHandle.setToExternal(const_cast<uint8_t*>(currentPasswordHandle->data()),
-                                       currentPasswordHandle->size());
-            curPwd.setToExternal(const_cast<uint8_t*>(currentPassword->data()),
-                                 currentPassword->size());
-        }
-
-        android::hardware::hidl_vec<uint8_t> newPwd;
-        newPwd.setToExternal(const_cast<uint8_t*>(desiredPassword.data()), desiredPassword.size());
-
-        uint32_t hw_userId = adjust_userId(userId);
-        uint64_t secureUserId = 0;
-        if (aidl_hw_device) {
-            // AIDL gatekeeper service
-            AidlGatekeeperEnrollResp rsp;
-            auto result = aidl_hw_device->enroll(hw_userId, curPwdHandle, curPwd, newPwd, &rsp);
-            if (!result.isOk()) {
-                LOG(ERROR) << "enroll transaction failed";
+    if (currentPasswordHandle && currentPassword) {
+        if (hw_device) {
+            // Hidl Implementations expects passwordHandle to be in
+            // gatekeeper::password_handle_t format.
+            if (currentPasswordHandle->size() != sizeof(gatekeeper::password_handle_t)) {
+                LOG(INFO) << "Password handle has wrong length";
                 return GK_ERROR;
             }
-            if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) {
-                *gkResponse = GKResponse::ok({rsp.data.begin(), rsp.data.end()});
-                secureUserId = static_cast<uint64_t>(rsp.secureUserId);
-            } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT &&
-                       rsp.timeoutMs > 0) {
-                *gkResponse = GKResponse::retry(rsp.timeoutMs);
-            } else {
-                *gkResponse = GKResponse::error();
-            }
-        } else if (hw_device) {
-            // HIDL gatekeeper service
-            Return<void> hwRes = hw_device->enroll(
+        }
+        curPwdHandle.setToExternal(const_cast<uint8_t*>(currentPasswordHandle->data()),
+                                   currentPasswordHandle->size());
+        curPwd.setToExternal(const_cast<uint8_t*>(currentPassword->data()),
+                             currentPassword->size());
+    }
+
+    android::hardware::hidl_vec<uint8_t> newPwd;
+    newPwd.setToExternal(const_cast<uint8_t*>(desiredPassword.data()), desiredPassword.size());
+
+    uint32_t hw_userId = 0;
+    Status result = adjust_userId(userId, &hw_userId);
+    if (!result.isOk()) {
+        return result;
+    }
+
+    uint64_t secureUserId = 0;
+    if (aidl_hw_device) {
+        // AIDL gatekeeper service
+        AidlGatekeeperEnrollResp rsp;
+        auto result = aidl_hw_device->enroll(hw_userId, curPwdHandle, curPwd, newPwd, &rsp);
+        if (!result.isOk()) {
+            LOG(ERROR) << "enroll transaction failed";
+            return GK_ERROR;
+        }
+        if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) {
+            *gkResponse = GKResponse::ok({rsp.data.begin(), rsp.data.end()});
+            secureUserId = static_cast<uint64_t>(rsp.secureUserId);
+        } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT && rsp.timeoutMs > 0) {
+            *gkResponse = GKResponse::retry(rsp.timeoutMs);
+        } else {
+            *gkResponse = GKResponse::error();
+        }
+    } else if (hw_device) {
+        // HIDL gatekeeper service
+        Return<void> hwRes = hw_device->enroll(
                 hw_userId, curPwdHandle, curPwd, newPwd,
                 [&gkResponse](const GatekeeperResponse& rsp) {
                     if (rsp.code >= GatekeeperStatusCode::STATUS_OK) {
@@ -251,110 +250,115 @@
                         *gkResponse = GKResponse::error();
                     }
                 });
-            if (!hwRes.isOk()) {
-                LOG(ERROR) << "enroll transaction failed";
+        if (!hwRes.isOk()) {
+            LOG(ERROR) << "enroll transaction failed";
+            return GK_ERROR;
+        }
+        if (gkResponse->response_code() == GKResponseCode::OK) {
+            if (gkResponse->payload().size() != sizeof(gatekeeper::password_handle_t)) {
+                LOG(ERROR) << "HAL returned password handle of invalid length "
+                           << gkResponse->payload().size();
                 return GK_ERROR;
             }
-            if (gkResponse->response_code() == GKResponseCode::OK) {
-                if (gkResponse->payload().size() != sizeof(gatekeeper::password_handle_t)) {
-                    LOG(ERROR) << "HAL returned password handle of invalid length "
-                               << gkResponse->payload().size();
-                    return GK_ERROR;
-                }
 
-                const gatekeeper::password_handle_t* handle =
+            const gatekeeper::password_handle_t* handle =
                     reinterpret_cast<const gatekeeper::password_handle_t*>(
-                        gkResponse->payload().data());
-                secureUserId = handle->user_id;
-            }
+                            gkResponse->payload().data());
+            secureUserId = handle->user_id;
         }
-
-        if (gkResponse->response_code() == GKResponseCode::OK && !gkResponse->should_reenroll()) {
-            store_sid(userId, secureUserId);
-
-            GKResponse verifyResponse;
-            // immediately verify this password so we don't ask the user to enter it again
-            // if they just created it.
-            auto status = verify(userId, gkResponse->payload(), desiredPassword, &verifyResponse);
-            if (!status.isOk() || verifyResponse.response_code() != GKResponseCode::OK) {
-                LOG(ERROR) << "Failed to verify password after enrolling";
-            }
-        }
-
-        return Status::ok();
     }
 
-    Status verify(int32_t userId, const ::std::vector<uint8_t>& enrolledPasswordHandle,
-                  const ::std::vector<uint8_t>& providedPassword, GKResponse* gkResponse) override {
-        return verifyChallenge(userId, 0 /* challenge */, enrolledPasswordHandle, providedPassword,
-                               gkResponse);
+    if (gkResponse->response_code() == GKResponseCode::OK && !gkResponse->should_reenroll()) {
+        store_sid(userId, secureUserId);
+
+        GKResponse verifyResponse;
+        // immediately verify this password so we don't ask the user to enter it again
+        // if they just created it.
+        auto status = verify(userId, gkResponse->payload(), desiredPassword, &verifyResponse);
+        if (!status.isOk() || verifyResponse.response_code() != GKResponseCode::OK) {
+            LOG(ERROR) << "Failed to verify password after enrolling";
+        }
     }
 
-    Status verifyChallenge(int32_t userId, int64_t challenge,
-                           const std::vector<uint8_t>& enrolledPasswordHandle,
-                           const std::vector<uint8_t>& providedPassword,
-                           GKResponse* gkResponse) override {
-        IPCThreadState* ipc = IPCThreadState::self();
-        const int calling_pid = ipc->getCallingPid();
-        const int calling_uid = ipc->getCallingUid();
-        if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+    return Status::ok();
+}
+
+Status GateKeeperProxy::verify(int32_t userId, const ::std::vector<uint8_t>& enrolledPasswordHandle,
+                               const ::std::vector<uint8_t>& providedPassword,
+                               GKResponse* gkResponse) {
+    return verifyChallenge(userId, 0 /* challenge */, enrolledPasswordHandle, providedPassword,
+                           gkResponse);
+}
+
+Status GateKeeperProxy::verifyChallenge(int32_t userId, int64_t challenge,
+                                        const std::vector<uint8_t>& enrolledPasswordHandle,
+                                        const std::vector<uint8_t>& providedPassword,
+                                        GKResponse* gkResponse) {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int calling_pid = ipc->getCallingPid();
+    const int calling_uid = ipc->getCallingUid();
+    if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+        return GK_ERROR;
+    }
+
+    // can't verify if we're missing either param
+    if (enrolledPasswordHandle.size() == 0 || providedPassword.size() == 0) return GK_ERROR;
+
+    if (!aidl_hw_device && !hw_device) {
+        LOG(ERROR) << "has no HAL to talk to";
+        return GK_ERROR;
+    }
+
+    if (hw_device) {
+        // Hidl Implementations expects passwordHandle to be in gatekeeper::password_handle_t
+        if (enrolledPasswordHandle.size() != sizeof(gatekeeper::password_handle_t)) {
+            LOG(INFO) << "Password handle has wrong length";
             return GK_ERROR;
         }
+    }
 
-        // can't verify if we're missing either param
-        if (enrolledPasswordHandle.size() == 0 || providedPassword.size() == 0) return GK_ERROR;
+    uint32_t hw_userId = 0;
+    Status result = adjust_userId(userId, &hw_userId);
+    if (!result.isOk()) {
+        return result;
+    }
 
-        if (!aidl_hw_device && !hw_device) {
-            LOG(ERROR) << "has no HAL to talk to";
+    android::hardware::hidl_vec<uint8_t> curPwdHandle;
+    curPwdHandle.setToExternal(const_cast<uint8_t*>(enrolledPasswordHandle.data()),
+                               enrolledPasswordHandle.size());
+    android::hardware::hidl_vec<uint8_t> enteredPwd;
+    enteredPwd.setToExternal(const_cast<uint8_t*>(providedPassword.data()),
+                             providedPassword.size());
+
+    uint64_t secureUserId = 0;
+    if (aidl_hw_device) {
+        // AIDL gatekeeper service
+        AidlGatekeeperVerifyResp rsp;
+        auto result = aidl_hw_device->verify(hw_userId, challenge, curPwdHandle, enteredPwd, &rsp);
+        if (!result.isOk()) {
+            LOG(ERROR) << "verify transaction failed";
             return GK_ERROR;
         }
-
-        if (hw_device) {
-            // Hidl Implementations expects passwordHandle to be in gatekeeper::password_handle_t
-            if (enrolledPasswordHandle.size() != sizeof(gatekeeper::password_handle_t)) {
-                LOG(INFO) << "Password handle has wrong length";
-                return GK_ERROR;
-            }
+        if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) {
+            secureUserId = rsp.hardwareAuthToken.userId;
+            // Serialize HardwareAuthToken to a vector as hw_auth_token_t.
+            *gkResponse = GKResponse::ok(
+                    authToken2AidlVec(rsp.hardwareAuthToken),
+                    rsp.statusCode == AidlIGatekeeper::STATUS_REENROLL /* reenroll */);
+        } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT) {
+            *gkResponse = GKResponse::retry(rsp.timeoutMs);
+        } else {
+            *gkResponse = GKResponse::error();
         }
-
-        uint32_t hw_userId = adjust_userId(userId);
-        android::hardware::hidl_vec<uint8_t> curPwdHandle;
-        curPwdHandle.setToExternal(const_cast<uint8_t*>(enrolledPasswordHandle.data()),
-                                   enrolledPasswordHandle.size());
-        android::hardware::hidl_vec<uint8_t> enteredPwd;
-        enteredPwd.setToExternal(const_cast<uint8_t*>(providedPassword.data()),
-                                 providedPassword.size());
-
-        uint64_t secureUserId = 0;
-        if (aidl_hw_device) {
-            // AIDL gatekeeper service
-            AidlGatekeeperVerifyResp rsp;
-            auto result =
-                aidl_hw_device->verify(hw_userId, challenge, curPwdHandle, enteredPwd, &rsp);
-            if (!result.isOk()) {
-                LOG(ERROR) << "verify transaction failed";
-                return GK_ERROR;
-            }
-            if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) {
-                secureUserId = rsp.hardwareAuthToken.userId;
-                // Serialize HardwareAuthToken to a vector as hw_auth_token_t.
-                *gkResponse = GKResponse::ok(authToken2AidlVec(rsp.hardwareAuthToken),
-                                             rsp.statusCode ==
-                                                 AidlIGatekeeper::STATUS_REENROLL /* reenroll */);
-            } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT) {
-                *gkResponse = GKResponse::retry(rsp.timeoutMs);
-            } else {
-                *gkResponse = GKResponse::error();
-            }
-        } else if (hw_device) {
-            // HIDL gatekeeper service
-            Return<void> hwRes = hw_device->verify(
+    } else if (hw_device) {
+        // HIDL gatekeeper service
+        Return<void> hwRes = hw_device->verify(
                 hw_userId, challenge, curPwdHandle, enteredPwd,
                 [&gkResponse](const GatekeeperResponse& rsp) {
                     if (rsp.code >= GatekeeperStatusCode::STATUS_OK) {
                         *gkResponse = GKResponse::ok(
-                            {rsp.data.begin(), rsp.data.end()},
-                            rsp.code == GatekeeperStatusCode::STATUS_REENROLL /* reenroll */);
+                                {rsp.data.begin(), rsp.data.end()},
+                                rsp.code == GatekeeperStatusCode::STATUS_REENROLL /* reenroll */);
                     } else if (rsp.code == GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) {
                         *gkResponse = GKResponse::retry(rsp.timeout);
                     } else {
@@ -362,149 +366,115 @@
                     }
                 });
 
-            if (!hwRes.isOk()) {
-                LOG(ERROR) << "verify transaction failed";
-                return GK_ERROR;
-            }
-            const gatekeeper::password_handle_t* handle =
-                reinterpret_cast<const gatekeeper::password_handle_t*>(
-                    enrolledPasswordHandle.data());
-            secureUserId = handle->user_id;
+        if (!hwRes.isOk()) {
+            LOG(ERROR) << "verify transaction failed";
+            return GK_ERROR;
         }
+        const gatekeeper::password_handle_t* handle =
+                reinterpret_cast<const gatekeeper::password_handle_t*>(
+                        enrolledPasswordHandle.data());
+        secureUserId = handle->user_id;
+    }
 
-        if (gkResponse->response_code() == GKResponseCode::OK) {
-            if (gkResponse->payload().size() != 0) {
-                // try to connect to IKeystoreAuthorization AIDL service first.
-                AIBinder* authzAIBinder =
-                        AServiceManager_getService("android.security.authorization");
-                ::ndk::SpAIBinder authzBinder(authzAIBinder);
-                auto authzService = IKeystoreAuthorization::fromBinder(authzBinder);
-                if (authzService) {
-                    if (gkResponse->payload().size() != sizeof(hw_auth_token_t)) {
-                        LOG(ERROR) << "Incorrect size of AuthToken payload.";
-                        return GK_ERROR;
-                    }
-
-                    const hw_auth_token_t* hwAuthToken =
-                            reinterpret_cast<const hw_auth_token_t*>(gkResponse->payload().data());
-                    HardwareAuthToken authToken;
-
-                    authToken.timestamp.milliSeconds = betoh64(hwAuthToken->timestamp);
-                    authToken.challenge = hwAuthToken->challenge;
-                    authToken.userId = hwAuthToken->user_id;
-                    authToken.authenticatorId = hwAuthToken->authenticator_id;
-                    authToken.authenticatorType = static_cast<HardwareAuthenticatorType>(
-                            betoh32(hwAuthToken->authenticator_type));
-                    authToken.mac.assign(&hwAuthToken->hmac[0], &hwAuthToken->hmac[32]);
-                    auto result = authzService->addAuthToken(authToken);
-                    if (!result.isOk()) {
-                        LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService.";
-                        return GK_ERROR;
-                    }
-                } else {
-                    LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with "
-                                  "Keystore.";
+    if (gkResponse->response_code() == GKResponseCode::OK) {
+        if (gkResponse->payload().size() != 0) {
+            // try to connect to IKeystoreAuthorization AIDL service first.
+            AIBinder* authzAIBinder = AServiceManager_getService("android.security.authorization");
+            ::ndk::SpAIBinder authzBinder(authzAIBinder);
+            auto authzService = IKeystoreAuthorization::fromBinder(authzBinder);
+            if (authzService) {
+                if (gkResponse->payload().size() != sizeof(hw_auth_token_t)) {
+                    LOG(ERROR) << "Incorrect size of AuthToken payload.";
                     return GK_ERROR;
                 }
+
+                const hw_auth_token_t* hwAuthToken =
+                        reinterpret_cast<const hw_auth_token_t*>(gkResponse->payload().data());
+                HardwareAuthToken authToken;
+
+                authToken.timestamp.milliSeconds = betoh64(hwAuthToken->timestamp);
+                authToken.challenge = hwAuthToken->challenge;
+                authToken.userId = hwAuthToken->user_id;
+                authToken.authenticatorId = hwAuthToken->authenticator_id;
+                authToken.authenticatorType = static_cast<HardwareAuthenticatorType>(
+                        betoh32(hwAuthToken->authenticator_type));
+                authToken.mac.assign(&hwAuthToken->hmac[0], &hwAuthToken->hmac[32]);
+                auto result = authzService->addAuthToken(authToken);
+                if (!result.isOk()) {
+                    LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService.";
+                    return GK_ERROR;
+                }
+            } else {
+                LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with "
+                              "Keystore.";
+                return GK_ERROR;
             }
-
-            maybe_store_sid(userId, secureUserId);
         }
 
-        return Status::ok();
+        maybe_store_sid(userId, secureUserId);
     }
 
-    Status getSecureUserId(int32_t userId, int64_t* sid) override {
-        *sid = read_sid(userId);
-        return Status::ok();
-    }
-
-    Status clearSecureUserId(int32_t userId) override {
-        IPCThreadState* ipc = IPCThreadState::self();
-        const int calling_pid = ipc->getCallingPid();
-        const int calling_uid = ipc->getCallingUid();
-        if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
-            ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid);
-            return Status::ok();
-        }
-        clear_sid(userId);
-
-        uint32_t hw_userId = adjust_userId(userId);
-        if (aidl_hw_device) {
-            aidl_hw_device->deleteUser(hw_userId);
-        } else if (hw_device) {
-            hw_device->deleteUser(hw_userId, [](const GatekeeperResponse&) {});
-        }
-        return Status::ok();
-    }
-
-    Status reportDeviceSetupComplete() override {
-        IPCThreadState* ipc = IPCThreadState::self();
-        const int calling_pid = ipc->getCallingPid();
-        const int calling_uid = ipc->getCallingUid();
-        if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
-            ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid);
-            return Status::ok();
-        }
-
-        clear_state_if_needed();
-        return Status::ok();
-    }
-
-    status_t dump(int fd, const Vector<String16>&) override {
-        IPCThreadState* ipc = IPCThreadState::self();
-        const int pid = ipc->getCallingPid();
-        const int uid = ipc->getCallingUid();
-        if (!PermissionCache::checkPermission(DUMP_PERMISSION, pid, uid)) {
-            return PERMISSION_DENIED;
-        }
-
-        if (aidl_hw_device == nullptr && hw_device == nullptr) {
-            const char* result = "Device not available";
-            write(fd, result, strlen(result) + 1);
-        } else {
-            const char* result = "OK";
-            write(fd, result, strlen(result) + 1);
-        }
-
-        return OK;
-    }
-
-  private:
-    // AIDL gatekeeper service.
-    std::shared_ptr<AidlIGatekeeper> aidl_hw_device;
-    // HIDL gatekeeper service.
-    sp<IGatekeeper> hw_device;
-
-    bool clear_state_if_needed_done;
-    bool is_running_gsi;
-};
-}  // namespace android
-
-int main(int argc, char* argv[]) {
-    ALOGI("Starting gatekeeperd...");
-    if (argc < 2) {
-        ALOGE("A directory must be specified!");
-        return 1;
-    }
-    if (chdir(argv[1]) == -1) {
-        ALOGE("chdir: %s: %s", argv[1], strerror(errno));
-        return 1;
-    }
-
-    android::sp<android::IServiceManager> sm = android::defaultServiceManager();
-    android::sp<android::GateKeeperProxy> proxy = new android::GateKeeperProxy();
-    android::status_t ret =
-        sm->addService(android::String16("android.service.gatekeeper.IGateKeeperService"), proxy);
-    if (ret != android::OK) {
-        ALOGE("Couldn't register binder service!");
-        return -1;
-    }
-
-    /*
-     * We're the only thread in existence, so we're just going to process
-     * Binder transaction as a single-threaded program.
-     */
-    android::IPCThreadState::self()->joinThreadPool();
-    return 0;
+    return Status::ok();
 }
+
+Status GateKeeperProxy::getSecureUserId(int32_t userId, int64_t* sid) {
+    *sid = read_sid(userId);
+    return Status::ok();
+}
+
+Status GateKeeperProxy::clearSecureUserId(int32_t userId) {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int calling_pid = ipc->getCallingPid();
+    const int calling_uid = ipc->getCallingUid();
+    if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+        ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid);
+        return Status::ok();
+    }
+    clear_sid(userId);
+
+    uint32_t hw_userId = 0;
+    Status result = adjust_userId(userId, &hw_userId);
+    if (!result.isOk()) {
+        return result;
+    }
+
+    if (aidl_hw_device) {
+        aidl_hw_device->deleteUser(hw_userId);
+    } else if (hw_device) {
+        hw_device->deleteUser(hw_userId, [](const GatekeeperResponse&) {});
+    }
+    return Status::ok();
+}
+
+Status GateKeeperProxy::reportDeviceSetupComplete() {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int calling_pid = ipc->getCallingPid();
+    const int calling_uid = ipc->getCallingUid();
+    if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) {
+        ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid);
+        return Status::ok();
+    }
+
+    clear_state_if_needed();
+    return Status::ok();
+}
+
+status_t GateKeeperProxy::dump(int fd, const Vector<String16>&) {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int pid = ipc->getCallingPid();
+    const int uid = ipc->getCallingUid();
+    if (!PermissionCache::checkPermission(DUMP_PERMISSION, pid, uid)) {
+        return PERMISSION_DENIED;
+    }
+
+    if (aidl_hw_device == nullptr && hw_device == nullptr) {
+        const char* result = "Device not available";
+        write(fd, result, strlen(result) + 1);
+    } else {
+        const char* result = "OK";
+        write(fd, result, strlen(result) + 1);
+    }
+
+    return OK;
+}
+}  // namespace android
diff --git a/gatekeeperd/gatekeeperd.h b/gatekeeperd/gatekeeperd.h
new file mode 100644
index 0000000..b1f08c6
--- /dev/null
+++ b/gatekeeperd/gatekeeperd.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 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 <aidl/android/hardware/gatekeeper/IGatekeeper.h>
+#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
+#include <android/service/gatekeeper/BnGateKeeperService.h>
+#include <gatekeeper/GateKeeperResponse.h>
+
+using ::android::hardware::gatekeeper::V1_0::IGatekeeper;
+using AidlIGatekeeper = ::aidl::android::hardware::gatekeeper::IGatekeeper;
+using ::android::binder::Status;
+using ::android::service::gatekeeper::BnGateKeeperService;
+using GKResponse = ::android::service::gatekeeper::GateKeeperResponse;
+
+namespace android {
+
+class GateKeeperProxy : public BnGateKeeperService {
+  public:
+    GateKeeperProxy();
+
+    virtual ~GateKeeperProxy() {}
+
+    void store_sid(uint32_t userId, uint64_t sid);
+
+    void clear_state_if_needed();
+
+    bool mark_cold_boot();
+
+    void maybe_store_sid(uint32_t userId, uint64_t sid);
+
+    uint64_t read_sid(uint32_t userId);
+
+    void clear_sid(uint32_t userId);
+
+    // This should only be called on userIds being passed to the GateKeeper HAL. It ensures that
+    // secure storage shared across a GSI image and a host image will not overlap.
+    Status adjust_userId(uint32_t userId, uint32_t* hw_userId);
+
+#define GK_ERROR *gkResponse = GKResponse::error(), Status::ok()
+
+    Status enroll(int32_t userId, const std::optional<std::vector<uint8_t>>& currentPasswordHandle,
+                  const std::optional<std::vector<uint8_t>>& currentPassword,
+                  const std::vector<uint8_t>& desiredPassword, GKResponse* gkResponse) override;
+
+    Status verify(int32_t userId, const ::std::vector<uint8_t>& enrolledPasswordHandle,
+                  const ::std::vector<uint8_t>& providedPassword, GKResponse* gkResponse) override;
+
+    Status verifyChallenge(int32_t userId, int64_t challenge,
+                           const std::vector<uint8_t>& enrolledPasswordHandle,
+                           const std::vector<uint8_t>& providedPassword,
+                           GKResponse* gkResponse) override;
+
+    Status getSecureUserId(int32_t userId, int64_t* sid) override;
+
+    Status clearSecureUserId(int32_t userId) override;
+
+    Status reportDeviceSetupComplete() override;
+
+    status_t dump(int fd, const Vector<String16>&) override;
+
+  private:
+    // AIDL gatekeeper service.
+    std::shared_ptr<AidlIGatekeeper> aidl_hw_device;
+    // HIDL gatekeeper service.
+    sp<IGatekeeper> hw_device;
+
+    bool clear_state_if_needed_done;
+    bool is_running_gsi;
+};
+}  // namespace android
diff --git a/gatekeeperd/main.cpp b/gatekeeperd/main.cpp
new file mode 100644
index 0000000..a01f9de
--- /dev/null
+++ b/gatekeeperd/main.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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 <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+
+#include <log/log.h>
+
+#include "gatekeeperd.h"
+
+int main(int argc, char* argv[]) {
+    ALOGI("Starting gatekeeperd...");
+    if (argc < 2) {
+        ALOGE("A directory must be specified!");
+        return 1;
+    }
+    if (chdir(argv[1]) == -1) {
+        ALOGE("chdir: %s: %s", argv[1], strerror(errno));
+        return 1;
+    }
+
+    android::sp<android::IServiceManager> sm = android::defaultServiceManager();
+    android::sp<android::GateKeeperProxy> proxy = new android::GateKeeperProxy();
+    android::status_t ret = sm->addService(
+            android::String16("android.service.gatekeeper.IGateKeeperService"), proxy);
+    if (ret != android::OK) {
+        ALOGE("Couldn't register binder service!");
+        return 1;
+    }
+
+    /*
+     * We're the only thread in existence, so we're just going to process
+     * Binder transaction as a single-threaded program.
+     */
+    android::IPCThreadState::self()->joinThreadPool();
+    return 1;
+}
diff --git a/init/Android.bp b/init/Android.bp
index 7b52903..1af398a 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -169,6 +169,7 @@
         "libfsverity_init",
         "liblmkd_utils",
         "liblz4",
+        "libzstd",
         "libmini_keyctl_static",
         "libmodprobe",
         "libprocinfo",
@@ -213,8 +214,8 @@
     visibility: [":__subpackages__"],
 }
 
-cc_library_static {
-    name: "libinit",
+cc_defaults {
+    name: "libinit_defaults",
     recovery_available: true,
     defaults: [
         "init_defaults",
@@ -227,7 +228,6 @@
     ],
     whole_static_libs: [
         "libcap",
-        "libcom.android.sysprop.apex",
         "libcom.android.sysprop.init",
     ],
     header_libs: ["bootimg_headers"],
@@ -251,10 +251,17 @@
             ],
         },
     },
-    visibility: [
-        "//system/apex/apexd",
-        "//frameworks/native/cmds/installd",
-    ],
+}
+
+cc_library_static {
+    name: "libinit",
+    defaults: ["libinit_defaults"],
+}
+
+cc_library_static {
+    name: "libinit.microdroid",
+    defaults: ["libinit_defaults"],
+    cflags: ["-DMICRODROID=1"],
 }
 
 phony {
@@ -264,12 +271,11 @@
     ],
 }
 
-cc_binary {
-    name: "init_second_stage",
+cc_defaults {
+    name: "init_second_stage_defaults",
     recovery_available: true,
     stem: "init",
     defaults: ["init_defaults"],
-    static_libs: ["libinit"],
     srcs: ["main.cpp"],
     symlinks: ["ueventd"],
     target: {
@@ -303,9 +309,24 @@
             ],
         },
     },
+}
+
+cc_binary {
+    name: "init_second_stage",
+    defaults: ["init_second_stage_defaults"],
+    static_libs: ["libinit"],
+}
+
+cc_binary {
+    name: "init_second_stage.microdroid",
+    defaults: ["init_second_stage_defaults"],
+    static_libs: ["libinit.microdroid"],
+    cflags: ["-DMICRODROID=1"],
+    installable: false,
     visibility: ["//packages/modules/Virtualization/microdroid"],
 }
 
+
 soong_config_module_type {
     name: "init_first_stage_cc_defaults",
     module_type: "cc_defaults",
@@ -323,12 +344,8 @@
             installable: false,
         },
     },
-}
 
-cc_binary {
-    name: "init_first_stage",
     stem: "init",
-    defaults: ["init_first_stage_defaults"],
 
     srcs: [
         "block_dev_initializer.cpp",
@@ -370,6 +387,7 @@
         "libprotobuf-cpp-lite",
         "libsnapshot_cow",
         "liblz4",
+        "libzstd",
         "libsnapshot_init",
         "update_metadata-protos",
         "libprocinfo",
@@ -441,6 +459,18 @@
     install_in_root: true,
 }
 
+cc_binary {
+    name: "init_first_stage",
+    defaults: ["init_first_stage_defaults"],
+}
+
+cc_binary {
+    name: "init_first_stage.microdroid",
+    defaults: ["init_first_stage_defaults"],
+    cflags: ["-DMICRODROID=1"],
+    installable: false,
+}
+
 phony {
     name: "init_system",
     required: ["init_second_stage"],
diff --git a/init/README.md b/init/README.md
index 6bdff4a..5fced19 100644
--- a/init/README.md
+++ b/init/README.md
@@ -344,11 +344,14 @@
   intended to be used with the `exec_start` builtin for any must-have checks during boot.
 
 `restart_period <seconds>`
-> If a non-oneshot service exits, it will be restarted at its start time plus
-  this period. It defaults to 5s to rate limit crashing services.
-  This can be increased for services that are meant to run periodically. For
-  example, it may be set to 3600 to indicate that the service should run every hour
-  or 86400 to indicate that the service should run every day.
+> If a non-oneshot service exits, it will be restarted at its previous start time plus this period.
+  The default value is 5s. This can be used to implement periodic services together with the
+  `timeout_period` command below. For example, it may be set to 3600 to indicate that the service
+  should run every hour or 86400 to indicate that the service should run every day. This can be set
+  to a value shorter than 5s for example 0, but the minimum 5s delay is enforced if the restart was
+  due to a crash. This is to rate limit persistentally crashing services. In other words,
+  `<seconds>` smaller than 5 is respected only when the service exits deliverately and successfully
+  (i.e. by calling exit(0)).
 
 `rlimit <resource> <cur> <max>`
 > This applies the given rlimit to the service. rlimits are inherited by child
diff --git a/init/builtins.cpp b/init/builtins.cpp
index bc23972..2176233 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -60,6 +60,8 @@
 #include <cutils/android_reboot.h>
 #include <fs_mgr.h>
 #include <fscrypt/fscrypt.h>
+#include <libdm/dm.h>
+#include <libdm/loop_control.h>
 #include <libgsi/libgsi.h>
 #include <logwrap/logwrap.h>
 #include <private/android_filesystem_config.h>
@@ -506,29 +508,29 @@
 
     if (android::base::StartsWith(source, "loop@")) {
         int mode = (flags & MS_RDONLY) ? O_RDONLY : O_RDWR;
-        unique_fd fd(TEMP_FAILURE_RETRY(open(source + 5, mode | O_CLOEXEC)));
-        if (fd < 0) return ErrnoError() << "open(" << source + 5 << ", " << mode << ") failed";
+        const char* file_path = source + strlen("loop@");
 
-        for (size_t n = 0;; n++) {
-            std::string tmp = android::base::StringPrintf("/dev/block/loop%zu", n);
-            unique_fd loop(TEMP_FAILURE_RETRY(open(tmp.c_str(), mode | O_CLOEXEC)));
-            if (loop < 0) return ErrnoError() << "open(" << tmp << ", " << mode << ") failed";
-
-            loop_info info;
-            /* if it is a blank loop device */
-            if (ioctl(loop.get(), LOOP_GET_STATUS, &info) < 0 && errno == ENXIO) {
-                /* if it becomes our loop device */
-                if (ioctl(loop.get(), LOOP_SET_FD, fd.get()) >= 0) {
-                    if (mount(tmp.c_str(), target, system, flags, options) < 0) {
-                        ioctl(loop.get(), LOOP_CLR_FD, 0);
-                        return ErrnoError() << "mount() failed";
-                    }
-                    return {};
-                }
-            }
+        // Open source file
+        if (wait) {
+            wait_for_file(file_path, kCommandRetryTimeout);
         }
 
-        return Error() << "out of loopback devices";
+        unique_fd fd(TEMP_FAILURE_RETRY(open(file_path, mode | O_CLOEXEC)));
+        if (fd < 0) {
+            return ErrnoError() << "open(" << file_path << ", " << mode << ") failed";
+        }
+
+        // Allocate loop device and attach it to file_path.
+        android::dm::LoopControl loop_control;
+        std::string loop_device;
+        if (!loop_control.Attach(fd.get(), 5s, &loop_device)) {
+            return ErrnoError() << "loop_control.Attach " << file_path << " failed";
+        }
+
+        if (mount(loop_device.c_str(), target, system, flags, options) < 0) {
+            loop_control.Detach(loop_device);
+            return ErrnoError() << "mount() failed";
+        }
     } else {
         if (wait)
             wait_for_file(source, kCommandRetryTimeout);
@@ -575,7 +577,7 @@
  *
  * return code is processed based on input code
  */
-static Result<void> queue_fs_event(int code, bool userdata_remount) {
+static Result<void> queue_fs_event(int code) {
     if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
         SetProperty("ro.crypto.state", "unsupported");
         ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
@@ -589,27 +591,9 @@
         const std::vector<std::string> options = {"--wipe_data", "--reason=fs_mgr_mount_all" };
         return reboot_into_recovery(options);
         /* If reboot worked, there is no return. */
-    } else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED) {
-        if (!FscryptInstallKeyring()) {
-            return Error() << "FscryptInstallKeyring() failed";
-        }
-        SetProperty("ro.crypto.state", "encrypted");
-
-        // Although encrypted, we have device key, so we do not need to
-        // do anything different from the nonencrypted case.
-        ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
-        return {};
-    } else if (code == FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED) {
-        if (!FscryptInstallKeyring()) {
-            return Error() << "FscryptInstallKeyring() failed";
-        }
-        SetProperty("ro.crypto.state", "encrypted");
-
-        // Although encrypted, vold has already set the device up, so we do not need to
-        // do anything different from the nonencrypted case.
-        ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
-        return {};
-    } else if (code == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
+    } else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED ||
+               code == FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED ||
+               code == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
         if (!FscryptInstallKeyring()) {
             return Error() << "FscryptInstallKeyring() failed";
         }
@@ -681,7 +665,7 @@
     if (queue_event) {
         /* queue_fs_event will queue event based on mount_fstab return code
          * and return processed return code*/
-        auto queue_fs_result = queue_fs_event(mount_fstab_result.code, false);
+        auto queue_fs_result = queue_fs_event(mount_fstab_result.code);
         if (!queue_fs_result.ok()) {
             return Error() << "queue_fs_event() failed: " << queue_fs_result.error();
         }
@@ -1215,7 +1199,7 @@
                                          "/metadata/userspacereboot/mount_info.txt");
         trigger_shutdown("reboot,mount_userdata_failed");
     }
-    if (auto result = queue_fs_event(initial_mount_fstab_return_code, true); !result.ok()) {
+    if (auto result = queue_fs_event(initial_mount_fstab_return_code); !result.ok()) {
         return Error() << "queue_fs_event() failed: " << result.error();
     }
     return {};
diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp
index b9fa58c..3c012fe 100644
--- a/init/firmware_handler.cpp
+++ b/init/firmware_handler.cpp
@@ -33,6 +33,7 @@
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
@@ -43,6 +44,7 @@
 using android::base::Timer;
 using android::base::Trim;
 using android::base::unique_fd;
+using android::base::WaitForProperty;
 using android::base::WriteFully;
 
 namespace android {
@@ -82,6 +84,33 @@
     return access("/dev/.booting", F_OK) == 0;
 }
 
+static bool IsApexActivated() {
+    static bool apex_activated = []() {
+        // Wait for com.android.runtime.apex activation
+        // Property name and value must be kept in sync with system/apexd/apex/apex_constants.h
+        // 60s is the default firmware sysfs fallback timeout. (/sys/class/firmware/timeout)
+        if (!WaitForProperty("apexd.status", "activated", 60s)) {
+            LOG(ERROR) << "Apexd activation wait timeout";
+            return false;
+        }
+        return true;
+    }();
+
+    return apex_activated;
+}
+
+static bool NeedsRerunExternalHandler() {
+    static bool first = true;
+
+    // Rerun external handler only on the first try and when apex is activated
+    if (first) {
+        first = false;
+        return IsApexActivated();
+    }
+
+    return first;
+}
+
 ExternalFirmwareHandler::ExternalFirmwareHandler(std::string devpath, uid_t uid, gid_t gid,
                                                  std::string handler_path)
     : devpath(std::move(devpath)), uid(uid), gid(gid), handler_path(std::move(handler_path)) {
@@ -210,6 +239,11 @@
 
             auto result = RunExternalHandler(external_handler.handler_path, external_handler.uid,
                                              external_handler.gid, uevent);
+            if (!result.ok() && NeedsRerunExternalHandler()) {
+                auto res = RunExternalHandler(external_handler.handler_path, external_handler.uid,
+                                              external_handler.gid, uevent);
+                result = std::move(res);
+            }
             if (!result.ok()) {
                 LOG(ERROR) << "Using default firmware; External firmware handler failed: "
                            << result.error();
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 107e99a..7fabbac 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -58,6 +58,12 @@
 
 namespace {
 
+enum class BootMode {
+    NORMAL_MODE,
+    RECOVERY_MODE,
+    CHARGER_MODE,
+};
+
 void FreeRamdisk(DIR* dir, dev_t dev) {
     int dfd = dirfd(dir);
 
@@ -149,13 +155,27 @@
 }
 }  // namespace
 
-std::string GetModuleLoadList(bool recovery, const std::string& dir_path) {
-    auto module_load_file = "modules.load";
-    if (recovery) {
-        struct stat fileStat;
-        std::string recovery_load_path = dir_path + "/modules.load.recovery";
-        if (!stat(recovery_load_path.c_str(), &fileStat)) {
+std::string GetModuleLoadList(BootMode boot_mode, const std::string& dir_path) {
+    std::string module_load_file;
+
+    switch (boot_mode) {
+        case BootMode::NORMAL_MODE:
+            module_load_file = "modules.load";
+            break;
+        case BootMode::RECOVERY_MODE:
             module_load_file = "modules.load.recovery";
+            break;
+        case BootMode::CHARGER_MODE:
+            module_load_file = "modules.load.charger";
+            break;
+    }
+
+    if (module_load_file != "modules.load") {
+        struct stat fileStat;
+        std::string load_path = dir_path + "/" + module_load_file;
+        // Fall back to modules.load if the other files aren't accessible
+        if (stat(load_path.c_str(), &fileStat)) {
+            module_load_file = "modules.load";
         }
     }
 
@@ -163,7 +183,8 @@
 }
 
 #define MODULE_BASE_DIR "/lib/modules"
-bool LoadKernelModules(bool recovery, bool want_console, bool want_parallel, int& modules_loaded) {
+bool LoadKernelModules(BootMode boot_mode, bool want_console, bool want_parallel,
+                       int& modules_loaded) {
     struct utsname uts;
     if (uname(&uts)) {
         LOG(FATAL) << "Failed to get kernel version.";
@@ -203,7 +224,7 @@
     for (const auto& module_dir : module_dirs) {
         std::string dir_path = MODULE_BASE_DIR "/";
         dir_path.append(module_dir);
-        Modprobe m({dir_path}, GetModuleLoadList(recovery, dir_path));
+        Modprobe m({dir_path}, GetModuleLoadList(boot_mode, dir_path));
         bool retval = m.LoadListedModules(!want_console);
         modules_loaded = m.GetModuleCount();
         if (modules_loaded > 0) {
@@ -211,7 +232,7 @@
         }
     }
 
-    Modprobe m({MODULE_BASE_DIR}, GetModuleLoadList(recovery, MODULE_BASE_DIR));
+    Modprobe m({MODULE_BASE_DIR}, GetModuleLoadList(boot_mode, MODULE_BASE_DIR));
     bool retval = (want_parallel) ? m.LoadModulesParallel(std::thread::hardware_concurrency())
                                   : m.LoadListedModules(!want_console);
     modules_loaded = m.GetModuleCount();
@@ -221,6 +242,31 @@
     return true;
 }
 
+static bool IsChargerMode(const std::string& cmdline, const std::string& bootconfig) {
+    return bootconfig.find("androidboot.mode = \"charger\"") != std::string::npos ||
+            cmdline.find("androidboot.mode=charger") != std::string::npos;
+}
+
+static BootMode GetBootMode(const std::string& cmdline, const std::string& bootconfig)
+{
+    if (IsChargerMode(cmdline, bootconfig))
+        return BootMode::CHARGER_MODE;
+    else if (IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig))
+        return BootMode::RECOVERY_MODE;
+
+    return BootMode::NORMAL_MODE;
+}
+
+static std::unique_ptr<FirstStageMount> CreateFirstStageMount(const std::string& cmdline) {
+    auto ret = FirstStageMount::Create(cmdline);
+    if (ret.ok()) {
+        return std::move(*ret);
+    } else {
+        LOG(ERROR) << "Failed to create FirstStageMount : " << ret.error();
+        return nullptr;
+    }
+}
+
 int FirstStageMain(int argc, char** argv) {
     if (REBOOT_BOOTLOADER_ON_PANIC) {
         InstallRebootSignalHandlers();
@@ -311,6 +357,18 @@
 
     LOG(INFO) << "init first stage started!";
 
+    // We only allow /vendor partition in debuggable Microdrod until it is verified during boot.
+    // TODO(b/285855436): remove this check.
+    if (IsMicrodroid()) {
+        bool mount_vendor =
+                cmdline.find("androidboot.microdroid.mount_vendor=1") != std::string::npos;
+        bool debuggable =
+                bootconfig.find("androidboot.microdroid.debuggable = \"1\"") != std::string::npos;
+        if (mount_vendor && !debuggable) {
+            LOG(FATAL) << "Attempted to mount /vendor partition for non-debuggable Microdroid VM";
+        }
+    }
+
     auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir};
     if (!old_root_dir) {
         PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk";
@@ -328,7 +386,8 @@
 
     boot_clock::time_point module_start_time = boot_clock::now();
     int module_count = 0;
-    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
+    BootMode boot_mode = GetBootMode(cmdline, bootconfig);
+    if (!LoadKernelModules(boot_mode, want_console,
                            want_parallel, module_count)) {
         if (want_console != FirstStageConsoleParam::DISABLED) {
             LOG(ERROR) << "Failed to load kernel modules, starting console";
@@ -344,12 +403,17 @@
                   << module_elapse_time.count() << " ms";
     }
 
+    std::unique_ptr<FirstStageMount> fsm;
+
     bool created_devices = false;
     if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
         if (!IsRecoveryMode()) {
-            created_devices = DoCreateDevices();
-            if (!created_devices) {
-                LOG(ERROR) << "Failed to create device nodes early";
+            fsm = CreateFirstStageMount(cmdline);
+            if (fsm) {
+                created_devices = fsm->DoCreateDevices();
+                if (!created_devices) {
+                    LOG(ERROR) << "Failed to create device nodes early";
+                }
             }
         }
         StartConsole(cmdline);
@@ -400,8 +464,23 @@
         SwitchRoot("/first_stage_ramdisk");
     }
 
-    if (!DoFirstStageMount(!created_devices)) {
-        LOG(FATAL) << "Failed to mount required partitions early ...";
+    if (IsRecoveryMode()) {
+        LOG(INFO) << "First stage mount skipped (recovery mode)";
+    } else {
+        if (!fsm) {
+            fsm = CreateFirstStageMount(cmdline);
+        }
+        if (!fsm) {
+            LOG(FATAL) << "FirstStageMount not available";
+        }
+
+        if (!created_devices && !fsm->DoCreateDevices()) {
+            LOG(FATAL) << "Failed to create devices required for first stage mount";
+        }
+
+        if (!fsm->DoFirstStageMount()) {
+            LOG(FATAL) << "Failed to mount required partitions early ...";
+        }
     }
 
     struct stat new_root_info;
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 07ce458..d0f68a8 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -76,21 +76,21 @@
 
 // Class Declarations
 // ------------------
-class FirstStageMount {
+class FirstStageMountVBootV2 : public FirstStageMount {
   public:
-    FirstStageMount(Fstab fstab);
-    virtual ~FirstStageMount() = default;
+    friend void SetInitAvbVersionInRecovery();
 
-    // The factory method to create a FirstStageMountVBootV2 instance.
-    static Result<std::unique_ptr<FirstStageMount>> Create();
-    bool DoCreateDevices();    // Creates devices and logical partitions from storage devices
-    bool DoFirstStageMount();  // Mounts fstab entries read from device tree.
+    FirstStageMountVBootV2(Fstab fstab);
+    virtual ~FirstStageMountVBootV2() = default;
+
+    bool DoCreateDevices() override;
+    bool DoFirstStageMount() override;
+
+  private:
     bool InitDevices();
-
-  protected:
     bool InitRequiredDevices(std::set<std::string> devices);
     bool CreateLogicalPartitions();
-    bool CreateSnapshotPartitions(android::snapshot::SnapshotManager* sm);
+    bool CreateSnapshotPartitions(SnapshotManager* sm);
     bool MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
                         Fstab::iterator* end = nullptr);
 
@@ -106,9 +106,10 @@
     // revocation check by DSU installation service.
     void CopyDsuAvbKeys();
 
-    // Pure virtual functions.
-    virtual bool GetDmVerityDevices(std::set<std::string>* devices) = 0;
-    virtual bool SetUpDmVerity(FstabEntry* fstab_entry) = 0;
+    bool GetDmVerityDevices(std::set<std::string>* devices);
+    bool SetUpDmVerity(FstabEntry* fstab_entry);
+
+    bool InitAvbHandle();
 
     bool need_dm_verity_;
     bool dsu_not_on_userdata_ = false;
@@ -122,19 +123,6 @@
     // Reads all AVB keys before chroot into /system, as they might be used
     // later when mounting other partitions, e.g., /vendor and /product.
     std::map<std::string, std::vector<std::string>> preload_avb_key_blobs_;
-};
-
-class FirstStageMountVBootV2 : public FirstStageMount {
-  public:
-    friend void SetInitAvbVersionInRecovery();
-
-    FirstStageMountVBootV2(Fstab fstab);
-    ~FirstStageMountVBootV2() override = default;
-
-  protected:
-    bool GetDmVerityDevices(std::set<std::string>* devices) override;
-    bool SetUpDmVerity(FstabEntry* fstab_entry) override;
-    bool InitAvbHandle();
 
     std::vector<std::string> vbmeta_partitions_;
     AvbUniquePtr avb_handle_;
@@ -150,7 +138,7 @@
     return is_android_dt_value_expected("vbmeta/compatible", "android,vbmeta");
 }
 
-static Result<Fstab> ReadFirstStageFstab() {
+static Result<Fstab> ReadFirstStageFstabAndroid() {
     Fstab fstab;
     if (!ReadFstabFromDt(&fstab)) {
         if (ReadDefaultFstab(&fstab)) {
@@ -166,6 +154,24 @@
     return fstab;
 }
 
+// Note: this is a temporary solution to avoid blocking devs that depend on /vendor partition in
+// Microdroid. For the proper solution the /vendor fstab should probably be defined in the DT.
+// TODO(b/285855430): refactor this
+// TODO(b/285855436): verify key microdroid-vendor was signed with.
+// TODO(b/285855436): should be mounted on top of dm-verity device.
+static Result<Fstab> ReadFirstStageFstabMicrodroid(const std::string& cmdline) {
+    Fstab fstab;
+    if (!ReadDefaultFstab(&fstab)) {
+        return Error() << "failed to read fstab";
+    }
+    if (cmdline.find("androidboot.microdroid.mount_vendor=1") == std::string::npos) {
+        // We weren't asked to mount /vendor partition, filter it out from the fstab.
+        auto predicate = [](const auto& entry) { return entry.mount_point == "/vendor"; };
+        fstab.erase(std::remove_if(fstab.begin(), fstab.end(), predicate), fstab.end());
+    }
+    return fstab;
+}
+
 static bool GetRootEntry(FstabEntry* root_entry) {
     Fstab proc_mounts;
     if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) {
@@ -218,14 +224,13 @@
     return rollbacked;
 }
 
-// Class Definitions
-// -----------------
-FirstStageMount::FirstStageMount(Fstab fstab) : need_dm_verity_(false), fstab_(std::move(fstab)) {
-    super_partition_name_ = fs_mgr_get_super_partition_name();
-}
-
-Result<std::unique_ptr<FirstStageMount>> FirstStageMount::Create() {
-    auto fstab = ReadFirstStageFstab();
+Result<std::unique_ptr<FirstStageMount>> FirstStageMount::Create(const std::string& cmdline) {
+    Result<Fstab> fstab;
+    if (IsMicrodroid()) {
+        fstab = ReadFirstStageFstabMicrodroid(cmdline);
+    } else {
+        fstab = ReadFirstStageFstabAndroid();
+    }
     if (!fstab.ok()) {
         return fstab.error();
     }
@@ -233,7 +238,7 @@
     return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
 }
 
-bool FirstStageMount::DoCreateDevices() {
+bool FirstStageMountVBootV2::DoCreateDevices() {
     if (!InitDevices()) return false;
 
     // Mount /metadata before creating logical partitions, since we need to
@@ -255,7 +260,7 @@
     return true;
 }
 
-bool FirstStageMount::DoFirstStageMount() {
+bool FirstStageMountVBootV2::DoFirstStageMount() {
     if (!IsDmLinearEnabled() && fstab_.empty()) {
         // Nothing to mount.
         LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
@@ -267,7 +272,7 @@
     return true;
 }
 
-bool FirstStageMount::InitDevices() {
+bool FirstStageMountVBootV2::InitDevices() {
     std::set<std::string> devices;
     GetSuperDeviceName(&devices);
 
@@ -288,14 +293,14 @@
     return true;
 }
 
-bool FirstStageMount::IsDmLinearEnabled() {
+bool FirstStageMountVBootV2::IsDmLinearEnabled() {
     for (const auto& entry : fstab_) {
         if (entry.fs_mgr_flags.logical) return true;
     }
     return false;
 }
 
-void FirstStageMount::GetSuperDeviceName(std::set<std::string>* devices) {
+void FirstStageMountVBootV2::GetSuperDeviceName(std::set<std::string>* devices) {
     // Add any additional devices required for dm-linear mappings.
     if (!IsDmLinearEnabled()) {
         return;
@@ -307,7 +312,7 @@
 // Creates devices with uevent->partition_name matching ones in the given set.
 // Found partitions will then be removed from it for the subsequent member
 // function to check which devices are NOT created.
-bool FirstStageMount::InitRequiredDevices(std::set<std::string> devices) {
+bool FirstStageMountVBootV2::InitRequiredDevices(std::set<std::string> devices) {
     if (!block_dev_init_.InitDeviceMapper()) {
         return false;
     }
@@ -317,7 +322,8 @@
     return block_dev_init_.InitDevices(std::move(devices));
 }
 
-bool FirstStageMount::InitDmLinearBackingDevices(const android::fs_mgr::LpMetadata& metadata) {
+bool FirstStageMountVBootV2::InitDmLinearBackingDevices(
+        const android::fs_mgr::LpMetadata& metadata) {
     std::set<std::string> devices;
 
     auto partition_names = android::fs_mgr::GetBlockDevicePartitionNames(metadata);
@@ -334,7 +340,7 @@
     return InitRequiredDevices(std::move(devices));
 }
 
-bool FirstStageMount::CreateLogicalPartitions() {
+bool FirstStageMountVBootV2::CreateLogicalPartitions() {
     if (!IsDmLinearEnabled()) {
         return true;
     }
@@ -365,7 +371,7 @@
     return android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_path_);
 }
 
-bool FirstStageMount::CreateSnapshotPartitions(SnapshotManager* sm) {
+bool FirstStageMountVBootV2::CreateSnapshotPartitions(SnapshotManager* sm) {
     // When COW images are present for snapshots, they are stored on
     // the data partition.
     if (!InitRequiredDevices({"userdata"})) {
@@ -400,8 +406,8 @@
     return true;
 }
 
-bool FirstStageMount::MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
-                                     Fstab::iterator* end) {
+bool FirstStageMountVBootV2::MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
+                                            Fstab::iterator* end) {
     // Sets end to begin + 1, so we can just return on failure below.
     if (end) {
         *end = begin + 1;
@@ -445,7 +451,7 @@
     return mounted;
 }
 
-void FirstStageMount::PreloadAvbKeys() {
+void FirstStageMountVBootV2::PreloadAvbKeys() {
     for (const auto& entry : fstab_) {
         // No need to cache the key content if it's empty, or is already cached.
         if (entry.avb_keys.empty() || preload_avb_key_blobs_.count(entry.avb_keys)) {
@@ -492,7 +498,7 @@
 // If system is in the fstab then we're not a system-as-root device, and in
 // this case, we mount system first then pivot to it.  From that point on,
 // we are effectively identical to a system-as-root device.
-bool FirstStageMount::TrySwitchSystemAsRoot() {
+bool FirstStageMountVBootV2::TrySwitchSystemAsRoot() {
     UseDsuIfPresent();
     // Preloading all AVB keys from the ramdisk before switching root to /system.
     PreloadAvbKeys();
@@ -521,7 +527,7 @@
     return true;
 }
 
-bool FirstStageMount::MountPartitions() {
+bool FirstStageMountVBootV2::MountPartitions() {
     if (!TrySwitchSystemAsRoot()) return false;
 
     if (!SkipMountingPartitions(&fstab_, true /* verbose */)) return false;
@@ -604,7 +610,7 @@
 // copy files to /metadata is NOT fatal, because it is auxiliary to perform
 // public key matching before booting into DSU images on next boot. The actual
 // public key matching will still be done on next boot to DSU.
-void FirstStageMount::CopyDsuAvbKeys() {
+void FirstStageMountVBootV2::CopyDsuAvbKeys() {
     std::error_code ec;
     // Removing existing keys in gsi::kDsuAvbKeyDir as they might be stale.
     std::filesystem::remove_all(gsi::kDsuAvbKeyDir, ec);
@@ -620,7 +626,7 @@
     }
 }
 
-void FirstStageMount::UseDsuIfPresent() {
+void FirstStageMountVBootV2::UseDsuIfPresent() {
     std::string error;
 
     if (!android::gsi::CanBootIntoGsi(&error)) {
@@ -657,10 +663,10 @@
     TransformFstabForDsu(&fstab_, active_dsu, dsu_partitions);
 }
 
-// First retrieve any vbmeta partitions from device tree (legacy) then read through the fstab
-// for any further vbmeta partitions.
 FirstStageMountVBootV2::FirstStageMountVBootV2(Fstab fstab)
-    : FirstStageMount(std::move(fstab)), avb_handle_(nullptr) {
+    : need_dm_verity_(false), fstab_(std::move(fstab)), avb_handle_(nullptr) {
+    super_partition_name_ = fs_mgr_get_super_partition_name();
+
     std::string device_tree_vbmeta_parts;
     read_android_dt_file("vbmeta/parts", &device_tree_vbmeta_parts);
 
@@ -793,46 +799,13 @@
     return true;
 }
 
-// Public functions
-// ----------------
-// Creates devices and logical partitions from storage devices
-bool DoCreateDevices() {
-    auto fsm = FirstStageMount::Create();
-    if (!fsm.ok()) {
-        LOG(ERROR) << "Failed to create FirstStageMount: " << fsm.error();
-        return false;
-    }
-    return (*fsm)->DoCreateDevices();
-}
-
-// Mounts partitions specified by fstab in device tree.
-bool DoFirstStageMount(bool create_devices) {
-    // Skips first stage mount if we're in recovery mode.
-    if (IsRecoveryMode()) {
-        LOG(INFO) << "First stage mount skipped (recovery mode)";
-        return true;
-    }
-
-    auto fsm = FirstStageMount::Create();
-    if (!fsm.ok()) {
-        LOG(ERROR) << "Failed to create FirstStageMount " << fsm.error();
-        return false;
-    }
-
-    if (create_devices) {
-        if (!(*fsm)->DoCreateDevices()) return false;
-    }
-
-    return (*fsm)->DoFirstStageMount();
-}
-
 void SetInitAvbVersionInRecovery() {
     if (!IsRecoveryMode()) {
         LOG(INFO) << "Skipped setting INIT_AVB_VERSION (not in recovery mode)";
         return;
     }
 
-    auto fstab = ReadFirstStageFstab();
+    auto fstab = ReadFirstStageFstabAndroid();
     if (!fstab.ok()) {
         LOG(ERROR) << fstab.error();
         return;
diff --git a/init/first_stage_mount.h b/init/first_stage_mount.h
index 2f4e663..54501d8 100644
--- a/init/first_stage_mount.h
+++ b/init/first_stage_mount.h
@@ -16,11 +16,28 @@
 
 #pragma once
 
+#include <memory>
+
+#include "result.h"
+
 namespace android {
 namespace init {
 
-bool DoCreateDevices();
-bool DoFirstStageMount(bool create_devices);
+class FirstStageMount {
+  public:
+    virtual ~FirstStageMount() = default;
+
+    // The factory method to create a FirstStageMount instance.
+    static Result<std::unique_ptr<FirstStageMount>> Create(const std::string& cmdline);
+    // Creates devices and logical partitions from storage devices
+    virtual bool DoCreateDevices() = 0;
+    // Mounts fstab entries read from device tree.
+    virtual bool DoFirstStageMount() = 0;
+
+  protected:
+    FirstStageMount() = default;
+};
+
 void SetInitAvbVersionInRecovery();
 
 }  // namespace init
diff --git a/init/fuzzer/Android.bp b/init/fuzzer/Android.bp
index c21a196..856ca8c 100644
--- a/init/fuzzer/Android.bp
+++ b/init/fuzzer/Android.bp
@@ -18,7 +18,7 @@
 }
 
 cc_defaults {
-    name: "libinit_defaults",
+    name: "libinit_fuzzer_defaults",
     static_libs: [
         "libc++fs",
         "liblmkd_utils",
@@ -53,7 +53,7 @@
     ],
     shared_libs: ["libhidlmetadata",],
     defaults: [
-        "libinit_defaults",
+        "libinit_fuzzer_defaults",
     ],
 }
 
@@ -62,7 +62,7 @@
     srcs: [
         "init_property_fuzzer.cpp",
     ],
-    defaults: ["libinit_defaults"],
+    defaults: ["libinit_fuzzer_defaults"],
 }
 
 cc_fuzz {
@@ -71,6 +71,6 @@
         "init_ueventHandler_fuzzer.cpp",
     ],
     defaults: [
-        "libinit_defaults",
+        "libinit_fuzzer_defaults",
     ],
 }
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 0fc3ffc..7e8513b 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -180,9 +180,11 @@
     std::string init_script = R"init(
 service A something
     class first
+    user nobody
 
 service A something
     class second
+    user nobody
     override
 
 )init";
@@ -610,6 +612,31 @@
     EXPECT_EQ(2, num_executed);
 }
 
+TEST(init, RejectsNoUserStartingInV) {
+    std::string init_script =
+            R"init(
+service A something
+    class first
+)init";
+
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    ASSERT_TRUE(android::base::WriteStringToFd(init_script, tf.fd));
+
+    ServiceList service_list;
+    Parser parser;
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+    ASSERT_TRUE(parser.ParseConfig(tf.path));
+
+    if (GetIntProperty("ro.vendor.api_level", 0) > __ANDROID_API_U__) {
+        ASSERT_EQ(1u, parser.parse_error_count());
+    } else {
+        ASSERT_EQ(0u, parser.parse_error_count());
+    }
+}
+
 TEST(init, RejectsCriticalAndOneshotService) {
     if (GetIntProperty("ro.product.first_api_level", 10000) < 30) {
         GTEST_SKIP() << "Test only valid for devices launching with R or later";
@@ -619,6 +646,7 @@
             R"init(
 service A something
   class first
+  user root
   critical
   oneshot
 )init";
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index fead371..5b53d50 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -21,7 +21,6 @@
 #include <string>
 #include <vector>
 
-#include <ApexProperties.sysprop.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
@@ -30,16 +29,6 @@
 
 #include "util.h"
 
-#ifndef RECOVERY
-#define ACTIVATE_FLATTENED_APEX 1
-#endif
-
-#ifdef ACTIVATE_FLATTENED_APEX
-#include <apex_manifest.pb.h>
-#include <com_android_apex.h>
-#include <selinux/android.h>
-#endif  // ACTIVATE_FLATTENED_APEX
-
 namespace android {
 namespace init {
 namespace {
@@ -77,15 +66,9 @@
     return ret;
 }
 
-static bool IsApexUpdatable() {
-    static bool updatable = android::sysprop::ApexProperties::updatable().value_or(false);
-    return updatable;
-}
-
 // In case we have two sets of APEXes (non-updatable, updatable), we need two separate mount
 // namespaces.
 static bool NeedsTwoMountNamespaces() {
-    if (!IsApexUpdatable()) return false;
     if (IsRecoveryMode()) return false;
     // In microdroid, there's only one set of APEXes in built-in directories include block devices.
     if (IsMicrodroid()) return false;
@@ -193,7 +176,7 @@
 // Switch the mount namespace of the current process from bootstrap to default OR from default to
 // bootstrap. If the current mount namespace is neither bootstrap nor default, keep it that way.
 Result<void> SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace) {
-    if (IsRecoveryMode() || !IsApexUpdatable()) {
+    if (IsRecoveryMode()) {
         // we don't have multiple namespaces in recovery mode or if apex is not updatable
         return {};
     }
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 2d084db..8da6982 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -57,12 +57,12 @@
 #include <android-base/result.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <private/android_filesystem_config.h>
 #include <property_info_parser/property_info_parser.h>
 #include <property_info_serializer/property_info_serializer.h>
 #include <selinux/android.h>
 #include <selinux/label.h>
 #include <selinux/selinux.h>
+
 #include "debug_ramdisk.h"
 #include "epoll.h"
 #include "init.h"
@@ -111,13 +111,12 @@
 
 static bool persistent_properties_loaded = false;
 
+static int property_set_fd = -1;
 static int from_init_socket = -1;
 static int init_socket = -1;
 static bool accept_messages = false;
 static std::mutex accept_messages_lock;
 static std::thread property_service_thread;
-static std::thread property_service_for_system_thread;
-static std::mutex set_property_lock;
 
 static std::unique_ptr<PersistWriteThread> persist_write_thread;
 
@@ -395,7 +394,6 @@
         return {PROP_ERROR_INVALID_VALUE};
     }
 
-    auto lock = std::lock_guard{set_property_lock};
     prop_info* pi = (prop_info*)__system_property_find(name.c_str());
     if (pi != nullptr) {
         // ro.* properties are actually "write-once".
@@ -581,10 +579,10 @@
     return *ret;
 }
 
-static void handle_property_set_fd(int fd) {
+static void handle_property_set_fd() {
     static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
 
-    int s = accept4(fd, nullptr, nullptr, SOCK_CLOEXEC);
+    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
     if (s == -1) {
         return;
     }
@@ -1421,21 +1419,19 @@
     }
 }
 
-static void PropertyServiceThread(int fd, bool listen_init) {
+static void PropertyServiceThread() {
     Epoll epoll;
     if (auto result = epoll.Open(); !result.ok()) {
         LOG(FATAL) << result.error();
     }
 
-    if (auto result = epoll.RegisterHandler(fd, std::bind(handle_property_set_fd, fd));
+    if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd);
         !result.ok()) {
         LOG(FATAL) << result.error();
     }
 
-    if (listen_init) {
-        if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result.ok()) {
-            LOG(FATAL) << result.error();
-        }
+    if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result.ok()) {
+        LOG(FATAL) << result.error();
     }
 
     while (true) {
@@ -1486,23 +1482,6 @@
     cv_.notify_all();
 }
 
-void StartThread(const char* name, int mode, int gid, std::thread& t, bool listen_init) {
-    int fd = -1;
-    if (auto result = CreateSocket(name, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
-                                   /*passcred=*/false, /*should_listen=*/false, mode, /*uid=*/0,
-                                   /*gid=*/gid, /*socketcon=*/{});
-        result.ok()) {
-        fd = *result;
-    } else {
-        LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
-    }
-
-    listen(fd, 8);
-
-    auto new_thread = std::thread(PropertyServiceThread, fd, listen_init);
-    t.swap(new_thread);
-}
-
 void StartPropertyService(int* epoll_socket) {
     InitPropertySet("ro.property_service.version", "2");
 
@@ -1514,9 +1493,19 @@
     init_socket = sockets[1];
     StartSendingMessages();
 
-    StartThread(PROP_SERVICE_FOR_SYSTEM_NAME, 0660, AID_SYSTEM, property_service_for_system_thread,
-                true);
-    StartThread(PROP_SERVICE_NAME, 0666, 0, property_service_thread, false);
+    if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+                                   /*passcred=*/false, /*should_listen=*/false, 0666, /*uid=*/0,
+                                   /*gid=*/0, /*socketcon=*/{});
+        result.ok()) {
+        property_set_fd = *result;
+    } else {
+        LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
+    }
+
+    listen(property_set_fd, 8);
+
+    auto new_thread = std::thread{PropertyServiceThread};
+    property_service_thread.swap(new_thread);
 
     auto async_persist_writes =
             android::base::GetBoolProperty("ro.property_service.async_persist_writes", false);
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 27a7876..3351c4c 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -680,8 +680,8 @@
                            << "': " << result.error();
             }
             s->SetShutdownCritical();
-        } else if (do_shutdown_animation) {
-            continue;
+        } else if (do_shutdown_animation && s->classnames().count("animation") > 0) {
+            // Need these for shutdown animations.
         } else if (s->IsShutdownCritical()) {
             // Start shutdown critical service if not started.
             if (auto result = s->Start(); !result.ok()) {
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 907eb80..e0ef491 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -498,7 +498,7 @@
 bool OpenMonolithicPolicy(PolicyFile* policy_file) {
     static constexpr char kSepolicyFile[] = "/sepolicy";
 
-    LOG(VERBOSE) << "Opening SELinux policy from monolithic file";
+    LOG(INFO) << "Opening SELinux policy from monolithic file " << kSepolicyFile;
     policy_file->fd.reset(open(kSepolicyFile, O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
     if (policy_file->fd < 0) {
         PLOG(ERROR) << "Failed to open monolithic SELinux policy";
@@ -851,6 +851,10 @@
 }
 
 int SelinuxGetVendorAndroidVersion() {
+    if (IsMicrodroid()) {
+        // As of now Microdroid doesn't have any vendor code.
+        return __ANDROID_API_FUTURE__;
+    }
     static int vendor_android_version = [] {
         if (!IsSplitPolicyDevice()) {
             // If this device does not split sepolicy files, it's not a Treble device and therefore,
@@ -954,6 +958,26 @@
     }
 }
 
+// Encapsulates steps to load SELinux policy in Microdroid.
+// So far the process is very straightforward - just load the precompiled policy from /system.
+void LoadSelinuxPolicyMicrodroid() {
+    constexpr const char kMicrodroidPrecompiledSepolicy[] =
+            "/system/etc/selinux/microdroid_precompiled_sepolicy";
+
+    LOG(INFO) << "Opening SELinux policy from " << kMicrodroidPrecompiledSepolicy;
+    unique_fd policy_fd(open(kMicrodroidPrecompiledSepolicy, O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
+    if (policy_fd < 0) {
+        PLOG(FATAL) << "Failed to open " << kMicrodroidPrecompiledSepolicy;
+    }
+
+    std::string policy;
+    if (!android::base::ReadFdToString(policy_fd, &policy)) {
+        PLOG(FATAL) << "Failed to read policy file: " << kMicrodroidPrecompiledSepolicy;
+    }
+
+    LoadSelinuxPolicy(policy);
+}
+
 // The SELinux setup process is carefully orchestrated around snapuserd. Policy
 // must be loaded off dynamic partitions, and during an OTA, those partitions
 // cannot be read without snapuserd. But, with kernel-privileged snapuserd
@@ -969,20 +993,9 @@
 //  (5) Re-launch snapuserd and attach it to the dm-user devices from step (2).
 //
 // After this sequence, it is safe to enable enforcing mode and continue booting.
-int SetupSelinux(char** argv) {
-    SetStdioToDevNull(argv);
-    InitKernelLogging(argv);
-
-    if (REBOOT_BOOTLOADER_ON_PANIC) {
-        InstallRebootSignalHandlers();
-    }
-
-    boot_clock::time_point start_time = boot_clock::now();
-
+void LoadSelinuxPolicyAndroid() {
     MountMissingSystemPartitions();
 
-    SelinuxSetupKernelLogging();
-
     LOG(INFO) << "Opening SELinux policy";
 
     PrepareApexSepolicy();
@@ -994,9 +1007,8 @@
 
     auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
     if (snapuserd_helper) {
-        // Kill the old snapused to avoid audit messages. After this we cannot
-        // read from /system (or other dynamic partitions) until we call
-        // FinishTransition().
+        // Kill the old snapused to avoid audit messages. After this we cannot read from /system
+        // (or other dynamic partitions) until we call FinishTransition().
         snapuserd_helper->StartTransition();
     }
 
@@ -1014,6 +1026,26 @@
     if (selinux_android_restorecon("/dev/selinux/", SELINUX_ANDROID_RESTORECON_RECURSE) == -1) {
         PLOG(FATAL) << "restorecon failed of /dev/selinux failed";
     }
+}
+
+int SetupSelinux(char** argv) {
+    SetStdioToDevNull(argv);
+    InitKernelLogging(argv);
+
+    if (REBOOT_BOOTLOADER_ON_PANIC) {
+        InstallRebootSignalHandlers();
+    }
+
+    boot_clock::time_point start_time = boot_clock::now();
+
+    SelinuxSetupKernelLogging();
+
+    // TODO(b/287206497): refactor into different headers to only include what we need.
+    if (IsMicrodroid()) {
+        LoadSelinuxPolicyMicrodroid();
+    } else {
+        LoadSelinuxPolicyAndroid();
+    }
 
     SelinuxSetEnforcement();
 
diff --git a/init/service.cpp b/init/service.cpp
index 35beaad..a0b3478 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -50,7 +50,6 @@
 #endif
 
 #ifdef INIT_FULL_SOURCES
-#include <ApexProperties.sysprop.h>
 #include <android/api-level.h>
 
 #include "mount_namespace.h"
@@ -140,9 +139,10 @@
 
 Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
                  const std::string& filename, const std::vector<std::string>& args)
-    : Service(name, 0, 0, 0, {}, 0, "", subcontext_for_restart_commands, filename, args) {}
+    : Service(name, 0, std::nullopt, 0, {}, 0, "", subcontext_for_restart_commands, filename,
+              args) {}
 
-Service::Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
+Service::Service(const std::string& name, unsigned flags, std::optional<uid_t> uid, gid_t gid,
                  const std::vector<gid_t>& supp_gids, int namespace_flags,
                  const std::string& seclabel, Subcontext* subcontext_for_restart_commands,
                  const std::string& filename, const std::vector<std::string>& args)
@@ -153,7 +153,7 @@
       crash_count_(0),
       proc_attr_{.ioprio_class = IoSchedClass_NONE,
                  .ioprio_pri = 0,
-                 .uid = uid,
+                 .parsed_uid = uid,
                  .gid = gid,
                  .supp_gids = supp_gids,
                  .priority = 0},
@@ -205,9 +205,9 @@
         int max_processes = 0;
         int r;
         if (signal == SIGTERM) {
-            r = killProcessGroupOnce(proc_attr_.uid, pid_, signal, &max_processes);
+            r = killProcessGroupOnce(uid(), pid_, signal, &max_processes);
         } else {
-            r = killProcessGroup(proc_attr_.uid, pid_, signal, &max_processes);
+            r = killProcessGroup(uid(), pid_, signal, &max_processes);
         }
 
         if (report_oneshot && max_processes > 0) {
@@ -228,7 +228,7 @@
 
 void Service::SetProcessAttributesAndCaps(InterprocessFifo setsid_finished) {
     // Keep capabilites on uid change.
-    if (capabilities_ && proc_attr_.uid) {
+    if (capabilities_ && uid()) {
         // If Android is running in a container, some securebits might already
         // be locked, so don't change those.
         unsigned long securebits = prctl(PR_GET_SECUREBITS);
@@ -255,7 +255,7 @@
         if (!SetCapsForExec(*capabilities_)) {
             LOG(FATAL) << "cannot set capabilities for " << name_;
         }
-    } else if (proc_attr_.uid) {
+    } else if (uid()) {
         // Inheritable caps can be non-zero when running in a container.
         if (!DropInheritableCaps()) {
             LOG(FATAL) << "cannot drop inheritable caps for " << name_;
@@ -307,6 +307,7 @@
     pid_ = 0;
     flags_ &= (~SVC_RUNNING);
     start_order_ = 0;
+    was_last_exit_ok_ = siginfo.si_code == CLD_EXITED && siginfo.si_status == 0;
 
     // Oneshot processes go into the disabled state on exit,
     // except when manually restarted.
@@ -321,7 +322,7 @@
     }
 
 #if INIT_FULL_SOURCES
-    static bool is_apex_updatable = android::sysprop::ApexProperties::updatable().value_or(false);
+    static bool is_apex_updatable = true;
 #else
     static bool is_apex_updatable = false;
 #endif
@@ -360,7 +361,8 @@
     // If we crash > 4 times in 'fatal_crash_window_' minutes or before boot_completed,
     // reboot into bootloader or set crashing property
     boot_clock::time_point now = boot_clock::now();
-    if (((flags_ & SVC_CRITICAL) || is_process_updatable) && !(flags_ & SVC_RESTART)) {
+    if (((flags_ & SVC_CRITICAL) || is_process_updatable) && !(flags_ & SVC_RESTART) &&
+        !was_last_exit_ok_) {
         bool boot_completed = GetBoolProperty("sys.boot_completed", false);
         if (now < time_crashed_ + fatal_crash_window_ || !boot_completed) {
             if (++crash_count_ > 4) {
@@ -434,8 +436,8 @@
     flags_ |= SVC_EXEC;
     is_exec_service_running_ = true;
 
-    LOG(INFO) << "SVC_EXEC service '" << name_ << "' pid " << pid_ << " (uid " << proc_attr_.uid
-              << " gid " << proc_attr_.gid << "+" << proc_attr_.supp_gids.size() << " context "
+    LOG(INFO) << "SVC_EXEC service '" << name_ << "' pid " << pid_ << " (uid " << uid() << " gid "
+              << proc_attr_.gid << "+" << proc_attr_.supp_gids.size() << " context "
               << (!seclabel_.empty() ? seclabel_ : "default") << ") started; waiting...";
 
     reboot_on_failure.Disable();
@@ -475,13 +477,13 @@
 // Configures the memory cgroup properties for the service.
 void Service::ConfigureMemcg() {
     if (swappiness_ != -1) {
-        if (!setProcessGroupSwappiness(proc_attr_.uid, pid_, swappiness_)) {
+        if (!setProcessGroupSwappiness(uid(), pid_, swappiness_)) {
             PLOG(ERROR) << "setProcessGroupSwappiness failed";
         }
     }
 
     if (soft_limit_in_bytes_ != -1) {
-        if (!setProcessGroupSoftLimit(proc_attr_.uid, pid_, soft_limit_in_bytes_)) {
+        if (!setProcessGroupSoftLimit(uid(), pid_, soft_limit_in_bytes_)) {
             PLOG(ERROR) << "setProcessGroupSoftLimit failed";
         }
     }
@@ -508,7 +510,7 @@
     }
 
     if (computed_limit_in_bytes != size_t(-1)) {
-        if (!setProcessGroupLimit(proc_attr_.uid, pid_, computed_limit_in_bytes)) {
+        if (!setProcessGroupLimit(uid(), pid_, computed_limit_in_bytes)) {
             PLOG(ERROR) << "setProcessGroupLimit failed";
         }
     }
@@ -705,21 +707,20 @@
     if (CgroupsAvailable()) {
         bool use_memcg = swappiness_ != -1 || soft_limit_in_bytes_ != -1 || limit_in_bytes_ != -1 ||
                          limit_percent_ != -1 || !limit_property_.empty();
-        errno = -createProcessGroup(proc_attr_.uid, pid_, use_memcg);
+        errno = -createProcessGroup(uid(), pid_, use_memcg);
         if (errno != 0) {
             Result<void> result = cgroups_activated.Write(kActivatingCgroupsFailed);
             if (!result.ok()) {
                 return Error() << "Sending notification failed: " << result.error();
             }
-            return Error() << "createProcessGroup(" << proc_attr_.uid << ", " << pid_ << ", "
-                           << use_memcg << ") failed for service '" << name_
-                           << "': " << strerror(errno);
+            return Error() << "createProcessGroup(" << uid() << ", " << pid_ << ", " << use_memcg
+                           << ") failed for service '" << name_ << "': " << strerror(errno);
         }
 
         // When the blkio controller is mounted in the v1 hierarchy, NormalIoPriority is
         // the default (/dev/blkio). When the blkio controller is mounted in the v2 hierarchy, the
         // NormalIoPriority profile has to be applied explicitly.
-        SetProcessProfiles(proc_attr_.uid, pid_, {"NormalIoPriority"});
+        SetProcessProfiles(uid(), pid_, {"NormalIoPriority"});
 
         if (use_memcg) {
             ConfigureMemcg();
@@ -727,7 +728,7 @@
     }
 
     if (oom_score_adjust_ != DEFAULT_OOM_SCORE_ADJUST) {
-        LmkdRegister(name_, proc_attr_.uid, pid_, oom_score_adjust_);
+        LmkdRegister(name_, uid(), pid_, oom_score_adjust_);
     }
 
     if (Result<void> result = cgroups_activated.Write(kCgroupsActivated); !result.ok()) {
diff --git a/init/service.h b/init/service.h
index 3ef8902..b858eef 100644
--- a/init/service.h
+++ b/init/service.h
@@ -19,6 +19,7 @@
 #include <signal.h>
 #include <sys/types.h>
 
+#include <algorithm>
 #include <chrono>
 #include <memory>
 #include <optional>
@@ -71,7 +72,7 @@
     Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
             const std::string& filename, const std::vector<std::string>& args);
 
-    Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
+    Service(const std::string& name, unsigned flags, std::optional<uid_t> uid, gid_t gid,
             const std::vector<gid_t>& supp_gids, int namespace_flags, const std::string& seclabel,
             Subcontext* subcontext_for_restart_commands, const std::string& filename,
             const std::vector<std::string>& args);
@@ -115,7 +116,8 @@
     pid_t pid() const { return pid_; }
     android::base::boot_clock::time_point time_started() const { return time_started_; }
     int crash_count() const { return crash_count_; }
-    uid_t uid() const { return proc_attr_.uid; }
+    int was_last_exit_ok() const { return was_last_exit_ok_; }
+    uid_t uid() const { return proc_attr_.uid(); }
     gid_t gid() const { return proc_attr_.gid; }
     int namespace_flags() const { return namespaces_.flags; }
     const std::vector<gid_t>& supp_gids() const { return proc_attr_.supp_gids; }
@@ -130,7 +132,15 @@
     bool process_cgroup_empty() const { return process_cgroup_empty_; }
     unsigned long start_order() const { return start_order_; }
     void set_sigstop(bool value) { sigstop_ = value; }
-    std::chrono::seconds restart_period() const { return restart_period_; }
+    std::chrono::seconds restart_period() const {
+        // If the service exited abnormally or due to timeout, late limit the restart even if
+        // restart_period is set to a very short value.
+        // If not, i.e. restart after a deliberate and successful exit, respect the period.
+        if (!was_last_exit_ok_) {
+            return std::max(restart_period_, default_restart_period_);
+        }
+        return restart_period_;
+    }
     std::optional<std::chrono::seconds> timeout_period() const { return timeout_period_; }
     const std::vector<std::string>& args() const { return args_; }
     bool is_updatable() const { return updatable_; }
@@ -172,6 +182,8 @@
     bool upgraded_mte_ = false;           // whether we upgraded async MTE -> sync MTE before
     std::chrono::minutes fatal_crash_window_ = 4min;  // fatal() when more than 4 crashes in it
     std::optional<std::string> fatal_reboot_target_;  // reboot target of fatal handler
+    bool was_last_exit_ok_ =
+            true;  // true if the service never exited, or exited with status code 0
 
     std::optional<CapSet> capabilities_;
     ProcessAttributes proc_attr_;
@@ -214,7 +226,8 @@
 
     bool sigstop_ = false;
 
-    std::chrono::seconds restart_period_ = 5s;
+    const std::chrono::seconds default_restart_period_ = 5s;
+    std::chrono::seconds restart_period_ = default_restart_period_;
     std::optional<std::chrono::seconds> timeout_period_;
 
     bool updatable_ = false;
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 3563084..a1b2cc5 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -25,6 +25,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <hidl-util/FQName.h>
 #include <processgroup/processgroup.h>
@@ -369,8 +370,8 @@
 
 Result<void> ServiceParser::ParseRestartPeriod(std::vector<std::string>&& args) {
     int period;
-    if (!ParseInt(args[1], &period, 5)) {
-        return Error() << "restart_period value must be an integer >= 5";
+    if (!ParseInt(args[1], &period, 0)) {
+        return Error() << "restart_period value must be an integer >= 0";
     }
     service_->restart_period_ = std::chrono::seconds(period);
     return {};
@@ -534,7 +535,7 @@
     if (!uid.ok()) {
         return Error() << "Unable to find UID for '" << args[1] << "': " << uid.error();
     }
-    service_->proc_attr_.uid = *uid;
+    service_->proc_attr_.parsed_uid = *uid;
     return {};
 }
 
@@ -677,6 +678,16 @@
         return {};
     }
 
+    if (service_->proc_attr_.parsed_uid == std::nullopt) {
+        if (android::base::GetIntProperty("ro.vendor.api_level", 0) > __ANDROID_API_U__) {
+            return Error() << "No user specified for service '" << service_->name()
+                           << "'. Defaults to root.";
+        } else {
+            LOG(WARNING) << "No user specified for service '" << service_->name()
+                         << "'. Defaults to root.";
+        }
+    }
+
     if (interface_inheritance_hierarchy_) {
         if (const auto& check_hierarchy_result = CheckInterfaceInheritanceHierarchy(
                     service_->interfaces(), *interface_inheritance_hierarchy_);
diff --git a/init/service_utils.cpp b/init/service_utils.cpp
index 7004d8d..0e19bcc 100644
--- a/init/service_utils.cpp
+++ b/init/service_utils.cpp
@@ -271,8 +271,8 @@
     if (setgroups(attr.supp_gids.size(), const_cast<gid_t*>(&attr.supp_gids[0])) != 0) {
         return ErrnoError() << "setgroups failed";
     }
-    if (attr.uid) {
-        if (setuid(attr.uid) != 0) {
+    if (attr.uid()) {
+        if (setuid(attr.uid()) != 0) {
             return ErrnoError() << "setuid failed";
         }
     }
diff --git a/init/service_utils.h b/init/service_utils.h
index d4143aa..27ec450 100644
--- a/init/service_utils.h
+++ b/init/service_utils.h
@@ -91,11 +91,13 @@
     IoSchedClass ioprio_class;
     int ioprio_pri;
     std::vector<std::pair<int, rlimit>> rlimits;
-    uid_t uid;
+    std::optional<uid_t> parsed_uid;
     gid_t gid;
     std::vector<gid_t> supp_gids;
     int priority;
     bool stdio_to_kmsg;
+
+    uid_t uid() const { return parsed_uid.value_or(0); }
 };
 
 inline bool RequiresConsole(const ProcessAttributes& attr) {
diff --git a/init/test_kill_services/init_kill_services_test.cpp b/init/test_kill_services/init_kill_services_test.cpp
index 5355703..510ad8a 100644
--- a/init/test_kill_services/init_kill_services_test.cpp
+++ b/init/test_kill_services/init_kill_services_test.cpp
@@ -16,14 +16,26 @@
 
 #include <gtest/gtest.h>
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 
 #include <iostream>
 
 using ::android::base::GetProperty;
 using ::android::base::SetProperty;
+using ::android::base::WaitForProperty;
+using std::literals::chrono_literals::operator""s;
 
 void ExpectKillingServiceRecovers(const std::string& service_name) {
+    LOG(INFO) << "before we say hi to " << service_name << ", I can't have apexd around!";
+
+    // b/280514080 - servicemanager will restart apexd, and apexd will restart the
+    // system when crashed. This is fine as the device recovers, but it causes
+    // flakes in this test.
+    ASSERT_TRUE(WaitForProperty("init.svc.apexd", "stopped", 120s))
+            << (system("cat /dev/binderfs/binder_logs/state"), "apexd won't stop");
+
+    LOG(INFO) << "hello " << service_name << "!";
     const std::string status_prop = "init.svc." + service_name;
     const std::string pid_prop = "init.svc_debug_pid." + service_name;
 
@@ -32,6 +44,7 @@
     ASSERT_EQ("running", GetProperty(status_prop, "")) << status_prop;
     ASSERT_NE("", initial_pid) << pid_prop;
 
+    LOG(INFO) << "okay, now goodbye " << service_name;
     EXPECT_EQ(0, system(("kill -9 " + initial_pid).c_str()));
 
     constexpr size_t kMaxWaitMilliseconds = 10000;
@@ -42,11 +55,16 @@
     for (size_t retry = 0; retry < kRetryTimes; retry++) {
         const std::string& pid = GetProperty(pid_prop, "");
         if (pid != initial_pid && pid != "") break;
+        LOG(INFO) << "I said goodbye " << service_name << "!";
         usleep(kRetryWaitMilliseconds * 1000);
     }
 
+    LOG(INFO) << "are you still there " << service_name << "?";
+
     // svc_debug_pid is set after svc property
     EXPECT_EQ("running", GetProperty(status_prop, ""));
+
+    LOG(INFO) << "I'm done with " << service_name;
 }
 
 class InitKillServicesTest : public ::testing::TestWithParam<std::string> {};
diff --git a/init/util.cpp b/init/util.cpp
index bc8ea6e..d0478e8 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -732,11 +732,6 @@
     is_default_mount_namespace_ready = true;
 }
 
-bool IsMicrodroid() {
-    static bool is_microdroid = android::base::GetProperty("ro.hardware", "") == "microdroid";
-    return is_microdroid;
-}
-
 bool Has32BitAbi() {
     static bool has = !android::base::GetProperty("ro.product.cpu.abilist32", "").empty();
     return has;
diff --git a/init/util.h b/init/util.h
index e58e70e..3f0a4e0 100644
--- a/init/util.h
+++ b/init/util.h
@@ -105,7 +105,14 @@
 bool IsDefaultMountNamespaceReady();
 void SetDefaultMountNamespaceReady();
 
-bool IsMicrodroid();
+inline constexpr bool IsMicrodroid() {
+#ifdef MICRODROID
+    return MICRODROID;
+#else
+    return false;
+#endif
+}
+
 bool Has32BitAbi();
 
 std::string GetApexNameFromFileName(const std::string& path);
diff --git a/libbinderwrapper/Android.bp b/libbinderwrapper/Android.bp
deleted file mode 100644
index 75f43ee..0000000
--- a/libbinderwrapper/Android.bp
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_defaults {
-    name: "libbinderwrapper_defaults",
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-
-        // for libchrome
-        "-Wno-sign-promo",
-    ],
-    export_include_dirs: ["include"],
-    shared_libs: [
-        "libbinder",
-        "libchrome",
-        "libutils",
-    ],
-}
-
-// libbinderwrapper shared library
-// ========================================================
-cc_library_shared {
-    name: "libbinderwrapper",
-    defaults: ["libbinderwrapper_defaults"],
-    vendor_available: true,
-
-    srcs: [
-        "binder_wrapper.cc",
-        "real_binder_wrapper.cc",
-    ],
-}
-
-// libbinderwrapper_test_support static library
-// ========================================================
-cc_library_static {
-    name: "libbinderwrapper_test_support",
-    defaults: ["libbinderwrapper_defaults"],
-
-    static_libs: ["libgtest"],
-    shared_libs: ["libbinderwrapper"],
-
-    srcs: [
-        "binder_test_base.cc",
-        "stub_binder_wrapper.cc",
-    ],
-}
diff --git a/libbinderwrapper/binder_test_base.cc b/libbinderwrapper/binder_test_base.cc
deleted file mode 100644
index af93a04..0000000
--- a/libbinderwrapper/binder_test_base.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <binderwrapper/binder_test_base.h>
-
-#include <binderwrapper/binder_wrapper.h>
-#include <binderwrapper/stub_binder_wrapper.h>
-
-namespace android {
-
-BinderTestBase::BinderTestBase() : binder_wrapper_(new StubBinderWrapper()) {
-  // Pass ownership.
-  BinderWrapper::InitForTesting(binder_wrapper_);
-}
-
-BinderTestBase::~BinderTestBase() {
-  BinderWrapper::Destroy();
-}
-
-}  // namespace android
diff --git a/libbinderwrapper/binder_wrapper.cc b/libbinderwrapper/binder_wrapper.cc
deleted file mode 100644
index ca9c1df..0000000
--- a/libbinderwrapper/binder_wrapper.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <binderwrapper/binder_wrapper.h>
-
-#include <base/logging.h>
-
-#include "real_binder_wrapper.h"
-
-namespace android {
-
-// Singleton instance.
-BinderWrapper* BinderWrapper::instance_ = nullptr;
-
-// static
-void BinderWrapper::Create() {
-  CHECK(!instance_) << "Already initialized; missing call to Destroy()?";
-  instance_ = new RealBinderWrapper();
-}
-
-// static
-void BinderWrapper::InitForTesting(BinderWrapper* wrapper) {
-  CHECK(!instance_) << "Already initialized; missing call to Destroy()?";
-  instance_ = wrapper;
-}
-
-// static
-void BinderWrapper::Destroy() {
-  CHECK(instance_) << "Not initialized; missing call to Create()?";
-  delete instance_;
-  instance_ = nullptr;
-}
-
-// static
-BinderWrapper* BinderWrapper::Get() {
-  CHECK(instance_) << "Not initialized; missing call to Create()?";
-  return instance_;
-}
-
-// static
-BinderWrapper* BinderWrapper::GetOrCreateInstance() {
-  if (!instance_)
-    instance_ = new RealBinderWrapper();
-  return instance_;
-}
-
-}  // namespace android
diff --git a/libbinderwrapper/include/binderwrapper/binder_test_base.h b/libbinderwrapper/include/binderwrapper/binder_test_base.h
deleted file mode 100644
index 06543de..0000000
--- a/libbinderwrapper/include/binderwrapper/binder_test_base.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SYSTEM_CORE_INCLUDE_BINDERWRAPPER_BINDER_TEST_BASE_H_
-#define SYSTEM_CORE_INCLUDE_BINDERWRAPPER_BINDER_TEST_BASE_H_
-
-#include <base/macros.h>
-#include <gtest/gtest.h>
-
-namespace android {
-
-class StubBinderWrapper;
-
-// Class that can be inherited from (or aliased via typedef/using) when writing
-// tests that use StubBinderManager.
-class BinderTestBase : public ::testing::Test {
- public:
-  BinderTestBase();
-  ~BinderTestBase() override;
-
-  StubBinderWrapper* binder_wrapper() { return binder_wrapper_; }
-
- protected:
-  StubBinderWrapper* binder_wrapper_;  // Not owned.
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(BinderTestBase);
-};
-
-}  // namespace android
-
-#endif  // SYSTEM_CORE_INCLUDE_BINDERWRAPPER_BINDER_TEST_BASE_H_
diff --git a/libbinderwrapper/include/binderwrapper/binder_wrapper.h b/libbinderwrapper/include/binderwrapper/binder_wrapper.h
deleted file mode 100644
index a104bff..0000000
--- a/libbinderwrapper/include/binderwrapper/binder_wrapper.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SYSTEM_CORE_INCLUDE_BINDERWRAPPER_BINDER_WRAPPER_H_
-#define SYSTEM_CORE_INCLUDE_BINDERWRAPPER_BINDER_WRAPPER_H_
-
-#include <sys/types.h>
-
-#include <string>
-
-#include <base/callback.h>
-#include <utils/StrongPointer.h>
-
-namespace android {
-
-class BBinder;
-class IBinder;
-
-// Wraps libbinder to make it testable.
-// NOTE: Static methods of this class are not thread-safe.
-class BinderWrapper {
- public:
-  virtual ~BinderWrapper() {}
-
-  // Creates and initializes the singleton (using a wrapper that communicates
-  // with the real binder system).
-  static void Create();
-
-  // Initializes |wrapper| as the singleton, taking ownership of it. Tests that
-  // want to inject their own wrappers should call this instead of Create().
-  static void InitForTesting(BinderWrapper* wrapper);
-
-  // Destroys the singleton. Must be called before calling Create() or
-  // InitForTesting() a second time.
-  static void Destroy();
-
-  // Returns the singleton instance previously created by Create() or set by
-  // InitForTesting().
-  static BinderWrapper* Get();
-
-  // Returns the singleton instance if it was previously created by Create() or
-  // set by InitForTesting(), or creates a new one by calling Create().
-  static BinderWrapper* GetOrCreateInstance();
-
-  // Gets the binder for communicating with the service identified by
-  // |service_name|, returning null immediately if it doesn't exist.
-  virtual sp<IBinder> GetService(const std::string& service_name) = 0;
-
-  // Registers |binder| as |service_name| with the service manager.
-  virtual bool RegisterService(const std::string& service_name,
-                               const sp<IBinder>& binder) = 0;
-
-  // Creates a local binder object.
-  virtual sp<BBinder> CreateLocalBinder() = 0;
-
-  // Registers |callback| to be invoked when |binder| dies. If another callback
-  // is currently registered for |binder|, it will be replaced.
-  virtual bool RegisterForDeathNotifications(
-      const sp<IBinder>& binder,
-      const ::base::Closure& callback) = 0;
-
-  // Unregisters the callback, if any, for |binder|.
-  virtual bool UnregisterForDeathNotifications(const sp<IBinder>& binder) = 0;
-
-  // When called while in a transaction, returns the caller's UID or PID.
-  virtual uid_t GetCallingUid() = 0;
-  virtual pid_t GetCallingPid() = 0;
-
- private:
-  static BinderWrapper* instance_;
-};
-
-}  // namespace android
-
-#endif  // SYSTEM_CORE_INCLUDE_BINDERWRAPPER_BINDER_WRAPPER_H_
diff --git a/libbinderwrapper/include/binderwrapper/stub_binder_wrapper.h b/libbinderwrapper/include/binderwrapper/stub_binder_wrapper.h
deleted file mode 100644
index 9d4578e..0000000
--- a/libbinderwrapper/include/binderwrapper/stub_binder_wrapper.h
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SYSTEM_CORE_INCLUDE_BINDERWRAPPER_STUB_BINDER_WRAPPER_H_
-#define SYSTEM_CORE_INCLUDE_BINDERWRAPPER_STUB_BINDER_WRAPPER_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include <base/macros.h>
-#include <binder/Binder.h>
-#include <binder/IBinder.h>
-#include <binderwrapper/binder_wrapper.h>
-
-namespace android {
-
-// Stub implementation of BinderWrapper for testing.
-//
-// Example usage:
-//
-// First, assuming a base IFoo binder interface, create a stub class that
-// derives from BnFoo to implement the receiver side of the communication:
-//
-//   class StubFoo : public BnFoo {
-//    public:
-//     ...
-//     status_t doSomething(int arg) override {
-//       // e.g. save passed-in value for later inspection by tests.
-//       return OK;
-//     }
-//   };
-//
-// Next, from your test code, inject a StubBinderManager either directly or by
-// inheriting from the BinderTestBase class:
-//
-//   StubBinderWrapper* wrapper = new StubBinderWrapper();
-//   BinderWrapper::InitForTesting(wrapper);  // Takes ownership.
-//
-// Also from your test, create a StubFoo and register it with the wrapper:
-//
-//   StubFoo* foo = new StubFoo();
-//   sp<IBinder> binder(foo);
-//   wrapper->SetBinderForService("foo", binder);
-//
-// The code being tested can now use the wrapper to get the stub and call it:
-//
-//   sp<IBinder> binder = BinderWrapper::Get()->GetService("foo");
-//   CHECK(binder.get());
-//   sp<IFoo> foo = interface_cast<IFoo>(binder);
-//   CHECK_EQ(foo->doSomething(3), OK);
-//
-// To create a local BBinder object, production code can call
-// CreateLocalBinder(). Then, a test can get the BBinder's address via
-// local_binders() to check that they're passed as expected in binder calls.
-//
-class StubBinderWrapper : public BinderWrapper {
- public:
-  StubBinderWrapper();
-  ~StubBinderWrapper() override;
-
-  const std::vector<sp<BBinder>>& local_binders() const {
-    return local_binders_;
-  }
-  void clear_local_binders() { local_binders_.clear(); }
-
-  void set_calling_uid(uid_t uid) { calling_uid_ = uid; }
-  void set_calling_pid(pid_t pid) { calling_pid_ = pid; }
-
-  // Sets the binder to return when |service_name| is passed to GetService() or
-  // WaitForService().
-  void SetBinderForService(const std::string& service_name,
-                           const sp<IBinder>& binder);
-
-  // Returns the binder previously registered for |service_name| via
-  // RegisterService(), or null if the service hasn't been registered.
-  sp<IBinder> GetRegisteredService(const std::string& service_name) const;
-
-  // Run the calback in |death_callbacks_| corresponding to |binder|.
-  void NotifyAboutBinderDeath(const sp<IBinder>& binder);
-
-  // BinderWrapper:
-  sp<IBinder> GetService(const std::string& service_name) override;
-  bool RegisterService(const std::string& service_name,
-                       const sp<IBinder>& binder) override;
-  sp<BBinder> CreateLocalBinder() override;
-  bool RegisterForDeathNotifications(const sp<IBinder>& binder,
-                                     const ::base::Closure& callback) override;
-  bool UnregisterForDeathNotifications(const sp<IBinder>& binder) override;
-  uid_t GetCallingUid() override;
-  pid_t GetCallingPid() override;
-
- private:
-  using ServiceMap = std::map<std::string, sp<IBinder>>;
-
-  // Map from service name to associated binder handle. Used by GetService() and
-  // WaitForService().
-  ServiceMap services_to_return_;
-
-  // Map from service name to associated binder handle. Updated by
-  // RegisterService().
-  ServiceMap registered_services_;
-
-  // Local binders returned by CreateLocalBinder().
-  std::vector<sp<BBinder>> local_binders_;
-
-  // Map from binder handle to the callback that should be invoked on binder
-  // death.
-  std::map<sp<IBinder>, ::base::Closure> death_callbacks_;
-
-  // Values to return from GetCallingUid() and GetCallingPid();
-  uid_t calling_uid_;
-  pid_t calling_pid_;
-
-  DISALLOW_COPY_AND_ASSIGN(StubBinderWrapper);
-};
-
-}  // namespace android
-
-#endif  // SYSTEM_CORE_INCLUDE_BINDERWRAPPER_STUB_BINDER_WRAPPER_H_
diff --git a/libbinderwrapper/real_binder_wrapper.cc b/libbinderwrapper/real_binder_wrapper.cc
deleted file mode 100644
index f93f183..0000000
--- a/libbinderwrapper/real_binder_wrapper.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "real_binder_wrapper.h"
-
-#include <base/logging.h>
-#include <binder/Binder.h>
-#include <binder/IBinder.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-
-namespace android {
-
-// Class that handles binder death notifications. libbinder wants the recipient
-// to be wrapped in sp<>, so registering RealBinderWrapper as a recipient would
-// be awkward.
-class RealBinderWrapper::DeathRecipient : public IBinder::DeathRecipient {
- public:
-  explicit DeathRecipient(const ::base::Closure& callback)
-      : callback_(callback) {}
-  ~DeathRecipient() = default;
-
-  // IBinder::DeathRecipient:
-  void binderDied(const wp<IBinder>& who) override {
-    callback_.Run();
-  }
-
- private:
-  // Callback to run in response to binder death.
-  ::base::Closure callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(DeathRecipient);
-};
-
-RealBinderWrapper::RealBinderWrapper() = default;
-
-RealBinderWrapper::~RealBinderWrapper() = default;
-
-sp<IBinder> RealBinderWrapper::GetService(const std::string& service_name) {
-  sp<IServiceManager> service_manager = defaultServiceManager();
-  if (!service_manager.get()) {
-    LOG(ERROR) << "Unable to get service manager";
-    return sp<IBinder>();
-  }
-  sp<IBinder> binder =
-      service_manager->checkService(String16(service_name.c_str()));
-  if (!binder.get())
-    LOG(ERROR) << "Unable to get \"" << service_name << "\" service";
-  return binder;
-}
-
-bool RealBinderWrapper::RegisterService(const std::string& service_name,
-                                        const sp<IBinder>& binder) {
-  sp<IServiceManager> service_manager = defaultServiceManager();
-  if (!service_manager.get()) {
-    LOG(ERROR) << "Unable to get service manager";
-    return false;
-  }
-  status_t status = defaultServiceManager()->addService(
-      String16(service_name.c_str()), binder);
-  if (status != OK) {
-    LOG(ERROR) << "Failed to register \"" << service_name << "\" with service "
-               << "manager";
-    return false;
-  }
-  return true;
-}
-
-sp<BBinder> RealBinderWrapper::CreateLocalBinder() {
-  return sp<BBinder>(new BBinder());
-}
-
-bool RealBinderWrapper::RegisterForDeathNotifications(
-    const sp<IBinder>& binder,
-    const ::base::Closure& callback) {
-  sp<DeathRecipient> recipient(new DeathRecipient(callback));
-  if (binder->linkToDeath(recipient) != OK) {
-    LOG(ERROR) << "Failed to register for death notifications on "
-               << binder.get();
-    return false;
-  }
-  death_recipients_[binder] = recipient;
-  return true;
-}
-
-bool RealBinderWrapper::UnregisterForDeathNotifications(
-    const sp<IBinder>& binder) {
-  auto it = death_recipients_.find(binder);
-  if (it == death_recipients_.end()) {
-    LOG(ERROR) << "Not registered for death notifications on " << binder.get();
-    return false;
-  }
-  if (binder->unlinkToDeath(it->second) != OK) {
-    LOG(ERROR) << "Failed to unregister for death notifications on "
-               << binder.get();
-    return false;
-  }
-  death_recipients_.erase(it);
-  return true;
-}
-
-uid_t RealBinderWrapper::GetCallingUid() {
-  return IPCThreadState::self()->getCallingUid();
-}
-
-pid_t RealBinderWrapper::GetCallingPid() {
-  return IPCThreadState::self()->getCallingPid();
-}
-
-}  // namespace android
diff --git a/libbinderwrapper/real_binder_wrapper.h b/libbinderwrapper/real_binder_wrapper.h
deleted file mode 100644
index fa05383..0000000
--- a/libbinderwrapper/real_binder_wrapper.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SYSTEM_CORE_LIBBINDERWRAPPER_REAL_BINDER_WRAPPER_H_
-#define SYSTEM_CORE_LIBBINDERWRAPPER_REAL_BINDER_WRAPPER_H_
-
-#include <map>
-
-#include <base/macros.h>
-#include <binderwrapper/binder_wrapper.h>
-
-namespace android {
-
-class IBinder;
-
-// Real implementation of BinderWrapper.
-class RealBinderWrapper : public BinderWrapper {
- public:
-  RealBinderWrapper();
-  ~RealBinderWrapper() override;
-
-  // BinderWrapper:
-  sp<IBinder> GetService(const std::string& service_name) override;
-  bool RegisterService(const std::string& service_name,
-                       const sp<IBinder>& binder) override;
-  sp<BBinder> CreateLocalBinder() override;
-  bool RegisterForDeathNotifications(const sp<IBinder>& binder,
-                                     const ::base::Closure& callback) override;
-  bool UnregisterForDeathNotifications(const sp<IBinder>& binder) override;
-  uid_t GetCallingUid() override;
-  pid_t GetCallingPid() override;
-
- private:
-  class DeathRecipient;
-
-  // Map from binder handle to object that should be notified of the binder's
-  // death.
-  std::map<sp<IBinder>, sp<DeathRecipient>> death_recipients_;
-
-  DISALLOW_COPY_AND_ASSIGN(RealBinderWrapper);
-};
-
-}  // namespace android
-
-#endif  // SYSTEM_CORE_LIBBINDER_WRAPPER_REAL_BINDER_WRAPPER_H_
diff --git a/libbinderwrapper/stub_binder_wrapper.cc b/libbinderwrapper/stub_binder_wrapper.cc
deleted file mode 100644
index 8e75f62..0000000
--- a/libbinderwrapper/stub_binder_wrapper.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <binderwrapper/stub_binder_wrapper.h>
-
-#include <base/logging.h>
-#include <binder/Binder.h>
-#include <binder/IBinder.h>
-
-namespace android {
-
-StubBinderWrapper::StubBinderWrapper()
-    : calling_uid_(-1),
-      calling_pid_(-1) {}
-
-StubBinderWrapper::~StubBinderWrapper() = default;
-
-void StubBinderWrapper::SetBinderForService(const std::string& service_name,
-                                            const sp<IBinder>& binder) {
-  services_to_return_[service_name] = binder;
-}
-
-sp<IBinder> StubBinderWrapper::GetRegisteredService(
-    const std::string& service_name) const {
-  const auto it = registered_services_.find(service_name);
-  return it != registered_services_.end() ? it->second : sp<IBinder>();
-}
-
-void StubBinderWrapper::NotifyAboutBinderDeath(const sp<IBinder>& binder) {
-  const auto it = death_callbacks_.find(binder);
-  if (it != death_callbacks_.end())
-    it->second.Run();
-}
-
-sp<IBinder> StubBinderWrapper::GetService(const std::string& service_name) {
-  const auto it = services_to_return_.find(service_name);
-  return it != services_to_return_.end() ? it->second : sp<IBinder>();
-}
-
-bool StubBinderWrapper::RegisterService(const std::string& service_name,
-                                        const sp<IBinder>& binder) {
-  registered_services_[service_name] = binder;
-  return true;
-}
-
-sp<BBinder> StubBinderWrapper::CreateLocalBinder() {
-  sp<BBinder> binder(new BBinder());
-  local_binders_.push_back(binder);
-  return binder;
-}
-
-bool StubBinderWrapper::RegisterForDeathNotifications(
-    const sp<IBinder>& binder,
-    const ::base::Closure& callback) {
-  death_callbacks_[binder] = callback;
-  return true;
-}
-
-bool StubBinderWrapper::UnregisterForDeathNotifications(
-    const sp<IBinder>& binder) {
-  death_callbacks_.erase(binder);
-  return true;
-}
-
-uid_t StubBinderWrapper::GetCallingUid() {
-  return calling_uid_;
-}
-
-pid_t StubBinderWrapper::GetCallingPid() {
-  return calling_pid_;
-}
-
-}  // namespace android
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0b5c125..92486e3 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -219,11 +219,19 @@
             exclude_srcs: [
                 "qtaguid.cpp",
             ],
+            header_abi_checker: {
+                enabled: true,
+                ref_dump_dirs: ["abi-dumps"],
+            },
         },
         product: {
             exclude_srcs: [
                 "qtaguid.cpp",
             ],
+            header_abi_checker: {
+                enabled: true,
+                ref_dump_dirs: ["abi-dumps"],
+            },
         },
     },
 
diff --git a/libcutils/KernelLibcutilsTest.xml b/libcutils/KernelLibcutilsTest.xml
index 40e4ef4..9750cbf 100644
--- a/libcutils/KernelLibcutilsTest.xml
+++ b/libcutils/KernelLibcutilsTest.xml
@@ -22,11 +22,11 @@
 
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
-        <option name="push" value="KernelLibcutilsTest->/data/local/tmp/KernelLibcutilsTest" />
+        <option name="push" value="KernelLibcutilsTest->/data/local/tests/unrestricted/KernelLibcutilsTest" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="native-test-device-path" value="/data/local/tests/unrestricted" />
         <option name="module-name" value="KernelLibcutilsTest" />
         <option name="include-filter" value="*AshmemTest*" />
     </test>
diff --git a/libcutils/TEST_MAPPING b/libcutils/TEST_MAPPING
index eb63aa5..78b3e44 100644
--- a/libcutils/TEST_MAPPING
+++ b/libcutils/TEST_MAPPING
@@ -12,6 +12,9 @@
   "kernel-presubmit": [
     {
       "name": "libcutils_test"
+    },
+    {
+      "name": "KernelLibcutilsTest"
     }
   ]
 }
diff --git a/libcutils/abi-dumps/arm64/source-based/libcutils.so.lsdump b/libcutils/abi-dumps/arm64/source-based/libcutils.so.lsdump
new file mode 100644
index 0000000..333e61c
--- /dev/null
+++ b/libcutils/abi-dumps/arm64/source-based/libcutils.so.lsdump
@@ -0,0 +1,2690 @@
+{
+ "array_types" :
+ [
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIA0_i",
+   "name" : "int[0]",
+   "referenced_type" : "_ZTIi",
+   "self_type" : "_ZTIA0_i",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  }
+ ],
+ "builtin_types" :
+ [
+  {
+   "alignment" : 1,
+   "is_integral" : true,
+   "linker_set_key" : "_ZTIa",
+   "name" : "signed char",
+   "referenced_type" : "_ZTIa",
+   "self_type" : "_ZTIa",
+   "size" : 1
+  },
+  {
+   "alignment" : 1,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIb",
+   "name" : "bool",
+   "referenced_type" : "_ZTIb",
+   "self_type" : "_ZTIb",
+   "size" : 1
+  },
+  {
+   "alignment" : 1,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIc",
+   "name" : "char",
+   "referenced_type" : "_ZTIc",
+   "self_type" : "_ZTIc",
+   "size" : 1
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIf",
+   "name" : "float",
+   "referenced_type" : "_ZTIf",
+   "self_type" : "_ZTIf",
+   "size" : 4
+  },
+  {
+   "alignment" : 4,
+   "is_integral" : true,
+   "linker_set_key" : "_ZTIi",
+   "name" : "int",
+   "referenced_type" : "_ZTIi",
+   "self_type" : "_ZTIi",
+   "size" : 4
+  },
+  {
+   "alignment" : 4,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIj",
+   "name" : "unsigned int",
+   "referenced_type" : "_ZTIj",
+   "self_type" : "_ZTIj",
+   "size" : 4
+  },
+  {
+   "alignment" : 8,
+   "is_integral" : true,
+   "linker_set_key" : "_ZTIl",
+   "name" : "long",
+   "referenced_type" : "_ZTIl",
+   "self_type" : "_ZTIl",
+   "size" : 8
+  },
+  {
+   "alignment" : 8,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIm",
+   "name" : "unsigned long",
+   "referenced_type" : "_ZTIm",
+   "self_type" : "_ZTIm",
+   "size" : 8
+  },
+  {
+   "linker_set_key" : "_ZTIv",
+   "name" : "void",
+   "referenced_type" : "_ZTIv",
+   "self_type" : "_ZTIv"
+  }
+ ],
+ "elf_functions" :
+ [
+  {
+   "name" : "_Z23socket_make_sockaddr_unPKciP11sockaddr_unPj"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZN7android4base4TrimIRNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEEEES8_OT_"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4syncEv"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE5imbueERKNS_6localeE"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE6setbufEPcl"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE7seekoffExNS_8ios_base7seekdirEj"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE7seekposENS_4fposI9mbstate_tEEj"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE8overflowEi"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE9pbackfailEi"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE9underflowEv"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEEC2Ev"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEED0Ev"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEED2Ev"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__124__put_character_sequenceIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_PKS4_m"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__16vectorI5EntryNS_9allocatorIS1_EEE24__emplace_back_slow_pathIJS1_EEEvDpOT_"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__17getlineIcNS_11char_traitsIcEENS_9allocatorIcEEEERNS_13basic_istreamIT_T0_EES9_RNS_12basic_stringIS6_S7_T1_EES6_"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc"
+  },
+  {
+   "name" : "android_get_control_file"
+  },
+  {
+   "name" : "android_get_control_socket"
+  },
+  {
+   "name" : "android_get_ioprio"
+  },
+  {
+   "name" : "android_reboot"
+  },
+  {
+   "name" : "android_set_ioprio"
+  },
+  {
+   "name" : "ashmem_create_region"
+  },
+  {
+   "name" : "ashmem_get_size_region"
+  },
+  {
+   "name" : "ashmem_pin_region"
+  },
+  {
+   "name" : "ashmem_set_prot_region"
+  },
+  {
+   "name" : "ashmem_unpin_region"
+  },
+  {
+   "name" : "ashmem_valid"
+  },
+  {
+   "name" : "atrace_async_begin_body"
+  },
+  {
+   "name" : "atrace_async_end_body"
+  },
+  {
+   "name" : "atrace_async_for_track_begin_body"
+  },
+  {
+   "name" : "atrace_async_for_track_end_body"
+  },
+  {
+   "name" : "atrace_begin_body"
+  },
+  {
+   "name" : "atrace_end_body"
+  },
+  {
+   "name" : "atrace_get_enabled_tags"
+  },
+  {
+   "name" : "atrace_init"
+  },
+  {
+   "name" : "atrace_instant_body"
+  },
+  {
+   "name" : "atrace_instant_for_track_body"
+  },
+  {
+   "name" : "atrace_int64_body"
+  },
+  {
+   "name" : "atrace_int_body"
+  },
+  {
+   "name" : "atrace_set_tracing_enabled"
+  },
+  {
+   "name" : "atrace_setup"
+  },
+  {
+   "name" : "atrace_update_tags"
+  },
+  {
+   "name" : "canned_fs_config"
+  },
+  {
+   "name" : "config_bool"
+  },
+  {
+   "name" : "config_find"
+  },
+  {
+   "name" : "config_free"
+  },
+  {
+   "name" : "config_load"
+  },
+  {
+   "name" : "config_load_file"
+  },
+  {
+   "name" : "config_node"
+  },
+  {
+   "name" : "config_set"
+  },
+  {
+   "name" : "config_str"
+  },
+  {
+   "name" : "fs_config"
+  },
+  {
+   "name" : "fs_mkdirs"
+  },
+  {
+   "name" : "fs_prepare_dir"
+  },
+  {
+   "name" : "fs_prepare_dir_strict"
+  },
+  {
+   "name" : "fs_prepare_file_strict"
+  },
+  {
+   "name" : "fs_read_atomic_int"
+  },
+  {
+   "name" : "fs_write_atomic_int"
+  },
+  {
+   "name" : "hashmapCreate"
+  },
+  {
+   "name" : "hashmapForEach"
+  },
+  {
+   "name" : "hashmapFree"
+  },
+  {
+   "name" : "hashmapGet"
+  },
+  {
+   "name" : "hashmapHash"
+  },
+  {
+   "name" : "hashmapLock"
+  },
+  {
+   "name" : "hashmapPut"
+  },
+  {
+   "name" : "hashmapRemove"
+  },
+  {
+   "name" : "hashmapUnlock"
+  },
+  {
+   "name" : "klog_set_level"
+  },
+  {
+   "name" : "klog_write"
+  },
+  {
+   "name" : "klog_writev"
+  },
+  {
+   "name" : "load_canned_fs_config"
+  },
+  {
+   "name" : "load_file"
+  },
+  {
+   "name" : "multiuser_convert_sdk_sandbox_to_app_uid"
+  },
+  {
+   "name" : "multiuser_get_app_id"
+  },
+  {
+   "name" : "multiuser_get_cache_gid"
+  },
+  {
+   "name" : "multiuser_get_ext_cache_gid"
+  },
+  {
+   "name" : "multiuser_get_ext_gid"
+  },
+  {
+   "name" : "multiuser_get_sdk_sandbox_uid"
+  },
+  {
+   "name" : "multiuser_get_shared_app_gid"
+  },
+  {
+   "name" : "multiuser_get_shared_gid"
+  },
+  {
+   "name" : "multiuser_get_uid"
+  },
+  {
+   "name" : "multiuser_get_user_id"
+  },
+  {
+   "name" : "native_handle_clone"
+  },
+  {
+   "name" : "native_handle_close"
+  },
+  {
+   "name" : "native_handle_close_with_tag"
+  },
+  {
+   "name" : "native_handle_create"
+  },
+  {
+   "name" : "native_handle_delete"
+  },
+  {
+   "name" : "native_handle_init"
+  },
+  {
+   "name" : "native_handle_set_fdsan_tag"
+  },
+  {
+   "name" : "native_handle_unset_fdsan_tag"
+  },
+  {
+   "name" : "partition_wiped"
+  },
+  {
+   "name" : "property_get"
+  },
+  {
+   "name" : "property_get_bool"
+  },
+  {
+   "name" : "property_get_int32"
+  },
+  {
+   "name" : "property_get_int64"
+  },
+  {
+   "name" : "property_list"
+  },
+  {
+   "name" : "property_set"
+  },
+  {
+   "name" : "record_stream_free"
+  },
+  {
+   "name" : "record_stream_get_next"
+  },
+  {
+   "name" : "record_stream_new"
+  },
+  {
+   "name" : "socket_close"
+  },
+  {
+   "name" : "socket_get_local_port"
+  },
+  {
+   "name" : "socket_inaddr_any_server"
+  },
+  {
+   "name" : "socket_local_client"
+  },
+  {
+   "name" : "socket_local_client_connect"
+  },
+  {
+   "name" : "socket_local_server"
+  },
+  {
+   "name" : "socket_local_server_bind"
+  },
+  {
+   "name" : "socket_network_client"
+  },
+  {
+   "name" : "socket_network_client_timeout"
+  },
+  {
+   "name" : "socket_send_buffers"
+  },
+  {
+   "name" : "str_parms_add_float"
+  },
+  {
+   "name" : "str_parms_add_int"
+  },
+  {
+   "name" : "str_parms_add_str"
+  },
+  {
+   "name" : "str_parms_create"
+  },
+  {
+   "name" : "str_parms_create_str"
+  },
+  {
+   "name" : "str_parms_del"
+  },
+  {
+   "name" : "str_parms_destroy"
+  },
+  {
+   "name" : "str_parms_dump"
+  },
+  {
+   "name" : "str_parms_get_float"
+  },
+  {
+   "name" : "str_parms_get_int"
+  },
+  {
+   "name" : "str_parms_get_str"
+  },
+  {
+   "name" : "str_parms_has_key"
+  },
+  {
+   "name" : "str_parms_to_str"
+  },
+  {
+   "name" : "uevent_kernel_multicast_recv"
+  },
+  {
+   "name" : "uevent_kernel_multicast_uid_recv"
+  },
+  {
+   "name" : "uevent_kernel_recv"
+  },
+  {
+   "name" : "uevent_open_socket"
+  }
+ ],
+ "elf_objects" :
+ [
+  {
+   "binding" : "weak",
+   "name" : "_ZTCNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE0_NS_13basic_istreamIcS2_EE"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZTTNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZTVNSt3__113basic_filebufIcNS_11char_traitsIcEEEE"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZTVNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE"
+  },
+  {
+   "name" : "atrace_enabled_tags"
+  },
+  {
+   "name" : "atrace_is_ready"
+  },
+  {
+   "name" : "atrace_marker_fd"
+  }
+ ],
+ "enum_types" :
+ [
+  {
+   "alignment" : 4,
+   "enum_fields" :
+   [
+    {
+     "enum_field_value" : 0,
+     "name" : "IoSchedClass_NONE"
+    },
+    {
+     "enum_field_value" : 1,
+     "name" : "IoSchedClass_RT"
+    },
+    {
+     "enum_field_value" : 2,
+     "name" : "IoSchedClass_BE"
+    },
+    {
+     "enum_field_value" : 3,
+     "name" : "IoSchedClass_IDLE"
+    }
+   ],
+   "linker_set_key" : "_ZTI12IoSchedClass",
+   "name" : "IoSchedClass",
+   "referenced_type" : "_ZTI12IoSchedClass",
+   "self_type" : "_ZTI12IoSchedClass",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h",
+   "underlying_type" : "_ZTIj"
+  }
+ ],
+ "function_types" :
+ [
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIFbPvS_E",
+   "name" : "bool (void *, void *)",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "referenced_type" : "_ZTIFbPvS_E",
+   "return_type" : "_ZTIb",
+   "self_type" : "_ZTIFbPvS_E",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIFbPvS_S_E",
+   "name" : "bool (void *, void *, void *)",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "referenced_type" : "_ZTIFbPvS_S_E",
+   "return_type" : "_ZTIb",
+   "self_type" : "_ZTIFbPvS_S_E",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIFiPvE",
+   "name" : "int (void *)",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "referenced_type" : "_ZTIFiPvE",
+   "return_type" : "_ZTIi",
+   "self_type" : "_ZTIFiPvE",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIFvPKcS0_PvE",
+   "name" : "void (const char *, const char *, void *)",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "referenced_type" : "_ZTIFvPKcS0_PvE",
+   "return_type" : "_ZTIv",
+   "self_type" : "_ZTIFvPKcS0_PvE",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  }
+ ],
+ "functions" :
+ [
+  {
+   "function_name" : "android_get_control_file",
+   "linker_set_key" : "android_get_control_file",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/android_get_control_file.h"
+  },
+  {
+   "function_name" : "android_get_control_socket",
+   "linker_set_key" : "android_get_control_socket",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "android_get_ioprio",
+   "linker_set_key" : "android_get_ioprio",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIP12IoSchedClass"
+    },
+    {
+     "referenced_type" : "_ZTIPi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h"
+  },
+  {
+   "function_name" : "android_reboot",
+   "linker_set_key" : "android_reboot",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/android_reboot.h"
+  },
+  {
+   "function_name" : "android_set_ioprio",
+   "linker_set_key" : "android_set_ioprio",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTI12IoSchedClass"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h"
+  },
+  {
+   "function_name" : "ashmem_create_region",
+   "linker_set_key" : "ashmem_create_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_get_size_region",
+   "linker_set_key" : "ashmem_get_size_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_pin_region",
+   "linker_set_key" : "ashmem_pin_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_set_prot_region",
+   "linker_set_key" : "ashmem_set_prot_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_unpin_region",
+   "linker_set_key" : "ashmem_unpin_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_valid",
+   "linker_set_key" : "ashmem_valid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "atrace_async_begin_body",
+   "linker_set_key" : "atrace_async_begin_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_async_end_body",
+   "linker_set_key" : "atrace_async_end_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_async_for_track_begin_body",
+   "linker_set_key" : "atrace_async_for_track_begin_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_async_for_track_end_body",
+   "linker_set_key" : "atrace_async_for_track_end_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_begin_body",
+   "linker_set_key" : "atrace_begin_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_end_body",
+   "linker_set_key" : "atrace_end_body",
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_get_enabled_tags",
+   "linker_set_key" : "atrace_get_enabled_tags",
+   "return_type" : "_ZTIm",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_init",
+   "linker_set_key" : "atrace_init",
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_instant_body",
+   "linker_set_key" : "atrace_instant_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_instant_for_track_body",
+   "linker_set_key" : "atrace_instant_for_track_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_int64_body",
+   "linker_set_key" : "atrace_int64_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIl"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_int_body",
+   "linker_set_key" : "atrace_int_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_set_tracing_enabled",
+   "linker_set_key" : "atrace_set_tracing_enabled",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIb"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_setup",
+   "linker_set_key" : "atrace_setup",
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_update_tags",
+   "linker_set_key" : "atrace_update_tags",
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "canned_fs_config",
+   "linker_set_key" : "canned_fs_config",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPm"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/private/canned_fs_config.h"
+  },
+  {
+   "function_name" : "config_bool",
+   "linker_set_key" : "config_bool",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_find",
+   "linker_set_key" : "config_find",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIP5cnode",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_free",
+   "linker_set_key" : "config_free",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_load",
+   "linker_set_key" : "config_load",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_load_file",
+   "linker_set_key" : "config_load_file",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_node",
+   "linker_set_key" : "config_node",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIP5cnode",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_set",
+   "linker_set_key" : "config_set",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_str",
+   "linker_set_key" : "config_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIPKc",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "fs_config",
+   "linker_set_key" : "fs_config",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPm"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/private/fs_config.h"
+  },
+  {
+   "function_name" : "fs_mkdirs",
+   "linker_set_key" : "fs_mkdirs",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_prepare_dir",
+   "linker_set_key" : "fs_prepare_dir",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_prepare_dir_strict",
+   "linker_set_key" : "fs_prepare_dir_strict",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_prepare_file_strict",
+   "linker_set_key" : "fs_prepare_file_strict",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_read_atomic_int",
+   "linker_set_key" : "fs_read_atomic_int",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_write_atomic_int",
+   "linker_set_key" : "fs_write_atomic_int",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "hashmapCreate",
+   "linker_set_key" : "hashmapCreate",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIm"
+    },
+    {
+     "referenced_type" : "_ZTIPFiPvE"
+    },
+    {
+     "referenced_type" : "_ZTIPFbPvS_E"
+    }
+   ],
+   "return_type" : "_ZTIP7Hashmap",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapForEach",
+   "linker_set_key" : "hashmapForEach",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    },
+    {
+     "referenced_type" : "_ZTIPFbPvS_S_E"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapFree",
+   "linker_set_key" : "hashmapFree",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapGet",
+   "linker_set_key" : "hashmapGet",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIPv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapHash",
+   "linker_set_key" : "hashmapHash",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapLock",
+   "linker_set_key" : "hashmapLock",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapPut",
+   "linker_set_key" : "hashmapPut",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIPv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapRemove",
+   "linker_set_key" : "hashmapRemove",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIPv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapUnlock",
+   "linker_set_key" : "hashmapUnlock",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "klog_set_level",
+   "linker_set_key" : "klog_set_level",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "function_name" : "klog_write",
+   "linker_set_key" : "klog_write",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "function_name" : "klog_writev",
+   "linker_set_key" : "klog_writev",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPK5iovec"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "function_name" : "load_canned_fs_config",
+   "linker_set_key" : "load_canned_fs_config",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/private/canned_fs_config.h"
+  },
+  {
+   "function_name" : "load_file",
+   "linker_set_key" : "load_file",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    }
+   ],
+   "return_type" : "_ZTIPv",
+   "source_file" : "system/core/libcutils/include/cutils/misc.h"
+  },
+  {
+   "function_name" : "multiuser_convert_sdk_sandbox_to_app_uid",
+   "linker_set_key" : "multiuser_convert_sdk_sandbox_to_app_uid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_app_id",
+   "linker_set_key" : "multiuser_get_app_id",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_cache_gid",
+   "linker_set_key" : "multiuser_get_cache_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_ext_cache_gid",
+   "linker_set_key" : "multiuser_get_ext_cache_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_ext_gid",
+   "linker_set_key" : "multiuser_get_ext_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_sdk_sandbox_uid",
+   "linker_set_key" : "multiuser_get_sdk_sandbox_uid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_shared_app_gid",
+   "linker_set_key" : "multiuser_get_shared_app_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_shared_gid",
+   "linker_set_key" : "multiuser_get_shared_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_uid",
+   "linker_set_key" : "multiuser_get_uid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_user_id",
+   "linker_set_key" : "multiuser_get_user_id",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "native_handle_clone",
+   "linker_set_key" : "native_handle_clone",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIP13native_handle",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_close",
+   "linker_set_key" : "native_handle_close",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_close_with_tag",
+   "linker_set_key" : "native_handle_close_with_tag",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_create",
+   "linker_set_key" : "native_handle_create",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIP13native_handle",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_delete",
+   "linker_set_key" : "native_handle_delete",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_init",
+   "linker_set_key" : "native_handle_init",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIP13native_handle",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_set_fdsan_tag",
+   "linker_set_key" : "native_handle_set_fdsan_tag",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_unset_fdsan_tag",
+   "linker_set_key" : "native_handle_unset_fdsan_tag",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "partition_wiped",
+   "linker_set_key" : "partition_wiped",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/partition_utils.h"
+  },
+  {
+   "function_name" : "property_get",
+   "linker_set_key" : "property_get",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_get_bool",
+   "linker_set_key" : "property_get_bool",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIa"
+    }
+   ],
+   "return_type" : "_ZTIa",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_get_int32",
+   "linker_set_key" : "property_get_int32",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_get_int64",
+   "linker_set_key" : "property_get_int64",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIl"
+    }
+   ],
+   "return_type" : "_ZTIl",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_list",
+   "linker_set_key" : "property_list",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPFvPKcS0_PvE"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_set",
+   "linker_set_key" : "property_set",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "record_stream_free",
+   "linker_set_key" : "record_stream_free",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP12RecordStream"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "function_name" : "record_stream_get_next",
+   "linker_set_key" : "record_stream_get_next",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP12RecordStream"
+    },
+    {
+     "referenced_type" : "_ZTIPPv"
+    },
+    {
+     "referenced_type" : "_ZTIPm"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "function_name" : "record_stream_new",
+   "linker_set_key" : "record_stream_new",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "return_type" : "_ZTIP12RecordStream",
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "function_name" : "socket_close",
+   "linker_set_key" : "socket_close",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_get_local_port",
+   "linker_set_key" : "socket_get_local_port",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_inaddr_any_server",
+   "linker_set_key" : "socket_inaddr_any_server",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_local_client",
+   "linker_set_key" : "socket_local_client",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_local_client_connect",
+   "linker_set_key" : "socket_local_client_connect",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_local_server",
+   "linker_set_key" : "socket_local_server",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_local_server_bind",
+   "linker_set_key" : "socket_local_server_bind",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_network_client",
+   "linker_set_key" : "socket_network_client",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_network_client_timeout",
+   "linker_set_key" : "socket_network_client_timeout",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_send_buffers",
+   "linker_set_key" : "socket_send_buffers",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPK22cutils_socket_buffer_t"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "return_type" : "_ZTIl",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "str_parms_add_float",
+   "linker_set_key" : "str_parms_add_float",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIf"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_add_int",
+   "linker_set_key" : "str_parms_add_int",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_add_str",
+   "linker_set_key" : "str_parms_add_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_create",
+   "linker_set_key" : "str_parms_create",
+   "return_type" : "_ZTIP9str_parms",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_create_str",
+   "linker_set_key" : "str_parms_create_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIP9str_parms",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_del",
+   "linker_set_key" : "str_parms_del",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_destroy",
+   "linker_set_key" : "str_parms_destroy",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_dump",
+   "linker_set_key" : "str_parms_dump",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_get_float",
+   "linker_set_key" : "str_parms_get_float",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPf"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_get_int",
+   "linker_set_key" : "str_parms_get_int",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_get_str",
+   "linker_set_key" : "str_parms_get_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_has_key",
+   "linker_set_key" : "str_parms_has_key",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_to_str",
+   "linker_set_key" : "str_parms_to_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    }
+   ],
+   "return_type" : "_ZTIPc",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "uevent_kernel_multicast_recv",
+   "linker_set_key" : "uevent_kernel_multicast_recv",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "return_type" : "_ZTIl",
+   "source_file" : "system/core/libcutils/include/cutils/uevent.h"
+  },
+  {
+   "function_name" : "uevent_kernel_multicast_uid_recv",
+   "linker_set_key" : "uevent_kernel_multicast_uid_recv",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    }
+   ],
+   "return_type" : "_ZTIl",
+   "source_file" : "system/core/libcutils/include/cutils/uevent.h"
+  },
+  {
+   "function_name" : "uevent_kernel_recv",
+   "linker_set_key" : "uevent_kernel_recv",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIm"
+    },
+    {
+     "referenced_type" : "_ZTIb"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    }
+   ],
+   "return_type" : "_ZTIl",
+   "source_file" : "system/core/libcutils/include/cutils/uevent.h"
+  },
+  {
+   "function_name" : "uevent_open_socket",
+   "linker_set_key" : "uevent_open_socket",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIb"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/uevent.h"
+  }
+ ],
+ "global_vars" :
+ [
+  {
+   "linker_set_key" : "atrace_enabled_tags",
+   "name" : "atrace_enabled_tags",
+   "referenced_type" : "_ZTIm",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "linker_set_key" : "atrace_is_ready",
+   "name" : "atrace_is_ready",
+   "referenced_type" : "_ZTINSt3__16atomicIbEE",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "linker_set_key" : "atrace_marker_fd",
+   "name" : "atrace_marker_fd",
+   "referenced_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  }
+ ],
+ "lvalue_reference_types" : [],
+ "pointer_types" :
+ [
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIP12IoSchedClass",
+   "name" : "IoSchedClass *",
+   "referenced_type" : "_ZTI12IoSchedClass",
+   "self_type" : "_ZTIP12IoSchedClass",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIP12RecordStream",
+   "name" : "RecordStream *",
+   "referenced_type" : "_ZTI12RecordStream",
+   "self_type" : "_ZTIP12RecordStream",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIP13native_handle",
+   "name" : "native_handle *",
+   "referenced_type" : "_ZTI13native_handle",
+   "self_type" : "_ZTIP13native_handle",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIP5cnode",
+   "name" : "cnode *",
+   "referenced_type" : "_ZTI5cnode",
+   "self_type" : "_ZTIP5cnode",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIP7Hashmap",
+   "name" : "Hashmap *",
+   "referenced_type" : "_ZTI7Hashmap",
+   "self_type" : "_ZTIP7Hashmap",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIP9str_parms",
+   "name" : "str_parms *",
+   "referenced_type" : "_ZTI9str_parms",
+   "self_type" : "_ZTIP9str_parms",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPFbPvS_E",
+   "name" : "bool (*)(void *, void *)",
+   "referenced_type" : "_ZTIFbPvS_E",
+   "self_type" : "_ZTIPFbPvS_E",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPFbPvS_S_E",
+   "name" : "bool (*)(void *, void *, void *)",
+   "referenced_type" : "_ZTIFbPvS_S_E",
+   "self_type" : "_ZTIPFbPvS_S_E",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPFiPvE",
+   "name" : "int (*)(void *)",
+   "referenced_type" : "_ZTIFiPvE",
+   "self_type" : "_ZTIPFiPvE",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPFvPKcS0_PvE",
+   "name" : "void (*)(const char *, const char *, void *)",
+   "referenced_type" : "_ZTIFvPKcS0_PvE",
+   "self_type" : "_ZTIPFvPKcS0_PvE",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPK13native_handle",
+   "name" : "const native_handle *",
+   "referenced_type" : "_ZTIK13native_handle",
+   "self_type" : "_ZTIPK13native_handle",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPK22cutils_socket_buffer_t",
+   "name" : "const cutils_socket_buffer_t *",
+   "referenced_type" : "_ZTIK22cutils_socket_buffer_t",
+   "self_type" : "_ZTIPK22cutils_socket_buffer_t",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPK5iovec",
+   "name" : "const iovec *",
+   "referenced_type" : "_ZTIK5iovec",
+   "self_type" : "_ZTIPK5iovec",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPKc",
+   "name" : "const char *",
+   "referenced_type" : "_ZTIKc",
+   "self_type" : "_ZTIPKc",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPKv",
+   "name" : "const void *",
+   "referenced_type" : "_ZTIKv",
+   "self_type" : "_ZTIPKv",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPPv",
+   "name" : "void **",
+   "referenced_type" : "_ZTIPv",
+   "self_type" : "_ZTIPPv",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPc",
+   "name" : "char *",
+   "referenced_type" : "_ZTIc",
+   "self_type" : "_ZTIPc",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPf",
+   "name" : "float *",
+   "referenced_type" : "_ZTIf",
+   "self_type" : "_ZTIPf",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPi",
+   "name" : "int *",
+   "referenced_type" : "_ZTIi",
+   "self_type" : "_ZTIPi",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPj",
+   "name" : "unsigned int *",
+   "referenced_type" : "_ZTIj",
+   "self_type" : "_ZTIPj",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/misc.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPm",
+   "name" : "unsigned long *",
+   "referenced_type" : "_ZTIm",
+   "self_type" : "_ZTIPm",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "alignment" : 8,
+   "linker_set_key" : "_ZTIPv",
+   "name" : "void *",
+   "referenced_type" : "_ZTIv",
+   "self_type" : "_ZTIPv",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/misc.h"
+  }
+ ],
+ "qualified_types" :
+ [
+  {
+   "alignment" : 4,
+   "is_const" : true,
+   "linker_set_key" : "_ZTIK13native_handle",
+   "name" : "const native_handle",
+   "referenced_type" : "_ZTI13native_handle",
+   "self_type" : "_ZTIK13native_handle",
+   "size" : 12,
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "alignment" : 8,
+   "is_const" : true,
+   "linker_set_key" : "_ZTIK22cutils_socket_buffer_t",
+   "name" : "const cutils_socket_buffer_t",
+   "referenced_type" : "_ZTI22cutils_socket_buffer_t",
+   "self_type" : "_ZTIK22cutils_socket_buffer_t",
+   "size" : 16,
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "alignment" : 8,
+   "is_const" : true,
+   "linker_set_key" : "_ZTIK5iovec",
+   "name" : "const iovec",
+   "referenced_type" : "_ZTI5iovec",
+   "self_type" : "_ZTIK5iovec",
+   "size" : 16,
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "alignment" : 1,
+   "is_const" : true,
+   "linker_set_key" : "_ZTIKc",
+   "name" : "const char",
+   "referenced_type" : "_ZTIc",
+   "self_type" : "_ZTIKc",
+   "size" : 1,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "is_const" : true,
+   "linker_set_key" : "_ZTIKv",
+   "name" : "const void",
+   "referenced_type" : "_ZTIv",
+   "self_type" : "_ZTIKv",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  }
+ ],
+ "record_types" :
+ [
+  {
+   "alignment" : 4,
+   "fields" :
+   [
+    {
+     "field_name" : "version",
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "field_name" : "numFds",
+     "field_offset" : 32,
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "field_name" : "numInts",
+     "field_offset" : 64,
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "field_name" : "data",
+     "field_offset" : 96,
+     "referenced_type" : "_ZTIA0_i"
+    }
+   ],
+   "linker_set_key" : "_ZTI13native_handle",
+   "name" : "native_handle",
+   "referenced_type" : "_ZTI13native_handle",
+   "self_type" : "_ZTI13native_handle",
+   "size" : 12,
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "alignment" : 8,
+   "fields" :
+   [
+    {
+     "field_name" : "data",
+     "referenced_type" : "_ZTIPKv"
+    },
+    {
+     "field_name" : "length",
+     "field_offset" : 64,
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "linker_set_key" : "_ZTI22cutils_socket_buffer_t",
+   "name" : "cutils_socket_buffer_t",
+   "referenced_type" : "_ZTI22cutils_socket_buffer_t",
+   "self_type" : "_ZTI22cutils_socket_buffer_t",
+   "size" : 16,
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "alignment" : 8,
+   "fields" :
+   [
+    {
+     "field_name" : "next",
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "field_name" : "first_child",
+     "field_offset" : 64,
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "field_name" : "last_child",
+     "field_offset" : 128,
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "field_name" : "name",
+     "field_offset" : 192,
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "field_name" : "value",
+     "field_offset" : 256,
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "linker_set_key" : "_ZTI5cnode",
+   "name" : "cnode",
+   "referenced_type" : "_ZTI5cnode",
+   "self_type" : "_ZTI5cnode",
+   "size" : 40,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  }
+ ],
+ "rvalue_reference_types" : []
+}
diff --git a/libcutils/abi-dumps/arm_arm64/source-based/libcutils.so.lsdump b/libcutils/abi-dumps/arm_arm64/source-based/libcutils.so.lsdump
new file mode 100644
index 0000000..f612fb9
--- /dev/null
+++ b/libcutils/abi-dumps/arm_arm64/source-based/libcutils.so.lsdump
@@ -0,0 +1,2700 @@
+{
+ "array_types" :
+ [
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIA0_i",
+   "name" : "int[0]",
+   "referenced_type" : "_ZTIi",
+   "self_type" : "_ZTIA0_i",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  }
+ ],
+ "builtin_types" :
+ [
+  {
+   "alignment" : 1,
+   "is_integral" : true,
+   "linker_set_key" : "_ZTIa",
+   "name" : "signed char",
+   "referenced_type" : "_ZTIa",
+   "self_type" : "_ZTIa",
+   "size" : 1
+  },
+  {
+   "alignment" : 1,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIb",
+   "name" : "bool",
+   "referenced_type" : "_ZTIb",
+   "self_type" : "_ZTIb",
+   "size" : 1
+  },
+  {
+   "alignment" : 1,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIc",
+   "name" : "char",
+   "referenced_type" : "_ZTIc",
+   "self_type" : "_ZTIc",
+   "size" : 1
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIf",
+   "name" : "float",
+   "referenced_type" : "_ZTIf",
+   "self_type" : "_ZTIf",
+   "size" : 4
+  },
+  {
+   "alignment" : 4,
+   "is_integral" : true,
+   "linker_set_key" : "_ZTIi",
+   "name" : "int",
+   "referenced_type" : "_ZTIi",
+   "self_type" : "_ZTIi",
+   "size" : 4
+  },
+  {
+   "alignment" : 4,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIj",
+   "name" : "unsigned int",
+   "referenced_type" : "_ZTIj",
+   "self_type" : "_ZTIj",
+   "size" : 4
+  },
+  {
+   "alignment" : 2,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIt",
+   "name" : "unsigned short",
+   "referenced_type" : "_ZTIt",
+   "self_type" : "_ZTIt",
+   "size" : 2
+  },
+  {
+   "linker_set_key" : "_ZTIv",
+   "name" : "void",
+   "referenced_type" : "_ZTIv",
+   "self_type" : "_ZTIv"
+  },
+  {
+   "alignment" : 8,
+   "is_integral" : true,
+   "linker_set_key" : "_ZTIx",
+   "name" : "long long",
+   "referenced_type" : "_ZTIx",
+   "self_type" : "_ZTIx",
+   "size" : 8
+  },
+  {
+   "alignment" : 8,
+   "is_integral" : true,
+   "is_unsigned" : true,
+   "linker_set_key" : "_ZTIy",
+   "name" : "unsigned long long",
+   "referenced_type" : "_ZTIy",
+   "self_type" : "_ZTIy",
+   "size" : 8
+  }
+ ],
+ "elf_functions" :
+ [
+  {
+   "name" : "_Z23socket_make_sockaddr_unPKciP11sockaddr_unPi"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZN7android4base4TrimIRNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEEEES8_OT_"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4syncEv"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE5imbueERKNS_6localeE"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE6setbufEPci"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE7seekoffExNS_8ios_base7seekdirEj"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE7seekposENS_4fposI9mbstate_tEEj"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE8overflowEi"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE9pbackfailEi"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE9underflowEv"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEEC2Ev"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEED0Ev"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEED2Ev"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__124__put_character_sequenceIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_PKS4_j"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__16vectorI5EntryNS_9allocatorIS1_EEE24__emplace_back_slow_pathIJS1_EEEvDpOT_"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__17getlineIcNS_11char_traitsIcEENS_9allocatorIcEEEERNS_13basic_istreamIT_T0_EES9_RNS_12basic_stringIS6_S7_T1_EES6_"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc"
+  },
+  {
+   "name" : "android_get_control_file"
+  },
+  {
+   "name" : "android_get_control_socket"
+  },
+  {
+   "name" : "android_get_ioprio"
+  },
+  {
+   "name" : "android_reboot"
+  },
+  {
+   "name" : "android_set_ioprio"
+  },
+  {
+   "name" : "ashmem_create_region"
+  },
+  {
+   "name" : "ashmem_get_size_region"
+  },
+  {
+   "name" : "ashmem_pin_region"
+  },
+  {
+   "name" : "ashmem_set_prot_region"
+  },
+  {
+   "name" : "ashmem_unpin_region"
+  },
+  {
+   "name" : "ashmem_valid"
+  },
+  {
+   "name" : "atrace_async_begin_body"
+  },
+  {
+   "name" : "atrace_async_end_body"
+  },
+  {
+   "name" : "atrace_async_for_track_begin_body"
+  },
+  {
+   "name" : "atrace_async_for_track_end_body"
+  },
+  {
+   "name" : "atrace_begin_body"
+  },
+  {
+   "name" : "atrace_end_body"
+  },
+  {
+   "name" : "atrace_get_enabled_tags"
+  },
+  {
+   "name" : "atrace_init"
+  },
+  {
+   "name" : "atrace_instant_body"
+  },
+  {
+   "name" : "atrace_instant_for_track_body"
+  },
+  {
+   "name" : "atrace_int64_body"
+  },
+  {
+   "name" : "atrace_int_body"
+  },
+  {
+   "name" : "atrace_set_tracing_enabled"
+  },
+  {
+   "name" : "atrace_setup"
+  },
+  {
+   "name" : "atrace_update_tags"
+  },
+  {
+   "name" : "canned_fs_config"
+  },
+  {
+   "name" : "config_bool"
+  },
+  {
+   "name" : "config_find"
+  },
+  {
+   "name" : "config_free"
+  },
+  {
+   "name" : "config_load"
+  },
+  {
+   "name" : "config_load_file"
+  },
+  {
+   "name" : "config_node"
+  },
+  {
+   "name" : "config_set"
+  },
+  {
+   "name" : "config_str"
+  },
+  {
+   "name" : "fs_config"
+  },
+  {
+   "name" : "fs_mkdirs"
+  },
+  {
+   "name" : "fs_prepare_dir"
+  },
+  {
+   "name" : "fs_prepare_dir_strict"
+  },
+  {
+   "name" : "fs_prepare_file_strict"
+  },
+  {
+   "name" : "fs_read_atomic_int"
+  },
+  {
+   "name" : "fs_write_atomic_int"
+  },
+  {
+   "name" : "hashmapCreate"
+  },
+  {
+   "name" : "hashmapForEach"
+  },
+  {
+   "name" : "hashmapFree"
+  },
+  {
+   "name" : "hashmapGet"
+  },
+  {
+   "name" : "hashmapHash"
+  },
+  {
+   "name" : "hashmapLock"
+  },
+  {
+   "name" : "hashmapPut"
+  },
+  {
+   "name" : "hashmapRemove"
+  },
+  {
+   "name" : "hashmapUnlock"
+  },
+  {
+   "name" : "klog_set_level"
+  },
+  {
+   "name" : "klog_write"
+  },
+  {
+   "name" : "klog_writev"
+  },
+  {
+   "name" : "load_canned_fs_config"
+  },
+  {
+   "name" : "load_file"
+  },
+  {
+   "name" : "multiuser_convert_sdk_sandbox_to_app_uid"
+  },
+  {
+   "name" : "multiuser_get_app_id"
+  },
+  {
+   "name" : "multiuser_get_cache_gid"
+  },
+  {
+   "name" : "multiuser_get_ext_cache_gid"
+  },
+  {
+   "name" : "multiuser_get_ext_gid"
+  },
+  {
+   "name" : "multiuser_get_sdk_sandbox_uid"
+  },
+  {
+   "name" : "multiuser_get_shared_app_gid"
+  },
+  {
+   "name" : "multiuser_get_shared_gid"
+  },
+  {
+   "name" : "multiuser_get_uid"
+  },
+  {
+   "name" : "multiuser_get_user_id"
+  },
+  {
+   "name" : "native_handle_clone"
+  },
+  {
+   "name" : "native_handle_close"
+  },
+  {
+   "name" : "native_handle_close_with_tag"
+  },
+  {
+   "name" : "native_handle_create"
+  },
+  {
+   "name" : "native_handle_delete"
+  },
+  {
+   "name" : "native_handle_init"
+  },
+  {
+   "name" : "native_handle_set_fdsan_tag"
+  },
+  {
+   "name" : "native_handle_unset_fdsan_tag"
+  },
+  {
+   "name" : "partition_wiped"
+  },
+  {
+   "name" : "property_get"
+  },
+  {
+   "name" : "property_get_bool"
+  },
+  {
+   "name" : "property_get_int32"
+  },
+  {
+   "name" : "property_get_int64"
+  },
+  {
+   "name" : "property_list"
+  },
+  {
+   "name" : "property_set"
+  },
+  {
+   "name" : "record_stream_free"
+  },
+  {
+   "name" : "record_stream_get_next"
+  },
+  {
+   "name" : "record_stream_new"
+  },
+  {
+   "name" : "socket_close"
+  },
+  {
+   "name" : "socket_get_local_port"
+  },
+  {
+   "name" : "socket_inaddr_any_server"
+  },
+  {
+   "name" : "socket_local_client"
+  },
+  {
+   "name" : "socket_local_client_connect"
+  },
+  {
+   "name" : "socket_local_server"
+  },
+  {
+   "name" : "socket_local_server_bind"
+  },
+  {
+   "name" : "socket_network_client"
+  },
+  {
+   "name" : "socket_network_client_timeout"
+  },
+  {
+   "name" : "socket_send_buffers"
+  },
+  {
+   "name" : "str_parms_add_float"
+  },
+  {
+   "name" : "str_parms_add_int"
+  },
+  {
+   "name" : "str_parms_add_str"
+  },
+  {
+   "name" : "str_parms_create"
+  },
+  {
+   "name" : "str_parms_create_str"
+  },
+  {
+   "name" : "str_parms_del"
+  },
+  {
+   "name" : "str_parms_destroy"
+  },
+  {
+   "name" : "str_parms_dump"
+  },
+  {
+   "name" : "str_parms_get_float"
+  },
+  {
+   "name" : "str_parms_get_int"
+  },
+  {
+   "name" : "str_parms_get_str"
+  },
+  {
+   "name" : "str_parms_has_key"
+  },
+  {
+   "name" : "str_parms_to_str"
+  },
+  {
+   "name" : "uevent_kernel_multicast_recv"
+  },
+  {
+   "name" : "uevent_kernel_multicast_uid_recv"
+  },
+  {
+   "name" : "uevent_kernel_recv"
+  },
+  {
+   "name" : "uevent_open_socket"
+  }
+ ],
+ "elf_objects" :
+ [
+  {
+   "binding" : "weak",
+   "name" : "_ZTCNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE0_NS_13basic_istreamIcS2_EE"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZTTNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZTVNSt3__113basic_filebufIcNS_11char_traitsIcEEEE"
+  },
+  {
+   "binding" : "weak",
+   "name" : "_ZTVNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE"
+  },
+  {
+   "name" : "atrace_enabled_tags"
+  },
+  {
+   "name" : "atrace_is_ready"
+  },
+  {
+   "name" : "atrace_marker_fd"
+  }
+ ],
+ "enum_types" :
+ [
+  {
+   "alignment" : 4,
+   "enum_fields" :
+   [
+    {
+     "enum_field_value" : 0,
+     "name" : "IoSchedClass_NONE"
+    },
+    {
+     "enum_field_value" : 1,
+     "name" : "IoSchedClass_RT"
+    },
+    {
+     "enum_field_value" : 2,
+     "name" : "IoSchedClass_BE"
+    },
+    {
+     "enum_field_value" : 3,
+     "name" : "IoSchedClass_IDLE"
+    }
+   ],
+   "linker_set_key" : "_ZTI12IoSchedClass",
+   "name" : "IoSchedClass",
+   "referenced_type" : "_ZTI12IoSchedClass",
+   "self_type" : "_ZTI12IoSchedClass",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h",
+   "underlying_type" : "_ZTIj"
+  }
+ ],
+ "function_types" :
+ [
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIFbPvS_E",
+   "name" : "bool (void *, void *)",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "referenced_type" : "_ZTIFbPvS_E",
+   "return_type" : "_ZTIb",
+   "self_type" : "_ZTIFbPvS_E",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIFbPvS_S_E",
+   "name" : "bool (void *, void *, void *)",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "referenced_type" : "_ZTIFbPvS_S_E",
+   "return_type" : "_ZTIb",
+   "self_type" : "_ZTIFbPvS_S_E",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIFiPvE",
+   "name" : "int (void *)",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "referenced_type" : "_ZTIFiPvE",
+   "return_type" : "_ZTIi",
+   "self_type" : "_ZTIFiPvE",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIFvPKcS0_PvE",
+   "name" : "void (const char *, const char *, void *)",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "referenced_type" : "_ZTIFvPKcS0_PvE",
+   "return_type" : "_ZTIv",
+   "self_type" : "_ZTIFvPKcS0_PvE",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  }
+ ],
+ "functions" :
+ [
+  {
+   "function_name" : "android_get_control_file",
+   "linker_set_key" : "android_get_control_file",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/android_get_control_file.h"
+  },
+  {
+   "function_name" : "android_get_control_socket",
+   "linker_set_key" : "android_get_control_socket",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "android_get_ioprio",
+   "linker_set_key" : "android_get_ioprio",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIP12IoSchedClass"
+    },
+    {
+     "referenced_type" : "_ZTIPi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h"
+  },
+  {
+   "function_name" : "android_reboot",
+   "linker_set_key" : "android_reboot",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/android_reboot.h"
+  },
+  {
+   "function_name" : "android_set_ioprio",
+   "linker_set_key" : "android_set_ioprio",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTI12IoSchedClass"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h"
+  },
+  {
+   "function_name" : "ashmem_create_region",
+   "linker_set_key" : "ashmem_create_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_get_size_region",
+   "linker_set_key" : "ashmem_get_size_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_pin_region",
+   "linker_set_key" : "ashmem_pin_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_set_prot_region",
+   "linker_set_key" : "ashmem_set_prot_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_unpin_region",
+   "linker_set_key" : "ashmem_unpin_region",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "ashmem_valid",
+   "linker_set_key" : "ashmem_valid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/ashmem.h"
+  },
+  {
+   "function_name" : "atrace_async_begin_body",
+   "linker_set_key" : "atrace_async_begin_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_async_end_body",
+   "linker_set_key" : "atrace_async_end_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_async_for_track_begin_body",
+   "linker_set_key" : "atrace_async_for_track_begin_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_async_for_track_end_body",
+   "linker_set_key" : "atrace_async_for_track_end_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_begin_body",
+   "linker_set_key" : "atrace_begin_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_end_body",
+   "linker_set_key" : "atrace_end_body",
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_get_enabled_tags",
+   "linker_set_key" : "atrace_get_enabled_tags",
+   "return_type" : "_ZTIy",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_init",
+   "linker_set_key" : "atrace_init",
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_instant_body",
+   "linker_set_key" : "atrace_instant_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_instant_for_track_body",
+   "linker_set_key" : "atrace_instant_for_track_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_int64_body",
+   "linker_set_key" : "atrace_int64_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIx"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_int_body",
+   "linker_set_key" : "atrace_int_body",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_set_tracing_enabled",
+   "linker_set_key" : "atrace_set_tracing_enabled",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIb"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_setup",
+   "linker_set_key" : "atrace_setup",
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "atrace_update_tags",
+   "linker_set_key" : "atrace_update_tags",
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "function_name" : "canned_fs_config",
+   "linker_set_key" : "canned_fs_config",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPy"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/private/canned_fs_config.h"
+  },
+  {
+   "function_name" : "config_bool",
+   "linker_set_key" : "config_bool",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_find",
+   "linker_set_key" : "config_find",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIP5cnode",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_free",
+   "linker_set_key" : "config_free",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_load",
+   "linker_set_key" : "config_load",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_load_file",
+   "linker_set_key" : "config_load_file",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_node",
+   "linker_set_key" : "config_node",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIP5cnode",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_set",
+   "linker_set_key" : "config_set",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "config_str",
+   "linker_set_key" : "config_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIPKc",
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "function_name" : "fs_config",
+   "linker_set_key" : "fs_config",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    },
+    {
+     "referenced_type" : "_ZTIPy"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/private/fs_config.h"
+  },
+  {
+   "function_name" : "fs_mkdirs",
+   "linker_set_key" : "fs_mkdirs",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIt"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_prepare_dir",
+   "linker_set_key" : "fs_prepare_dir",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIt"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_prepare_dir_strict",
+   "linker_set_key" : "fs_prepare_dir_strict",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIt"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_prepare_file_strict",
+   "linker_set_key" : "fs_prepare_file_strict",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIt"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_read_atomic_int",
+   "linker_set_key" : "fs_read_atomic_int",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "fs_write_atomic_int",
+   "linker_set_key" : "fs_write_atomic_int",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/fs.h"
+  },
+  {
+   "function_name" : "hashmapCreate",
+   "linker_set_key" : "hashmapCreate",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIPFiPvE"
+    },
+    {
+     "referenced_type" : "_ZTIPFbPvS_E"
+    }
+   ],
+   "return_type" : "_ZTIP7Hashmap",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapForEach",
+   "linker_set_key" : "hashmapForEach",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    },
+    {
+     "referenced_type" : "_ZTIPFbPvS_S_E"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapFree",
+   "linker_set_key" : "hashmapFree",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapGet",
+   "linker_set_key" : "hashmapGet",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIPv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapHash",
+   "linker_set_key" : "hashmapHash",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapLock",
+   "linker_set_key" : "hashmapLock",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapPut",
+   "linker_set_key" : "hashmapPut",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIPv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapRemove",
+   "linker_set_key" : "hashmapRemove",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIPv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "hashmapUnlock",
+   "linker_set_key" : "hashmapUnlock",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP7Hashmap"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "function_name" : "klog_set_level",
+   "linker_set_key" : "klog_set_level",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "function_name" : "klog_write",
+   "linker_set_key" : "klog_write",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "function_name" : "klog_writev",
+   "linker_set_key" : "klog_writev",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPK5iovec"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "function_name" : "load_canned_fs_config",
+   "linker_set_key" : "load_canned_fs_config",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/private/canned_fs_config.h"
+  },
+  {
+   "function_name" : "load_file",
+   "linker_set_key" : "load_file",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    }
+   ],
+   "return_type" : "_ZTIPv",
+   "source_file" : "system/core/libcutils/include/cutils/misc.h"
+  },
+  {
+   "function_name" : "multiuser_convert_sdk_sandbox_to_app_uid",
+   "linker_set_key" : "multiuser_convert_sdk_sandbox_to_app_uid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_app_id",
+   "linker_set_key" : "multiuser_get_app_id",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_cache_gid",
+   "linker_set_key" : "multiuser_get_cache_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_ext_cache_gid",
+   "linker_set_key" : "multiuser_get_ext_cache_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_ext_gid",
+   "linker_set_key" : "multiuser_get_ext_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_sdk_sandbox_uid",
+   "linker_set_key" : "multiuser_get_sdk_sandbox_uid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_shared_app_gid",
+   "linker_set_key" : "multiuser_get_shared_app_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_shared_gid",
+   "linker_set_key" : "multiuser_get_shared_gid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_uid",
+   "linker_set_key" : "multiuser_get_uid",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "multiuser_get_user_id",
+   "linker_set_key" : "multiuser_get_user_id",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIj",
+   "source_file" : "system/core/libcutils/include/cutils/multiuser.h"
+  },
+  {
+   "function_name" : "native_handle_clone",
+   "linker_set_key" : "native_handle_clone",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIP13native_handle",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_close",
+   "linker_set_key" : "native_handle_close",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_close_with_tag",
+   "linker_set_key" : "native_handle_close_with_tag",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_create",
+   "linker_set_key" : "native_handle_create",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIP13native_handle",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_delete",
+   "linker_set_key" : "native_handle_delete",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_init",
+   "linker_set_key" : "native_handle_init",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIP13native_handle",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_set_fdsan_tag",
+   "linker_set_key" : "native_handle_set_fdsan_tag",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "native_handle_unset_fdsan_tag",
+   "linker_set_key" : "native_handle_unset_fdsan_tag",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPK13native_handle"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "function_name" : "partition_wiped",
+   "linker_set_key" : "partition_wiped",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/partition_utils.h"
+  },
+  {
+   "function_name" : "property_get",
+   "linker_set_key" : "property_get",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_get_bool",
+   "linker_set_key" : "property_get_bool",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIa"
+    }
+   ],
+   "return_type" : "_ZTIa",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_get_int32",
+   "linker_set_key" : "property_get_int32",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_get_int64",
+   "linker_set_key" : "property_get_int64",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIx"
+    }
+   ],
+   "return_type" : "_ZTIx",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_list",
+   "linker_set_key" : "property_list",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPFvPKcS0_PvE"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "property_set",
+   "linker_set_key" : "property_set",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "function_name" : "record_stream_free",
+   "linker_set_key" : "record_stream_free",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP12RecordStream"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "function_name" : "record_stream_get_next",
+   "linker_set_key" : "record_stream_get_next",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP12RecordStream"
+    },
+    {
+     "referenced_type" : "_ZTIPPv"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "function_name" : "record_stream_new",
+   "linker_set_key" : "record_stream_new",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIP12RecordStream",
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "function_name" : "socket_close",
+   "linker_set_key" : "socket_close",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_get_local_port",
+   "linker_set_key" : "socket_get_local_port",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_inaddr_any_server",
+   "linker_set_key" : "socket_inaddr_any_server",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_local_client",
+   "linker_set_key" : "socket_local_client",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_local_client_connect",
+   "linker_set_key" : "socket_local_client_connect",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_local_server",
+   "linker_set_key" : "socket_local_server",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_local_server_bind",
+   "linker_set_key" : "socket_local_server_bind",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_network_client",
+   "linker_set_key" : "socket_network_client",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_network_client_timeout",
+   "linker_set_key" : "socket_network_client_timeout",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "socket_send_buffers",
+   "linker_set_key" : "socket_send_buffers",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPK22cutils_socket_buffer_t"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "function_name" : "str_parms_add_float",
+   "linker_set_key" : "str_parms_add_float",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIf"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_add_int",
+   "linker_set_key" : "str_parms_add_int",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_add_str",
+   "linker_set_key" : "str_parms_add_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_create",
+   "linker_set_key" : "str_parms_create",
+   "return_type" : "_ZTIP9str_parms",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_create_str",
+   "linker_set_key" : "str_parms_create_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIP9str_parms",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_del",
+   "linker_set_key" : "str_parms_del",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_destroy",
+   "linker_set_key" : "str_parms_destroy",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_dump",
+   "linker_set_key" : "str_parms_dump",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    }
+   ],
+   "return_type" : "_ZTIv",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_get_float",
+   "linker_set_key" : "str_parms_get_float",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPf"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_get_int",
+   "linker_set_key" : "str_parms_get_int",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_get_str",
+   "linker_set_key" : "str_parms_get_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIPc"
+    },
+    {
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_has_key",
+   "linker_set_key" : "str_parms_has_key",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "str_parms_to_str",
+   "linker_set_key" : "str_parms_to_str",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIP9str_parms"
+    }
+   ],
+   "return_type" : "_ZTIPc",
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "function_name" : "uevent_kernel_multicast_recv",
+   "linker_set_key" : "uevent_kernel_multicast_recv",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/uevent.h"
+  },
+  {
+   "function_name" : "uevent_kernel_multicast_uid_recv",
+   "linker_set_key" : "uevent_kernel_multicast_uid_recv",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/uevent.h"
+  },
+  {
+   "function_name" : "uevent_kernel_recv",
+   "linker_set_key" : "uevent_kernel_recv",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIPv"
+    },
+    {
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "referenced_type" : "_ZTIb"
+    },
+    {
+     "referenced_type" : "_ZTIPj"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/uevent.h"
+  },
+  {
+   "function_name" : "uevent_open_socket",
+   "linker_set_key" : "uevent_open_socket",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "referenced_type" : "_ZTIb"
+    }
+   ],
+   "return_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/uevent.h"
+  }
+ ],
+ "global_vars" :
+ [
+  {
+   "linker_set_key" : "atrace_enabled_tags",
+   "name" : "atrace_enabled_tags",
+   "referenced_type" : "_ZTIy",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "linker_set_key" : "atrace_is_ready",
+   "name" : "atrace_is_ready",
+   "referenced_type" : "_ZTINSt3__16atomicIbEE",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  },
+  {
+   "linker_set_key" : "atrace_marker_fd",
+   "name" : "atrace_marker_fd",
+   "referenced_type" : "_ZTIi",
+   "source_file" : "system/core/libcutils/include/cutils/trace.h"
+  }
+ ],
+ "lvalue_reference_types" : [],
+ "pointer_types" :
+ [
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIP12IoSchedClass",
+   "name" : "IoSchedClass *",
+   "referenced_type" : "_ZTI12IoSchedClass",
+   "self_type" : "_ZTIP12IoSchedClass",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIP12RecordStream",
+   "name" : "RecordStream *",
+   "referenced_type" : "_ZTI12RecordStream",
+   "self_type" : "_ZTIP12RecordStream",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIP13native_handle",
+   "name" : "native_handle *",
+   "referenced_type" : "_ZTI13native_handle",
+   "self_type" : "_ZTIP13native_handle",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIP5cnode",
+   "name" : "cnode *",
+   "referenced_type" : "_ZTI5cnode",
+   "self_type" : "_ZTIP5cnode",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIP7Hashmap",
+   "name" : "Hashmap *",
+   "referenced_type" : "_ZTI7Hashmap",
+   "self_type" : "_ZTIP7Hashmap",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIP9str_parms",
+   "name" : "str_parms *",
+   "referenced_type" : "_ZTI9str_parms",
+   "self_type" : "_ZTIP9str_parms",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPFbPvS_E",
+   "name" : "bool (*)(void *, void *)",
+   "referenced_type" : "_ZTIFbPvS_E",
+   "self_type" : "_ZTIPFbPvS_E",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPFbPvS_S_E",
+   "name" : "bool (*)(void *, void *, void *)",
+   "referenced_type" : "_ZTIFbPvS_S_E",
+   "self_type" : "_ZTIPFbPvS_S_E",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPFiPvE",
+   "name" : "int (*)(void *)",
+   "referenced_type" : "_ZTIFiPvE",
+   "self_type" : "_ZTIPFiPvE",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/hashmap.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPFvPKcS0_PvE",
+   "name" : "void (*)(const char *, const char *, void *)",
+   "referenced_type" : "_ZTIFvPKcS0_PvE",
+   "self_type" : "_ZTIPFvPKcS0_PvE",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/properties.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPK13native_handle",
+   "name" : "const native_handle *",
+   "referenced_type" : "_ZTIK13native_handle",
+   "self_type" : "_ZTIPK13native_handle",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPK22cutils_socket_buffer_t",
+   "name" : "const cutils_socket_buffer_t *",
+   "referenced_type" : "_ZTIK22cutils_socket_buffer_t",
+   "self_type" : "_ZTIPK22cutils_socket_buffer_t",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPK5iovec",
+   "name" : "const iovec *",
+   "referenced_type" : "_ZTIK5iovec",
+   "self_type" : "_ZTIPK5iovec",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPKc",
+   "name" : "const char *",
+   "referenced_type" : "_ZTIKc",
+   "self_type" : "_ZTIPKc",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPKv",
+   "name" : "const void *",
+   "referenced_type" : "_ZTIKv",
+   "self_type" : "_ZTIPKv",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPPv",
+   "name" : "void **",
+   "referenced_type" : "_ZTIPv",
+   "self_type" : "_ZTIPPv",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/record_stream.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPc",
+   "name" : "char *",
+   "referenced_type" : "_ZTIc",
+   "self_type" : "_ZTIPc",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPf",
+   "name" : "float *",
+   "referenced_type" : "_ZTIf",
+   "self_type" : "_ZTIPf",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/str_parms.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPi",
+   "name" : "int *",
+   "referenced_type" : "_ZTIi",
+   "self_type" : "_ZTIPi",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/iosched_policy.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPj",
+   "name" : "unsigned int *",
+   "referenced_type" : "_ZTIj",
+   "self_type" : "_ZTIPj",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/misc.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPv",
+   "name" : "void *",
+   "referenced_type" : "_ZTIv",
+   "self_type" : "_ZTIPv",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/cutils/misc.h"
+  },
+  {
+   "alignment" : 4,
+   "linker_set_key" : "_ZTIPy",
+   "name" : "unsigned long long *",
+   "referenced_type" : "_ZTIy",
+   "self_type" : "_ZTIPy",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/private/canned_fs_config.h"
+  }
+ ],
+ "qualified_types" :
+ [
+  {
+   "alignment" : 4,
+   "is_const" : true,
+   "linker_set_key" : "_ZTIK13native_handle",
+   "name" : "const native_handle",
+   "referenced_type" : "_ZTI13native_handle",
+   "self_type" : "_ZTIK13native_handle",
+   "size" : 12,
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "alignment" : 4,
+   "is_const" : true,
+   "linker_set_key" : "_ZTIK22cutils_socket_buffer_t",
+   "name" : "const cutils_socket_buffer_t",
+   "referenced_type" : "_ZTI22cutils_socket_buffer_t",
+   "self_type" : "_ZTIK22cutils_socket_buffer_t",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "alignment" : 4,
+   "is_const" : true,
+   "linker_set_key" : "_ZTIK5iovec",
+   "name" : "const iovec",
+   "referenced_type" : "_ZTI5iovec",
+   "self_type" : "_ZTIK5iovec",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/klog.h"
+  },
+  {
+   "alignment" : 1,
+   "is_const" : true,
+   "linker_set_key" : "_ZTIKc",
+   "name" : "const char",
+   "referenced_type" : "_ZTIc",
+   "self_type" : "_ZTIKc",
+   "size" : 1,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "is_const" : true,
+   "linker_set_key" : "_ZTIKv",
+   "name" : "const void",
+   "referenced_type" : "_ZTIv",
+   "self_type" : "_ZTIKv",
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  }
+ ],
+ "record_types" :
+ [
+  {
+   "alignment" : 4,
+   "fields" :
+   [
+    {
+     "field_name" : "version",
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "field_name" : "numFds",
+     "field_offset" : 32,
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "field_name" : "numInts",
+     "field_offset" : 64,
+     "referenced_type" : "_ZTIi"
+    },
+    {
+     "field_name" : "data",
+     "field_offset" : 96,
+     "referenced_type" : "_ZTIA0_i"
+    }
+   ],
+   "linker_set_key" : "_ZTI13native_handle",
+   "name" : "native_handle",
+   "referenced_type" : "_ZTI13native_handle",
+   "self_type" : "_ZTI13native_handle",
+   "size" : 12,
+   "source_file" : "system/core/libcutils/include/cutils/native_handle.h"
+  },
+  {
+   "alignment" : 4,
+   "fields" :
+   [
+    {
+     "field_name" : "data",
+     "referenced_type" : "_ZTIPKv"
+    },
+    {
+     "field_name" : "length",
+     "field_offset" : 32,
+     "referenced_type" : "_ZTIj"
+    }
+   ],
+   "linker_set_key" : "_ZTI22cutils_socket_buffer_t",
+   "name" : "cutils_socket_buffer_t",
+   "referenced_type" : "_ZTI22cutils_socket_buffer_t",
+   "self_type" : "_ZTI22cutils_socket_buffer_t",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/cutils/sockets.h"
+  },
+  {
+   "alignment" : 4,
+   "fields" :
+   [
+    {
+     "field_name" : "next",
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "field_name" : "first_child",
+     "field_offset" : 32,
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "field_name" : "last_child",
+     "field_offset" : 64,
+     "referenced_type" : "_ZTIP5cnode"
+    },
+    {
+     "field_name" : "name",
+     "field_offset" : 96,
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "field_name" : "value",
+     "field_offset" : 128,
+     "referenced_type" : "_ZTIPKc"
+    }
+   ],
+   "linker_set_key" : "_ZTI5cnode",
+   "name" : "cnode",
+   "referenced_type" : "_ZTI5cnode",
+   "self_type" : "_ZTI5cnode",
+   "size" : 20,
+   "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  }
+ ],
+ "rvalue_reference_types" : []
+}
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index 3867f34..7f57637 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -89,6 +89,36 @@
 #error ATRACE_TAG must be defined to be one of the tags defined in cutils/trace.h
 #endif
 
+/** Internal implementation detail. Do not use. */
+void atrace_begin_body(const char*);
+
+/** Internal implementation detail. Do not use. */
+void atrace_end_body();
+
+/** Internal implementation detail. Do not use. */
+void atrace_async_begin_body(const char*, int32_t);
+
+/** Internal implementation detail. Do not use. */
+void atrace_async_end_body(const char*, int32_t);
+
+/** Internal implementation detail. Do not use. */
+void atrace_async_for_track_begin_body(const char*, const char*, int32_t);
+
+/** Internal implementation detail. Do not use. */
+void atrace_async_for_track_end_body(const char*, int32_t);
+
+/** Internal implementation detail. Do not use. */
+void atrace_instant_body(const char*);
+
+/** Internal implementation detail. Do not use. */
+void atrace_instant_for_track_body(const char*, const char*);
+
+/** Internal implementation detail. Do not use. */
+void atrace_int_body(const char*, int32_t);
+
+/** Internal implementation detail. Do not use. */
+void atrace_int64_body(const char*, int64_t);
+
 /**
  * Opens the trace file for writing and reads the property for initial tags.
  * The atrace.tags.enableflags property sets the tags to trace.
@@ -159,7 +189,6 @@
 static inline void atrace_begin(uint64_t tag, const char* name)
 {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_begin_body(const char*);
         atrace_begin_body(name);
     }
 }
@@ -172,7 +201,6 @@
 static inline void atrace_end(uint64_t tag)
 {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_end_body();
         atrace_end_body();
     }
 }
@@ -190,7 +218,6 @@
         int32_t cookie)
 {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_async_begin_body(const char*, int32_t);
         atrace_async_begin_body(name, cookie);
     }
 }
@@ -203,7 +230,6 @@
 static inline void atrace_async_end(uint64_t tag, const char* name, int32_t cookie)
 {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_async_end_body(const char*, int32_t);
         atrace_async_end_body(name, cookie);
     }
 }
@@ -221,7 +247,6 @@
 static inline void atrace_async_for_track_begin(uint64_t tag, const char* track_name,
                                                 const char* name, int32_t cookie) {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_async_for_track_begin_body(const char*, const char*, int32_t);
         atrace_async_for_track_begin_body(track_name, name, cookie);
     }
 }
@@ -235,7 +260,6 @@
 static inline void atrace_async_for_track_end(uint64_t tag, const char* track_name,
                                               int32_t cookie) {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_async_for_track_end_body(const char*, int32_t);
         atrace_async_for_track_end_body(track_name, cookie);
     }
 }
@@ -252,7 +276,6 @@
 #define ATRACE_INSTANT(name) atrace_instant(ATRACE_TAG, name)
 static inline void atrace_instant(uint64_t tag, const char* name) {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_instant_body(const char*);
         atrace_instant_body(name);
     }
 }
@@ -269,7 +292,6 @@
 static inline void atrace_instant_for_track(uint64_t tag, const char* track_name,
                                             const char* name) {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_instant_for_track_body(const char*, const char*);
         atrace_instant_for_track_body(track_name, name);
     }
 }
@@ -282,7 +304,6 @@
 static inline void atrace_int(uint64_t tag, const char* name, int32_t value)
 {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_int_body(const char*, int32_t);
         atrace_int_body(name, value);
     }
 }
@@ -295,7 +316,6 @@
 static inline void atrace_int64(uint64_t tag, const char* name, int64_t value)
 {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_int64_body(const char*, int64_t);
         atrace_int64_body(name, value);
     }
 }
diff --git a/libcutils/trace-host.cpp b/libcutils/trace-host.cpp
index e9f58c3..2bf57eb 100644
--- a/libcutils/trace-host.cpp
+++ b/libcutils/trace-host.cpp
@@ -20,7 +20,6 @@
 int                     atrace_marker_fd     = -1;
 uint64_t                atrace_enabled_tags  = 0;
 
-void atrace_set_debuggable(bool /*debuggable*/) {}
 void atrace_set_tracing_enabled(bool /*enabled*/) {}
 void atrace_update_tags() { }
 void atrace_setup() { }
diff --git a/libdiskconfig/Android.bp b/libdiskconfig/Android.bp
deleted file mode 100644
index f523d4e..0000000
--- a/libdiskconfig/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_library {
-    name: "libdiskconfig",
-    vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
-    srcs: [
-        "diskconfig.c",
-        "diskutils.c",
-        "write_lst.c",
-        "config_mbr.c",
-    ],
-
-    shared_libs: [
-        "libcutils",
-        "liblog",
-    ],
-    cflags: ["-Werror"],
-    export_include_dirs: ["include"],
-    local_include_dirs: ["include"],
-
-    target: {
-        darwin: {
-            enabled: false,
-        },
-        host_linux: {
-            cflags: [
-                "-D_LARGEFILE64_SOURCE",
-            ],
-        },
-    },
-}
diff --git a/libdiskconfig/config_mbr.c b/libdiskconfig/config_mbr.c
deleted file mode 100644
index ace9bbf..0000000
--- a/libdiskconfig/config_mbr.c
+++ /dev/null
@@ -1,351 +0,0 @@
-/* libs/diskconfig/diskconfig.c
- *
- * Copyright 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "config_mbr"
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <diskconfig/diskconfig.h>
-#include <log/log.h>
-
-/* start and len are in LBA units */
-static void
-cfg_pentry(struct pc_partition *pentry, uint8_t status, uint8_t type,
-           uint32_t start, uint32_t len)
-{
-    if (len > 0) {
-        /* seems that somes BIOSens can get wedged on boot while verifying
-         * the mbr if these are 0 */
-        memset(&pentry->start, 0xff, sizeof(struct chs));
-        memset(&pentry->end, 0xff, sizeof(struct chs));
-    } else {
-        /* zero out the c/h/s entries.. they are not used */
-        memset(&pentry->start, 0, sizeof(struct chs));
-        memset(&pentry->end, 0, sizeof(struct chs));
-    }
-
-    pentry->status = status;
-    pentry->type = type;
-    pentry->start_lba = start;
-    pentry->len_lba = len;
-
-    ALOGI("Configuring pentry. status=0x%x type=0x%x start_lba=%u len_lba=%u",
-         pentry->status, pentry->type, pentry->start_lba, pentry->len_lba);
-}
-
-
-static inline uint32_t
-kb_to_lba(uint32_t len_kb, uint32_t sect_size)
-{
-    uint64_t lba;
-
-    lba = (uint64_t)len_kb * 1024;
-    /* bump it up to the next LBA boundary just in case  */
-    lba = (lba + (uint64_t)sect_size - 1) & ~((uint64_t)sect_size - 1);
-    lba /= (uint64_t)sect_size;
-    if (lba >= 0xffffffffULL)
-        ALOGE("Error converting kb -> lba. 32bit overflow, expect weirdness");
-    return (uint32_t)(lba & 0xffffffffULL);
-}
-
-
-static struct write_list *
-mk_pri_pentry(struct disk_info *dinfo, struct part_info *pinfo, int pnum,
-              uint32_t *lba)
-{
-    struct write_list *item;
-    struct pc_partition *pentry;
-
-    if (pnum >= PC_NUM_BOOT_RECORD_PARTS) {
-        ALOGE("Maximum number of primary partition exceeded.");
-        return NULL;
-    }
-
-    if (!(item = alloc_wl(sizeof(struct pc_partition)))) {
-        ALOGE("Unable to allocate memory for partition entry.");
-        return NULL;
-    }
-
-    {
-        /* DO NOT DEREFERENCE */
-        struct pc_boot_record *mbr = (void *)PC_MBR_DISK_OFFSET; 
-        /* grab the offset in mbr where to write this partition entry. */
-        item->offset = (loff_t)((uintptr_t)((uint8_t *)(&mbr->ptable[pnum])));
-    }
-
-    pentry = (struct pc_partition *) &item->data;
-
-    /* need a standard primary partition entry */
-    if (pinfo) {
-        /* need this to be 64 bit in case len_kb is large */
-        uint64_t len_lba; 
-
-        if (pinfo->len_kb != (uint32_t)-1) {
-            /* bump it up to the next LBA boundary just in case */
-            len_lba = ((uint64_t)pinfo->len_kb * 1024);
-            len_lba += ((uint64_t)dinfo->sect_size - 1);
-            len_lba &= ~((uint64_t)dinfo->sect_size - 1);
-            len_lba /= (uint64_t)dinfo->sect_size;
-        } else {
-            /* make it fill the rest of disk */
-            len_lba = dinfo->num_lba - *lba;
-        }
-
-        cfg_pentry(pentry, ((pinfo->flags & PART_ACTIVE_FLAG) ?
-                            PC_PART_ACTIVE : PC_PART_NORMAL),
-                   pinfo->type, *lba, (uint32_t)len_lba);
-
-        pinfo->start_lba = *lba;
-        *lba += (uint32_t)len_lba;
-    } else {
-        /* this should be made an extended partition, and should take
-         * up the rest of the disk as a primary partition */
-        cfg_pentry(pentry, PC_PART_NORMAL, PC_PART_TYPE_EXTENDED,
-                   *lba, dinfo->num_lba - *lba);
-
-        /* note that we do not update the *lba because we now have to
-         * create a chain of extended partition tables, and first one is at
-         * *lba */
-    }
-
-    return item;
-}
-
-
-/* This function configures an extended boot record at the beginning of an
- * extended partition. This creates a logical partition and a pointer to
- * the next EBR.
- *
- * ext_lba == The start of the toplevel extended partition (pointed to by the
- * entry in the MBR).
- */
-static struct write_list *
-mk_ext_pentry(struct disk_info *dinfo, struct part_info *pinfo, uint32_t *lba,
-              uint32_t ext_lba, struct part_info *pnext)
-{
-    struct write_list *item;
-    struct pc_boot_record *ebr;
-    uint32_t len; /* in lba units */
-
-    if (!(item = alloc_wl(sizeof(struct pc_boot_record)))) {
-        ALOGE("Unable to allocate memory for EBR.");
-        return NULL;
-    }
-
-    /* we are going to write the ebr at the current LBA, and then bump the
-     * lba counter since that is where the logical data partition will start */
-    item->offset = ((loff_t)(*lba)) * dinfo->sect_size;
-    (*lba)++;
-
-    ebr = (struct pc_boot_record *) &item->data;
-    memset(ebr, 0, sizeof(struct pc_boot_record));
-    ebr->mbr_sig = PC_BIOS_BOOT_SIG;
-
-    if (pinfo->len_kb != (uint32_t)-1)
-        len = kb_to_lba(pinfo->len_kb, dinfo->sect_size);
-    else {
-        if (pnext) {
-            ALOGE("Only the last partition can be specified to fill the disk "
-                 "(name = '%s')", pinfo->name);
-            goto fail;
-        }
-        len = dinfo->num_lba - *lba;
-        /* update the pinfo structure to reflect the new size, for
-         * bookkeeping */
-        pinfo->len_kb =
-            (uint32_t)(((uint64_t)len * (uint64_t)dinfo->sect_size) /
-                       ((uint64_t)1024));
-    }
-
-    cfg_pentry(&ebr->ptable[PC_EBR_LOGICAL_PART], PC_PART_NORMAL,
-               pinfo->type, 1, len);
-
-    pinfo->start_lba = *lba;
-    *lba += len;
-
-    /* If this is not the last partition, we have to create a link to the
-     * next extended partition.
-     *
-     * Otherwise, there's nothing to do since the "pointer entry" is
-     * already zero-filled.
-     */
-    if (pnext) {
-        /* The start lba for next partition is an offset from the beginning
-         * of the top-level extended partition */
-        uint32_t next_start_lba = *lba - ext_lba;
-        uint32_t next_len_lba;
-        if (pnext->len_kb != (uint32_t)-1)
-            next_len_lba = 1 + kb_to_lba(pnext->len_kb, dinfo->sect_size);
-        else
-            next_len_lba = dinfo->num_lba - *lba;
-        cfg_pentry(&ebr->ptable[PC_EBR_NEXT_PTR_PART], PC_PART_NORMAL,
-                   PC_PART_TYPE_EXTENDED, next_start_lba, next_len_lba);
-    }
-
-    return item;
-
-fail:
-    free_wl(item);
-    return NULL;
-}
-
-
-static struct write_list *
-mk_mbr_sig()
-{
-    struct write_list *item;
-    if (!(item = alloc_wl(sizeof(uint16_t)))) {
-        ALOGE("Unable to allocate memory for MBR signature.");
-        return NULL;
-    }
-
-    {
-        /* DO NOT DEREFERENCE */
-        struct pc_boot_record *mbr = (void *)PC_MBR_DISK_OFFSET;
-        /* grab the offset in mbr where to write mbr signature. */
-        item->offset = (loff_t)((uintptr_t)((uint8_t *)(&mbr->mbr_sig)));
-    }
-
-    *((uint16_t*)item->data) = PC_BIOS_BOOT_SIG;
-    return item;
-}
-
-struct write_list *
-config_mbr(struct disk_info *dinfo)
-{
-    struct part_info *pinfo;
-    uint32_t cur_lba = dinfo->skip_lba;
-    uint32_t ext_lba = 0;
-    struct write_list *wr_list = NULL;
-    struct write_list *temp_wr = NULL;
-    int cnt = 0;
-    int extended = 0;
-
-    if (!dinfo->part_lst)
-        return NULL;
-
-    for (cnt = 0; cnt < dinfo->num_parts; ++cnt) {
-        pinfo = &dinfo->part_lst[cnt];
-
-        /* Should we create an extedned partition? */
-        if (cnt == (PC_NUM_BOOT_RECORD_PARTS - 1)) {
-            if (cnt + 1 < dinfo->num_parts) {
-                extended = 1;
-                ext_lba = cur_lba;
-                if ((temp_wr = mk_pri_pentry(dinfo, NULL, cnt, &cur_lba)))
-                    wlist_add(&wr_list, temp_wr);
-                else {
-                    ALOGE("Cannot create primary extended partition.");
-                    goto fail;
-                }
-            }
-        }
-
-        /* if extended, need 1 lba for ebr */
-        if ((cur_lba + extended) >= dinfo->num_lba)
-            goto nospace;
-        else if (pinfo->len_kb != (uint32_t)-1) {
-            uint32_t sz_lba = (pinfo->len_kb / dinfo->sect_size) * 1024;
-            if ((cur_lba + sz_lba + extended) > dinfo->num_lba)
-                goto nospace;
-        }
-
-        if (!extended)
-            temp_wr = mk_pri_pentry(dinfo, pinfo, cnt, &cur_lba);
-        else {
-            struct part_info *pnext;
-            pnext = cnt + 1 < dinfo->num_parts ? &dinfo->part_lst[cnt+1] : NULL;
-            temp_wr = mk_ext_pentry(dinfo, pinfo, &cur_lba, ext_lba, pnext);
-        }
-
-        if (temp_wr)
-            wlist_add(&wr_list, temp_wr);
-        else {
-            ALOGE("Cannot create partition %d (%s).", cnt, pinfo->name);
-            goto fail;
-        }
-    }
-
-    /* fill in the rest of the MBR with empty parts (if needed). */
-    for (; cnt < PC_NUM_BOOT_RECORD_PARTS; ++cnt) {
-        struct part_info blank;
-        cur_lba = 0;
-        memset(&blank, 0, sizeof(struct part_info));
-        if (!(temp_wr = mk_pri_pentry(dinfo, &blank, cnt, &cur_lba))) {
-            ALOGE("Cannot create blank partition %d.", cnt);
-            goto fail;
-        }
-        wlist_add(&wr_list, temp_wr);
-    }
-
-    if ((temp_wr = mk_mbr_sig()))
-        wlist_add(&wr_list, temp_wr);
-    else {
-        ALOGE("Cannot set MBR signature");
-        goto fail;
-    }
-
-    return wr_list;
-
-nospace:
-    ALOGE("Not enough space to add parttion '%s'.", pinfo->name);
-
-fail:
-    wlist_free(wr_list);
-    return NULL;
-}
-
-
-/* Returns the device path of the partition referred to by 'name'
- * Must be freed by the caller.
- */
-char *
-find_mbr_part(struct disk_info *dinfo, const char *name)
-{
-    struct part_info *plist = dinfo->part_lst;
-    int num = 0;
-    char *dev_name = NULL;
-    int has_extended = (dinfo->num_parts > PC_NUM_BOOT_RECORD_PARTS);
-
-    for(num = 1; num <= dinfo->num_parts; ++num) {
-        if (!strcmp(plist[num-1].name, name))
-            break;
-    }
-
-    if (num > dinfo->num_parts)
-        return NULL;
-
-    if (has_extended && (num >= PC_NUM_BOOT_RECORD_PARTS))
-        num++;
-
-    if (!(dev_name = malloc(MAX_NAME_LEN))) {
-        ALOGE("Cannot allocate memory.");
-        return NULL;
-    }
-
-    num = snprintf(dev_name, MAX_NAME_LEN, "%s%d", dinfo->device, num);
-    if (num >= MAX_NAME_LEN) {
-        ALOGE("Device name is too long?!");
-        free(dev_name);
-        return NULL;
-    }
-
-    return dev_name;
-}
diff --git a/libdiskconfig/diskconfig.c b/libdiskconfig/diskconfig.c
deleted file mode 100644
index 5f34748..0000000
--- a/libdiskconfig/diskconfig.c
+++ /dev/null
@@ -1,535 +0,0 @@
-/* libs/diskconfig/diskconfig.c
- *
- * Copyright 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "diskconfig"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <linux/fs.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <cutils/config_utils.h>
-#include <log/log.h>
-
-#include <diskconfig/diskconfig.h>
-
-static int
-parse_len(const char *str, uint64_t *plen)
-{
-    char tmp[64];
-    int len_str;
-    uint32_t multiple = 1;
-
-    strncpy(tmp, str, sizeof(tmp));
-    tmp[sizeof(tmp)-1] = '\0';
-    len_str = strlen(tmp);
-    if (!len_str) {
-        ALOGE("Invalid disk length specified.");
-        return 1;
-    }
-
-    switch(tmp[len_str - 1]) {
-        case 'M': case 'm':
-            /* megabyte */
-            multiple <<= 10;
-        case 'K': case 'k':
-            /* kilobytes */
-            multiple <<= 10;
-            tmp[len_str - 1] = '\0';
-            break;
-        default:
-            break;
-    }
-
-    *plen = strtoull(tmp, NULL, 0);
-    if (!*plen) {
-        ALOGE("Invalid length specified: %s", str);
-        return 1;
-    }
-
-    if (*plen == (uint64_t)-1) {
-        if (multiple > 1) {
-            ALOGE("Size modifier illegal when len is -1");
-            return 1;
-        }
-    } else {
-        /* convert len to kilobytes */
-        if (multiple > 1024)
-            multiple >>= 10;
-        *plen *= multiple;
-
-        if (*plen > 0xffffffffULL) {
-            ALOGE("Length specified is too large!: %"PRIu64" KB", *plen);
-            return 1;
-        }
-    }
-
-    return 0;
-}
-
-
-static int
-load_partitions(cnode *root, struct disk_info *dinfo)
-{
-    cnode *partnode;
-
-    dinfo->num_parts = 0;
-    for (partnode = root->first_child; partnode; partnode = partnode->next) {
-        struct part_info *pinfo = &dinfo->part_lst[dinfo->num_parts];
-        const char *tmp;
-
-        /* bleh, i will leak memory here, but i DONT CARE since
-         * the only right thing to do when this function fails
-         * is to quit */
-        pinfo->name = strdup(partnode->name);
-
-        if(config_bool(partnode, "active", 0))
-            pinfo->flags |= PART_ACTIVE_FLAG;
-
-        if (!(tmp = config_str(partnode, "type", NULL))) {
-            ALOGE("Partition type required: %s", pinfo->name);
-            return 1;
-        }
-
-        /* possible values are: linux, fat32 */
-        if (!strcmp(tmp, "linux")) {
-            pinfo->type = PC_PART_TYPE_LINUX;
-        } else if (!strcmp(tmp, "fat32")) {
-            pinfo->type = PC_PART_TYPE_FAT32;
-        } else {
-            ALOGE("Unsupported partition type found: %s", tmp);
-            return 1;
-        }
-
-        if ((tmp = config_str(partnode, "len", NULL)) != NULL) {
-            uint64_t len;
-            if (parse_len(tmp, &len))
-                return 1;
-            pinfo->len_kb = (uint32_t) len;
-        } else 
-            pinfo->len_kb = 0;
-
-        ++dinfo->num_parts;
-    }
-
-    return 0;
-}
-
-struct disk_info *
-load_diskconfig(const char *fn, char *path_override)
-{
-    struct disk_info *dinfo;
-    cnode *devroot;
-    cnode *partnode;
-    cnode *root = config_node("", "");
-    const char *tmp;
-
-    if (!(dinfo = malloc(sizeof(struct disk_info)))) {
-        ALOGE("Could not malloc disk_info");
-        return NULL;
-    }
-    memset(dinfo, 0, sizeof(struct disk_info));
-
-    if (!(dinfo->part_lst = malloc(MAX_NUM_PARTS * sizeof(struct part_info)))) {
-        ALOGE("Could not malloc part_lst");
-        goto fail;
-    }
-    memset(dinfo->part_lst, 0,
-           (MAX_NUM_PARTS * sizeof(struct part_info)));
-
-    config_load_file(root, fn);
-    if (root->first_child == NULL) {
-        ALOGE("Could not read config file %s", fn);
-        goto fail;
-    }
-
-    if (!(devroot = config_find(root, "device"))) {
-        ALOGE("Could not find device section in config file '%s'", fn);
-        goto fail;
-    }
-
-
-    if (!(tmp = config_str(devroot, "path", path_override))) {
-        ALOGE("device path is requried");
-        goto fail;
-    }
-    dinfo->device = strdup(tmp);
-
-    /* find the partition scheme */
-    if (!(tmp = config_str(devroot, "scheme", NULL))) {
-        ALOGE("partition scheme is required");
-        goto fail;
-    } else if (!strcmp(tmp, "mbr")) {
-        dinfo->scheme = PART_SCHEME_MBR;
-    } else if (!strcmp(tmp, "gpt")) {
-        ALOGE("'gpt' partition scheme not supported yet.");
-        goto fail;
-    } else {
-        ALOGE("Unknown partition scheme specified: %s", tmp);
-        goto fail;
-    }
-
-    /* grab the sector size (in bytes) */
-    tmp = config_str(devroot, "sector_size", "512");
-    dinfo->sect_size = strtol(tmp, NULL, 0);
-    if (!dinfo->sect_size) {
-        ALOGE("Invalid sector size: %s", tmp);
-        goto fail;
-    }
-
-    /* first lba where the partitions will start on disk */
-    if (!(tmp = config_str(devroot, "start_lba", NULL))) {
-        ALOGE("start_lba must be provided");
-        goto fail;
-    }
-
-    if (!(dinfo->skip_lba = strtol(tmp, NULL, 0))) {
-        ALOGE("Invalid starting LBA (or zero): %s", tmp);
-        goto fail;
-    }
-
-    /* Number of LBAs on disk */
-    if (!(tmp = config_str(devroot, "num_lba", NULL))) {
-        ALOGE("num_lba is required");
-        goto fail;
-    }
-    dinfo->num_lba = strtoul(tmp, NULL, 0);
-
-    if (!(partnode = config_find(devroot, "partitions"))) {
-        ALOGE("Device must specify partition list");
-        goto fail;
-    }
-
-    if (load_partitions(partnode, dinfo))
-        goto fail;
-
-    return dinfo;
-
-fail:
-    if (dinfo->part_lst)
-        free(dinfo->part_lst);
-    if (dinfo->device)
-        free(dinfo->device);
-    free(dinfo);
-    return NULL;
-}
-
-static int
-sync_ptable(int fd)
-{
-    struct stat stat;
-    int rv;
-
-    sync();
-
-    if (fstat(fd, &stat)) {
-       ALOGE("Cannot stat, errno=%d.", errno);
-       return -1;
-    }
-
-    if (S_ISBLK(stat.st_mode) && ((rv = ioctl(fd, BLKRRPART, NULL)) < 0)) {
-        ALOGE("Could not re-read partition table. REBOOT!. (errno=%d)", errno);
-        return -1;
-    }
-
-    return 0;
-}
-
-/* This function verifies that the disk info provided is valid, and if so,
- * returns an open file descriptor.
- *
- * This does not necessarily mean that it will later be successfully written
- * though. If we use the pc-bios partitioning scheme, we must use extended
- * partitions, which eat up some hd space. If the user manually provisioned
- * every single partition, but did not account for the extra needed space,
- * then we will later fail.
- *
- * TODO: Make validation more complete.
- */
-static int
-validate(struct disk_info *dinfo)
-{
-    int fd;
-    int sect_sz;
-    uint64_t disk_size;
-    uint64_t total_size;
-    int cnt;
-    struct stat stat;
-
-    if (!dinfo)
-        return -1;
-
-    if ((fd = open(dinfo->device, O_RDWR)) < 0) {
-        ALOGE("Cannot open device '%s' (errno=%d)", dinfo->device, errno);
-        return -1;
-    }
-
-    if (fstat(fd, &stat)) {
-        ALOGE("Cannot stat file '%s', errno=%d.", dinfo->device, errno);
-        goto fail;
-    }
-
-
-    /* XXX: Some of the code below is kind of redundant and should probably
-     * be refactored a little, but it will do for now. */
-
-    /* Verify that we can operate on the device that was requested.
-     * We presently only support block devices and regular file images. */
-    if (S_ISBLK(stat.st_mode)) {
-        /* get the sector size and make sure we agree */
-        if (ioctl(fd, BLKSSZGET, &sect_sz) < 0) {
-            ALOGE("Cannot get sector size (errno=%d)", errno);
-            goto fail;
-        }
-
-        if (!sect_sz || sect_sz != dinfo->sect_size) {
-            ALOGE("Device sector size is zero or sector sizes do not match!");
-            goto fail;
-        }
-
-        /* allow the user override the "disk size" if they provided num_lba */
-        if (!dinfo->num_lba) {
-            if (ioctl(fd, BLKGETSIZE64, &disk_size) < 0) {
-                ALOGE("Could not get block device size (errno=%d)", errno);
-                goto fail;
-            }
-            /* XXX: we assume that the disk has < 2^32 sectors :-) */
-            dinfo->num_lba = (uint32_t)(disk_size / (uint64_t)dinfo->sect_size);
-        } else
-            disk_size = (uint64_t)dinfo->num_lba * (uint64_t)dinfo->sect_size;
-    } else if (S_ISREG(stat.st_mode)) {
-        ALOGI("Requesting operation on a regular file, not block device.");
-        if (!dinfo->sect_size) {
-            ALOGE("Sector size for regular file images cannot be zero");
-            goto fail;
-        }
-        if (dinfo->num_lba)
-            disk_size = (uint64_t)dinfo->num_lba * (uint64_t)dinfo->sect_size;
-        else {
-            dinfo->num_lba = (uint32_t)(stat.st_size / dinfo->sect_size);
-            disk_size = (uint64_t)stat.st_size;
-        }
-    } else {
-        ALOGE("Device does not refer to a regular file or a block device!");
-        goto fail;
-    }
-
-#if 1
-    ALOGV("Device/file %s: size=%" PRIu64 " bytes, num_lba=%u, sect_size=%d",
-         dinfo->device, disk_size, dinfo->num_lba, dinfo->sect_size);
-#endif
-
-    /* since this is our offset into the disk, we start off with that as
-     * our size of needed partitions */
-    total_size = dinfo->skip_lba * dinfo->sect_size;
-
-    /* add up all the partition sizes and make sure it fits */
-    for (cnt = 0; cnt < dinfo->num_parts; ++cnt) {
-        struct part_info *part = &dinfo->part_lst[cnt];
-        if (part->len_kb != (uint32_t)-1) {
-            total_size += part->len_kb * 1024;
-        } else if (part->len_kb == 0) {
-            ALOGE("Zero-size partition '%s' is invalid.", part->name);
-            goto fail;
-        } else {
-            /* the partition requests the rest of the disk. */
-            if (cnt + 1 != dinfo->num_parts) {
-                ALOGE("Only the last partition in the list can request to fill "
-                     "the rest of disk.");
-                goto fail;
-            }
-        }
-
-        if ((part->type != PC_PART_TYPE_LINUX) &&
-            (part->type != PC_PART_TYPE_FAT32)) {
-            ALOGE("Unknown partition type (0x%x) encountered for partition "
-                 "'%s'\n", part->type, part->name);
-            goto fail;
-        }
-    }
-
-    /* only matters for disks, not files */
-    if (S_ISBLK(stat.st_mode) && total_size > disk_size) {
-        ALOGE("Total requested size of partitions (%"PRIu64") is greater than disk "
-             "size (%"PRIu64").", total_size, disk_size);
-        goto fail;
-    }
-
-    return fd;
-
-fail:
-    close(fd);
-    return -1;
-}
-
-static int
-validate_and_config(struct disk_info *dinfo, int *fd, struct write_list **lst)
-{
-    *lst = NULL;
-    *fd = -1;
-
-    if ((*fd = validate(dinfo)) < 0)
-        return 1;
-
-    switch (dinfo->scheme) {
-        case PART_SCHEME_MBR:
-            *lst = config_mbr(dinfo);
-            return *lst == NULL;
-        case PART_SCHEME_GPT:
-            /* not supported yet */
-        default:
-            ALOGE("Unknown partition scheme.");
-            break;
-    }
-
-    close(*fd);
-    *lst = NULL;
-    return 1;
-}
-
-/* validate and process the disk layout configuration.
- * This will cause an update to the partitions' start lba.
- *
- * Basically, this does the same thing as apply_disk_config in test mode,
- * except that wlist_commit is not called to print out the data to be
- * written.
- */
-int
-process_disk_config(struct disk_info *dinfo)
-{
-    struct write_list *lst;
-    int fd;
-
-    if (validate_and_config(dinfo, &fd, &lst) != 0)
-        return 1;
-
-    close(fd);
-    wlist_free(lst);
-    return 0;
-}
-
-
-int
-apply_disk_config(struct disk_info *dinfo, int test)
-{
-    int fd;
-    struct write_list *wr_lst = NULL;
-    int rv;
-
-    if (validate_and_config(dinfo, &fd, &wr_lst) != 0) {
-        ALOGE("Configuration is invalid.");
-        goto fail;
-    }
-
-    if ((rv = wlist_commit(fd, wr_lst, test)) >= 0)
-        rv = test ? 0 : sync_ptable(fd);
-
-    close(fd);
-    wlist_free(wr_lst);
-    return rv;
-
-fail:
-    close(fd);
-    if (wr_lst)
-        wlist_free(wr_lst);
-    return 1;
-}
-
-int
-dump_disk_config(struct disk_info *dinfo)
-{
-    int cnt;
-    struct part_info *part;
-
-    printf("Device: %s\n", dinfo->device);
-    printf("Scheme: ");
-    switch (dinfo->scheme) {
-        case PART_SCHEME_MBR:
-            printf("MBR");
-            break;
-        case PART_SCHEME_GPT:
-            printf("GPT (unsupported)");
-            break;
-        default:
-            printf("Unknown");
-            break;
-    }
-    printf ("\n");
-
-    printf("Sector size: %d\n", dinfo->sect_size);
-    printf("Skip leading LBAs: %u\n", dinfo->skip_lba);
-    printf("Number of LBAs: %u\n", dinfo->num_lba);
-    printf("Partitions:\n");
-
-    for (cnt = 0; cnt < dinfo->num_parts; ++cnt) {
-        part = &dinfo->part_lst[cnt];
-        printf("\tname = %s\n", part->name);
-        printf("\t\tflags = %s\n",
-               part->flags & PART_ACTIVE_FLAG ? "Active" : "None");
-        printf("\t\ttype = %s\n",
-               part->type == PC_PART_TYPE_LINUX ? "Linux" : "Unknown");
-        if (part->len_kb == (uint32_t)-1)
-            printf("\t\tlen = rest of disk\n");
-        else
-            printf("\t\tlen = %uKB\n", part->len_kb);
-    }
-    printf("Total number of partitions: %d\n", cnt);
-    printf("\n");
-
-    return 0;
-}
-
-struct part_info *
-find_part(struct disk_info *dinfo, const char *name)
-{
-    struct part_info *pinfo;
-    int cnt;
-
-    for (cnt = 0; cnt < dinfo->num_parts; ++cnt) {
-        pinfo = &dinfo->part_lst[cnt];
-        if (!strcmp(pinfo->name, name))
-            return pinfo;
-    }
-
-    return NULL;
-}
-
-/* NOTE: If the returned ptr is non-NULL, it must be freed by the caller. */
-char *
-find_part_device(struct disk_info *dinfo, const char *name)
-{
-    switch (dinfo->scheme) {
-        case PART_SCHEME_MBR:
-            return find_mbr_part(dinfo, name);
-        case PART_SCHEME_GPT:
-            ALOGE("GPT is presently not supported");
-            break;
-        default:
-            ALOGE("Unknown partition table scheme");
-            break;
-    }
-
-    return NULL;
-}
-
-
diff --git a/libdiskconfig/diskutils.c b/libdiskconfig/diskutils.c
deleted file mode 100644
index fe1b4c1..0000000
--- a/libdiskconfig/diskutils.c
+++ /dev/null
@@ -1,118 +0,0 @@
-/* libs/diskconfig/diskutils.c
- *
- * Copyright 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "diskutils"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <log/log.h>
-
-#include <diskconfig/diskconfig.h>
-
-int
-write_raw_image(const char *dst, const char *src, loff_t offset, int test)
-{
-    int dst_fd = -1;
-    int src_fd = -1;
-    uint8_t buffer[2048];
-    ssize_t nr_bytes;
-    ssize_t tmp;
-    int done = 0;
-    uint64_t total = 0;
-
-    ALOGI("Writing RAW image '%s' to '%s' (offset=%llu)", src, dst, (unsigned long long)offset);
-    if ((src_fd = open(src, O_RDONLY)) < 0) {
-        ALOGE("Could not open %s for reading (errno=%d).", src, errno);
-        goto fail;
-    }
-
-    if (!test) {
-        if ((dst_fd = open(dst, O_RDWR)) < 0) {
-            ALOGE("Could not open '%s' for read/write (errno=%d).", dst, errno);
-            goto fail;
-        }
-
-        if (lseek64(dst_fd, offset, SEEK_SET) != offset) {
-            ALOGE("Could not seek to offset %lld in %s.", (long long)offset, dst);
-            goto fail;
-        }
-    }
-
-    while (!done) {
-        if ((nr_bytes = read(src_fd, buffer, sizeof(buffer))) < 0) {
-            /* XXX: Should we not even bother with EINTR? */
-            if (errno == EINTR)
-                continue;
-            ALOGE("Error (%d) while reading from '%s'", errno, src);
-            goto fail;
-        }
-
-        if (!nr_bytes) {
-            /* we're done. */
-            done = 1;
-            break;
-        }
-
-        total += nr_bytes;
-
-        /* skip the write loop if we're testing */
-        if (test)
-            nr_bytes = 0;
-
-        while (nr_bytes > 0) {
-            if ((tmp = write(dst_fd, buffer, nr_bytes)) < 0) {
-                /* XXX: Should we not even bother with EINTR? */
-                if (errno == EINTR)
-                    continue;
-                ALOGE("Error (%d) while writing to '%s'", errno, dst);
-                goto fail;
-            }
-            if (!tmp)
-                continue;
-            nr_bytes -= tmp;
-        }
-    }
-
-    if (!done) {
-        ALOGE("Exited read/write loop without setting flag! WTF?!");
-        goto fail;
-    }
-
-    if (dst_fd >= 0)
-        fsync(dst_fd);
-
-    ALOGI("Wrote %" PRIu64 " bytes to %s @ %lld", total, dst, (long long)offset);
-
-    close(src_fd);
-    if (dst_fd >= 0)
-        close(dst_fd);
-    return 0;
-
-fail:
-    if (dst_fd >= 0)
-        close(dst_fd);
-    if (src_fd >= 0)
-        close(src_fd);
-    return 1;
-}
diff --git a/libdiskconfig/dump_diskconfig.c b/libdiskconfig/dump_diskconfig.c
deleted file mode 100644
index 3c4f620..0000000
--- a/libdiskconfig/dump_diskconfig.c
+++ /dev/null
@@ -1,43 +0,0 @@
-/* libs/diskconfig/dump_diskconfig.c
- *
- * Copyright 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "dump_diskconfig"
-
-#include <stdio.h>
-
-#include <log/log.h>
-
-#include "diskconfig.h"
-
-int
-main(int argc, char *argv[])
-{
-    struct disk_info *dinfo;
-
-    if (argc < 2) {
-        ALOGE("usage: %s <conf file>", argv[0]);
-        return 1;
-    }
-
-    if (!(dinfo = load_diskconfig(argv[1], NULL)))
-        return 1;
-
-    dump_disk_config(dinfo);
-
-    return 0;
-}
-
diff --git a/libdiskconfig/include/diskconfig/diskconfig.h b/libdiskconfig/include/diskconfig/diskconfig.h
deleted file mode 100644
index d45b99e..0000000
--- a/libdiskconfig/include/diskconfig/diskconfig.h
+++ /dev/null
@@ -1,130 +0,0 @@
-/* system/core/include/diskconfig/diskconfig.h
- *
- * Copyright 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __LIBS_DISKCONFIG_H
-#define __LIBS_DISKCONFIG_H
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define MAX_NAME_LEN                 512
-#define MAX_NUM_PARTS                16
-
-/* known partition schemes */
-#define PART_SCHEME_MBR              0x1
-#define PART_SCHEME_GPT              0x2
-
-/* PC Bios partition status */
-#define PC_PART_ACTIVE               0x80
-#define PC_PART_NORMAL               0x0
-
-/* Known (rather, used by us) partition types */
-#define PC_PART_TYPE_LINUX           0x83
-#define PC_PART_TYPE_EXTENDED        0x05
-#define PC_PART_TYPE_FAT32           0x0c
-
-#define PC_NUM_BOOT_RECORD_PARTS     4
-
-#define PC_EBR_LOGICAL_PART          0
-#define PC_EBR_NEXT_PTR_PART         1
-
-#define PC_BIOS_BOOT_SIG             0xAA55
-
-#define PC_MBR_DISK_OFFSET           0
-#define PC_MBR_SIZE                  512
-
-#define PART_ACTIVE_FLAG             0x1
-
-struct chs {
-    uint8_t head;
-    uint8_t sector;
-    uint8_t cylinder;
-} __attribute__((__packed__));
-
-/* 16 byte pc partition descriptor that sits in MBR and EPBR.
- * Note: multi-byte entities have little-endian layout on disk */
-struct pc_partition {
-    uint8_t status;     /* byte  0     */
-    struct chs start;   /* bytes 1-3   */
-    uint8_t type;       /* byte  4     */
-    struct chs end;     /* bytes 5-7   */
-    uint32_t start_lba; /* bytes 8-11  */
-    uint32_t len_lba;   /* bytes 12-15 */
-} __attribute__((__packed__));
-
-struct pc_boot_record {
-    uint8_t code[440];                                      /* bytes 0-439   */
-    uint32_t disk_sig;                                      /* bytes 440-443 */
-    uint16_t pad;                                           /* bytes 444-445 */
-    struct pc_partition ptable[PC_NUM_BOOT_RECORD_PARTS];   /* bytes 446-509 */
-    uint16_t mbr_sig;                                       /* bytes 510-511 */
-} __attribute__((__packed__));
-
-struct part_info {
-    char *name;
-    uint8_t flags;
-    uint8_t type;
-    uint32_t len_kb;       /* in 1K-bytes */
-    uint32_t start_lba;    /* the LBA where this partition begins */
-};
-
-struct disk_info {
-    char *device;
-    uint8_t scheme;
-    int sect_size;       /* expected sector size in bytes. MUST BE POWER OF 2 */
-    uint32_t skip_lba;   /* in sectors (1 unit of LBA) */
-    uint32_t num_lba;    /* the size of the disk in LBA units */
-    struct part_info *part_lst;
-    int num_parts;
-};
-
-struct write_list {
-    struct write_list *next;
-    loff_t offset;
-    uint32_t len;
-    uint8_t data[0];
-};
-
-
-struct write_list *alloc_wl(uint32_t data_len);
-void free_wl(struct write_list *item);
-struct write_list *wlist_add(struct write_list **lst, struct write_list *item);
-void wlist_free(struct write_list *lst);
-int wlist_commit(int fd, struct write_list *lst, int test);
-
-struct disk_info *load_diskconfig(const char *fn, char *path_override);
-int dump_disk_config(struct disk_info *dinfo);
-int apply_disk_config(struct disk_info *dinfo, int test);
-char *find_part_device(struct disk_info *dinfo, const char *name);
-int process_disk_config(struct disk_info *dinfo);
-struct part_info *find_part(struct disk_info *dinfo, const char *name);
-
-int write_raw_image(const char *dst, const char *src, loff_t offset, int test);
-
-/* For MBR partition schemes */
-struct write_list *config_mbr(struct disk_info *dinfo);
-char *find_mbr_part(struct disk_info *dinfo, const char *name);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __LIBS_DISKCONFIG_H */
diff --git a/libdiskconfig/write_lst.c b/libdiskconfig/write_lst.c
deleted file mode 100644
index c3d5c0a..0000000
--- a/libdiskconfig/write_lst.c
+++ /dev/null
@@ -1,92 +0,0 @@
-/* libs/diskconfig/write_lst.c
- *
- * Copyright 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "write_lst"
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <diskconfig/diskconfig.h>
-#include <log/log.h>
-
-struct write_list *
-alloc_wl(uint32_t data_len)
-{
-    struct write_list *item;
-
-    if (!(item = malloc(sizeof(struct write_list) + data_len))) {
-        ALOGE("Unable to allocate memory.");
-        return NULL;
-    }
-
-    item->len = data_len;
-    return item;
-}
-
-void
-free_wl(struct write_list *item)
-{
-    if (item)
-        free(item);
-}
-
-struct write_list *
-wlist_add(struct write_list **lst, struct write_list *item)
-{
-    item->next = (*lst);
-    *lst = item;
-    return item;
-}
-
-void
-wlist_free(struct write_list *lst)
-{
-    struct write_list *temp_wr;
-    while (lst) {
-        temp_wr = lst->next;
-        free_wl(lst);
-        lst = temp_wr;
-    }
-}
-
-int
-wlist_commit(int fd, struct write_list *lst, int test)
-{
-    for(; lst; lst = lst->next) {
-        if (lseek64(fd, lst->offset, SEEK_SET) != (loff_t)lst->offset) {
-            ALOGE("Cannot seek to the specified position (%lld).", (long long)lst->offset);
-            goto fail;
-        }
-
-        if (!test) {
-            if (write(fd, lst->data, lst->len) != (int)lst->len) {
-                ALOGE("Failed writing %u bytes at position %lld.", lst->len,
-                     (long long)lst->offset);
-                goto fail;
-            }
-        } else
-            ALOGI("Would write %d bytes @ offset %lld.", lst->len, (long long)lst->offset);
-    }
-
-    return 0;
-
-fail:
-    return -1;
-}
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 1971f01..5023c79 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -230,7 +230,7 @@
     }
 
     std::vector<std::string> lines = android::base::Split(cfg_contents, "\n");
-    for (const std::string line : lines) {
+    for (const auto& line : lines) {
         if (line.empty() || line[0] == '#') {
             continue;
         }
@@ -421,7 +421,8 @@
     }
 
     if (strict && !module_loaded) {
-        LOG(ERROR) << "LoadWithAliases was unable to load " << module_name;
+        LOG(ERROR) << "LoadWithAliases was unable to load " << module_name
+                   << ", tried: " << android::base::Join(modules_to_load, ", ");
         return false;
     }
     return true;
@@ -439,54 +440,58 @@
     return module_blocklist_.count(canonical_name) > 0;
 }
 
-// Another option to load kernel modules. load in independent modules in parallel
-// and then update dependency list of other remaining modules, repeat these steps
-// until all modules are loaded.
+// Another option to load kernel modules. load independent modules dependencies
+// in parallel and then update dependency list of other remaining modules,
+// repeat these steps until all modules are loaded.
+// Discard all blocklist.
+// Softdeps are taken care in InsmodWithDeps().
 bool Modprobe::LoadModulesParallel(int num_threads) {
     bool ret = true;
-    int count = -1;
-    std::map<std::string, std::set<std::string>> mod_with_deps;
+    std::unordered_map<std::string, std::vector<std::string>> mod_with_deps;
 
     // Get dependencies
     for (const auto& module : module_load_) {
+        // Skip blocklist modules
+        if (IsBlocklisted(module)) {
+            LOG(VERBOSE) << "LMP: Blocklist: Module " << module << " skipping...";
+            continue;
+        }
         auto dependencies = GetDependencies(MakeCanonical(module));
-
-        for (auto dep = dependencies.rbegin(); dep != dependencies.rend(); dep++) {
-            mod_with_deps[module].emplace(*dep);
+        if (dependencies.empty()) {
+            LOG(ERROR) << "LMP: Hard-dep: Module " << module
+                       << " not in .dep file";
+            return false;
         }
+        mod_with_deps[MakeCanonical(module)] = dependencies;
     }
 
-    // Get soft dependencies
-    for (const auto& [it_mod, it_softdep] : module_pre_softdep_) {
-        if (mod_with_deps.find(MakeCanonical(it_softdep)) != mod_with_deps.end()) {
-            mod_with_deps[MakeCanonical(it_mod)].emplace(
-                GetDependencies(MakeCanonical(it_softdep))[0]);
-        }
-    }
-
-    // Get soft post dependencies
-    for (const auto& [it_mod, it_softdep] : module_post_softdep_) {
-        if (mod_with_deps.find(MakeCanonical(it_softdep)) != mod_with_deps.end()) {
-            mod_with_deps[MakeCanonical(it_softdep)].emplace(
-                GetDependencies(MakeCanonical(it_mod))[0]);
-        }
-    }
-
-    while (!mod_with_deps.empty() &&  count != module_loaded_.size()) {
+    while (!mod_with_deps.empty()) {
         std::vector<std::thread> threads;
         std::vector<std::string> mods_path_to_load;
         std::mutex vector_lock;
-        count = module_loaded_.size();
 
         // Find independent modules
         for (const auto& [it_mod, it_dep] : mod_with_deps) {
-            if (it_dep.size() == 1) {
-                if (module_options_[it_mod].find("load_sequential=1") != std::string::npos) {
-                    if (!LoadWithAliases(it_mod, true) && !IsBlocklisted(it_mod)) {
-                      return false;
-                    }
-                } else {
-                    mods_path_to_load.emplace_back(it_mod);
+            auto itd_last = it_dep.rbegin();
+            if (itd_last == it_dep.rend())
+                continue;
+
+            auto cnd_last = MakeCanonical(*itd_last);
+            // Hard-dependencies cannot be blocklisted
+            if (IsBlocklisted(cnd_last)) {
+                LOG(ERROR) << "LMP: Blocklist: Module-dep " << cnd_last
+                           << " : failed to load module " << it_mod;
+                return false;
+            }
+
+            if (module_options_[cnd_last].find("load_sequential=1") != std::string::npos) {
+                if (!LoadWithAliases(cnd_last, true)) {
+                    return false;
+                }
+            } else {
+                if (std::find(mods_path_to_load.begin(), mods_path_to_load.end(),
+                            cnd_last) == mods_path_to_load.end()) {
+                    mods_path_to_load.emplace_back(cnd_last);
                 }
             }
         }
@@ -502,7 +507,7 @@
                 lk.unlock();
                 ret_load &= LoadWithAliases(mod_to_load, true);
                 lk.lock();
-                if (!ret_load && !IsBlocklisted(mod_to_load)) {
+                if (!ret_load) {
                     ret &= ret_load;
                 }
             }
@@ -520,14 +525,16 @@
 
         std::lock_guard guard(module_loaded_lock_);
         // Remove loaded module form mod_with_deps and soft dependencies of other modules
-        for (const auto& module_loaded : module_loaded_) {
+        for (const auto& module_loaded : module_loaded_)
             mod_with_deps.erase(module_loaded);
-        }
 
         // Remove loaded module form dependencies of other modules which are not loaded yet
         for (const auto& module_loaded_path : module_loaded_paths_) {
             for (auto& [mod, deps] : mod_with_deps) {
-                deps.erase(module_loaded_path);
+                auto it = std::find(deps.begin(), deps.end(), module_loaded_path);
+                if (it != deps.end()) {
+                    deps.erase(it);
+                }
             }
         }
     }
diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp
index 94a1dc4..c4519e3 100644
--- a/libmodprobe/libmodprobe_ext.cpp
+++ b/libmodprobe/libmodprobe_ext.cpp
@@ -84,7 +84,7 @@
 }
 
 bool Modprobe::ModuleExists(const std::string& module_name) {
-    struct stat fileStat;
+    struct stat fileStat {};
     if (blocklist_enabled && module_blocklist_.count(module_name)) {
         LOG(INFO) << "module " << module_name << " is blocklisted";
         return false;
@@ -95,7 +95,7 @@
         return false;
     }
     if (stat(deps.front().c_str(), &fileStat)) {
-        LOG(INFO) << "module " << module_name << " does not exist";
+        PLOG(INFO) << "module " << module_name << " can't be loaded; can't access " << deps.front();
         return false;
     }
     if (!S_ISREG(fileStat.st_mode)) {
diff --git a/libnetutils/ifc_utils.c b/libnetutils/ifc_utils.c
index 5999e39..7cca105 100644
--- a/libnetutils/ifc_utils.c
+++ b/libnetutils/ifc_utils.c
@@ -362,14 +362,19 @@
     return err->error;
 }
 
+// Pass bitwise complement of prefix length to disable DAD, ie. use ~64 instead of 64.
 // Returns zero on success and negative errno on failure.
 int ifc_add_address(const char *name, const char *address, int prefixlen) {
-    return ifc_act_on_address(RTM_NEWADDR, name, address, prefixlen, /*nodad*/ false);
+    bool nodad = (prefixlen < 0);
+    if (nodad) prefixlen = ~prefixlen;
+    return ifc_act_on_address(RTM_NEWADDR, name, address, prefixlen, nodad);
 }
 
 // Returns zero on success and negative errno on failure.
 int ifc_del_address(const char *name, const char * address, int prefixlen) {
-    return ifc_act_on_address(RTM_DELADDR, name, address, prefixlen, /*nodad*/ false);
+    bool nodad = (prefixlen < 0);
+    if (nodad) prefixlen = ~prefixlen;
+    return ifc_act_on_address(RTM_DELADDR, name, address, prefixlen, nodad);
 }
 
 /*
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 234b793..06d386f 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -216,7 +216,6 @@
 static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
     int ret = 0;
     auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
-    auto uid_path = ConvertUidToPath(cgroup, uid);
 
     while (retries--) {
         ret = rmdir(uid_pid_path.c_str());
@@ -224,6 +223,13 @@
         std::this_thread::sleep_for(5ms);
     }
 
+    if (!ret && uid >= AID_ISOLATED_START && uid <= AID_ISOLATED_END) {
+        // Isolated UIDs are unlikely to be reused soon after removal,
+        // so free up the kernel resources for the UID level cgroup.
+        const auto uid_path = ConvertUidToPath(cgroup, uid);
+        ret = rmdir(uid_path.c_str());
+    }
+
     return ret;
 }
 
diff --git a/libsparse/Android.bp b/libsparse/Android.bp
index 8e83e16..5a7d0fc 100644
--- a/libsparse/Android.bp
+++ b/libsparse/Android.bp
@@ -28,6 +28,9 @@
         "libbase",
     ],
     target: {
+        darwin: {
+            enabled: true,
+        },
         windows: {
             enabled: true,
         },
@@ -52,6 +55,11 @@
     ],
 
     cflags: ["-Werror"],
+    target: {
+        darwin: {
+            enabled: true,
+        },
+    },
 }
 
 cc_binary {
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index 85a38f8..4609e6b 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -28,7 +28,6 @@
     ],
     source_stem: "bindings",
     bindgen_flags: [
-        "--size_t-is-usize",
         "--allowlist-function=AStatsEventList_addStatsEvent",
         "--allowlist-function=AStatsEvent_.*",
         "--allowlist-function=AStatsManager_.*",
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
index 819066e..c5c1934 100644
--- a/libstats/push_compat/Android.bp
+++ b/libstats/push_compat/Android.bp
@@ -26,7 +26,7 @@
 cc_defaults {
     name: "libstatspush_compat_defaults",
     srcs: [
-        "statsd_writer.c",
+        "statsd_writer.cpp",
         "stats_event_list.c",
         "StatsEventCompat.cpp"
     ],
diff --git a/libstats/push_compat/statsd_writer.c b/libstats/push_compat/statsd_writer.cpp
similarity index 97%
rename from libstats/push_compat/statsd_writer.c
rename to libstats/push_compat/statsd_writer.cpp
index 4818d11..a3600f3 100644
--- a/libstats/push_compat/statsd_writer.c
+++ b/libstats/push_compat/statsd_writer.cpp
@@ -15,9 +15,9 @@
  */
 #include "statsd_writer.h"
 
+#include <android-base/threads.h>
 #include <cutils/fs.h>
 #include <cutils/sockets.h>
-#include <cutils/threads.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -108,7 +108,7 @@
                     case -ECONNREFUSED:
                     case -ENOENT:
                         i = atomic_exchange(&statsdLoggerWrite.sock, ret);
-                    /* FALLTHRU */
+                        break;
                     default:
                         break;
                 }
@@ -188,7 +188,7 @@
      *  };
      */
 
-    header.tid = gettid();
+    header.tid = android::base::GetThreadId();
     header.realtime.tv_sec = ts->tv_sec;
     header.realtime.tv_nsec = ts->tv_nsec;
 
@@ -272,7 +272,7 @@
             if (ret < 0) {
                 ret = -errno;
             }
-        /* FALLTHRU */
+            break;
         default:
             break;
     }
diff --git a/libstats/push_compat/statsd_writer.h b/libstats/push_compat/statsd_writer.h
index fe2d37c..f030b96 100644
--- a/libstats/push_compat/statsd_writer.h
+++ b/libstats/push_compat/statsd_writer.h
@@ -21,6 +21,8 @@
 #include <stdatomic.h>
 #include <sys/socket.h>
 
+__BEGIN_DECLS
+
 /**
  * Internal lock should not be exposed. This is bad design.
  * TODO: rewrite it in c++ code and encapsulate the functionality in a
@@ -42,4 +44,6 @@
     void (*noteDrop)(int error, int tag);
 };
 
+__END_DECLS
+
 #endif  // ANDROID_STATS_LOG_STATS_WRITER_H
diff --git a/libsysutils/Android.bp b/libsysutils/Android.bp
index 5f472b2..1b41a6b 100644
--- a/libsysutils/Android.bp
+++ b/libsysutils/Android.bp
@@ -29,6 +29,10 @@
         "liblog",
     ],
 
+    header_libs: [
+        "bpf_headers",
+    ],
+
     export_include_dirs: ["include"],
 
     tidy: true,
diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 515cc10..55bbe46 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -37,10 +37,12 @@
 #include <sys/utsname.h>
 
 #include <android-base/parseint.h>
+#include <bpf/KernelUtils.h>
 #include <log/log.h>
 #include <sysutils/NetlinkEvent.h>
 
 using android::base::ParseInt;
+using android::bpf::isKernel64Bit;
 
 /* From kernel's net/netfilter/xt_quota2.c */
 const int LOCAL_QLOG_NL_EVENT = 112;
@@ -138,60 +140,6 @@
 static_assert(sizeof(ulog_packet_msg32_t) == 168);
 static_assert(sizeof(ulog_packet_msg64_t) == 192);
 
-// Figure out the bitness of userspace.
-// Trivial and known at compile time.
-static bool isUserspace64bit(void) {
-    return sizeof(long) == 8;
-}
-
-// Figure out the bitness of the kernel.
-static bool isKernel64Bit(void) {
-    // a 64-bit userspace requires a 64-bit kernel
-    if (isUserspace64bit()) return true;
-
-    static bool init = false;
-    static bool cache = false;
-    if (init) return cache;
-
-    // Retrieve current personality - on Linux this system call *cannot* fail.
-    int p = personality(0xffffffff);
-    // But if it does just assume kernel and userspace (which is 32-bit) match...
-    if (p == -1) return false;
-
-    // This will effectively mask out the bottom 8 bits, and switch to 'native'
-    // personality, and then return the previous personality of this thread
-    // (likely PER_LINUX or PER_LINUX32) with any extra options unmodified.
-    int q = personality((p & ~PER_MASK) | PER_LINUX);
-    // Per man page this theoretically could error out with EINVAL,
-    // but kernel code analysis suggests setting PER_LINUX cannot fail.
-    // Either way, assume kernel and userspace (which is 32-bit) match...
-    if (q != p) return false;
-
-    struct utsname u;
-    (void)uname(&u);  // only possible failure is EFAULT, but u is on stack.
-
-    // Switch back to previous personality.
-    // Theoretically could fail with EINVAL on arm64 with no 32-bit support,
-    // but then we wouldn't have fetched 'p' from the kernel in the first place.
-    // Either way there's nothing meaningul we can do in case of error.
-    // Since PER_LINUX32 vs PER_LINUX only affects uname.machine it doesn't
-    // really hurt us either.  We're really just switching back to be 'clean'.
-    (void)personality(p);
-
-    // Possible values of utsname.machine observed on x86_64 desktop (arm via qemu):
-    //   x86_64 i686 aarch64 armv7l
-    // additionally observed on arm device:
-    //   armv8l
-    // presumably also might just be possible:
-    //   i386 i486 i586
-    // and there might be other weird arm32 cases.
-    // We note that the 64 is present in both 64-bit archs,
-    // and in general is likely to be present in only 64-bit archs.
-    cache = !!strstr(u.machine, "64");
-    init = true;
-    return cache;
-}
-
 /******************************************************************************/
 
 NetlinkEvent::NetlinkEvent() {
@@ -202,15 +150,10 @@
 }
 
 NetlinkEvent::~NetlinkEvent() {
-    int i;
-    if (mPath)
-        free(mPath);
-    if (mSubsystem)
-        free(mSubsystem);
-    for (i = 0; i < NL_PARAMS_MAX; i++) {
-        if (!mParams[i])
-            break;
-        free(mParams[i]);
+    free(mPath);
+    free(mSubsystem);
+    for (auto param : mParams) {
+        free(param);
     }
 }
 
diff --git a/libutils/Errors.cpp b/libutils/Errors.cpp
index 74f3bef..dfb4d9b 100644
--- a/libutils/Errors.cpp
+++ b/libutils/Errors.cpp
@@ -15,6 +15,8 @@
  */
 #include <utils/Errors.h>
 
+#include <string.h>
+
 namespace android {
 
 std::string statusToString(status_t s) {
diff --git a/libutils/LruCache_test.cpp b/libutils/LruCache_test.cpp
index 8b16947..5cd3cbb 100644
--- a/libutils/LruCache_test.cpp
+++ b/libutils/LruCache_test.cpp
@@ -29,6 +29,8 @@
 struct ComplexKey {
     int k;
 
+    explicit ComplexKey() : k(0) { instanceCount += 1; }
+
     explicit ComplexKey(int k) : k(k) {
         instanceCount += 1;
     }
@@ -57,6 +59,8 @@
 struct ComplexValue {
     int v;
 
+    explicit ComplexValue() : v(0) { instanceCount += 1; }
+
     explicit ComplexValue(int v) : v(v) {
         instanceCount += 1;
     }
@@ -83,10 +87,9 @@
 
 struct KeyFailsOnCopy : public ComplexKey {
     public:
-    KeyFailsOnCopy(const KeyFailsOnCopy& key) : ComplexKey(key) {
-        ADD_FAILURE();
-    }
-    KeyFailsOnCopy(int key) : ComplexKey(key) { }
+      KeyFailsOnCopy() : ComplexKey() {}
+      KeyFailsOnCopy(const KeyFailsOnCopy& key) : ComplexKey(key) { ADD_FAILURE(); }
+      KeyFailsOnCopy(int key) : ComplexKey(key) {}
 };
 
 } // namespace
diff --git a/libutils/RefBase_test.cpp b/libutils/RefBase_test.cpp
index aed3b9b..d675598 100644
--- a/libutils/RefBase_test.cpp
+++ b/libutils/RefBase_test.cpp
@@ -28,7 +28,7 @@
 
 using namespace android;
 
-static constexpr int NITERS = 1000000;
+static constexpr int NITERS = 500000;
 
 static constexpr int INITIAL_STRONG_VALUE = 1 << 28;  // Mirroring RefBase definition.
 
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 3ffcf7e..364a177 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -280,158 +280,181 @@
            : 0);
 }
 
+// is_any_surrogate() returns true if w is either a high or low surrogate
+static constexpr bool is_any_surrogate(char16_t w) {
+    return (w & 0xf800) == 0xd800;
+}
+
+// is_surrogate_pair() returns true if w1 and w2 form a valid surrogate pair
+static constexpr bool is_surrogate_pair(char16_t w1, char16_t w2) {
+    return ((w1 & 0xfc00) == 0xd800) && ((w2 & 0xfc00) == 0xdc00);
+}
+
+// TODO: currently utf16_to_utf8_length() returns -1 if src_len == 0,
+// which is inconsistent with utf8_to_utf16_length(), here we keep the
+// current behavior as intended not to break compatibility
+ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len)
+{
+    if (src == nullptr || src_len == 0)
+        return -1;
+
+    const char16_t* const end = src + src_len;
+    const char16_t* in = src;
+    size_t utf8_len = 0;
+
+    while (in < end) {
+        char16_t w = *in++;
+        if (LIKELY(w < 0x0080)) {
+            utf8_len += 1;
+            continue;
+        }
+        if (LIKELY(w < 0x0800)) {
+            utf8_len += 2;
+            continue;
+        }
+        if (LIKELY(!is_any_surrogate(w))) {
+            utf8_len += 3;
+            continue;
+        }
+        if (in < end && is_surrogate_pair(w, *in)) {
+            utf8_len += 4;
+            in++;
+            continue;
+        }
+        /* skip if at the end of the string or invalid surrogate pair */
+    }
+    return (in == end && utf8_len < SSIZE_MAX) ? utf8_len : -1;
+}
+
 void utf16_to_utf8(const char16_t* src, size_t src_len, char* dst, size_t dst_len)
 {
     if (src == nullptr || src_len == 0 || dst == nullptr) {
         return;
     }
 
-    const char16_t* cur_utf16 = src;
-    const char16_t* const end_utf16 = src + src_len;
-    char *cur = dst;
-    while (cur_utf16 < end_utf16) {
-        char32_t utf32;
-        // surrogate pairs
-        if((*cur_utf16 & 0xFC00) == 0xD800 && (cur_utf16 + 1) < end_utf16
-                && (*(cur_utf16 + 1) & 0xFC00) == 0xDC00) {
-            utf32 = (*cur_utf16++ - 0xD800) << 10;
-            utf32 |= *cur_utf16++ - 0xDC00;
-            utf32 += 0x10000;
-        } else {
-            utf32 = (char32_t) *cur_utf16++;
+    const char16_t* in = src;
+    const char16_t* const in_end = src + src_len;
+    char* out = dst;
+    const char* const out_end = dst + dst_len;
+    char16_t w2;
+
+    auto err_out = [&out, &out_end, &dst_len]() {
+        LOG_ALWAYS_FATAL_IF(out >= out_end,
+                "target utf8 string size %zu too short", dst_len);
+    };
+
+    while (in < in_end) {
+        char16_t w = *in++;
+        if (LIKELY(w < 0x0080)) {
+            if (out + 1 > out_end)
+                return err_out();
+            *out++ = (char)(w & 0xff);
+            continue;
         }
-        const size_t len = utf32_codepoint_utf8_length(utf32);
-        LOG_ALWAYS_FATAL_IF(dst_len < len, "%zu < %zu", dst_len, len);
-        utf32_codepoint_to_utf8((uint8_t*)cur, utf32, len);
-        cur += len;
-        dst_len -= len;
+        if (LIKELY(w < 0x0800)) {
+            if (out + 2 > out_end)
+                return err_out();
+            *out++ = (char)(0xc0 | ((w >> 6) & 0x1f));
+            *out++ = (char)(0x80 | ((w >> 0) & 0x3f));
+            continue;
+        }
+        if (LIKELY(!is_any_surrogate(w))) {
+            if (out + 3 > out_end)
+                return err_out();
+            *out++ = (char)(0xe0 | ((w >> 12) & 0xf));
+            *out++ = (char)(0x80 | ((w >> 6) & 0x3f));
+            *out++ = (char)(0x80 | ((w >> 0) & 0x3f));
+            continue;
+        }
+        /* surrogate pair */
+        if (in < in_end && (w2 = *in, is_surrogate_pair(w, w2))) {
+            if (out + 4 > out_end)
+                return err_out();
+            char32_t dw = (char32_t)(0x10000 + ((w - 0xd800) << 10) + (w2 - 0xdc00));
+            *out++ = (char)(0xf0 | ((dw >> 18) & 0x07));
+            *out++ = (char)(0x80 | ((dw >> 12) & 0x3f));
+            *out++ = (char)(0x80 | ((dw >> 6)  & 0x3f));
+            *out++ = (char)(0x80 | ((dw >> 0)  & 0x3f));
+            in++;
+        }
+        /* We reach here in two cases:
+         *  1) (in == in_end), which means end of the input string
+         *  2) (w2 & 0xfc00) != 0xdc00, which means invalid surrogate pair
+         * In either case, we intentionally do nothing and skip
+         */
     }
-    LOG_ALWAYS_FATAL_IF(dst_len < 1, "%zu < 1", dst_len);
-    *cur = '\0';
+    *out = '\0';
+    return;
 }
 
 // --------------------------------------------------------------------------
 // UTF-8
 // --------------------------------------------------------------------------
 
-ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len)
-{
-    if (src == nullptr || src_len == 0) {
-        return -1;
-    }
-
-    size_t ret = 0;
-    const char16_t* const end = src + src_len;
-    while (src < end) {
-        size_t char_len;
-        if ((*src & 0xFC00) == 0xD800 && (src + 1) < end
-                && (*(src + 1) & 0xFC00) == 0xDC00) {
-            // surrogate pairs are always 4 bytes.
-            char_len = 4;
-            src += 2;
-        } else {
-            char_len = utf32_codepoint_utf8_length((char32_t)*src++);
-        }
-        if (SSIZE_MAX - char_len < ret) {
-            // If this happens, we would overflow the ssize_t type when
-            // returning from this function, so we cannot express how
-            // long this string is in an ssize_t.
-            android_errorWriteLog(0x534e4554, "37723026");
-            return -1;
-        }
-        ret += char_len;
-    }
-    return ret;
+static char32_t utf8_4b_to_utf32(uint8_t c1, uint8_t c2, uint8_t c3, uint8_t c4) {
+    return ((c1 & 0x07) << 18) | ((c2 & 0x3f) << 12) | ((c3 & 0x3f) << 6) | (c4 & 0x3f);
 }
 
-/**
- * Returns 1-4 based on the number of leading bits.
- *
- * 1111 -> 4
- * 1110 -> 3
- * 110x -> 2
- * 10xx -> 1
- * 0xxx -> 1
- */
-static inline size_t utf8_codepoint_len(uint8_t ch)
-{
-    return ((0xe5000000 >> ((ch >> 3) & 0x1e)) & 3) + 1;
-}
-
-static inline void utf8_shift_and_mask(uint32_t* codePoint, const uint8_t byte)
-{
-    *codePoint <<= 6;
-    *codePoint |= 0x3F & byte;
-}
-
-static inline uint32_t utf8_to_utf32_codepoint(const uint8_t *src, size_t length)
-{
-    uint32_t unicode;
-
-    switch (length)
-    {
-        case 1:
-            return src[0];
-        case 2:
-            unicode = src[0] & 0x1f;
-            utf8_shift_and_mask(&unicode, src[1]);
-            return unicode;
-        case 3:
-            unicode = src[0] & 0x0f;
-            utf8_shift_and_mask(&unicode, src[1]);
-            utf8_shift_and_mask(&unicode, src[2]);
-            return unicode;
-        case 4:
-            unicode = src[0] & 0x07;
-            utf8_shift_and_mask(&unicode, src[1]);
-            utf8_shift_and_mask(&unicode, src[2]);
-            utf8_shift_and_mask(&unicode, src[3]);
-            return unicode;
-        default:
-            return 0xffff;
-    }
-
-    //printf("Char at %p: len=%d, utf-16=%p\n", src, length, (void*)result);
-}
+// TODO: current behavior of converting UTF8 to UTF-16 has a few issues below
+//
+// 1. invalid trailing bytes (i.e. not b'10xxxxxx) are treated as valid trailing
+//    bytes and follows normal conversion rules
+// 2. invalid leading byte (b'10xxxxxx) is treated as a valid single UTF-8 byte
+// 3. invalid leading byte (b'11111xxx) is treated as a valid leading byte
+//    (same as b'11110xxx) for a 4-byte UTF-8 sequence
+// 4. an invalid 4-byte UTF-8 sequence that translates to a codepoint < U+10000
+//    will be converted as a valid UTF-16 character
+//
+// We keep the current behavior as is but with warnings logged, so as not to
+// break compatibility.  However, this needs to be addressed later.
 
 ssize_t utf8_to_utf16_length(const uint8_t* u8str, size_t u8len, bool overreadIsFatal)
 {
-    const uint8_t* const u8end = u8str + u8len;
-    const uint8_t* u8cur = u8str;
-
-    /* Validate that the UTF-8 is the correct len */
-    size_t u16measuredLen = 0;
-    while (u8cur < u8end) {
-        u16measuredLen++;
-        int u8charLen = utf8_codepoint_len(*u8cur);
-        // Malformed utf8, some characters are beyond the end.
-        // Cases:
-        // If u8charLen == 1, this becomes u8cur >= u8end, which cannot happen as u8cur < u8end,
-        // then this condition fail and we continue, as expected.
-        // If u8charLen == 2, this becomes u8cur + 1 >= u8end, which fails only if
-        // u8cur == u8end - 1, that is, there was only one remaining character to read but we need
-        // 2 of them. This condition holds and we return -1, as expected.
-        if (u8cur + u8charLen - 1 >= u8end) {
-            if (overreadIsFatal) {
-                LOG_ALWAYS_FATAL("Attempt to overread computing length of utf8 string");
-            } else {
-                return -1;
-            }
-        }
-        uint32_t codepoint = utf8_to_utf32_codepoint(u8cur, u8charLen);
-        if (codepoint > 0xFFFF) u16measuredLen++; // this will be a surrogate pair in utf16
-        u8cur += u8charLen;
-    }
-
-    /**
-     * Make sure that we ended where we thought we would and the output UTF-16
-     * will be exactly how long we were told it would be.
-     */
-    if (u8cur != u8end) {
+    if (u8str == nullptr)
         return -1;
-    }
 
-    return u16measuredLen;
+    const uint8_t* const in_end = u8str + u8len;
+    const uint8_t* in = u8str;
+    size_t utf16_len = 0;
+
+    while (in < in_end) {
+        uint8_t c = *in;
+        utf16_len++;
+        if (LIKELY((c & 0x80) == 0)) {
+            in++;
+            continue;
+        }
+        if (UNLIKELY(c < 0xc0)) {
+            ALOGW("Invalid UTF-8 leading byte: 0x%02x", c);
+            in++;
+            continue;
+        }
+        if (LIKELY(c < 0xe0)) {
+            in += 2;
+            continue;
+        }
+        if (LIKELY(c < 0xf0)) {
+            in += 3;
+            continue;
+        } else {
+            uint8_t c2, c3, c4;
+            if (UNLIKELY(c >= 0xf8)) {
+                ALOGW("Invalid UTF-8 leading byte: 0x%02x", c);
+            }
+            c2 = in[1]; c3 = in[2]; c4 = in[3];
+            if (utf8_4b_to_utf32(c, c2, c3, c4) >= 0x10000) {
+                utf16_len++;
+            }
+            in += 4;
+            continue;
+        }
+    }
+    if (in == in_end) {
+        return utf16_len < SSIZE_MAX ? utf16_len : -1;
+    }
+    if (overreadIsFatal)
+        LOG_ALWAYS_FATAL("Attempt to overread computing length of utf8 string");
+    return -1;
 }
 
 char16_t* utf8_to_utf16(const uint8_t* u8str, size_t u8len, char16_t* u16str, size_t u16len) {
@@ -444,38 +467,75 @@
 
 char16_t* utf8_to_utf16_no_null_terminator(
         const uint8_t* src, size_t srcLen, char16_t* dst, size_t dstLen) {
-    if (dstLen == 0) {
+    if (src == nullptr || srcLen == 0 || dstLen == 0) {
         return dst;
     }
     // A value > SSIZE_MAX is probably a negative value returned as an error and casted.
     LOG_ALWAYS_FATAL_IF(dstLen > SSIZE_MAX, "dstLen is %zu", dstLen);
-    const uint8_t* const u8end = src + srcLen;
-    const uint8_t* u8cur = src;
-    const char16_t* const u16end = dst + dstLen;
-    char16_t* u16cur = dst;
 
-    while (u8cur < u8end && u16cur < u16end) {
-        size_t u8len = utf8_codepoint_len(*u8cur);
-        uint32_t codepoint = utf8_to_utf32_codepoint(u8cur, u8len);
+    const uint8_t* const in_end = src + srcLen;
+    const uint8_t* in = src;
+    const char16_t* const out_end = dst + dstLen;
+    char16_t* out = dst;
+    uint8_t c, c2, c3, c4;
+    char32_t w;
 
-        // Convert the UTF32 codepoint to one or more UTF16 codepoints
-        if (codepoint <= 0xFFFF) {
-            // Single UTF16 character
-            *u16cur++ = (char16_t) codepoint;
-        } else {
-            // Multiple UTF16 characters with surrogates
-            codepoint = codepoint - 0x10000;
-            *u16cur++ = (char16_t) ((codepoint >> 10) + 0xD800);
-            if (u16cur >= u16end) {
-                // Ooops...  not enough room for this surrogate pair.
-                return u16cur-1;
-            }
-            *u16cur++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00);
+    auto err_in = [&c, &out]() {
+        ALOGW("Unended UTF-8 byte: 0x%02x", c);
+        return out;
+    };
+
+    while (in < in_end && out < out_end) {
+        c = *in++;
+        if (LIKELY((c & 0x80) == 0)) {
+            *out++ = (char16_t)(c);
+            continue;
         }
-
-        u8cur += u8len;
+        if (UNLIKELY(c < 0xc0)) {
+            ALOGW("Invalid UTF-8 leading byte: 0x%02x", c);
+            *out++ = (char16_t)(c);
+            continue;
+        }
+        if (LIKELY(c < 0xe0)) {
+            if (UNLIKELY(in + 1 > in_end)) {
+                return err_in();
+            }
+            c2 = *in++;
+            *out++ = (char16_t)(((c & 0x1f) << 6) | (c2 & 0x3f));
+            continue;
+        }
+        if (LIKELY(c < 0xf0)) {
+            if (UNLIKELY(in + 2 > in_end)) {
+                return err_in();
+            }
+            c2 = *in++; c3 = *in++;
+            *out++ = (char16_t)(((c & 0x0f) << 12) |
+                                ((c2 & 0x3f) << 6) | (c3 & 0x3f));
+            continue;
+        } else {
+            if (UNLIKELY(in + 3 > in_end)) {
+                return err_in();
+            }
+            if (UNLIKELY(c >= 0xf8)) {
+                ALOGW("Invalid UTF-8 leading byte: 0x%02x", c);
+            }
+            // Multiple UTF16 characters with surrogates
+            c2 = *in++; c3 = *in++; c4 = *in++;
+            w = utf8_4b_to_utf32(c, c2, c3, c4);
+            if (UNLIKELY(w < 0x10000)) {
+                *out++ = (char16_t)(w);
+            } else {
+                if (UNLIKELY(out + 2 > out_end)) {
+                    // Ooops.... not enough room for this surrogate pair.
+                    return out;
+                }
+                *out++ = (char16_t)(((w - 0x10000) >> 10) + 0xd800);
+                *out++ = (char16_t)(((w - 0x10000) & 0x3ff) + 0xdc00);
+            }
+            continue;
+        }
     }
-    return u16cur;
+    return out;
 }
 
 }
diff --git a/libutils/Vector_fuzz.cpp b/libutils/Vector_fuzz.cpp
index f6df051..6fd2baf 100644
--- a/libutils/Vector_fuzz.cpp
+++ b/libutils/Vector_fuzz.cpp
@@ -13,71 +13,203 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include "fuzzer/FuzzedDataProvider.h"
-#include "utils/Vector.h"
-static constexpr uint16_t MAX_VEC_SIZE = 5000;
+#include <fuzzer/FuzzedDataProvider.h>
+#include <utils/Log.h>
+#include <utils/Vector.h>
 
-void runVectorFuzz(const uint8_t* data, size_t size) {
-    FuzzedDataProvider dataProvider(data, size);
-    android::Vector<uint8_t> vec = android::Vector<uint8_t>();
-    // We want to test handling of sizeof as well.
-    android::Vector<uint32_t> vec32 = android::Vector<uint32_t>();
+#include <functional>
 
-    // We're going to generate two vectors of this size
-    size_t vectorSize = dataProvider.ConsumeIntegralInRange<size_t>(0, MAX_VEC_SIZE);
-    vec.setCapacity(vectorSize);
-    vec32.setCapacity(vectorSize);
-    for (size_t i = 0; i < vectorSize; i++) {
-        uint8_t count = dataProvider.ConsumeIntegralInRange<uint8_t>(1, 5);
-        vec.insertAt((uint8_t)i, i, count);
-        vec32.insertAt((uint32_t)i, i, count);
-        vec.push_front(i);
-        vec32.push(i);
+using android::Vector;
+
+static constexpr uint16_t MAX_VEC_SIZE = 100;
+static constexpr bool kLog = false;
+
+struct NonTrivialDestructor {
+    NonTrivialDestructor() : mInit(1) {}
+    ~NonTrivialDestructor() {
+        LOG_ALWAYS_FATAL_IF(mInit != 1, "mInit should be 1, but it's: %d", mInit);
+        mInit--;
+        LOG_ALWAYS_FATAL_IF(mInit != 0, "mInit should be 0, but it's: %d", mInit);
     }
 
-    // Now we'll perform some test operations with any remaining data
-    // Index to perform operations at
-    size_t index = dataProvider.ConsumeIntegralInRange<size_t>(0, vec.size());
-    std::vector<uint8_t> remainingVec = dataProvider.ConsumeRemainingBytes<uint8_t>();
-    // Insert an array and vector
-    vec.insertArrayAt(remainingVec.data(), index, remainingVec.size());
-    android::Vector<uint8_t> vecCopy = android::Vector<uint8_t>(vec);
-    vec.insertVectorAt(vecCopy, index);
-    // Same thing for 32 bit vector
-    android::Vector<uint32_t> vec32Copy = android::Vector<uint32_t>(vec32);
-    vec32.insertArrayAt(vec32Copy.array(), index, vec32.size());
-    vec32.insertVectorAt(vec32Copy, index);
-    // Replace single character
-    if (remainingVec.size() > 0) {
-        vec.replaceAt(remainingVec[0], index);
-        vec32.replaceAt(static_cast<uint32_t>(remainingVec[0]), index);
-    } else {
-        vec.replaceAt(0, index);
-        vec32.replaceAt(0, index);
+  private:
+    uint8_t mInit;
+};
+
+template <typename T>
+struct VectorFuzzerData {
+    Vector<T> vector;
+    const std::vector<std::function<void(FuzzedDataProvider&, Vector<T>&)>> funcs = {
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                // operator= Vector<TYPE>, still needs for SortedVector
+                if (kLog) ALOGI("operator=");
+                vector = testVector(provider);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("clear");
+                vector.clear();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("size");
+                vector.size();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("isEmpty");
+                vector.isEmpty();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("capacity");
+                vector.capacity();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                size_t vectorSize = provider.ConsumeIntegralInRange<size_t>(0, MAX_VEC_SIZE);
+                if (kLog) ALOGI("setCapacity");
+                vector.setCapacity(vectorSize);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                size_t vectorSize = provider.ConsumeIntegralInRange<size_t>(0, MAX_VEC_SIZE);
+                if (kLog) ALOGI("resize");
+                vector.resize(vectorSize);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("array");
+                vector.array();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("editArray");
+                vector.editArray();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                if (vector.size() == 0) return;
+                size_t idx = provider.ConsumeIntegralInRange<size_t>(0, vector.size() - 1);
+                if (kLog) ALOGI("operator[]");
+                vector[idx];  // returns a const value for Vector
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                if (vector.size() == 0) return;
+                size_t idx = provider.ConsumeIntegralInRange<size_t>(0, vector.size() - 1);
+                if (kLog) ALOGI("itemAt");
+                vector.itemAt(idx);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (vector.size() == 0) return;
+                if (kLog) ALOGI("top");
+                vector.top();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                if (vector.size() == 0) return;
+                size_t idx = provider.ConsumeIntegralInRange<size_t>(0, vector.size() - 1);
+                if (kLog) ALOGI("editItemAt");
+                vector.editItemAt(idx);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (vector.size() == 0) return;
+                if (kLog) ALOGI("editTop");
+                vector.editTop() = T{};
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, vector.size());
+                Vector vec2 = testVector(provider);
+                if (vec2.size() == 0) return;  // TODO: maybe we should support this?
+                if (kLog) ALOGI("insertVectorAt %d of size %zu", idx, vec2.size());
+                vector.insertVectorAt(vec2, idx);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                if (kLog) ALOGI("appendVector");
+                vector.appendVector(testVector(provider));
+            },
+            // TODO: insertArrayAt
+            // TODO: appendArray
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, vector.size());
+                uint8_t numItems = provider.ConsumeIntegralInRange<uint8_t>(1, 100);
+                if (kLog) ALOGI("insertAt");
+                vector.insertAt(idx, numItems);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, vector.size());
+                uint8_t numItems = provider.ConsumeIntegralInRange<uint8_t>(1, 100);
+                if (kLog) ALOGI("insertAt");
+                vector.insertAt(T{}, idx, numItems);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (vector.size() == 0) return;
+                if (kLog) ALOGI("pop");
+                vector.pop();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("push");
+                vector.push();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("add");
+                vector.add();
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("add");
+                vector.add(T{});
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, vector.size() - 1);
+                if (kLog) ALOGI("replaceAt");
+                vector.replaceAt(idx);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, vector.size() - 1);
+                if (kLog) ALOGI("replaceAt");
+                vector.replaceAt(T{}, idx);
+            },
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                if (vector.size() == 0) return;
+                uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, vector.size() - 1);
+                if (kLog) ALOGI("remoteItemsAt");
+                vector.removeItemsAt(idx);  // TODO: different count
+            },
+            // removeAt is alias for removeItemsAt
+            // TODO: sort
+            [&](FuzzedDataProvider& provider, Vector<T>& vector) {
+                (void)provider;
+                if (kLog) ALOGI("getItemSize");
+                vector.getItemSize();
+            },
+            // TODO: iterators
+    };
+
+    Vector<T> testVector(FuzzedDataProvider& provider) {
+        Vector<T> vec;
+        size_t vectorSize = provider.ConsumeIntegralInRange<size_t>(0, MAX_VEC_SIZE);
+        return vec;
     }
-    // Add any remaining bytes
-    for (uint8_t i : remainingVec) {
-        vec.add(i);
-        vec32.add(static_cast<uint32_t>(i));
+
+    void fuzz(FuzzedDataProvider&& provider) {
+        while (provider.remaining_bytes()) {
+            size_t funcIdx = provider.ConsumeIntegralInRange<size_t>(0, funcs.size() - 1);
+            funcs[funcIdx](provider, vector);
+        }
     }
-    // Shrink capactiy
-    vec.setCapacity(remainingVec.size());
-    vec32.setCapacity(remainingVec.size());
-    // Iterate through each pointer
-    size_t sum = 0;
-    for (auto& it : vec) {
-        sum += it;
-    }
-    for (auto& it : vec32) {
-        sum += it;
-    }
-    // Cleanup
-    vec.clear();
-    vecCopy.clear();
-    vec32.clear();
-    vec32Copy.clear();
-}
+};
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    runVectorFuzz(data, size);
+    FuzzedDataProvider provider(data, size);
+
+    provider.PickValueInArray<std::function<void()>>({
+            [&]() { VectorFuzzerData<uint8_t>().fuzz(std::move(provider)); },
+            [&]() { VectorFuzzerData<int32_t>().fuzz(std::move(provider)); },
+            [&]() { VectorFuzzerData<NonTrivialDestructor>().fuzz(std::move(provider)); },
+    })();
+
     return 0;
 }
diff --git a/libutils/include/utils/LruCache.h b/libutils/include/utils/LruCache.h
index b4243a3..70901b6 100644
--- a/libutils/include/utils/LruCache.h
+++ b/libutils/include/utils/LruCache.h
@@ -161,12 +161,12 @@
 // Implementation is here, because it's fully templated
 template <typename TKey, typename TValue>
 LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity)
-    : mSet(new LruCacheSet())
-    , mListener(nullptr)
-    , mOldest(nullptr)
-    , mYoungest(nullptr)
-    , mMaxCapacity(maxCapacity)
-    , mNullValue(0) {
+    : mSet(new LruCacheSet()),
+      mListener(nullptr),
+      mOldest(nullptr),
+      mYoungest(nullptr),
+      mMaxCapacity(maxCapacity),
+      mNullValue{} {
     mSet->max_load_factor(1.0);
 };
 
diff --git a/libutils/include/utils/Vector.h b/libutils/include/utils/Vector.h
index be35ea2..d5db3cb 100644
--- a/libutils/include/utils/Vector.h
+++ b/libutils/include/utils/Vector.h
@@ -67,13 +67,10 @@
     virtual                 ~Vector();
 
     /*! copy operator */
-            const Vector<TYPE>&     operator = (const Vector<TYPE>& rhs) const;
-            Vector<TYPE>&           operator = (const Vector<TYPE>& rhs);
+    Vector<TYPE>& operator=(const Vector<TYPE>& rhs);        // NOLINT(cert-oop54-cpp)
+    Vector<TYPE>& operator=(const SortedVector<TYPE>& rhs);  // NOLINT(cert-oop54-cpp)
 
-            const Vector<TYPE>&     operator = (const SortedVector<TYPE>& rhs) const;
-            Vector<TYPE>&           operator = (const SortedVector<TYPE>& rhs);
-
-            /*
+    /*
      * empty the vector
      */
 
@@ -248,27 +245,18 @@
     finish_vector();
 }
 
-template<class TYPE> inline
-Vector<TYPE>& Vector<TYPE>::operator = (const Vector<TYPE>& rhs) {
-    VectorImpl::operator = (rhs);
+template <class TYPE>
+inline Vector<TYPE>& Vector<TYPE>::operator=(const Vector<TYPE>& rhs)  // NOLINT(cert-oop54-cpp)
+{
+    VectorImpl::operator=(rhs);
     return *this;
 }
 
-template<class TYPE> inline
-const Vector<TYPE>& Vector<TYPE>::operator = (const Vector<TYPE>& rhs) const {
-    VectorImpl::operator = (static_cast<const VectorImpl&>(rhs));
-    return *this;
-}
-
-template<class TYPE> inline
-Vector<TYPE>& Vector<TYPE>::operator = (const SortedVector<TYPE>& rhs) {
-    VectorImpl::operator = (static_cast<const VectorImpl&>(rhs));
-    return *this;
-}
-
-template<class TYPE> inline
-const Vector<TYPE>& Vector<TYPE>::operator = (const SortedVector<TYPE>& rhs) const {
-    VectorImpl::operator = (rhs);
+template <class TYPE>
+inline Vector<TYPE>& Vector<TYPE>::operator=(
+        const SortedVector<TYPE>& rhs)  // NOLINT(cert-oop54-cpp)
+{
+    VectorImpl::operator=(static_cast<const VectorImpl&>(rhs));
     return *this;
 }
 
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 7da2646..2f63d6a 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -553,7 +553,7 @@
     # Should be before netd, but after apex, properties and logging is available.
     trigger load_bpf_programs
 
-    # Now we can start zygote for devices with file based encryption
+    # Now we can start zygote.
     trigger zygote-start
 
     # Remove a file to wake up anything waiting for firmware.
@@ -939,15 +939,22 @@
     # encryption policies apply recursively.  These directories should never
     # contain any subdirectories other than the per-user ones.  /data/media/obb
     # is an exception that exists for legacy reasons.
-    mkdir /data/media 0770 media_rw media_rw encryption=None
-    mkdir /data/misc_ce 01771 system misc encryption=None
-    mkdir /data/misc_de 01771 system misc encryption=None
-    mkdir /data/system_ce 0770 system system encryption=None
-    mkdir /data/system_de 0770 system system encryption=None
-    mkdir /data/user 0711 system system encryption=None
-    mkdir /data/user_de 0711 system system encryption=None
-    mkdir /data/vendor_ce 0771 root root encryption=None
-    mkdir /data/vendor_de 0771 root root encryption=None
+    #
+    # Don't use any write mode bits (0222) for any of these directories, since
+    # the only process that should write to them directly is vold (since it
+    # needs to set up file-based encryption on the subdirectories), which runs
+    # as root with CAP_DAC_OVERRIDE.  This is also fully enforced via the
+    # SELinux policy.  But we also set the DAC file modes accordingly, to try to
+    # minimize differences in behavior if SELinux is set to permissive mode.
+    mkdir /data/media 0550 media_rw media_rw encryption=None
+    mkdir /data/misc_ce 0551 system misc encryption=None
+    mkdir /data/misc_de 0551 system misc encryption=None
+    mkdir /data/system_ce 0550 system system encryption=None
+    mkdir /data/system_de 0550 system system encryption=None
+    mkdir /data/user 0511 system system encryption=None
+    mkdir /data/user_de 0511 system system encryption=None
+    mkdir /data/vendor_ce 0551 root root encryption=None
+    mkdir /data/vendor_de 0551 root root encryption=None
 
     # Set the casefold flag on /data/media.  For upgrades, a restorecon can be
     # needed first to relabel the directory from media_rw_data_file.
@@ -1069,28 +1076,10 @@
 
 # It is recommended to put unnecessary data/ initialization from post-fs-data
 # to start-zygote in device's init.rc to unblock zygote start.
-on zygote-start && property:ro.crypto.state=unencrypted
+on zygote-start
     wait_for_prop odsign.verification.done 1
     # A/B update verifier that marks a successful boot.
-    exec_start update_verifier_nonencrypted
-    start statsd
-    start netd
-    start zygote
-    start zygote_secondary
-
-on zygote-start && property:ro.crypto.state=unsupported
-    wait_for_prop odsign.verification.done 1
-    # A/B update verifier that marks a successful boot.
-    exec_start update_verifier_nonencrypted
-    start statsd
-    start netd
-    start zygote
-    start zygote_secondary
-
-on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
-    wait_for_prop odsign.verification.done 1
-    # A/B update verifier that marks a successful boot.
-    exec_start update_verifier_nonencrypted
+    exec_start update_verifier
     start statsd
     start netd
     start zygote
diff --git a/rootdir/init.usb.rc b/rootdir/init.usb.rc
index 0730cce..dde784e 100644
--- a/rootdir/init.usb.rc
+++ b/rootdir/init.usb.rc
@@ -18,6 +18,7 @@
     disabled
     updatable
     seclabel u:r:adbd:s0
+    user root
 
 on property:vendor.sys.usb.adb.disabled=*
     setprop sys.usb.adb.disabled ${vendor.sys.usb.adb.disabled}
diff --git a/rootdir/ramdisk_node_list b/rootdir/ramdisk_node_list
index d3ab8a6..4f45faa 100644
--- a/rootdir/ramdisk_node_list
+++ b/rootdir/ramdisk_node_list
@@ -1,3 +1,4 @@
 dir dev 0755 0 0
 nod dev/null 0600 0 0 c 1 3
 nod dev/console 0600 0 0 c 5 1
+nod dev/urandom 0600 0 0 c 1 9
diff --git a/storaged/Android.bp b/storaged/Android.bp
index c3447d2..fe8c1f3 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -136,3 +136,37 @@
     ],
     path: "binder",
 }
+
+cc_defaults {
+    name: "storaged_service_fuzzer_defaults",
+    defaults: [
+        "storaged_defaults",
+        "service_fuzzer_defaults",
+        "fuzzer_disable_leaks",
+    ],
+    static_libs: [
+        "libstoraged",
+    ],
+    fuzz_config: {
+        cc: [
+            "dvander@google.com",
+        ],
+        triage_assignee: "waghpawan@google.com",
+    },
+}
+
+cc_fuzz {
+    name: "storaged_service_fuzzer",
+    defaults: [
+        "storaged_service_fuzzer_defaults",
+    ],
+    srcs: ["tests/fuzzers/storaged_service_fuzzer.cpp"],
+}
+
+cc_fuzz {
+    name: "storaged_private_service_fuzzer",
+    defaults: [
+        "storaged_service_fuzzer_defaults",
+    ],
+    srcs: ["tests/fuzzers/storaged_private_service_fuzzer.cpp"],
+}
diff --git a/storaged/include/storaged_service.h b/storaged/include/storaged_service.h
index 7ec6864..bf7af80 100644
--- a/storaged/include/storaged_service.h
+++ b/storaged/include/storaged_service.h
@@ -28,6 +28,7 @@
 using namespace android::os;
 using namespace android::os::storaged;
 
+namespace android {
 class StoragedService : public BinderService<StoragedService>, public BnStoraged {
 private:
     void dumpUidRecordsDebug(int fd, const vector<struct uid_record>& entries);
@@ -53,4 +54,5 @@
 
 sp<IStoragedPrivate> get_storaged_pri_service();
 
+}  // namespace android
 #endif /* _STORAGED_SERVICE_H_ */
\ No newline at end of file
diff --git a/storaged/storaged_service.cpp b/storaged/storaged_service.cpp
index 45f1d4d..00d36d7 100644
--- a/storaged/storaged_service.cpp
+++ b/storaged/storaged_service.cpp
@@ -38,6 +38,7 @@
 
 extern sp<storaged_t> storaged_sp;
 
+namespace android {
 status_t StoragedService::start() {
     return BinderService<StoragedService>::publish();
 }
@@ -218,3 +219,4 @@
 
     return interface_cast<IStoragedPrivate>(binder);
 }
+}  // namespace android
\ No newline at end of file
diff --git a/storaged/tests/fuzzers/storaged_private_service_fuzzer.cpp b/storaged/tests/fuzzers/storaged_private_service_fuzzer.cpp
new file mode 100644
index 0000000..82eb796
--- /dev/null
+++ b/storaged/tests/fuzzers/storaged_private_service_fuzzer.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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 <fuzzbinder/libbinder_driver.h>
+
+#include <storaged.h>
+#include <storaged_service.h>
+
+sp<storaged_t> storaged_sp;
+
+extern "C" int LLVMFuzzerInitialize(int /**argc*/, char /****argv*/) {
+    storaged_sp = new storaged_t();
+    storaged_sp->init();
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    auto storagedPrivateService = new StoragedPrivateService();
+    fuzzService(storagedPrivateService, FuzzedDataProvider(data, size));
+    return 0;
+}
\ No newline at end of file
diff --git a/storaged/tests/fuzzers/storaged_service_fuzzer.cpp b/storaged/tests/fuzzers/storaged_service_fuzzer.cpp
new file mode 100644
index 0000000..d11ecc3
--- /dev/null
+++ b/storaged/tests/fuzzers/storaged_service_fuzzer.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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 <fuzzbinder/libbinder_driver.h>
+
+#include <storaged.h>
+#include <storaged_service.h>
+
+sp<storaged_t> storaged_sp;
+
+extern "C" int LLVMFuzzerInitialize(int /**argc*/, char /****argv*/) {
+    storaged_sp = new storaged_t();
+    storaged_sp->init();
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    auto storagedService = new StoragedService();
+    fuzzService(storagedService, FuzzedDataProvider(data, size));
+    return 0;
+}
\ No newline at end of file
diff --git a/trusty/confirmationui/fuzz/Android.bp b/trusty/confirmationui/fuzz/Android.bp
index 4780943..96804bd 100644
--- a/trusty/confirmationui/fuzz/Android.bp
+++ b/trusty/confirmationui/fuzz/Android.bp
@@ -26,7 +26,8 @@
         "-DTRUSTY_APP_FILENAME=\"confirmationui.syms.elf\"",
     ],
     fuzz_config: {
-       cc: ["trong@google.com"],
+       cc: ["mikemcternan@google.com"],
+       componentid: 1290237,
     },
 
 }
@@ -40,7 +41,8 @@
         "libdmabufheap",
     ],
     fuzz_config: {
-       cc: ["trong@google.com"],
+       cc: ["mikemcternan@google.com"],
+       componentid: 1290237,
     },
 
     // The initial corpus for this fuzzer was derived by dumping messages from/to
diff --git a/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp b/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
index e944167..6b8f90f 100644
--- a/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
+++ b/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
@@ -17,8 +17,10 @@
 #include <getopt.h>
 
 #include <string>
+#include <vector>
 
 #include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <trusty_keymaster/ipc/trusty_keymaster_ipc.h>
 
 namespace {
@@ -34,14 +36,66 @@
         {"model", required_argument, nullptr, 'm'},
         {"imei", required_argument, nullptr, 'i'},
         {"meid", required_argument, nullptr, 'c'},
+        {"imei2", required_argument, nullptr, '2'},
         {0, 0, 0, 0},
 };
 
+std::string TELEPHONY_CMD_GET_IMEI = "cmd phone get-imei ";
+
+// Run a shell command and collect the output of it. If any error, set an empty string as the
+// output.
+std::string exec_command(const std::string& command) {
+    char buffer[128];
+    std::string result = "";
+
+    FILE* pipe = popen(command.c_str(), "r");
+    if (!pipe) {
+        fprintf(stderr, "popen('%s') failed\n", command.c_str());
+        return result;
+    }
+
+    while (!feof(pipe)) {
+        if (fgets(buffer, 128, pipe) != NULL) {
+            result += buffer;
+        }
+    }
+
+    pclose(pipe);
+    return result;
+}
+
+// Get IMEI using Telephony service shell command. If any error while executing the command
+// then empty string will be returned as output.
+std::string get_imei(int slot) {
+    std::string cmd = TELEPHONY_CMD_GET_IMEI + std::to_string(slot);
+    std::string output = exec_command(cmd);
+
+    if (output.empty()) {
+        fprintf(stderr, "Retrieve IMEI command ('%s') failed\n", cmd.c_str());
+        return "";
+    }
+
+    std::vector<std::string> out =
+            ::android::base::Tokenize(::android::base::Trim(output), "Device IMEI:");
+
+    if (out.size() != 1) {
+        fprintf(stderr, "Error parsing command ('%s') output '%s'\n", cmd.c_str(), output.c_str());
+        return "";
+    }
+
+    std::string imei = ::android::base::Trim(out[0]);
+    if (imei.compare("null") == 0) {
+        fprintf(stderr, "IMEI value from command ('%s') is null, skipping", cmd.c_str());
+        return "";
+    }
+    return imei;
+}
+
 std::string buf2string(const keymaster::Buffer& buf) {
     return std::string(reinterpret_cast<const char*>(buf.peek_read()), buf.available_read());
 }
 
-void print_usage(const char* prog, const keymaster::SetAttestationIdsRequest& req) {
+void print_usage(const char* prog, const keymaster::SetAttestationIdsKM3Request& req) {
     fprintf(stderr,
             "Usage: %s [options]\n"
             "\n"
@@ -55,34 +109,53 @@
             "  -m, --model <val>          set model (default '%s')\n"
             "  -i, --imei <val>           set IMEI (default '%s')\n"
             "  -c, --meid <val>           set MEID (default '%s')\n"
+            "  -2, --imei2 <val>          set second IMEI (default '%s')\n"
             "\n",
-            prog, buf2string(req.brand).c_str(), buf2string(req.device).c_str(),
-            buf2string(req.product).c_str(), buf2string(req.serial).c_str(),
-            buf2string(req.manufacturer).c_str(), buf2string(req.model).c_str(),
-            buf2string(req.imei).c_str(), buf2string(req.meid).c_str());
+            prog, buf2string(req.base.brand).c_str(), buf2string(req.base.device).c_str(),
+            buf2string(req.base.product).c_str(), buf2string(req.base.serial).c_str(),
+            buf2string(req.base.manufacturer).c_str(), buf2string(req.base.model).c_str(),
+            buf2string(req.base.imei).c_str(), buf2string(req.base.meid).c_str(),
+            buf2string(req.second_imei).c_str());
+}
+
+void set_to(keymaster::Buffer* buf, const std::string& value) {
+    if (!value.empty()) {
+        buf->Reinitialize(value.data(), value.size());
+    }
 }
 
 void set_from_prop(keymaster::Buffer* buf, const std::string& prop) {
     std::string prop_value = ::android::base::GetProperty(prop, /* default_value = */ "");
-    if (!prop_value.empty()) {
-        buf->Reinitialize(prop_value.data(), prop_value.size());
-    }
+    set_to(buf, prop_value);
 }
 
-void populate_ids(keymaster::SetAttestationIdsRequest* req) {
+void populate_base_ids(keymaster::SetAttestationIdsRequest* req) {
     set_from_prop(&req->brand, "ro.product.brand");
     set_from_prop(&req->device, "ro.product.device");
     set_from_prop(&req->product, "ro.product.name");
     set_from_prop(&req->serial, "ro.serialno");
     set_from_prop(&req->manufacturer, "ro.product.manufacturer");
     set_from_prop(&req->model, "ro.product.model");
+    std::string imei = get_imei(0);
+    set_to(&req->imei, imei);
+}
+
+void populate_ids(keymaster::SetAttestationIdsKM3Request* req) {
+    populate_base_ids(&req->base);
+
+    // - "What about IMEI?"
+    // - "You've already had it."
+    // - "We've had one, yes. What about second IMEI?"
+    // - "I don't think he knows about second IMEI, Pip."
+    std::string imei2 = get_imei(1);
+    set_to(&req->second_imei, imei2);
 }
 
 }  // namespace
 
 int main(int argc, char** argv) {
     // By default, set attestation IDs to the values in userspace properties.
-    keymaster::SetAttestationIdsRequest req(/* ver = */ 4);
+    keymaster::SetAttestationIdsKM3Request req(/* ver = */ 4);
     populate_ids(&req);
 
     while (true) {
@@ -94,28 +167,31 @@
 
         switch (c) {
             case 'b':
-                req.brand.Reinitialize(optarg, strlen(optarg));
+                req.base.brand.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'd':
-                req.device.Reinitialize(optarg, strlen(optarg));
+                req.base.device.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'p':
-                req.product.Reinitialize(optarg, strlen(optarg));
+                req.base.product.Reinitialize(optarg, strlen(optarg));
                 break;
             case 's':
-                req.serial.Reinitialize(optarg, strlen(optarg));
+                req.base.serial.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'M':
-                req.manufacturer.Reinitialize(optarg, strlen(optarg));
+                req.base.manufacturer.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'm':
-                req.model.Reinitialize(optarg, strlen(optarg));
+                req.base.model.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'i':
-                req.imei.Reinitialize(optarg, strlen(optarg));
+                req.base.imei.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'c':
-                req.meid.Reinitialize(optarg, strlen(optarg));
+                req.base.meid.Reinitialize(optarg, strlen(optarg));
+                break;
+            case '2':
+                req.second_imei.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'h':
                 print_usage(argv[0], req);
@@ -144,19 +220,33 @@
            "  manufacturer: %s\n"
            "  model:        %s\n"
            "  IMEI:         %s\n"
-           "  MEID:         %s\n",
-           buf2string(req.brand).c_str(), buf2string(req.device).c_str(),
-           buf2string(req.product).c_str(), buf2string(req.serial).c_str(),
-           buf2string(req.manufacturer).c_str(), buf2string(req.model).c_str(),
-           buf2string(req.imei).c_str(), buf2string(req.meid).c_str());
+           "  MEID:         %s\n"
+           "  SECOND_IMEI:  %s\n\n",
+           buf2string(req.base.brand).c_str(), buf2string(req.base.device).c_str(),
+           buf2string(req.base.product).c_str(), buf2string(req.base.serial).c_str(),
+           buf2string(req.base.manufacturer).c_str(), buf2string(req.base.model).c_str(),
+           buf2string(req.base.imei).c_str(), buf2string(req.base.meid).c_str(),
+           buf2string(req.second_imei).c_str());
+    fflush(stdout);
 
     keymaster::EmptyKeymasterResponse rsp(/* ver = */ 4);
-    ret = trusty_keymaster_send(KM_SET_ATTESTATION_IDS, req, &rsp);
-    if (ret) {
-        fprintf(stderr, "SET_ATTESTATION_IDS failed: %d\n", ret);
-        trusty_keymaster_disconnect();
-        return EXIT_FAILURE;
+    const char* msg;
+    if (req.second_imei.available_read() == 0) {
+        // No SECOND_IMEI set, use base command.
+        ret = trusty_keymaster_send(KM_SET_ATTESTATION_IDS, req.base, &rsp);
+        msg = "SET_ATTESTATION_IDS";
+    } else {
+        // SECOND_IMEI is set, use updated command.
+        ret = trusty_keymaster_send(KM_SET_ATTESTATION_IDS_KM3, req, &rsp);
+        msg = "SET_ATTESTATION_IDS_KM3";
     }
+    trusty_keymaster_disconnect();
 
-    return EXIT_SUCCESS;
+    if (ret) {
+        fprintf(stderr, "%s failed: %d\n", msg, ret);
+        return EXIT_FAILURE;
+    } else {
+        printf("done\n");
+        return EXIT_SUCCESS;
+    }
 }