Merge "Snap for 10214594 from 0cea31e50bcf9efdcf268ddfe51fba7717a44a95 to sdk-release" into sdk-release
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/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 a00a202..4cd6193 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -20,6 +20,7 @@
 #include <fcntl.h>
 #include <linux/prctl.h>
 #include <malloc.h>
+#include <pthread.h>
 #include <stdlib.h>
 #include <sys/capability.h>
 #include <sys/mman.h>
@@ -2703,3 +2704,103 @@
   }
   ASSERT_TRUE(found_valid_elf) << "Did not find any elf files with valid BuildIDs to check.";
 }
+
+const char kLogMessage[] = "Should not see this log message.";
+
+// Verify that the logd process does not read the log.
+TEST_F(CrasherTest, logd_skips_reading_logs) {
+  StartProcess([]() {
+    pthread_setname_np(pthread_self(), "logd");
+    LOG(INFO) << kLogMessage;
+    abort();
+  });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  // logd should not contain our log message.
+  ASSERT_NOT_MATCH(result, kLogMessage);
+}
+
+// Verify that the logd process does not read the log when the non-main
+// thread crashes.
+TEST_F(CrasherTest, logd_skips_reading_logs_not_main_thread) {
+  StartProcess([]() {
+    pthread_setname_np(pthread_self(), "logd");
+    LOG(INFO) << kLogMessage;
+
+    std::thread thread([]() {
+      pthread_setname_np(pthread_self(), "not_logd_thread");
+      // Raise the signal on the side thread.
+      raise_debugger_signal(kDebuggerdTombstone);
+    });
+    thread.join();
+    _exit(0);
+  });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd, kDebuggerdTombstone);
+  FinishCrasher();
+  AssertDeath(0);
+
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal");
+  ASSERT_NOT_MATCH(result, kLogMessage);
+}
+
+// Disable this test since there is a high liklihood that this would
+// be flaky since it requires 500 messages being in the log.
+TEST_F(CrasherTest, DISABLED_max_log_messages) {
+  StartProcess([]() {
+    for (size_t i = 0; i < 600; i++) {
+      LOG(INFO) << "Message number " << i;
+    }
+    abort();
+  });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_NOT_MATCH(result, "Message number 99");
+  ASSERT_MATCH(result, "Message number 100");
+  ASSERT_MATCH(result, "Message number 599");
+}
+
+TEST_F(CrasherTest, log_with_newline) {
+  StartProcess([]() {
+    LOG(INFO) << "This line has a newline.\nThis is on the next line.";
+    abort();
+  });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, ":\\s*This line has a newline.");
+  ASSERT_MATCH(result, ":\\s*This is on the next line.");
+}
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index baa5bfb..c6a535a 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -566,20 +566,23 @@
     process_info = g_callbacks.get_process_info();
   }
 
-  // GWP-ASan catches use-after-free and heap-buffer-overflow by using PROT_NONE
-  // guard pages, which lead to SEGV. Normally, debuggerd prints a bug report
-  // and the process terminates, but in some cases, we actually want to print
-  // the bug report and let the signal handler return, and restart the process.
-  // In order to do that, we need to disable GWP-ASan's guard pages. The
-  // following callbacks handle this case.
-  gwp_asan_callbacks_t gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks();
-  if (signal_number == SIGSEGV && signal_has_si_addr(info) &&
-      gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery &&
-      gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report &&
-      gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report &&
-      gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery(info->si_addr)) {
-    gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report(info->si_addr);
-    process_info.recoverable_gwp_asan_crash = true;
+  gwp_asan_callbacks_t gwp_asan_callbacks = {};
+  if (g_callbacks.get_gwp_asan_callbacks != nullptr) {
+    // GWP-ASan catches use-after-free and heap-buffer-overflow by using PROT_NONE
+    // guard pages, which lead to SEGV. Normally, debuggerd prints a bug report
+    // and the process terminates, but in some cases, we actually want to print
+    // the bug report and let the signal handler return, and restart the process.
+    // In order to do that, we need to disable GWP-ASan's guard pages. The
+    // following callbacks handle this case.
+    gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks();
+    if (signal_number == SIGSEGV && signal_has_si_addr(info) &&
+        gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery &&
+        gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report &&
+        gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report &&
+        gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery(info->si_addr)) {
+      gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report(info->si_addr);
+      process_info.recoverable_gwp_asan_crash = true;
+    }
   }
 
   // If sival_int is ~0, it means that the fallback handler has been called
@@ -764,6 +767,7 @@
 bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context) {
   if (signal_number != SIGSEGV || !signal_has_si_addr(info)) return false;
 
+  if (g_callbacks.get_gwp_asan_callbacks == nullptr) return false;
   gwp_asan_callbacks_t gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks();
   if (gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery == nullptr ||
       gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report == nullptr ||
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 9a565de..7b2e068 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -70,6 +70,9 @@
 
 using android::base::StringPrintf;
 
+// The maximum number of messages to save in the protobuf per file.
+static constexpr size_t kMaxLogMessages = 500;
+
 // Use the demangler from libc++.
 extern "C" char* __cxa_demangle(const char*, char*, size_t*, int* status);
 
@@ -491,8 +494,8 @@
 }
 
 static void dump_log_file(Tombstone* tombstone, const char* logger, pid_t pid) {
-  logger_list* logger_list =
-      android_logger_list_open(android_name_to_log_id(logger), ANDROID_LOG_NONBLOCK, 0, pid);
+  logger_list* logger_list = android_logger_list_open(android_name_to_log_id(logger),
+                                                      ANDROID_LOG_NONBLOCK, kMaxLogMessages, pid);
 
   LogBuffer buffer;
 
@@ -690,7 +693,14 @@
 
   // Only dump logs on debuggable devices.
   if (android::base::GetBoolProperty("ro.debuggable", false)) {
-    dump_logcat(&result, main_thread.pid);
+    // Get the thread that corresponds to the main pid of the process.
+    const ThreadInfo& thread = threads.at(main_thread.pid);
+
+    // Do not attempt to dump logs of the logd process because the gathering
+    // of logs can hang until a timeout occurs.
+    if (thread.thread_name != "logd") {
+      dump_logcat(&result, main_thread.pid);
+    }
   }
 
   dump_open_fds(&result, open_files);
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..f5fd3a1 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -95,6 +95,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;
@@ -1485,13 +1487,20 @@
     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) {
     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)) {
+            die("could not load '%s': %s", fname, strerror(errno));
+        }
+    } else if (!load_buf(fname, &buf)) {
         die("cannot load '%s': %s", fname, strerror(errno));
     }
+
     if (is_logical(pname)) {
         fb->ResizePartition(pname, std::to_string(buf.image_size));
     }
@@ -1590,7 +1599,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,
@@ -1663,28 +1672,15 @@
     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 +1692,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 +1719,7 @@
             continue;
         }
         if (command.size() > 1 && command[0] == "version") {
-            if (!CheckFastbootInfoRequirements(command)) {
+            if (!CheckFastbootInfoRequirements(command, FASTBOOT_INFO_VERSION)) {
                 return {};
             }
             continue;
@@ -1733,8 +1731,6 @@
         }
         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));
@@ -1759,18 +1755,6 @@
     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() {
@@ -1790,15 +1774,20 @@
 
     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()) {
+    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";
         HardcodedFlash();
         return;
     }
+
+    std::vector<std::unique_ptr<Task>> tasks =
+            ParseFastbootInfo(fp_, Split({contents.data(), contents.size()}, "\n"));
+
+    if (tasks.empty()) {
+        LOG(FATAL) << "Invalid fastboot-info.txt file.";
+    }
     LOG(VERBOSE) << "Flashing from fastboot-info.txt";
     for (auto& task : tasks) {
         task->Run();
@@ -2124,7 +2113,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 +2153,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 +2162,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 +2178,7 @@
     }
 
     std::string message;
-    if (!wipe_super(*metadata.get(), slot, &message)) {
+    if (!wipe_super(*metadata.get(), slot, &message, fp)) {
         die(message);
     }
 }
@@ -2485,7 +2475,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);
@@ -2580,7 +2570,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()) {
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index b3dc67d..0a1f1eb 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -124,7 +124,8 @@
 
 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,6 +134,8 @@
 std::string get_current_slot();
 
 // Code for Parsing fastboot-info.txt
+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,
diff --git a/fastboot/fastboot_driver_mock.h b/fastboot/fastboot_driver_mock.h
index 62a5708..d2a123b 100644
--- a/fastboot/fastboot_driver_mock.h
+++ b/fastboot/fastboot_driver_mock.h
@@ -22,8 +22,7 @@
 
 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));
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 054c1ed..cb12060 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,7 +41,7 @@
                 "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);
 }
@@ -65,7 +65,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();
diff --git a/fastboot/task.h b/fastboot/task.h
index 34e3e92..82e8ebf 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -46,7 +46,7 @@
 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; }
 
     std::string GetPartition() { return pname_; }
@@ -60,6 +60,7 @@
     const std::string fname_;
     const std::string slot_;
     const bool apply_vbmeta_;
+    const FlashingPlan* fp_;
 };
 
 class RebootTask : public Task {
@@ -118,7 +119,7 @@
 
 class DeleteTask : public Task {
   public:
-    DeleteTask(const FlashingPlan* _fp, const std::string& _pname);
+    DeleteTask(const FlashingPlan* fp, const std::string& pname);
     void Run() override;
 
   private:
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
index 400e27f..b4e139b 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>
@@ -24,6 +25,7 @@
 #include <unordered_map>
 #include "android-base/strings.h"
 using android::base::Split;
+using testing::_;
 
 class ParseTest : public ::testing ::Test {
   protected:
@@ -53,7 +55,12 @@
     return tasks;
 }
 
-TEST_F(ParseTest, CORRECT_FlASH_TASK_FORMED) {
+std::unique_ptr<Task> ParseCommand(FlashingPlan* fp, std::string command) {
+    std::vector<std::string> vec_command = android::base::Split(command, " ");
+    return ParseFastbootInfoLine(fp, vec_command);
+}
+
+TEST_F(ParseTest, CorrectFlashTaskFormed) {
     std::vector<std::string> commands = {"flash dtbo", "flash --slot-other system system_other.img",
                                          "flash system", "flash --apply-vbmeta vbmeta"};
 
@@ -80,3 +87,75 @@
         ASSERT_EQ(task->GetImageName(), expected_values[i][3]);
     }
 }
+
+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", "version 100.00", "version 1 2"};
+
+    for (auto& version : correct_versions) {
+        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, " "), 100))
+                << version;
+    }
+}
+
+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);
+    ASSERT_EQ(ParseCommand(fp.get(), "if-wipe"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "if-wipe flash"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "wipe dtbo"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "update-super dtbo"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "flash system system.img system"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "reboot bootloader fastboot"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(),
+                           "flash --slot-other --apply-vbmeta system system_other.img system"),
+              nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "erase"), nullptr);
+    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();
+    }
+}
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index db27cf0..d357e45 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -24,9 +24,8 @@
     {
       "name": "vab_legacy_tests"
     },
-    {
-      "name": "vabc_legacy_tests"
-    },
+    // TODO: b/279009697
+    //{"name": "vabc_legacy_tests"},
     {
       "name": "cow_api_test"
     }
@@ -43,9 +42,8 @@
     },
     {
       "name": "vab_legacy_tests"
-    },
-    {
-      "name": "vabc_legacy_tests"
     }
+    // TODO: b/279009697
+    //{"name": "vabc_legacy_tests"}
   ]
 }
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 6349c20..f04fc8d 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -1083,7 +1083,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) {
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index deffae1..1e8c14f 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -243,6 +243,25 @@
     return true;
 }
 
+bool DeviceMapper::GetDeviceNameAndUuid(dev_t dev, std::string* name, std::string* uuid) {
+    struct dm_ioctl io;
+    InitIo(&io, {});
+    io.dev = dev;
+
+    if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) {
+        PLOG(ERROR) << "Failed to find device dev: " << major(dev) << ":" << minor(dev);
+        return false;
+    }
+
+    if (name) {
+        *name = io.name;
+    }
+    if (uuid) {
+        *uuid = io.uuid;
+    }
+    return true;
+}
+
 std::optional<DeviceMapper::Info> DeviceMapper::GetDetailedInfo(const std::string& name) const {
     struct dm_ioctl io;
     InitIo(&io, name);
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/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index 788cf51..c522eaf 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -30,6 +30,7 @@
 #include <thread>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
@@ -42,16 +43,40 @@
 using namespace std;
 using namespace std::chrono_literals;
 using namespace android::dm;
-using unique_fd = android::base::unique_fd;
+using android::base::make_scope_guard;
+using android::base::unique_fd;
 
-TEST(libdm, HasMinimumTargets) {
+class DmTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        const testing::TestInfo* const test_info =
+                testing::UnitTest::GetInstance()->current_test_info();
+        test_name_ = test_info->name();
+        test_full_name_ = test_info->test_suite_name() + "/"s + test_name_;
+
+        LOG(INFO) << "Starting test: " << test_full_name_;
+    }
+    void TearDown() override {
+        LOG(INFO) << "Tearing down test: " << test_full_name_;
+
+        auto& dm = DeviceMapper::Instance();
+        ASSERT_TRUE(dm.DeleteDeviceIfExists(test_name_));
+
+        LOG(INFO) << "Teardown complete for test: " << test_full_name_;
+    }
+
+    std::string test_name_;
+    std::string test_full_name_;
+};
+
+TEST_F(DmTest, HasMinimumTargets) {
     DmTargetTypeInfo info;
 
     DeviceMapper& dm = DeviceMapper::Instance();
     ASSERT_TRUE(dm.GetTargetByName("linear", &info));
 }
 
-TEST(libdm, DmLinear) {
+TEST_F(DmTest, DmLinear) {
     unique_fd tmp1(CreateTempFile("file_1", 4096));
     ASSERT_GE(tmp1, 0);
     unique_fd tmp2(CreateTempFile("file_2", 4096));
@@ -127,7 +152,7 @@
     ASSERT_TRUE(dev.Destroy());
 }
 
-TEST(libdm, DmSuspendResume) {
+TEST_F(DmTest, DmSuspendResume) {
     unique_fd tmp1(CreateTempFile("file_suspend_resume", 512));
     ASSERT_GE(tmp1, 0);
 
@@ -156,7 +181,7 @@
     ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE);
 }
 
-TEST(libdm, DmVerityArgsAvb2) {
+TEST_F(DmTest, DmVerityArgsAvb2) {
     std::string device = "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a";
     std::string algorithm = "sha1";
     std::string digest = "4be7e823b8c40f7bd5c8ccd5123f0722c5baca21";
@@ -178,7 +203,7 @@
     EXPECT_EQ(target.GetParameterString(), expected);
 }
 
-TEST(libdm, DmSnapshotArgs) {
+TEST_F(DmTest, DmSnapshotArgs) {
     DmTargetSnapshot target1(0, 512, "base", "cow", SnapshotStorageMode::Persistent, 8);
     if (DmTargetSnapshot::ReportsOverflow("snapshot")) {
         EXPECT_EQ(target1.GetParameterString(), "base cow PO 8");
@@ -200,7 +225,7 @@
     EXPECT_EQ(target3.name(), "snapshot-merge");
 }
 
-TEST(libdm, DmSnapshotOriginArgs) {
+TEST_F(DmTest, DmSnapshotOriginArgs) {
     DmTargetSnapshotOrigin target(0, 512, "base");
     EXPECT_EQ(target.GetParameterString(), "base");
     EXPECT_EQ(target.name(), "snapshot-origin");
@@ -330,7 +355,7 @@
     return true;
 }
 
-TEST(libdm, DmSnapshot) {
+TEST_F(DmTest, DmSnapshot) {
     if (!CheckSnapshotAvailability()) {
         return;
     }
@@ -374,7 +399,7 @@
     ASSERT_EQ(read, data);
 }
 
-TEST(libdm, DmSnapshotOverflow) {
+TEST_F(DmTest, DmSnapshotOverflow) {
     if (!CheckSnapshotAvailability()) {
         return;
     }
@@ -421,7 +446,7 @@
     }
 }
 
-TEST(libdm, ParseStatusText) {
+TEST_F(DmTest, ParseStatusText) {
     DmTargetSnapshot::Status status;
 
     // Bad inputs
@@ -448,7 +473,7 @@
     EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Overflow", &status));
 }
 
-TEST(libdm, DmSnapshotMergePercent) {
+TEST_F(DmTest, DmSnapshotMergePercent) {
     DmTargetSnapshot::Status status;
 
     // Correct input
@@ -502,7 +527,7 @@
     EXPECT_LE(DmTargetSnapshot::MergePercent(status, 0), 0.0);
 }
 
-TEST(libdm, CryptArgs) {
+TEST_F(DmTest, CryptArgs) {
     DmTargetCrypt target1(0, 512, "sha1", "abcdefgh", 50, "/dev/loop0", 100);
     ASSERT_EQ(target1.name(), "crypt");
     ASSERT_TRUE(target1.Valid());
@@ -518,7 +543,7 @@
               "iv_large_sectors sector_size:64");
 }
 
-TEST(libdm, DefaultKeyArgs) {
+TEST_F(DmTest, DefaultKeyArgs) {
     DmTargetDefaultKey target(0, 4096, "aes-xts-plain64", "abcdef0123456789", "/dev/loop0", 0);
     target.SetSetDun();
     ASSERT_EQ(target.name(), "default-key");
@@ -529,7 +554,7 @@
               "iv_large_sectors");
 }
 
-TEST(libdm, DefaultKeyLegacyArgs) {
+TEST_F(DmTest, DefaultKeyLegacyArgs) {
     DmTargetDefaultKey target(0, 4096, "AES-256-XTS", "abcdef0123456789", "/dev/loop0", 0);
     target.SetUseLegacyOptionsFormat();
     ASSERT_EQ(target.name(), "default-key");
@@ -537,7 +562,7 @@
     ASSERT_EQ(target.GetParameterString(), "AES-256-XTS abcdef0123456789 /dev/loop0 0");
 }
 
-TEST(libdm, DeleteDeviceWithTimeout) {
+TEST_F(DmTest, DeleteDeviceWithTimeout) {
     unique_fd tmp(CreateTempFile("file_1", 4096));
     ASSERT_GE(tmp, 0);
     LoopDevice loop(tmp, 10s);
@@ -561,7 +586,7 @@
     ASSERT_EQ(ENOENT, errno);
 }
 
-TEST(libdm, IsDmBlockDevice) {
+TEST_F(DmTest, IsDmBlockDevice) {
     unique_fd tmp(CreateTempFile("file_1", 4096));
     ASSERT_GE(tmp, 0);
     LoopDevice loop(tmp, 10s);
@@ -580,7 +605,7 @@
     ASSERT_FALSE(dm.IsDmBlockDevice(loop.device()));
 }
 
-TEST(libdm, GetDmDeviceNameByPath) {
+TEST_F(DmTest, GetDmDeviceNameByPath) {
     unique_fd tmp(CreateTempFile("file_1", 4096));
     ASSERT_GE(tmp, 0);
     LoopDevice loop(tmp, 10s);
@@ -601,7 +626,7 @@
     ASSERT_EQ("libdm-test-dm-linear", *name);
 }
 
-TEST(libdm, GetParentBlockDeviceByPath) {
+TEST_F(DmTest, GetParentBlockDeviceByPath) {
     unique_fd tmp(CreateTempFile("file_1", 4096));
     ASSERT_GE(tmp, 0);
     LoopDevice loop(tmp, 10s);
@@ -621,7 +646,7 @@
     ASSERT_EQ(loop.device(), *sub_block_device);
 }
 
-TEST(libdm, DeleteDeviceDeferredNoReferences) {
+TEST_F(DmTest, DeleteDeviceDeferredNoReferences) {
     unique_fd tmp(CreateTempFile("file_1", 4096));
     ASSERT_GE(tmp, 0);
     LoopDevice loop(tmp, 10s);
@@ -647,7 +672,7 @@
     ASSERT_EQ(ENOENT, errno);
 }
 
-TEST(libdm, DeleteDeviceDeferredWaitsForLastReference) {
+TEST_F(DmTest, DeleteDeviceDeferredWaitsForLastReference) {
     unique_fd tmp(CreateTempFile("file_1", 4096));
     ASSERT_GE(tmp, 0);
     LoopDevice loop(tmp, 10s);
@@ -682,7 +707,7 @@
     ASSERT_EQ(ENOENT, errno);
 }
 
-TEST(libdm, CreateEmptyDevice) {
+TEST_F(DmTest, CreateEmptyDevice) {
     DeviceMapper& dm = DeviceMapper::Instance();
     ASSERT_TRUE(dm.CreateEmptyDevice("empty-device"));
     auto guard =
@@ -692,9 +717,7 @@
     ASSERT_EQ(DmDeviceState::SUSPENDED, dm.GetState("empty-device"));
 }
 
-TEST(libdm, UeventAfterLoadTable) {
-    static const char* kDeviceName = "libdm-test-uevent-load-table";
-
+TEST_F(DmTest, UeventAfterLoadTable) {
     struct utsname u;
     ASSERT_EQ(uname(&u), 0);
 
@@ -706,18 +729,31 @@
     }
 
     DeviceMapper& dm = DeviceMapper::Instance();
-    ASSERT_TRUE(dm.CreateEmptyDevice(kDeviceName));
+    ASSERT_TRUE(dm.CreateEmptyDevice(test_name_));
 
     DmTable table;
     table.Emplace<DmTargetError>(0, 1);
-    ASSERT_TRUE(dm.LoadTable(kDeviceName, table));
+    ASSERT_TRUE(dm.LoadTable(test_name_, table));
 
     std::string ignore_path;
-    ASSERT_TRUE(dm.WaitForDevice(kDeviceName, 5s, &ignore_path));
+    ASSERT_TRUE(dm.WaitForDevice(test_name_, 5s, &ignore_path));
 
-    auto info = dm.GetDetailedInfo(kDeviceName);
+    auto info = dm.GetDetailedInfo(test_name_);
     ASSERT_TRUE(info.has_value());
     ASSERT_TRUE(info->IsSuspended());
 
-    ASSERT_TRUE(dm.DeleteDevice(kDeviceName));
+    ASSERT_TRUE(dm.DeleteDevice(test_name_));
+}
+
+TEST_F(DmTest, GetNameAndUuid) {
+    auto& dm = DeviceMapper::Instance();
+    ASSERT_TRUE(dm.CreatePlaceholderDevice(test_name_));
+
+    dev_t dev;
+    ASSERT_TRUE(dm.GetDeviceNumber(test_name_, &dev));
+
+    std::string name, uuid;
+    ASSERT_TRUE(dm.GetDeviceNameAndUuid(dev, &name, &uuid));
+    ASSERT_EQ(name, test_name_);
+    ASSERT_FALSE(uuid.empty());
 }
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index dbef8f9..3e7ecc6 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -298,6 +298,8 @@
     // a placeholder table containing dm-error.
     bool CreatePlaceholderDevice(const std::string& name);
 
+    bool GetDeviceNameAndUuid(dev_t dev, std::string* name, std::string* uuid);
+
   private:
     // Maximum possible device mapper targets registered in the kernel.
     // This is only used to read the list of targets from kernel so we allocate
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/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..d3bd904 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -163,6 +163,7 @@
         "libbrotli",
         "libz",
         "liblz4",
+        "libzstd",
     ],
     export_include_dirs: ["include"],
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index ba75a8d..c3ca00a 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,9 +28,6 @@
 
 static constexpr uint32_t kCowVersionManifest = 2;
 
-static constexpr size_t BLOCK_SZ = 4096;
-static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
-
 // 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:
 //
@@ -158,7 +157,8 @@
     kCowCompressNone = 0,
     kCowCompressGz = 1,
     kCowCompressBrotli = 2,
-    kCowCompressLz4 = 3
+    kCowCompressLz4 = 3,
+    kCowCompressZstd = 4,
 };
 
 static constexpr uint8_t kCowReadAheadNotStarted = 0;
@@ -196,5 +196,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..95a1270 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -29,42 +29,13 @@
 
 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:
     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 +54,43 @@
     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;
 };
 
-// Iterate over a sequence of COW operations.
+// 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 {
@@ -126,7 +111,6 @@
     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;
@@ -139,7 +123,10 @@
     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(uint64_t offset, void* buffer, size_t len, size_t* read);
 
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 24c91a8..f45d4ed 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -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_api_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
index 862ce55..edc9d65 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
@@ -24,6 +24,7 @@
 #include <gtest/gtest.h>
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
+#include "cow_decompress.h"
 
 using testing::AssertionFailure;
 using testing::AssertionResult;
@@ -44,23 +45,10 @@
     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;
@@ -74,24 +62,25 @@
     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));
+
+    const auto& header = reader.GetHeader();
     ASSERT_EQ(header.magic, kCowMagicNumber);
     ASSERT_EQ(header.major_version, kCowVersionMajor);
     ASSERT_EQ(header.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);
@@ -122,21 +111,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));
+
+    const auto& header = reader.GetHeader();
     ASSERT_EQ(header.magic, kCowMagicNumber);
     ASSERT_EQ(header.major_version, kCowVersionMajor);
     ASSERT_EQ(header.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 +134,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_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 +159,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,7 +169,7 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, ReadWriteXor) {
@@ -200,21 +190,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));
+
+    const auto& header = reader.GetHeader();
     ASSERT_EQ(header.magic, kCowMagicNumber);
     ASSERT_EQ(header.major_version, kCowVersionMajor);
     ASSERT_EQ(header.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 +213,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_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_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 +239,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,7 +249,7 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, CompressGz) {
@@ -282,25 +273,25 @@
 
     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_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;
@@ -337,36 +328,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_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,7 +368,7 @@
     ASSERT_EQ(total_blocks, expected_blocks);
 }
 
-TEST_P(CompressionRWTest, NoBatchWrites) {
+TEST_P(CompressionTest, NoBatchWrites) {
     CowOptions options;
     options.compression = GetParam();
     options.num_compress_threads = 1;
@@ -410,27 +402,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,7 +432,66 @@
     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;
@@ -467,43 +518,44 @@
 
     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_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();
+    sink = {};
+    sink.resize(data2.size(), '\0');
     ASSERT_EQ(op->compression, kCowCompressGz);
     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) {
@@ -527,59 +579,19 @@
 
     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_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;
@@ -641,35 +653,36 @@
     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) {
@@ -705,25 +718,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, 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) {
@@ -765,18 +776,16 @@
     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) {
@@ -816,55 +825,55 @@
     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) {
@@ -897,72 +906,71 @@
     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) {
@@ -997,33 +1005,33 @@
     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) {
@@ -1058,21 +1066,20 @@
     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();
@@ -1111,18 +1118,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++;
         }
@@ -1172,18 +1179,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++;
         }
@@ -1224,17 +1231,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++;
         }
@@ -1268,14 +1275,14 @@
     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) {
@@ -1319,7 +1326,7 @@
     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));
@@ -1334,17 +1341,17 @@
     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) {
@@ -1385,16 +1392,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, LegacyRevMergeOpItrTest) {
@@ -1434,16 +1441,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) {
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..2157d0f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -57,8 +57,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;
 }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 45be191..6749cf1 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 "libsnapshot/cow_format.h"
 
 namespace android {
 namespace snapshot {
@@ -508,47 +509,42 @@
 
 bool CowReader::VerifyMergeOps() {
     auto itr = GetMergeOpIter(true);
-    std::unordered_map<uint64_t, CowOperation> overwritten_blocks;
-    while (!itr->Done()) {
-        CowOperation op = itr->Get();
+    std::unordered_map<uint64_t, const CowOperation*> overwritten_blocks;
+    while (!itr->AtEnd()) {
+        const auto& op = itr->Get();
         uint64_t block;
         bool offset;
-        if (op.type == kCowCopyOp) {
-            block = op.source;
+        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 if (op->type == kCowXorOp) {
+            block = op->source / header_.block_size;
+            offset = (op->source % header_.block_size) != 0;
         } else {
             itr->Next();
             continue;
         }
 
-        CowOperation* overwrite = nullptr;
+        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];
+            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 +561,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 +578,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 +606,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 +625,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 +647,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 +679,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) {
@@ -747,18 +743,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 +766,11 @@
     size_t remaining_;
 };
 
-bool CowReader::ReadData(const CowOperation& op, IByteSink* sink) {
+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 (op->compression) {
         case kCowCompressNone:
-            decompressor = IDecompressor::Uncompressed();
             break;
         case kCowCompressGz:
             decompressor = IDecompressor::Gz();
@@ -782,24 +778,36 @@
         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: " << op->compression;
+            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 = op->source;
     }
-    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);
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
index 56b48f0..0e18979 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
@@ -37,6 +37,14 @@
 #include <sys/ioctl.h>
 #include <unistd.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 {
 
@@ -194,18 +202,13 @@
 }
 
 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()) {
+    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;
@@ -239,10 +242,10 @@
                 return false;
             }
             cow_image_size_ = size_in_bytes;
-            LOG(INFO) << "COW image " << file_path << " has 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.";
+            LOG_INFO << "COW image " << file_path
+                     << " is not a block device, assuming unlimited space.";
         }
     }
     return true;
@@ -271,12 +274,12 @@
     }
 
     std::string batch_write = batch_write_ ? "enabled" : "disabled";
-    LOG(INFO) << "Batch writes: " << batch_write;
+    LOG_INFO << "Batch writes: " << batch_write;
 }
 
 void CowWriter::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,7 +288,7 @@
         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) {
@@ -389,10 +392,11 @@
     auto reader = std::make_unique<CowReader>();
     std::queue<CowOperation> toAdd;
 
-    if (!reader->Parse(fd_, {label}) || !reader->GetHeader(&header_)) {
+    if (!reader->Parse(fd_, {label})) {
         return false;
     }
 
+    header_ = reader->GetHeader();
     options_.block_size = header_.block_size;
     options_.cluster_ops = header_.cluster_ops;
 
@@ -402,8 +406,8 @@
 
     auto iter = reader->GetOpIter();
 
-    while (!iter->Done()) {
-        AddOperation(iter->Get());
+    while (!iter->AtEnd()) {
+        AddOperation(*iter->Get());
         iter->Next();
     }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 167ff8c..917cec4 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>
@@ -38,16 +39,16 @@
 }
 
 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";
+    std::cerr << "Usage: inspect_cow [-sd] <COW_FILE>\n";
+    std::cerr << "\t -s Run Silent\n";
+    std::cerr << "\t -d Attempt to decompress\n";
+    std::cerr << "\t -b Show data for failed decompress\n";
+    std::cerr << "\t -l Show ops\n";
+    std::cerr << "\t -m Show ops in reverse merge order\n";
+    std::cerr << "\t -n Show ops in merge order\n";
+    std::cerr << "\t -a Include merged ops in any merge order listing\n";
+    std::cerr << "\t -o Shows sequence op block order\n";
+    std::cerr << "\t -v Verifies merge order has no conflicts\n";
 }
 
 enum OpIter { Normal, RevMerge, Merge };
@@ -63,37 +64,19 @@
     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->source, 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";
         }
     }
@@ -107,37 +90,40 @@
     }
 
     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 << "Version: " << header.major_version << "." << header.minor_version << "\n";
         std::cout << "Header size: " << header.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.silent) {
+        std::cout << "Parse time: " << (parse_time.count() * 1000) << "ms\n";
+    }
+
     if (opt.verify_sequence) {
+        std::cout << "\n";
         if (reader.VerifyMergeOps()) {
             std::cout << "\nMerge sequence is consistent.\n";
         } else {
@@ -153,34 +139,35 @@
     } else if (opt.iter_type == Merge) {
         iter = reader.GetMergeOpIter(opt.include_merged);
     }
-    StringSink sink;
+
+    std::string buffer(header.block_size, '\0');
+
     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 (!opt.silent && opt.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 (opt.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);
             }
-            sink.Reset();
         }
 
-        if (op.type == kCowSequenceOp && opt.show_seq) {
+        if (op->type == kCowSequenceOp && opt.show_seq) {
             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->source, 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";
+                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,13 +175,13 @@
             }
         }
 
-        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++;
         }
 
@@ -203,9 +190,11 @@
 
     if (!opt.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;
@@ -254,15 +243,17 @@
                 break;
             default:
                 android::snapshot::usage();
+                return 1;
         }
     }
-    android::base::InitLogging(argv, android::snapshot::MyLogger);
 
     if (argc < optind + 1) {
         android::snapshot::usage();
         return 1;
     }
 
+    android::base::InitLogging(argv, android::snapshot::MyLogger);
+
     if (!android::snapshot::Inspect(argv[optind], opt)) {
         return 1;
     }
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 64637c2..2661482 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -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);
 }
 
@@ -3279,7 +3280,10 @@
 
     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) {
@@ -3496,7 +3500,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;
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index 6546c2a..4e75ba7 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -80,16 +80,13 @@
 bool CompressedSnapshotReader::SetCow(std::unique_ptr<CowReader>&& cow) {
     cow_ = std::move(cow);
 
-    CowHeader header;
-    if (!cow_->GetHeader(&header)) {
-        return false;
-    }
+    const auto& header = cow_->GetHeader();
     block_size_ = header.block_size;
 
     // Populate the operation map.
     op_iter_ = cow_->GetOpIter();
-    while (!op_iter_->Done()) {
-        const CowOperation* op = &op_iter_->Get();
+    while (!op_iter_->AtEnd()) {
+        const CowOperation* op = op_iter_->Get();
         if (IsMetadataOp(*op)) {
             op_iter_->Next();
             continue;
@@ -128,37 +125,6 @@
     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_;
@@ -167,69 +133,48 @@
     // Chop off the first N bytes if the position is not block-aligned.
     size_t start_offset = offset_ % block_size_;
 
-    MemoryByteSink sink(buf, count);
+    uint8_t* buf_pos = reinterpret_cast<uint8_t*>(buf);
+    size_t buf_remaining = count;
 
-    size_t initial_bytes = std::min(block_size_ - start_offset, sink.remaining());
-    ssize_t rv = ReadBlock(start_chunk, &sink, start_offset, initial_bytes);
+    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, &sink, 0);
+        ssize_t rv = ReadBlock(chunk, 0, buf_pos, buf_remaining);
         if (rv < 0) {
             return -1;
         }
         offset_ += rv;
+        buf_pos += rv;
+        buf_remaining -= rv;
     }
 
-    if (sink.remaining()) {
-        ssize_t rv = ReadBlock(end_chunk, &sink, 0, {sink.remaining()});
+    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;
-
-    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;
-    }
+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.
@@ -240,17 +185,6 @@
         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) {
@@ -271,8 +205,7 @@
     } 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)) {
+        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;
@@ -285,18 +218,20 @@
         }
 
         off64_t offset = op->source + start_offset;
-        char data[BLOCK_SZ];
-        if (!android::base::ReadFullyAtOffset(fd, &data, bytes_to_read, 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;
         }
-        PartialSink partial_sink(buffer, bytes_to_read, start_offset);
-        if (!cow_->ReadData(*op, &partial_sink)) {
+
+        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];
         }
diff --git a/fs_mgr/libsnapshot/snapshot_reader.h b/fs_mgr/libsnapshot/snapshot_reader.h
index a3e7e22..5e19c62 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.h
+++ b/fs_mgr/libsnapshot/snapshot_reader.h
@@ -65,8 +65,7 @@
     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_;
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 9adc655..f25023d 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -140,6 +140,18 @@
         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) {
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 460d49d..22731e7 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();
@@ -654,6 +666,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 +777,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
@@ -1344,6 +1364,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_);
     {
@@ -1407,6 +1431,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 +1504,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 +1616,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 +1822,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 +1964,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 +2071,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 +2107,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 +2188,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 +2231,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,20 +2372,44 @@
             << "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));
+// Get max file size and free space.
+std::pair<uint64_t, uint64_t> GetBigFileLimit() {
+    struct statvfs fs;
+    if (statvfs("/data", &fs) < 0) {
+        PLOG(ERROR) << "statfs failed";
+        return {0, 0};
+    }
 
-    // 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.
+    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};
+}
+
+TEST_F(SnapshotUpdateTest, LowSpace) {
+    // To make the low space test more reliable, we force a large cow estimate.
+    // However legacy VAB ignores the COW estimate and uses InstallOperations
+    // to compute the exact size required for dm-snapshot. It's difficult to
+    // make this work reliably (we'd need to somehow fake an extremely large
+    // super partition, and we don't have that level of dependency injection).
+    //
+    // For now, just skip this test on legacy VAB.
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "Skipping test on legacy VAB";
+    }
+
+    auto fs = GetBigFileLimit();
+    ASSERT_NE(fs.first, 0);
+
     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);
+    sys_->set_estimate_cow_size(fs.first);
+    vnd_->set_estimate_cow_size(fs.first);
+    prd_->set_estimate_cow_size(fs.first);
 
     AddOperationForPartitions();
 
@@ -2334,8 +2418,12 @@
     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);
+
+    // It's hard to predict exactly how much free space is needed, since /data
+    // is writable and the test is not the only process running. Divide by two
+    // as a rough lower bound, and adjust this in the future as necessary.
+    auto expected_delta = fs.first - fs.second;
+    ASSERT_GE(res.required_size(), expected_delta / 2);
 }
 
 TEST_F(SnapshotUpdateTest, AddPartition) {
@@ -2397,6 +2485,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 +2675,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");
@@ -2708,6 +2805,7 @@
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
         CleanUp();
+        SnapshotTest::TearDown();
     }
     void CleanUp() {
         if (!image_manager_) {
@@ -2721,26 +2819,13 @@
 };
 
 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));
+    auto fs = GetBigFileLimit();
+    ASSERT_NE(fs.first, 0);
 
-        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";
+    auto res = image_manager_->CreateBackingImage(kImageName, fs.first,
+                                                  IImageManager::CREATE_IMAGE_DEFAULT);
+    ASSERT_FALSE(res);
+    ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
 }
 
 bool Mkdir(const std::string& path) {
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 1e03683..9fe567a 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -112,6 +112,7 @@
         "liblz4",
         "libext4_utils",
         "liburing",
+        "libzstd",
     ],
 
     header_libs: [
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 8926030..efa43b7 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:
             //
@@ -514,10 +509,8 @@
             // in the file.
             //===========================================================
             uint64_t block_source = cow_op->source;
-            uint64_t block_offset = 0;
             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,8 +626,7 @@
 }
 
 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;
@@ -832,8 +821,7 @@
 }
 
 uint64_t Snapuserd::GetBufferMetadataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     size_t size = header.header_size + sizeof(BufferState);
     return size;
@@ -848,16 +836,14 @@
  *
  */
 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());
 }
@@ -866,16 +852,14 @@
  * (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);
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..a32c2bf 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -173,16 +173,11 @@
 
 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))) {
+    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);
 }
 
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..922df34 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;
 }
 
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/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 25ce0ae..a519639 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,8 +238,7 @@
 }
 
 bool SnapshotHandler::MmapMetadata() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
 
@@ -367,8 +360,7 @@
 }
 
 uint64_t SnapshotHandler::GetBufferMetadataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     return (header.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,8 +388,7 @@
 }
 
 size_t SnapshotHandler::GetBufferDataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     return (header.header_size + GetBufferMetadataSize());
 }
@@ -407,8 +397,7 @@
  * (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,8 +410,7 @@
 }
 
 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);
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..7858216 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;
 }
 
@@ -125,12 +129,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;
 }
 
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..17f1f0e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -114,6 +114,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 +163,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 +182,6 @@
         if (!(num_ops == 0)) {
             SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
                             << " Pending ops: " << num_ops;
-            snapuserd_->ReadAheadIOFailed();
             return false;
         }
 
@@ -175,11 +194,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 +486,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 +518,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 +546,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 +556,7 @@
 
     // Done with merging ordered ops
     if (RAIterDone() && total_blocks_merged_ == 0) {
+        notify_read_ahead_failed.Cancel();
         return true;
     }
 
@@ -560,20 +587,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 +636,7 @@
     bm->new_block = 0;
     bm->file_offset = 0;
 
+    notify_read_ahead_failed.Cancel();
     return true;
 }
 
@@ -776,7 +809,7 @@
 }
 
 bool ReadAhead::RAIterDone() {
-    if (cowop_iter_->Done()) {
+    if (cowop_iter_->AtEnd()) {
         return true;
     }
 
@@ -794,15 +827,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/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 9f1d676..a224f6b 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -214,68 +214,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/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 eb43a33..7987167 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,172 +61,180 @@
 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;
-        hw_device = IGatekeeper::getService();
-        ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(gatekeeperServiceName));
-        aidl_hw_device = AidlIGatekeeper::fromBinder(ks2Binder);
-        is_running_gsi = android::base::GetBoolProperty(android::gsi::kGsiBootedProp, false);
+GateKeeperProxy::GateKeeperProxy() {
+    clear_state_if_needed_done = false;
+    hw_device = IGatekeeper::getService();
+    ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(gatekeeperServiceName));
+    aidl_hw_device = AidlIGatekeeper::fromBinder(ks2Binder);
+    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) {
@@ -247,110 +246,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 {
@@ -358,149 +362,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/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 66e1e63..bd7955a 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -242,6 +242,10 @@
         value = BatteryHealth::DEAD;
     else if (status == BatteryMonitor::BH_FAILED)
         value = BatteryHealth::UNSPECIFIED_FAILURE;
+    else if (status == BatteryMonitor::BH_NOT_AVAILABLE)
+        value = BatteryHealth::NOT_AVAILABLE;
+    else if (status == BatteryMonitor::BH_INCONSISTENT)
+        value = BatteryHealth::INCONSISTENT;
     else
         value = BatteryHealth::UNKNOWN;
 
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index 7b4f46c..e9998ba 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -62,6 +62,8 @@
         BH_MARGINAL,
         BH_NEEDS_REPLACEMENT,
         BH_FAILED,
+        BH_NOT_AVAILABLE,
+        BH_INCONSISTENT,
     };
 
     BatteryMonitor();
diff --git a/init/Android.bp b/init/Android.bp
index 7b52903..41c7a95 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -169,6 +169,7 @@
         "libfsverity_init",
         "liblmkd_utils",
         "liblz4",
+        "libzstd",
         "libmini_keyctl_static",
         "libmodprobe",
         "libprocinfo",
@@ -370,6 +371,7 @@
         "libprotobuf-cpp-lite",
         "libsnapshot_cow",
         "liblz4",
+        "libzstd",
         "libsnapshot_init",
         "update_metadata-protos",
         "libprocinfo",
diff --git a/init/builtins.cpp b/init/builtins.cpp
index bc23972..585eca2 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);
diff --git a/init/devices.cpp b/init/devices.cpp
index 39442a0..d29ffd6 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -32,6 +32,7 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <libdm/dm.h>
 #include <private/android_filesystem_config.h>
 #include <selinux/android.h>
 #include <selinux/selinux.h>
@@ -112,17 +113,14 @@
 // the supplied buffer with the dm module's instantiated name.
 // If it doesn't start with a virtual block device, or there is some
 // error, return false.
-static bool FindDmDevice(const std::string& path, std::string* name, std::string* uuid) {
-    if (!StartsWith(path, "/devices/virtual/block/dm-")) return false;
+static bool FindDmDevice(const Uevent& uevent, std::string* name, std::string* uuid) {
+    if (!StartsWith(uevent.path, "/devices/virtual/block/dm-")) return false;
+    if (uevent.action == "remove") return false;  // Avoid error spam from ioctl
 
-    if (!ReadFileToString("/sys" + path + "/dm/name", name)) {
-        return false;
-    }
-    ReadFileToString("/sys" + path + "/dm/uuid", uuid);
+    dev_t dev = makedev(uevent.major, uevent.minor);
 
-    *name = android::base::Trim(*name);
-    *uuid = android::base::Trim(*uuid);
-    return true;
+    auto& dm = android::dm::DeviceMapper::Instance();
+    return dm.GetDeviceNameAndUuid(dev, name, uuid);
 }
 
 Permissions::Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid,
@@ -392,7 +390,7 @@
         type = "pci";
     } else if (FindVbdDevicePrefix(uevent.path, &device)) {
         type = "vbd";
-    } else if (FindDmDevice(uevent.path, &partition, &uuid)) {
+    } else if (FindDmDevice(uevent, &partition, &uuid)) {
         std::vector<std::string> symlinks = {"/dev/block/mapper/" + partition};
         if (!uuid.empty()) {
             symlinks.emplace_back("/dev/block/mapper/by-uuid/" + uuid);
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..bff80c5 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,21 @@
     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;
+}
+
 int FirstStageMain(int argc, char** argv) {
     if (REBOOT_BOOTLOADER_ON_PANIC) {
         InstallRebootSignalHandlers();
@@ -328,7 +364,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";
diff --git a/init/init.cpp b/init/init.cpp
index be1ebee..da63fdc 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -1043,6 +1043,12 @@
     SetProperty(gsi::kGsiBootedProp, is_running);
     auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
     SetProperty(gsi::kGsiInstalledProp, is_installed);
+    if (android::gsi::IsGsiRunning()) {
+        std::string dsu_slot;
+        if (android::gsi::GetActiveDsu(&dsu_slot)) {
+            SetProperty(gsi::kDsuSlotProp, dsu_slot);
+        }
+    }
 
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
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/service.cpp b/init/service.cpp
index 35beaad..c152081 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -140,9 +140,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 +154,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 +206,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 +229,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 +256,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_;
@@ -434,8 +435,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 +476,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 +509,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 +706,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 +727,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..ce7c0da 100644
--- a/init/service.h
+++ b/init/service.h
@@ -71,7 +71,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 +115,7 @@
     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; }
+    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; }
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 3563084..d46e1f7 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>
@@ -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..d9fcd9d 100644
--- a/init/test_kill_services/init_kill_services_test.cpp
+++ b/init/test_kill_services/init_kill_services_test.cpp
@@ -16,14 +16,23 @@
 
 #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) {
+    // 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", 60s)) << "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 +41,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 +52,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/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 79d79dd..f90a1bc 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -84,14 +84,12 @@
     { 00777, AID_ROOT,         AID_ROOT,         0, "sdcard" },
     { 00751, AID_ROOT,         AID_SDCARD_R,     0, "storage" },
     { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering/bin/for-system" },
-    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/bin" },
     { 00755, AID_ROOT,         AID_ROOT,         0, "system/etc/ppp" },
     { 00755, AID_ROOT,         AID_SHELL,        0, "system/vendor" },
     { 00750, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/apex/*/bin" },
     { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering/bin/for-system" },
-    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/apex/*/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "vendor/bin" },
@@ -199,9 +197,7 @@
     // the following files have enhanced capabilities and ARE included
     // in user builds.
     { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering/bin/for-system/clatd" },
-    { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
     { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering/bin/for-system/clatd" },
-    { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
     { 00700, AID_SYSTEM,    AID_SHELL,     CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
                                               "system/bin/inputflinger" },
     { 00750, AID_ROOT,      AID_SHELL,     CAP_MASK_LONG(CAP_SETUID) |
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/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 48bc0b7..dbaeb93 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -96,6 +96,10 @@
 // Returns false in case of error, true in case of success
 bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
 
+// Check if a profile can be applied without failing.
+// Returns true if it can be applied without failing, false otherwise
+bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid);
+
 #endif // __ANDROID_VNDK__
 
 __END_DECLS
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index a021594..1f29040 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());
@@ -657,3 +656,13 @@
 bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
     return CgroupGetAttributePathForTask(attr_name, tid, path);
 }
+
+bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid) {
+    const TaskProfile* tp = TaskProfiles::GetInstance().GetProfile(profile_name);
+
+    if (tp == nullptr) {
+        return false;
+    }
+
+    return tp->IsValidForProcess(uid, pid);
+}
\ No newline at end of file
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 1731828..44dba2a 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -259,6 +259,31 @@
     return true;
 }
 
+bool SetAttributeAction::IsValidForProcess(uid_t, pid_t pid) const {
+    return IsValidForTask(pid);
+}
+
+bool SetAttributeAction::IsValidForTask(int tid) const {
+    std::string path;
+
+    if (!attribute_->GetPathForTask(tid, &path)) {
+        return false;
+    }
+
+    if (!access(path.c_str(), W_OK)) {
+        // operation will succeed
+        return true;
+    }
+
+    if (!access(path.c_str(), F_OK)) {
+        // file exists but not writable
+        return false;
+    }
+
+    // file does not exist, ignore if optional
+    return optional_;
+}
+
 SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
     : controller_(c), path_(p) {
     FdCacheHelper::Init(controller_.GetTasksFilePath(path_), fd_[ProfileAction::RCT_TASK]);
@@ -396,6 +421,39 @@
     FdCacheHelper::Drop(fd_[cache_type]);
 }
 
+bool SetCgroupAction::IsValidForProcess(uid_t uid, pid_t pid) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_PROCESS])) {
+        return true;
+    }
+
+    if (fd_[ProfileAction::RCT_PROCESS] == FdCacheHelper::FDS_INACCESSIBLE) {
+        return false;
+    }
+
+    std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
+    return access(procs_path.c_str(), W_OK) == 0;
+}
+
+bool SetCgroupAction::IsValidForTask(int) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_TASK])) {
+        return true;
+    }
+
+    if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_INACCESSIBLE) {
+        return false;
+    }
+
+    if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_APP_DEPENDENT) {
+        // application-dependent path can't be used with tid
+        return false;
+    }
+
+    std::string tasks_path = controller()->GetTasksFilePath(path_);
+    return access(tasks_path.c_str(), W_OK) == 0;
+}
+
 WriteFileAction::WriteFileAction(const std::string& task_path, const std::string& proc_path,
                                  const std::string& value, bool logfailures)
     : task_path_(task_path), proc_path_(proc_path), value_(value), logfailures_(logfailures) {
@@ -532,6 +590,37 @@
     FdCacheHelper::Drop(fd_[cache_type]);
 }
 
+bool WriteFileAction::IsValidForProcess(uid_t, pid_t) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_PROCESS])) {
+        return true;
+    }
+
+    if (fd_[ProfileAction::RCT_PROCESS] == FdCacheHelper::FDS_INACCESSIBLE) {
+        return false;
+    }
+
+    return access(proc_path_.empty() ? task_path_.c_str() : proc_path_.c_str(), W_OK) == 0;
+}
+
+bool WriteFileAction::IsValidForTask(int) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_TASK])) {
+        return true;
+    }
+
+    if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_INACCESSIBLE) {
+        return false;
+    }
+
+    if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_APP_DEPENDENT) {
+        // application-dependent path can't be used with tid
+        return false;
+    }
+
+    return access(task_path_.c_str(), W_OK) == 0;
+}
+
 bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
     for (const auto& profile : profiles_) {
         profile->ExecuteForProcess(uid, pid);
@@ -558,6 +647,24 @@
     }
 }
 
+bool ApplyProfileAction::IsValidForProcess(uid_t uid, pid_t pid) const {
+    for (const auto& profile : profiles_) {
+        if (!profile->IsValidForProcess(uid, pid)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool ApplyProfileAction::IsValidForTask(int tid) const {
+    for (const auto& profile : profiles_) {
+        if (!profile->IsValidForTask(tid)) {
+            return false;
+        }
+    }
+    return true;
+}
+
 void TaskProfile::MoveTo(TaskProfile* profile) {
     profile->elements_ = std::move(elements_);
     profile->res_cached_ = res_cached_;
@@ -620,6 +727,20 @@
     res_cached_ = false;
 }
 
+bool TaskProfile::IsValidForProcess(uid_t uid, pid_t pid) const {
+    for (const auto& element : elements_) {
+        if (!element->IsValidForProcess(uid, pid)) return false;
+    }
+    return true;
+}
+
+bool TaskProfile::IsValidForTask(int tid) const {
+    for (const auto& element : elements_) {
+        if (!element->IsValidForTask(tid)) return false;
+    }
+    return true;
+}
+
 void TaskProfiles::DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const {
     for (auto& iter : profiles_) {
         iter.second->DropResourceCaching(cache_type);
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index a8ecb87..a62c5b0 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -72,12 +72,14 @@
     virtual const char* Name() const = 0;
 
     // Default implementations will fail
-    virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
-    virtual bool ExecuteForTask(int) const { return false; };
-    virtual bool ExecuteForUID(uid_t) const { return false; };
+    virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; }
+    virtual bool ExecuteForTask(int) const { return false; }
+    virtual bool ExecuteForUID(uid_t) const { return false; }
 
     virtual void EnableResourceCaching(ResourceCacheType) {}
     virtual void DropResourceCaching(ResourceCacheType) {}
+    virtual bool IsValidForProcess(uid_t uid, pid_t pid) const { return false; }
+    virtual bool IsValidForTask(int tid) const { return false; }
 
   protected:
     enum CacheUseResult { SUCCESS, FAIL, UNUSED };
@@ -103,6 +105,8 @@
 
     const char* Name() const override { return "SetTimerSlack"; }
     bool ExecuteForTask(int tid) const override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override { return true; }
+    bool IsValidForTask(int tid) const override { return true; }
 
   private:
     unsigned long slack_;
@@ -120,6 +124,8 @@
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
     bool ExecuteForTask(int tid) const override;
     bool ExecuteForUID(uid_t uid) const override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+    bool IsValidForTask(int tid) const override;
 
   private:
     const IProfileAttribute* attribute_;
@@ -137,6 +143,8 @@
     bool ExecuteForTask(int tid) const override;
     void EnableResourceCaching(ResourceCacheType cache_type) override;
     void DropResourceCaching(ResourceCacheType cache_type) override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+    bool IsValidForTask(int tid) const override;
 
     const CgroupController* controller() const { return &controller_; }
 
@@ -161,6 +169,8 @@
     bool ExecuteForTask(int tid) const override;
     void EnableResourceCaching(ResourceCacheType cache_type) override;
     void DropResourceCaching(ResourceCacheType cache_type) override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+    bool IsValidForTask(int tid) const override;
 
   private:
     std::string task_path_, proc_path_, value_;
@@ -186,6 +196,8 @@
     bool ExecuteForUID(uid_t uid) const;
     void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
+    bool IsValidForProcess(uid_t uid, pid_t pid) const;
+    bool IsValidForTask(int tid) const;
 
   private:
     const std::string name_;
@@ -204,6 +216,8 @@
     bool ExecuteForTask(int tid) const override;
     void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+    bool IsValidForTask(int tid) const override;
 
   private:
     std::vector<std::shared_ptr<TaskProfile>> profiles_;
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index 6a5b48b..eadbe76 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -175,6 +175,32 @@
     }
 }
 
+class TaskProfileFixture : public TestWithParam<TestParam> {
+  public:
+    ~TaskProfileFixture() = default;
+};
+
+TEST_P(TaskProfileFixture, TaskProfile) {
+    // Treehugger runs host tests inside a container without cgroupv2 support.
+    if (!IsCgroupV2MountedRw()) {
+        GTEST_SKIP();
+        return;
+    }
+    const TestParam params = GetParam();
+    ProfileAttributeMock pa(params.attr_name);
+    // Test simple profile with one action
+    std::shared_ptr<TaskProfile> tp = std::make_shared<TaskProfile>("test_profile");
+    tp->Add(std::make_unique<SetAttributeAction>(&pa, params.attr_value, params.optional_attr));
+    EXPECT_EQ(tp->IsValidForProcess(getuid(), getpid()), params.result);
+    EXPECT_EQ(tp->IsValidForTask(getpid()), params.result);
+    // Test aggregate profile
+    TaskProfile tp2("meta_profile");
+    std::vector<std::shared_ptr<TaskProfile>> profiles = {tp};
+    tp2.Add(std::make_unique<ApplyProfileAction>(profiles));
+    EXPECT_EQ(tp2.IsValidForProcess(getuid(), getpid()), params.result);
+    EXPECT_EQ(tp2.IsValidForTask(getpid()), params.result);
+}
+
 // Test the four combinations of optional_attr {false, true} and cgroup attribute { does not exist,
 // exists }.
 INSTANTIATE_TEST_SUITE_P(
@@ -215,4 +241,28 @@
                         .log_prefix = "Failed to write",
                         .log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"}));
 
+// Test TaskProfile IsValid calls.
+INSTANTIATE_TEST_SUITE_P(
+        TaskProfileTestSuite, TaskProfileFixture,
+        Values(
+                // Test operating on non-existing cgroup attribute fails.
+                TestParam{.attr_name = "no-such-attribute",
+                          .attr_value = ".",
+                          .optional_attr = false,
+                          .result = false},
+                // Test operating on optional non-existing cgroup attribute succeeds.
+                TestParam{.attr_name = "no-such-attribute",
+                          .attr_value = ".",
+                          .optional_attr = true,
+                          .result = true},
+                // Test operating on existing cgroup attribute succeeds.
+                TestParam{.attr_name = "cgroup.procs",
+                          .attr_value = ".",
+                          .optional_attr = false,
+                          .result = true},
+                // Test operating on optional existing cgroup attribute succeeds.
+                TestParam{.attr_name = "cgroup.procs",
+                          .attr_value = ".",
+                          .optional_attr = true,
+                          .result = true}));
 }  // namespace
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..cd9db54 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() {
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/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
index 17f8156..17d4e31 100644
--- a/toolbox/modprobe.cpp
+++ b/toolbox/modprobe.cpp
@@ -85,6 +85,26 @@
     }
 }
 
+// Find directories in format of "/lib/modules/x.y.z-*".
+static int KernelVersionNameFilter(const dirent* de) {
+    unsigned int major, minor;
+    static std::string kernel_version;
+    utsname uts;
+
+    if (kernel_version.empty()) {
+        if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
+            LOG(ERROR) << "Could not parse the kernel version from uname";
+            return 0;
+        }
+        kernel_version = android::base::StringPrintf("%u.%u", major, minor);
+    }
+
+    if (android::base::StartsWith(de->d_name, kernel_version)) {
+        return 1;
+    }
+    return 0;
+}
+
 }  // anonymous namespace
 
 extern "C" int modprobe_main(int argc, char** argv) {
@@ -192,9 +212,22 @@
     }
 
     if (mod_dirs.empty()) {
-        utsname uts;
-        uname(&uts);
-        mod_dirs.emplace_back(android::base::StringPrintf("/lib/modules/%s", uts.release));
+        static constexpr auto LIB_MODULES_PREFIX = "/lib/modules/";
+        dirent** kernel_dirs = NULL;
+
+        int n = scandir(LIB_MODULES_PREFIX, &kernel_dirs, KernelVersionNameFilter, NULL);
+        if (n == -1) {
+            PLOG(ERROR) << "Failed to scan dir " << LIB_MODULES_PREFIX;
+            return EXIT_FAILURE;
+        } else if (n > 0) {
+            while (n--) {
+                mod_dirs.emplace_back(LIB_MODULES_PREFIX + std::string(kernel_dirs[n]->d_name));
+            }
+        }
+        free(kernel_dirs);
+
+        // Allow modules to be directly inside /lib/modules
+        mod_dirs.emplace_back(LIB_MODULES_PREFIX);
     }
 
     LOG(DEBUG) << "mode is " << mode;
@@ -212,11 +245,6 @@
             return EXIT_FAILURE;
         }
     }
-    if (mod_dirs.empty()) {
-        LOG(ERROR) << "No module configuration directories given.";
-        print_usage();
-        return EXIT_FAILURE;
-    }
     if (parameter_count && modules.size() > 1) {
         LOG(ERROR) << "Only one module may be loaded when specifying module parameters.";
         print_usage();
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;
+    }
 }