Merge "Revert "Move StagedRollbackTest from postsubmit to presubmit"" into main
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9b96f36..f47c317 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,7 @@
 [Builtin Hooks]
 clang_format = true
 rustfmt = true
+bpfmt = true
 
 [Builtin Hooks Options]
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 2d55e5a..d402bf1 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -830,7 +830,7 @@
   return subReason;
 }
 
-bool addKernelPanicSubReason(const pstoreConsole& console, std::string& ret) {
+void addKernelPanicSubReason(const pstoreConsole& console, std::string& ret) {
   // Check for kernel panic types to refine information
   if ((console.rfind("SysRq : Trigger a crash") != std::string::npos) ||
       (console.rfind("PC is at sysrq_handle_crash+") != std::string::npos)) {
@@ -842,63 +842,61 @@
     if (pos != std::string::npos) {
       ret += "," + getSubreason(console, pos + strlen(sysrqSubreason), /* quoted */ true);
     }
-    return true;
+    return;
   }
   if (console.rfind("Unable to handle kernel NULL pointer dereference at virtual address") !=
       std::string::npos) {
     ret = "kernel_panic,null";
-    return true;
+    return;
   }
   if (console.rfind("Kernel BUG at ") != std::string::npos) {
     ret = "kernel_panic,bug";
-    return true;
+    return;
   }
 
   std::string panic("Kernel panic - not syncing: ");
   auto pos = console.rfind(panic);
-  if (pos != std::string::npos) {
-    static const std::vector<std::pair<const std::string, const std::string>> panicReasons = {
-        {"Out of memory", "oom"},
-        {"out of memory", "oom"},
-        {"Oh boy, that early out of memory", "oom"},  // omg
-        {"BUG!", "bug"},
-        {"hung_task: blocked tasks", "hung"},
-        {"audit: ", "audit"},
-        {"scheduling while atomic", "atomic"},
-        {"Attempted to kill init!", "init"},
-        {"Requested init", "init"},
-        {"No working init", "init"},
-        {"Could not decompress init", "init"},
-        {"RCU Stall", "hung,rcu"},
-        {"stack-protector", "stack"},
-        {"kernel stack overflow", "stack"},
-        {"Corrupt kernel stack", "stack"},
-        {"low stack detected", "stack"},
-        {"corrupted stack end", "stack"},
-        {"subsys-restart: Resetting the SoC - modem crashed.", "modem"},
-        {"subsys-restart: Resetting the SoC - adsp crashed.", "adsp"},
-        {"subsys-restart: Resetting the SoC - dsps crashed.", "dsps"},
-        {"subsys-restart: Resetting the SoC - wcnss crashed.", "wcnss"},
-    };
+  if (pos == std::string::npos) return;
 
-    ret = "kernel_panic";
-    for (auto& s : panicReasons) {
-      if (console.find(panic + s.first, pos) != std::string::npos) {
-        ret += "," + s.second;
-        return true;
-      }
+  static const std::vector<std::pair<const std::string, const std::string>> panicReasons = {
+      {"Out of memory", "oom"},
+      {"out of memory", "oom"},
+      {"Oh boy, that early out of memory", "oom"},  // omg
+      {"BUG!", "bug"},
+      {"hung_task: blocked tasks", "hung"},
+      {"audit: ", "audit"},
+      {"scheduling while atomic", "atomic"},
+      {"Attempted to kill init!", "init"},
+      {"Requested init", "init"},
+      {"No working init", "init"},
+      {"Could not decompress init", "init"},
+      {"RCU Stall", "hung,rcu"},
+      {"stack-protector", "stack"},
+      {"kernel stack overflow", "stack"},
+      {"Corrupt kernel stack", "stack"},
+      {"low stack detected", "stack"},
+      {"corrupted stack end", "stack"},
+      {"subsys-restart: Resetting the SoC - modem crashed.", "modem"},
+      {"subsys-restart: Resetting the SoC - adsp crashed.", "adsp"},
+      {"subsys-restart: Resetting the SoC - dsps crashed.", "dsps"},
+      {"subsys-restart: Resetting the SoC - wcnss crashed.", "wcnss"},
+  };
+
+  ret = "kernel_panic";
+  for (auto& s : panicReasons) {
+    if (console.find(panic + s.first, pos) != std::string::npos) {
+      ret += "," + s.second;
+      return;
     }
-    auto reason = getSubreason(console, pos + panic.length(), /* newline */ false);
-    if (reason.length() > 3) {
-      ret += "," + reason;
-    }
-    return true;
   }
-  return false;
+  auto reason = getSubreason(console, pos + panic.length(), /* newline */ false);
+  if (reason.length() > 3) {
+    ret += "," + reason;
+  }
 }
 
-bool addKernelPanicSubReason(const std::string& content, std::string& ret) {
-  return addKernelPanicSubReason(pstoreConsole(content), ret);
+void addKernelPanicSubReason(const std::string& content, std::string& ret) {
+  addKernelPanicSubReason(pstoreConsole(content), ret);
 }
 
 const char system_reboot_reason_property[] = "sys.boot.reason";
@@ -1079,12 +1077,7 @@
       }
 
       // Check for kernel panics, allowed to override reboot command.
-      if (!addKernelPanicSubReason(console, ret) &&
-          // check for long-press power down
-          ((console.rfind("Power held for ") != std::string::npos) ||
-           (console.rfind("charger: [") != std::string::npos))) {
-        ret = "cold";
-      }
+      (void)addKernelPanicSubReason(console, ret);
     }
 
     // TODO: use the HAL to get battery level (http://b/77725702).
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 7d20995..0c5543e 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -21,8 +21,11 @@
     local_include_dirs: ["include"],
     product_variables: {
         debuggable: {
-            cflags: ["-UANDROID_DEBUGGABLE", "-DANDROID_DEBUGGABLE=1"],
-        }
+            cflags: [
+                "-UANDROID_DEBUGGABLE",
+                "-DANDROID_DEBUGGABLE=1",
+            ],
+        },
     },
 }
 
@@ -237,11 +240,16 @@
         "libdebuggerd/backtrace.cpp",
         "libdebuggerd/gwp_asan.cpp",
         "libdebuggerd/open_files_list.cpp",
+        "libdebuggerd/scudo.cpp",
         "libdebuggerd/tombstone.cpp",
         "libdebuggerd/tombstone_proto.cpp",
         "libdebuggerd/utility.cpp",
     ],
 
+    cflags: [
+        "-DUSE_SCUDO",
+    ],
+
     local_include_dirs: ["libdebuggerd/include"],
     export_include_dirs: ["libdebuggerd/include"],
 
@@ -253,10 +261,11 @@
         "bionic_libc_platform_headers",
         "gwp_asan_headers",
         "liblog_headers",
+        "scudo_headers",
     ],
 
     static_libs: [
-        "libdexfile_support",  // libunwindstack dependency
+        "libdexfile_support", // libunwindstack dependency
         "libunwindstack",
         "liblzma",
         "libbase",
@@ -270,6 +279,7 @@
         "libtombstone_proto",
         "libprocinfo",
         "libprotobuf-cpp-lite",
+        "libscudo",
     ],
 
     target: {
@@ -299,7 +309,7 @@
         },
         android: {
             runtime_libs: [
-                "libdexfile",           // libdexfile_support dependency
+                "libdexfile", // libdexfile_support dependency
             ],
         },
     },
@@ -309,11 +319,9 @@
             cflags: ["-DROOT_POSSIBLE"],
         },
 
-        malloc_not_svelte: {
-            cflags: ["-DUSE_SCUDO"],
-            whole_static_libs: ["libscudo"],
-            srcs: ["libdebuggerd/scudo.cpp"],
-            header_libs: ["scudo_headers"],
+        malloc_low_memory: {
+            cflags: ["-UUSE_SCUDO"],
+            exclude_static_libs: ["libscudo"],
         },
     },
     apex_available: [
@@ -368,6 +376,10 @@
         },
     },
 
+    sanitize: {
+        memtag_heap: true,
+    },
+
     shared_libs: [
         "libbase",
         "libcutils",
@@ -440,6 +452,7 @@
 
     header_libs: [
         "bionic_libc_platform_headers",
+        "libnative_bridge_support_accessor_headers",
     ],
 
     static_libs: [
@@ -449,6 +462,8 @@
 
         "libtombstone_proto",
         "libprotobuf-cpp-lite",
+
+        "libnative_bridge_guest_state_accessor",
     ],
 
     shared_libs: [
@@ -464,6 +479,15 @@
 
     // Required for tests.
     required: ["crash_dump.policy"],
+
+    target: {
+        android: {
+            header_libs: [
+                "libnative_bridge_support_accessor_headers", // For dlext_namespaces.h
+            ],
+            shared_libs: ["libdl_android"], // For android_get_exported_namespace implementation
+        },
+    },
 }
 
 cc_binary {
@@ -494,7 +518,7 @@
 
     header_libs: [
         "bionic_libc_platform_headers",
-        "libdebuggerd_common_headers"
+        "libdebuggerd_common_headers",
     ],
 
     static_libs: [
@@ -552,7 +576,6 @@
     },
 }
 
-
 // This installs the "other" architecture (so 32-bit on 64-bit device).
 prebuilt_etc {
     name: "crash_dump.policy_other",
diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING
index 8633cb8..824d20a 100644
--- a/debuggerd/TEST_MAPPING
+++ b/debuggerd/TEST_MAPPING
@@ -4,6 +4,10 @@
       "name": "debuggerd_test"
     },
     {
+      "name": "debuggerd_test",
+      "keywords": ["primary-device"]
+    },
+    {
       "name": "libtombstoned_client_rust_test"
     },
     {
@@ -14,5 +18,10 @@
     {
       "name": "debuggerd_test"
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsCrashDetailHostTestCases"
+    }
   ]
 }
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index bd1e91d..af1bb81 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -116,7 +116,6 @@
 
 bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms,
                             unique_fd output_fd) {
-  pid_t pid = tid;
   if (dump_type == kDebuggerdJavaBacktrace) {
     // Java dumps always get sent to the tgid, so we need to resolve our tid to a tgid.
     android::procinfo::ProcessInfo procinfo;
@@ -125,10 +124,10 @@
       log_error(output_fd, 0, "failed to get process info: %s", error.c_str());
       return false;
     }
-    pid = procinfo.pid;
+    tid = procinfo.pid;
   }
 
-  LOG(INFO) << TAG "started dumping process " << pid;
+  LOG(INFO) << TAG "started dumping process " << tid;
 
   // Rather than try to deal with poll() all the way through the flow, we update
   // the socket timeout between each step (and only use poll() during the final
@@ -172,7 +171,7 @@
 
   InterceptRequest req = {
       .dump_type = dump_type,
-      .pid = pid,
+      .pid = tid,
   };
 
   // Create an intermediate pipe to pass to the other end.
@@ -235,8 +234,8 @@
   // Send the signal.
   const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
   sigval val = {.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0};
-  if (sigqueue(pid, signal, val) != 0) {
-    log_error(output_fd, errno, "failed to send signal to pid %d", pid);
+  if (sigqueue(tid, signal, val) != 0) {
+    log_error(output_fd, errno, "failed to send signal to pid %d", tid);
     return false;
   }
 
@@ -299,7 +298,7 @@
     }
   }
 
-  LOG(INFO) << TAG "done dumping process " << pid;
+  LOG(INFO) << TAG "done dumping process " << tid;
 
   return true;
 }
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 0899111..8dd2b0d 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -25,6 +25,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <cstdint>
 #include <limits>
 #include <map>
 #include <memory>
@@ -42,6 +43,7 @@
 #include <android-base/unique_fd.h>
 #include <bionic/macros.h>
 #include <bionic/reserved_signals.h>
+#include <bionic/tls_defines.h>
 #include <cutils/sockets.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
@@ -52,7 +54,18 @@
 
 #include <unwindstack/AndroidUnwinder.h>
 #include <unwindstack/Error.h>
+#include <unwindstack/MachineArm.h>
+#include <unwindstack/MachineArm64.h>
+#include <unwindstack/MachineRiscv64.h>
 #include <unwindstack/Regs.h>
+#include <unwindstack/RegsArm.h>
+#include <unwindstack/RegsArm64.h>
+#include <unwindstack/RegsRiscv64.h>
+#include <unwindstack/UserArm.h>
+#include <unwindstack/UserArm64.h>
+#include <unwindstack/UserRiscv64.h>
+
+#include <native_bridge_support/guest_state_accessor/accessor.h>
 
 #include "libdebuggerd/backtrace.h"
 #include "libdebuggerd/tombstone.h"
@@ -68,6 +81,10 @@
 using android::base::StringPrintf;
 using android::base::unique_fd;
 
+// This stores guest architecture. When the architecture is supported, tombstone file will output
+// guest state information.
+static Architecture g_guest_arch = Architecture::NONE;
+
 static bool pid_contains_tid(int pid_proc_fd, pid_t tid) {
   struct stat st;
   std::string task_path = StringPrintf("task/%d", tid);
@@ -143,7 +160,7 @@
 }
 
 static bool activity_manager_notify(pid_t pid, int signal, const std::string& amfd_data,
-                                    bool recoverable_gwp_asan_crash) {
+                                    bool recoverable_crash) {
   ATRACE_CALL();
   android::base::unique_fd amfd(socket_local_client(
       "/data/system/ndebugsocket", ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM));
@@ -169,7 +186,7 @@
   // Activity Manager protocol:
   //  - 32-bit network-byte-order: pid
   //  - 32-bit network-byte-order: signal number
-  //  - byte: recoverable_gwp_asan_crash
+  //  - byte: recoverable_crash
   //  - bytes: raw text of the dump
   //  - null terminator
 
@@ -185,10 +202,9 @@
     return false;
   }
 
-  uint8_t recoverable_gwp_asan_crash_byte = recoverable_gwp_asan_crash ? 1 : 0;
-  if (!android::base::WriteFully(amfd, &recoverable_gwp_asan_crash_byte,
-                                 sizeof(recoverable_gwp_asan_crash_byte))) {
-    PLOG(ERROR) << "AM recoverable_gwp_asan_crash_byte write failed";
+  uint8_t recoverable_crash_byte = recoverable_crash ? 1 : 0;
+  if (!android::base::WriteFully(amfd, &recoverable_crash_byte, sizeof(recoverable_crash_byte))) {
+    PLOG(ERROR) << "AM recoverable_crash_byte write failed";
     return false;
   }
 
@@ -280,35 +296,34 @@
 
 static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
                           std::unique_ptr<unwindstack::Regs>* regs, ProcessInfo* process_info,
-                          bool* recoverable_gwp_asan_crash) {
+                          bool* recoverable_crash) {
   std::aligned_storage<sizeof(CrashInfo) + 1, alignof(CrashInfo)>::type buf;
   CrashInfo* crash_info = reinterpret_cast<CrashInfo*>(&buf);
   ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &buf, sizeof(buf)));
-  *recoverable_gwp_asan_crash = false;
+  *recoverable_crash = false;
   if (rc == -1) {
     PLOG(FATAL) << "failed to read target ucontext";
-  } else {
-    ssize_t expected_size = 0;
-    switch (crash_info->header.version) {
-      case 1:
-      case 2:
-      case 3:
-        expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataStatic);
-        break;
+  }
+  ssize_t expected_size = 0;
+  switch (crash_info->header.version) {
+    case 1:
+    case 2:
+    case 3:
+      expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataStatic);
+      break;
 
-      case 4:
-        expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataDynamic);
-        break;
+    case 4:
+      expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataDynamic);
+      break;
 
-      default:
-        LOG(FATAL) << "unexpected CrashInfo version: " << crash_info->header.version;
-        break;
-    };
+    default:
+      LOG(FATAL) << "unexpected CrashInfo version: " << crash_info->header.version;
+      break;
+  }
 
-    if (rc < expected_size) {
-      LOG(FATAL) << "read " << rc << " bytes when reading target crash information, expected "
-                 << expected_size;
-    }
+  if (rc < expected_size) {
+    LOG(FATAL) << "read " << rc << " bytes when reading target crash information, expected "
+                << expected_size;
   }
 
   switch (crash_info->header.version) {
@@ -321,7 +336,8 @@
       process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
       process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer;
       process_info->scudo_ring_buffer_size = crash_info->data.d.scudo_ring_buffer_size;
-      *recoverable_gwp_asan_crash = crash_info->data.d.recoverable_gwp_asan_crash;
+      *recoverable_crash = crash_info->data.d.recoverable_crash;
+      process_info->crash_detail_page = crash_info->data.d.crash_detail_page;
       FALLTHROUGH_INTENDED;
     case 1:
     case 2:
@@ -403,6 +419,116 @@
   sigaction(SIGPIPE, &action, nullptr);
 }
 
+static bool PtracePeek(int request, pid_t tid, uintptr_t addr, void* data, std::string_view err_msg,
+                       uintptr_t* result) {
+  errno = 0;
+  *result = ptrace(request, tid, addr, data);
+  if (errno != 0) {
+    PLOG(ERROR) << err_msg;
+    return false;
+  }
+  return true;
+}
+
+static bool GetGuestRegistersFromCrashedProcess([[maybe_unused]] pid_t tid,
+                                                NativeBridgeGuestRegs* guest_regs) {
+  auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(tid);
+
+  uintptr_t header_ptr = 0;
+  uintptr_t base = 0;
+#if defined(__x86_64__)
+  if (!PtracePeek(PTRACE_PEEKUSER, tid, offsetof(user_regs_struct, fs_base), nullptr,
+                  "failed to read thread register for thread " + std::to_string(tid), &base)) {
+    return false;
+  }
+#elif defined(__aarch64__)
+  // base is implicitly casted to uint64_t.
+  struct iovec pt_iov {
+    .iov_base = &base, .iov_len = sizeof(base),
+  };
+
+  if (ptrace(PTRACE_GETREGSET, tid, NT_ARM_TLS, &pt_iov) != 0) {
+    PLOG(ERROR) << "failed to read thread register for thread " << tid;
+    return false;
+  }
+#elif defined(__riscv)
+  struct user_regs_struct regs;
+  struct iovec pt_iov = {.iov_base = &regs, .iov_len = sizeof(regs)};
+  if (ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &pt_iov) != 0) {
+    PLOG(ERROR) << "failed to read thread register for thread " << tid;
+    return false;
+  }
+  base = reinterpret_cast<uintptr_t>(regs.tp);
+#else
+  // TODO(b/339287219): Add case for Riscv host.
+  return false;
+#endif
+  auto ptr_to_guest_slot = base + TLS_SLOT_NATIVE_BRIDGE_GUEST_STATE * sizeof(uintptr_t);
+  if (!process_memory->ReadFully(ptr_to_guest_slot, &header_ptr, sizeof(uintptr_t))) {
+    PLOG(ERROR) << "failed to get guest state TLS slot content for thread " << tid;
+    return false;
+  }
+
+  NativeBridgeGuestStateHeader header;
+  if (!process_memory->ReadFully(header_ptr, &header, sizeof(NativeBridgeGuestStateHeader))) {
+    PLOG(ERROR) << "failed to get the guest state header for thread " << tid;
+    return false;
+  }
+  if (header.signature != NATIVE_BRIDGE_GUEST_STATE_SIGNATURE) {
+    // Return when ptr points to unmapped memory or no valid guest state.
+    return false;
+  }
+  auto guest_state_data_copy = std::make_unique<unsigned char[]>(header.guest_state_data_size);
+  if (!process_memory->ReadFully(reinterpret_cast<uintptr_t>(header.guest_state_data),
+                                 guest_state_data_copy.get(), header.guest_state_data_size)) {
+    PLOG(ERROR) << "failed to read the guest state data for thread " << tid;
+    return false;
+  }
+
+  LoadGuestStateRegisters(guest_state_data_copy.get(), header.guest_state_data_size, guest_regs);
+  return true;
+}
+
+static void ReadGuestRegisters([[maybe_unused]] std::unique_ptr<unwindstack::Regs>* regs,
+                               pid_t tid) {
+  // TODO: remove [[maybe_unused]], when the ARM32 case is removed from the native bridge support.
+  NativeBridgeGuestRegs guest_regs;
+  if (!GetGuestRegistersFromCrashedProcess(tid, &guest_regs)) {
+    return;
+  }
+
+  switch (guest_regs.guest_arch) {
+#if defined(__LP64__)
+    case NATIVE_BRIDGE_ARCH_ARM64: {
+      unwindstack::arm64_user_regs arm64_user_regs = {};
+      for (size_t i = 0; i < unwindstack::ARM64_REG_R31; i++) {
+        arm64_user_regs.regs[i] = guest_regs.regs_arm64.x[i];
+      }
+      arm64_user_regs.sp = guest_regs.regs_arm64.sp;
+      arm64_user_regs.pc = guest_regs.regs_arm64.ip;
+      regs->reset(unwindstack::RegsArm64::Read(&arm64_user_regs));
+
+      g_guest_arch = Architecture::ARM64;
+      break;
+    }
+    case NATIVE_BRIDGE_ARCH_RISCV64: {
+      unwindstack::riscv64_user_regs riscv64_user_regs = {};
+      // RISCV64_REG_PC is at the first position.
+      riscv64_user_regs.regs[0] = guest_regs.regs_riscv64.ip;
+      for (size_t i = 1; i < unwindstack::RISCV64_REG_REAL_COUNT; i++) {
+        riscv64_user_regs.regs[i] = guest_regs.regs_riscv64.x[i];
+      }
+      regs->reset(unwindstack::RegsRiscv64::Read(&riscv64_user_regs, tid));
+
+      g_guest_arch = Architecture::RISCV64;
+      break;
+    }
+#endif
+    default:
+      break;
+  }
+}
+
 int main(int argc, char** argv) {
   DefuseSignalHandlers();
   InstallSigPipeHandler();
@@ -486,7 +612,7 @@
   std::map<pid_t, ThreadInfo> thread_info;
   siginfo_t siginfo;
   std::string error;
-  bool recoverable_gwp_asan_crash = false;
+  bool recoverable_crash = false;
 
   {
     ATRACE_NAME("ptrace");
@@ -538,8 +664,7 @@
 
       if (thread == g_target_thread) {
         // Read the thread's registers along with the rest of the crash info out of the pipe.
-        ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info,
-                      &recoverable_gwp_asan_crash);
+        ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info, &recoverable_crash);
         info.siginfo = &siginfo;
         info.signo = info.siginfo->si_signo;
 
@@ -552,6 +677,7 @@
           continue;
         }
       }
+      ReadGuestRegisters(&info.guest_registers, thread);
 
       thread_info[thread] = std::move(info);
     }
@@ -661,15 +787,35 @@
 
     {
       ATRACE_NAME("engrave_tombstone");
-      engrave_tombstone(std::move(g_output_fd), std::move(g_proto_fd), &unwinder, thread_info,
-                        g_target_thread, process_info, &open_files, &amfd_data);
+      unwindstack::ArchEnum regs_arch = unwindstack::ARCH_UNKNOWN;
+      switch (g_guest_arch) {
+        case Architecture::ARM64: {
+          regs_arch = unwindstack::ARCH_ARM64;
+          break;
+        }
+        case Architecture::RISCV64: {
+          regs_arch = unwindstack::ARCH_RISCV64;
+          break;
+        }
+        default: {
+        }
+      }
+      if (regs_arch == unwindstack::ARCH_UNKNOWN) {
+        engrave_tombstone(std::move(g_output_fd), std::move(g_proto_fd), &unwinder, thread_info,
+                          g_target_thread, process_info, &open_files, &amfd_data);
+      } else {
+        unwindstack::AndroidRemoteUnwinder guest_unwinder(vm_pid, regs_arch);
+        engrave_tombstone(std::move(g_output_fd), std::move(g_proto_fd), &unwinder, thread_info,
+                          g_target_thread, process_info, &open_files, &amfd_data, &g_guest_arch,
+                          &guest_unwinder);
+      }
     }
   }
 
   if (fatal_signal) {
     // Don't try to notify ActivityManager if it just crashed, or we might hang until timeout.
     if (thread_info[target_process].thread_name != "system_server") {
-      activity_manager_notify(target_process, signo, amfd_data, recoverable_gwp_asan_crash);
+      activity_manager_notify(target_process, signo, amfd_data, recoverable_crash);
     }
   }
 
diff --git a/debuggerd/crasher/Android.bp b/debuggerd/crasher/Android.bp
index fe1689c..4c6a400 100644
--- a/debuggerd/crasher/Android.bp
+++ b/debuggerd/crasher/Android.bp
@@ -15,7 +15,7 @@
         "-fstack-protector-all",
         "-Wno-date-time",
     ],
-    tidy: false,  // crasher.cpp tests many memory access errors
+    tidy: false, // crasher.cpp tests many memory access errors
     srcs: ["crasher.cpp"],
     arch: {
         arm: {
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 3b52776..05143ed 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -202,7 +202,9 @@
     fprintf(stderr, "  fdsan_file            close a file descriptor that's owned by a FILE*\n");
     fprintf(stderr, "  fdsan_dir             close a file descriptor that's owned by a DIR*\n");
     fprintf(stderr, "  seccomp               fail a seccomp check\n");
+#if defined(__LP64__)
     fprintf(stderr, "  xom                   read execute-only memory\n");
+#endif
     fprintf(stderr, "\n");
     fprintf(stderr, "  LOG_ALWAYS_FATAL      call liblog LOG_ALWAYS_FATAL\n");
     fprintf(stderr, "  LOG_ALWAYS_FATAL_IF   call liblog LOG_ALWAYS_FATAL_IF\n");
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index c0522aa..baddf65 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -21,6 +21,7 @@
 #include <linux/prctl.h>
 #include <malloc.h>
 #include <pthread.h>
+#include <setjmp.h>
 #include <stdlib.h>
 #include <sys/capability.h>
 #include <sys/mman.h>
@@ -37,6 +38,7 @@
 #include <string>
 #include <thread>
 
+#include <android/crash_detail.h>
 #include <android/dlext.h>
 #include <android/fdsan.h>
 #include <android/set_abort_message.h>
@@ -330,12 +332,7 @@
 
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
-#ifdef __LP64__
-  ASSERT_MATCH(result,
-               R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x000000000000dead)");
-#else
-  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0000dead)");
-#endif
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+dead)");
 
   if (mte_supported()) {
     // Test that the default TAGGED_ADDR_CTRL value is set.
@@ -600,6 +597,54 @@
 #endif
 }
 
+__attribute__((noinline)) void mte_illegal_setjmp_helper(jmp_buf& jump_buf) {
+  // This frame is at least 8 bytes for storing and restoring the LR before the
+  // setjmp below. So this can never get an empty stack frame, even if we omit
+  // the frame pointer. So, the SP of this is always less (numerically) than the
+  // calling function frame.
+  setjmp(jump_buf);
+}
+
+TEST_F(CrasherTest, DISABLED_mte_illegal_setjmp) {
+  // This setjmp is illegal because it jumps back into a function that already returned.
+  // Quoting man 3 setjmp:
+  //     If the function which called setjmp() returns before longjmp() is
+  //     called, the behavior is undefined.  Some kind of subtle or
+  //     unsubtle chaos is sure to result.
+  // https://man7.org/linux/man-pages/man3/longjmp.3.html
+#if defined(__aarch64__)
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&]() {
+    SetTagCheckingLevelSync();
+    jmp_buf jump_buf;
+    mte_illegal_setjmp_helper(jump_buf);
+    longjmp(jump_buf, 1);
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  // In our test-case, we have a NEGATIVE stack adjustment, which is being
+  // interpreted as unsigned integer, and thus is "too large".
+  // TODO(fmayer): fix the error message for this
+  ASSERT_MATCH(result, R"(memtag_handle_longjmp: stack adjustment too large)");
+#else
+  GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
 TEST_F(CrasherTest, mte_async) {
 #if defined(__aarch64__)
   if (!mte_supported()) {
@@ -939,6 +984,233 @@
   ASSERT_MATCH(result, R"(Abort message: 'x{4045}')");
 }
 
+static char g_crash_detail_value_changes[] = "crash_detail_value";
+static char g_crash_detail_value[] = "crash_detail_value";
+static char g_crash_detail_value2[] = "crash_detail_value2";
+
+inline crash_detail_t* _Nullable android_register_crash_detail_strs(const char* _Nonnull name,
+                                                                    const char* _Nonnull data) {
+  return android_crash_detail_register(name, strlen(name), data, strlen(data));
+}
+
+TEST_F(CrasherTest, crash_detail_single) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    android_register_crash_detail_strs("CRASH_DETAIL_NAME", g_crash_detail_value);
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'crash_detail_value')");
+}
+
+TEST_F(CrasherTest, crash_detail_replace_data) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    auto *cd = android_register_crash_detail_strs("CRASH_DETAIL_NAME", "original_data");
+    android_crash_detail_replace_data(cd, "new_data", strlen("new_data"));
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'new_data')");
+  // Ensure the old one no longer shows up, i.e. that we actually replaced
+  // it, not added a new one.
+  ASSERT_NOT_MATCH(result, R"(CRASH_DETAIL_NAME: 'original_data')");
+}
+
+TEST_F(CrasherTest, crash_detail_replace_name) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    auto *cd = android_register_crash_detail_strs("old_name", g_crash_detail_value);
+    android_crash_detail_replace_name(cd, "new_name", strlen("new_name"));
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(new_name: 'crash_detail_value')");
+  // Ensure the old one no longer shows up, i.e. that we actually replaced
+  // it, not added a new one.
+  ASSERT_NOT_MATCH(result, R"(old_name: 'crash_detail_value')");
+}
+
+TEST_F(CrasherTest, crash_detail_single_byte_name) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    android_register_crash_detail_strs("CRASH_DETAIL_NAME\1", g_crash_detail_value);
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME\\1: 'crash_detail_value')");
+}
+
+
+TEST_F(CrasherTest, crash_detail_single_bytes) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    android_crash_detail_register("CRASH_DETAIL_NAME", strlen("CRASH_DETAIL_NAME"), "\1",
+                                  sizeof("\1"));
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: '\\1\\0')");
+}
+
+TEST_F(CrasherTest, crash_detail_mixed) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    const char data[] = "helloworld\1\255\3";
+    android_register_crash_detail_strs("CRASH_DETAIL_NAME", data);
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'helloworld\\1\\255\\3')");
+}
+
+TEST_F(CrasherTest, crash_detail_many) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    for (int i = 0; i < 1000; ++i) {
+      std::string name = "CRASH_DETAIL_NAME" + std::to_string(i);
+      std::string value = "CRASH_DETAIL_VALUE" + std::to_string(i);
+      auto* h = android_register_crash_detail_strs(name.data(), value.data());
+      android_crash_detail_unregister(h);
+    }
+
+    android_register_crash_detail_strs("FINAL_NAME", "FINAL_VALUE");
+    android_register_crash_detail_strs("FINAL_NAME2", "FINAL_VALUE2");
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_NOT_MATCH(result, "CRASH_DETAIL_NAME");
+  ASSERT_NOT_MATCH(result, "CRASH_DETAIL_VALUE");
+  ASSERT_MATCH(result, R"(FINAL_NAME: 'FINAL_VALUE')");
+  ASSERT_MATCH(result, R"(FINAL_NAME2: 'FINAL_VALUE2')");
+}
+
+TEST_F(CrasherTest, crash_detail_single_changes) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    android_register_crash_detail_strs("CRASH_DETAIL_NAME", g_crash_detail_value_changes);
+    g_crash_detail_value_changes[0] = 'C';
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'Crash_detail_value')");
+}
+
+TEST_F(CrasherTest, crash_detail_multiple) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    android_register_crash_detail_strs("CRASH_DETAIL_NAME", g_crash_detail_value);
+    android_register_crash_detail_strs("CRASH_DETAIL_NAME2", g_crash_detail_value2);
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'crash_detail_value')");
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME2: 'crash_detail_value2')");
+}
+
+TEST_F(CrasherTest, crash_detail_remove) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    auto* detail1 = android_register_crash_detail_strs("CRASH_DETAIL_NAME", g_crash_detail_value);
+    android_crash_detail_unregister(detail1);
+    android_register_crash_detail_strs("CRASH_DETAIL_NAME2", g_crash_detail_value2);
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_NOT_MATCH(result, R"(CRASH_DETAIL_NAME: 'crash_detail_value')");
+  ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME2: 'crash_detail_value2')");
+}
+
 TEST_F(CrasherTest, abort_message_newline_trimmed) {
   int intercept_result;
   unique_fd output_fd;
@@ -1390,6 +1662,9 @@
 
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(
+      result,
+      R"(signal 6 \(SIGABRT\))");
   ASSERT_BACKTRACE_FRAME(result, "abort");
 }
 
@@ -1549,10 +1824,14 @@
      "Use After Free, 0 bytes into a 7-byte allocation"},
     {/* alloc_size */ 15, /* free_before_access */ true, /* access_offset */ 1,
      "Use After Free, 1 byte into a 15-byte allocation"},
-    {/* alloc_size */ 4096, /* free_before_access */ false, /* access_offset */ 4098,
-     "Buffer Overflow, 2 bytes right of a 4096-byte allocation"},
-    {/* alloc_size */ 4096, /* free_before_access */ false, /* access_offset */ -1,
-     "Buffer Underflow, 1 byte left of a 4096-byte allocation"},
+    {/* alloc_size */ static_cast<size_t>(getpagesize()), /* free_before_access */ false,
+     /* access_offset */ getpagesize() + 2,
+     android::base::StringPrintf("Buffer Overflow, 2 bytes right of a %d-byte allocation",
+                                 getpagesize())},
+    {/* alloc_size */ static_cast<size_t>(getpagesize()), /* free_before_access */ false,
+     /* access_offset */ -1,
+     android::base::StringPrintf("Buffer Underflow, 1 byte left of a %d-byte allocation",
+                                 getpagesize())},
 };
 
 INSTANTIATE_TEST_SUITE_P(
@@ -1597,8 +1876,8 @@
   StartProcess([&recoverable]() {
     const char* env[] = {"GWP_ASAN_SAMPLE_RATE=1", "GWP_ASAN_PROCESS_SAMPLING=1",
                          "GWP_ASAN_MAX_ALLOCS=40000", nullptr, nullptr};
-    if (recoverable) {
-      env[3] = "GWP_ASAN_RECOVERABLE=true";
+    if (!recoverable) {
+      env[3] = "GWP_ASAN_RECOVERABLE=false";
     }
     std::string test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name();
     test_name = std::regex_replace(test_name, std::regex("run_gwp_asan_test"),
@@ -2419,7 +2698,7 @@
   match_str += format_full_pointer(crash_uptr);
   ASSERT_MATCH(result, match_str);
 
-  ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
+  ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->\)\n)");
 
   // Verifies that the fault address error message is at the end of the
   // maps section. To do this, the check below looks for the start of the
@@ -2471,7 +2750,7 @@
   match_str += format_full_pointer(reinterpret_cast<uintptr_t>(middle_ptr));
   ASSERT_MATCH(result, match_str);
 
-  ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
+  ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->\)\n)");
 
   match_str = android::base::StringPrintf(
       R"(    %s.*\n--->Fault address falls at %s between mapped regions\n    %s)",
@@ -2509,7 +2788,7 @@
   match_str += format_full_pointer(reinterpret_cast<uintptr_t>(ptr));
   ASSERT_MATCH(result, match_str);
 
-  ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
+  ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->\)\n)");
 
   match_str = android::base::StringPrintf(R"(\n--->%s.*\n)", format_pointer(ptr).c_str());
   ASSERT_MATCH(result, match_str);
@@ -2697,30 +2976,34 @@
   std::string match_str;
   // Verify none.
   match_str = android::base::StringPrintf(
-      "    %s-%s ---         0      1000\\n",
+      "    %s-%s ---         0      %x\\n",
       format_map_pointer(reinterpret_cast<uintptr_t>(none_map)).c_str(),
-      format_map_pointer(reinterpret_cast<uintptr_t>(none_map) + getpagesize() - 1).c_str());
+      format_map_pointer(reinterpret_cast<uintptr_t>(none_map) + getpagesize() - 1).c_str(),
+      getpagesize());
   ASSERT_MATCH(result, match_str);
 
   // Verify read-only.
   match_str = android::base::StringPrintf(
-      "    %s-%s r--         0      1000\\n",
+      "    %s-%s r--         0      %x\\n",
       format_map_pointer(reinterpret_cast<uintptr_t>(r_map)).c_str(),
-      format_map_pointer(reinterpret_cast<uintptr_t>(r_map) + getpagesize() - 1).c_str());
+      format_map_pointer(reinterpret_cast<uintptr_t>(r_map) + getpagesize() - 1).c_str(),
+      getpagesize());
   ASSERT_MATCH(result, match_str);
 
   // Verify write-only.
   match_str = android::base::StringPrintf(
-      "    %s-%s -w-         0      1000\\n",
+      "    %s-%s -w-         0      %x\\n",
       format_map_pointer(reinterpret_cast<uintptr_t>(w_map)).c_str(),
-      format_map_pointer(reinterpret_cast<uintptr_t>(w_map) + getpagesize() - 1).c_str());
+      format_map_pointer(reinterpret_cast<uintptr_t>(w_map) + getpagesize() - 1).c_str(),
+      getpagesize());
   ASSERT_MATCH(result, match_str);
 
   // Verify exec-only.
   match_str = android::base::StringPrintf(
-      "    %s-%s --x         0      1000\\n",
+      "    %s-%s --x         0      %x\\n",
       format_map_pointer(reinterpret_cast<uintptr_t>(x_map)).c_str(),
-      format_map_pointer(reinterpret_cast<uintptr_t>(x_map) + getpagesize() - 1).c_str());
+      format_map_pointer(reinterpret_cast<uintptr_t>(x_map) + getpagesize() - 1).c_str(),
+      getpagesize());
   ASSERT_MATCH(result, match_str);
 
   // Verify file map with non-zero offset and a name.
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index ea07ce2..e26746b 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -108,7 +108,7 @@
                            "persist.device_config.memory_safety_native.permissive.process.%s",
                            getprogname());
   // DO NOT REPLACE this with GetBoolProperty. That uses std::string which allocates, so it is
-  // not async-safe (and this functiong gets used in a signal handler).
+  // not async-safe, and this function gets used in a signal handler.
   return property_parse_bool("persist.sys.mte.permissive") ||
          property_parse_bool("persist.device_config.memory_safety_native.permissive.default") ||
          property_parse_bool(process_sysprop_name) ||
@@ -275,10 +275,6 @@
   }
 }
 
-static pid_t __fork() {
-  return clone(nullptr, nullptr, 0, nullptr);
-}
-
 // Double-clone, with CLONE_FILES to share the file descriptor table for kcmp validation.
 // Returns 0 in the orphaned child, the pid of the orphan in the original process, or -1 on failure.
 static void create_vm_process() {
@@ -396,7 +392,8 @@
     ASSERT_SAME_OFFSET(scudo_ring_buffer, scudo_ring_buffer);
     ASSERT_SAME_OFFSET(scudo_ring_buffer_size, scudo_ring_buffer_size);
     ASSERT_SAME_OFFSET(scudo_stack_depot_size, scudo_stack_depot_size);
-    ASSERT_SAME_OFFSET(recoverable_gwp_asan_crash, recoverable_gwp_asan_crash);
+    ASSERT_SAME_OFFSET(recoverable_crash, recoverable_crash);
+    ASSERT_SAME_OFFSET(crash_detail_page, crash_detail_page);
 #undef ASSERT_SAME_OFFSET
 
     iovs[3] = {.iov_base = &thread_info->process_info,
@@ -425,7 +422,7 @@
   }
 
   // Don't use fork(2) to avoid calling pthread_atfork handlers.
-  pid_t crash_dump_pid = __fork();
+  pid_t crash_dump_pid = _Fork();
   if (crash_dump_pid == -1) {
     async_safe_format_log(ANDROID_LOG_FATAL, "libc",
                           "failed to fork in debuggerd signal handler: %s", strerror(errno));
@@ -572,6 +569,7 @@
   }
 
   gwp_asan_callbacks_t gwp_asan_callbacks = {};
+  bool recoverable_gwp_asan_crash = false;
   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
@@ -586,10 +584,30 @@
         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;
+      recoverable_gwp_asan_crash = true;
+      process_info.recoverable_crash = true;
     }
   }
 
+  if (info->si_signo == SIGSEGV &&
+      (info->si_code == SEGV_MTESERR || info->si_code == SEGV_MTEAERR) && is_permissive_mte()) {
+    process_info.recoverable_crash = true;
+    // If we are in permissive MTE mode, we do not crash, but instead disable MTE on this thread,
+    // and then let the failing instruction be retried. The second time should work (except
+    // if there is another non-MTE fault).
+    int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+    if (tagged_addr_ctrl < 0) {
+      fatal_errno("failed to PR_GET_TAGGED_ADDR_CTRL");
+    }
+    tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_NONE;
+    if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) < 0) {
+      fatal_errno("failed to PR_SET_TAGGED_ADDR_CTRL");
+    }
+    async_safe_format_log(ANDROID_LOG_ERROR, "libc",
+                          "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING.");
+    pthread_mutex_unlock(&crash_mutex);
+  }
+
   // If sival_int is ~0, it means that the fallback handler has been called
   // once before and this function is being called again to dump the stack
   // of a specific thread. It is possible that the prctl call might return 1,
@@ -601,7 +619,7 @@
     // you can only set NO_NEW_PRIVS to 1, and the effect should be at worst a single missing
     // ANR trace.
     debuggerd_fallback_handler(info, ucontext, process_info.abort_msg);
-    if (no_new_privs && process_info.recoverable_gwp_asan_crash) {
+    if (no_new_privs && recoverable_gwp_asan_crash) {
       gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
       return;
     }
@@ -678,29 +696,14 @@
     // If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
     // starting to dump right before our death.
     pthread_mutex_unlock(&crash_mutex);
-  } else if (process_info.recoverable_gwp_asan_crash) {
-    gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
+  } else if (process_info.recoverable_crash) {
+    if (recoverable_gwp_asan_crash) {
+      gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
+    }
     pthread_mutex_unlock(&crash_mutex);
   }
 #ifdef __aarch64__
-  else if (info->si_signo == SIGSEGV &&
-           (info->si_code == SEGV_MTESERR || info->si_code == SEGV_MTEAERR) &&
-           is_permissive_mte()) {
-    // If we are in permissive MTE mode, we do not crash, but instead disable MTE on this thread,
-    // and then let the failing instruction be retried. The second time should work (except
-    // if there is another non-MTE fault).
-    int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
-    if (tagged_addr_ctrl < 0) {
-      fatal_errno("failed to PR_GET_TAGGED_ADDR_CTRL");
-    }
-    tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_NONE;
-    if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) < 0) {
-      fatal_errno("failed to PR_SET_TAGGED_ADDR_CTRL");
-    }
-    async_safe_format_log(ANDROID_LOG_ERROR, "libc",
-                          "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING.");
-    pthread_mutex_unlock(&crash_mutex);
-  } else if (info->si_signo == SIGSEGV && info->si_code == SEGV_MTEAERR && getppid() == 1) {
+  else if (info->si_signo == SIGSEGV && info->si_code == SEGV_MTEAERR && getppid() == 1) {
     // Back channel to init (see system/core/init/service.cpp) to signal that
     // this process crashed due to an ASYNC MTE fault and should be considered
     // for upgrade to SYNC mode. We are re-using the ART profiler signal, which
@@ -760,18 +763,7 @@
   debuggerd_register_handlers(&action);
 }
 
-// When debuggerd's signal handler is the first handler called, it's great at
-// handling the recoverable GWP-ASan mode. For apps, sigchain (from libart) is
-// always the first signal handler, and so the following function is what
-// sigchain must call before processing the signal. This allows for processing
-// of a potentially recoverable GWP-ASan crash. If the signal requires GWP-ASan
-// recovery, then dump a report (via the regular debuggerd hanndler), and patch
-// up the allocator, and allow the process to continue (indicated by returning
-// 'true'). If the crash has nothing to do with GWP-ASan, or recovery isn't
-// possible, return 'false'.
-bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context) {
-  if (signal_number != SIGSEGV || !signal_has_si_addr(info)) return false;
-
+bool debuggerd_handle_gwp_asan_signal(int signal_number, siginfo_t* info, void* context) {
   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 ||
@@ -809,3 +801,33 @@
   pthread_mutex_unlock(&first_crash_mutex);
   return true;
 }
+
+// When debuggerd's signal handler is the first handler called, it's great at
+// handling the recoverable GWP-ASan and permissive MTE modes. For apps,
+// sigchain (from libart) is always the first signal handler, and so the
+// following function is what sigchain must call before processing the signal.
+// This allows for processing of a potentially recoverable GWP-ASan or MTE
+// crash. If the signal requires recovery, then dump a report (via the regular
+// debuggerd hanndler), and patch up the allocator (in the case of GWP-ASan) or
+// disable MTE on the thread, and allow the process to continue (indicated by
+// returning 'true'). If the crash has nothing to do with GWP-ASan/MTE, or
+// recovery isn't possible, return 'false'.
+bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context) {
+  if (signal_number != SIGSEGV) return false;
+  if (info->si_code == SEGV_MTEAERR || info->si_code == SEGV_MTESERR) {
+    if (!is_permissive_mte()) return false;
+    // Because permissive MTE disables MTE for the entire thread, we're less
+    // worried about getting a whole bunch of crashes in a row. ActivityManager
+    // doesn't like multiple native crashes for an app in a short period of time
+    // (see the comment about recoverable GWP-ASan in
+    // `debuggerd_handle_gwp_asan_signal`), but that shouldn't happen if MTE is
+    // disabled for the entire thread. This might need to be changed if there's
+    // some low-hanging bug that happens across multiple threads in quick
+    // succession.
+    debuggerd_signal_handler(signal_number, info, context);
+    return true;
+  }
+
+  if (!signal_has_si_addr(info)) return false;
+  return debuggerd_handle_gwp_asan_signal(signal_number, info, context);
+}
diff --git a/debuggerd/include/debuggerd/client.h b/debuggerd/include/debuggerd/client.h
index b7284b0..e7401cc 100644
--- a/debuggerd/include/debuggerd/client.h
+++ b/debuggerd/include/debuggerd/client.h
@@ -26,7 +26,7 @@
 
 // Trigger a dump of specified process to output_fd.
 // output_fd is consumed, timeout of 0 will wait forever.
-bool debuggerd_trigger_dump(pid_t pid, enum DebuggerdDumpType dump_type, unsigned int timeout_ms,
+bool debuggerd_trigger_dump(pid_t tid, enum DebuggerdDumpType dump_type, unsigned int timeout_ms,
                             android::base::unique_fd output_fd);
 
 int dump_backtrace_to_file(pid_t tid, enum DebuggerdDumpType dump_type, int output_fd);
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index de12fc6..954f049 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -33,6 +33,8 @@
 struct AllocationMetadata;
 };  // namespace gwp_asan
 
+struct crash_detail_page_t;
+
 // When updating this data structure, CrashInfoDataDynamic and the code in
 // ReadCrashInfo() must also be updated.
 struct __attribute__((packed)) debugger_process_info {
@@ -45,7 +47,8 @@
   const char* scudo_ring_buffer;
   size_t scudo_ring_buffer_size;
   size_t scudo_stack_depot_size;
-  bool recoverable_gwp_asan_crash;
+  bool recoverable_crash;
+  struct crash_detail_page_t* crash_detail_page;
 };
 
 // GWP-ASan calbacks to support the recoverable mode. Separate from the
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
index a506859..89bf5a9 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#if defined(USE_SCUDO)
+
 #include "types.h"
 #include "utility.h"
 
@@ -49,3 +51,5 @@
   void FillInCause(Cause* cause, const scudo_error_report* report,
                    unwindstack::AndroidUnwinder* unwinder) const;
 };
+
+#endif  // USE_SCUDO
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
index be999e0..dfdfabd 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
@@ -28,6 +28,7 @@
 #include <android-base/unique_fd.h>
 
 #include "open_files_list.h"
+#include "tombstone.pb.h"
 #include "types.h"
 
 // Forward declarations
@@ -54,14 +55,17 @@
                        unwindstack::AndroidUnwinder* unwinder,
                        const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread,
                        const ProcessInfo& process_info, OpenFilesList* open_files,
-                       std::string* amfd_data);
+                       std::string* amfd_data, const Architecture* guest_arch = nullptr,
+                       unwindstack::AndroidUnwinder* guest_unwinder = nullptr);
 
 void engrave_tombstone_ucontext(int tombstone_fd, int proto_fd, uint64_t abort_msg_address,
                                 siginfo_t* siginfo, ucontext_t* ucontext);
 
 void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder,
                              const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
-                             const ProcessInfo& process_info, const OpenFilesList* open_files);
+                             const ProcessInfo& process_info, const OpenFilesList* open_files,
+                             const Architecture* guest_arch,
+                             unwindstack::AndroidUnwinder* guest_unwinder);
 
 bool tombstone_proto_to_text(
     const Tombstone& tombstone,
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index 075b12c..c799f24 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -39,6 +39,8 @@
 
   int signo = 0;
   siginfo_t* siginfo = nullptr;
+
+  std::unique_ptr<unwindstack::Regs> guest_registers;
 };
 
 // This struct is written into a pipe from inside the crashing process.
@@ -56,4 +58,5 @@
   bool has_fault_address = false;
   uintptr_t untagged_fault_address = 0;
   uintptr_t maybe_tagged_fault_address = 0;
+  uintptr_t crash_detail_page = 0;
 };
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index 3fa3bd0..4ee87c8 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#if defined(USE_SCUDO)
+
 #include "libdebuggerd/scudo.h"
 #include "libdebuggerd/tombstone.h"
 
@@ -141,3 +143,5 @@
     FillInCause(tombstone->add_causes(), &error_info_.reports[report_num++], unwinder);
   }
 }
+
+#endif  // USE_SCUDO
diff --git a/debuggerd/libdebuggerd/test/dump_memory_test.cpp b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
index 5be145a..dee7b48 100644
--- a/debuggerd/libdebuggerd/test/dump_memory_test.cpp
+++ b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
@@ -20,6 +20,8 @@
 #include <string>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 #include <gtest/gtest.h>
 #include <unwindstack/Memory.h>
 
@@ -27,61 +29,64 @@
 
 #include "log_fake.h"
 
-const char g_expected_full_dump[] =
-"\nmemory near r1:\n"
-#if defined(__LP64__)
-"    0000000012345650 0706050403020100 0f0e0d0c0b0a0908  ................\n"
-"    0000000012345660 1716151413121110 1f1e1d1c1b1a1918  ................\n"
-"    0000000012345670 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
-"    0000000012345680 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
-"    0000000012345690 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    00000000123456a0 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
-"    00000000123456b0 6766656463626160 6f6e6d6c6b6a6968  `abcdefghijklmno\n"
-"    00000000123456c0 7776757473727170 7f7e7d7c7b7a7978  pqrstuvwxyz{|}~.\n"
-"    00000000123456d0 8786858483828180 8f8e8d8c8b8a8988  ................\n"
-"    00000000123456e0 9796959493929190 9f9e9d9c9b9a9998  ................\n"
-"    00000000123456f0 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
-"    0000000012345700 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
-"    0000000012345710 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    0000000012345720 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    0000000012345730 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
-"    0000000012345740 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
-#else
-"    12345650 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
-"    12345660 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
-"    12345670 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
-"    12345680 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
-"    12345690 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
-"    123456a0 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
-"    123456b0 63626160 67666564 6b6a6968 6f6e6d6c  `abcdefghijklmno\n"
-"    123456c0 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.\n"
-"    123456d0 83828180 87868584 8b8a8988 8f8e8d8c  ................\n"
-"    123456e0 93929190 97969594 9b9a9998 9f9e9d9c  ................\n"
-"    123456f0 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................\n"
-"    12345700 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................\n"
-"    12345710 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    12345720 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
-"    12345730 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................\n"
-"    12345740 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................\n";
-#endif
+std::string GetMemoryString(uintptr_t addr, const std::vector<uint64_t>& data) {
+  // Must be even number of data values.
+  CHECK((data.size() & 1) == 0);
 
-const char g_expected_partial_dump[] = \
-"\nmemory near pc:\n"
+  std::string str;
+  for (size_t i = 0; i < data.size(); i += 2) {
+    str += "    ";
+    std::string ascii_str = "";
+    for (size_t j = 0; j < 2; j++) {
+      for (size_t k = 0; k < 8; k++) {
+        uint8_t c = (data[i + j] >> (k * 8)) & 0xff;
+        if (c >= 0x20 && c < 0x7f) {
+          ascii_str += c;
+        } else {
+          ascii_str += '.';
+        }
+      }
+    }
 #if defined(__LP64__)
-"    00000000123455e0 0706050403020100 0f0e0d0c0b0a0908  ................\n"
-"    00000000123455f0 1716151413121110 1f1e1d1c1b1a1918  ................\n"
-"    0000000012345600 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
-"    0000000012345610 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
-"    0000000012345620 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    0000000012345630 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n";
+    str += android::base::StringPrintf("%016zx %016zx %016zx  ", addr, data[i], data[i + 1]);
 #else
-"    123455e0 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
-"    123455f0 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
-"    12345600 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
-"    12345610 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
-"    12345620 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
-"    12345630 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n";
+    str += android::base::StringPrintf(
+        "%08zx %08zx %08zx %08zx %08zx  ", addr, static_cast<uintptr_t>(data[i] & 0xffffffff),
+        static_cast<uintptr_t>(data[i] >> 32), static_cast<uintptr_t>(data[i + 1] & 0xffffffff),
+        static_cast<uintptr_t>(data[i + 1] >> 32));
 #endif
+    str += ascii_str + "\n";
+    addr += 0x10;
+  }
+  return str;
+}
+
+const std::vector<uint64_t>& GetDefaultData() {
+  static std::vector<uint64_t> data(
+      {0x0706050403020100UL, 0x0f0e0d0c0b0a0908UL, 0x1716151413121110UL, 0x1f1e1d1c1b1a1918UL,
+       0x2726252423222120UL, 0x2f2e2d2c2b2a2928UL, 0x3736353433323130UL, 0x3f3e3d3c3b3a3938UL,
+       0x4746454443424140UL, 0x4f4e4d4c4b4a4948UL, 0x5756555453525150UL, 0x5f5e5d5c5b5a5958UL,
+       0x6766656463626160UL, 0x6f6e6d6c6b6a6968UL, 0x7776757473727170UL, 0x7f7e7d7c7b7a7978UL,
+       0x8786858483828180UL, 0x8f8e8d8c8b8a8988UL, 0x9796959493929190UL, 0x9f9e9d9c9b9a9998UL,
+       0xa7a6a5a4a3a2a1a0UL, 0xafaeadacabaaa9a8UL, 0xb7b6b5b4b3b2b1b0UL, 0xbfbebdbcbbbab9b8UL,
+       0xc7c6c5c4c3c2c1c0UL, 0xcfcecdcccbcac9c8UL, 0xd7d6d5d4d3d2d1d0UL, 0xdfdedddcdbdad9d8UL,
+       0xe7e6e5e4e3e2e1e0UL, 0xefeeedecebeae9e8UL, 0xf7f6f5f4f3f2f1f0UL, 0xfffefdfcfbfaf9f8UL});
+  return data;
+}
+
+std::string GetFullDumpString() {
+  std::string str = "\nmemory near r1:\n";
+  str += GetMemoryString(0x12345650U, GetDefaultData());
+  return str;
+}
+
+std::string GetPartialDumpString() {
+  std::string str = "\nmemory near pc:\n";
+  std::vector<uint64_t> data = GetDefaultData();
+  data.resize(12);
+  str += GetMemoryString(0x123455e0U, data);
+  return str;
+}
 
 class MemoryMock : public unwindstack::Memory {
  public:
@@ -189,7 +194,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ(g_expected_full_dump, tombstone_contents.c_str());
+  ASSERT_EQ(GetFullDumpString(), tombstone_contents);
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -209,7 +214,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ(g_expected_full_dump, tombstone_contents.c_str());
+  ASSERT_EQ(GetFullDumpString(), tombstone_contents);
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -228,7 +233,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ(g_expected_full_dump, tombstone_contents.c_str());
+  ASSERT_EQ(GetFullDumpString(), tombstone_contents);
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -260,7 +265,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ(g_expected_partial_dump, tombstone_contents.c_str());
+  ASSERT_EQ(GetPartialDumpString(), tombstone_contents);
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -280,7 +285,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ(g_expected_partial_dump, tombstone_contents.c_str());
+  ASSERT_EQ(GetPartialDumpString(), tombstone_contents);
 
 #if defined(__LP64__)
   ASSERT_STREQ("6 DEBUG Bytes read 102, is not a multiple of 8\n", getFakeLogPrint().c_str());
@@ -305,7 +310,7 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ(g_expected_partial_dump, tombstone_contents.c_str());
+  ASSERT_EQ(GetPartialDumpString(), tombstone_contents);
 
 #if defined(__LP64__)
   ASSERT_STREQ("6 DEBUG Bytes read 45, is not a multiple of 8\n"
@@ -331,44 +336,9 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory near r1:\n"
-#if defined(__LP64__)
-"    0000000000001000 0000000000000000 0000000000000000  ................\n"
-"    0000000000001010 0000000000000000 0000000000000000  ................\n"
-"    0000000000001020 0000000000000000 0000000000000000  ................\n"
-"    0000000000001030 0000000000000000 0000000000000000  ................\n"
-"    0000000000001040 0000000000000000 0000000000000000  ................\n"
-"    0000000000001050 0000000000000000 0000000000000000  ................\n"
-"    0000000000001060 0000000000000000 0000000000000000  ................\n"
-"    0000000000001070 0000000000000000 0000000000000000  ................\n"
-"    0000000000001080 0000000000000000 0000000000000000  ................\n"
-"    0000000000001090 0000000000000000 0000000000000000  ................\n"
-"    00000000000010a0 0000000000000000 0000000000000000  ................\n"
-"    00000000000010b0 0000000000000000 0000000000000000  ................\n"
-"    00000000000010c0 0000000000000000 0000000000000000  ................\n"
-"    00000000000010d0 0000000000000000 0000000000000000  ................\n"
-"    00000000000010e0 0000000000000000 0000000000000000  ................\n"
-"    00000000000010f0 0000000000000000 0000000000000000  ................\n";
-#else
-"    00001000 00000000 00000000 00000000 00000000  ................\n"
-"    00001010 00000000 00000000 00000000 00000000  ................\n"
-"    00001020 00000000 00000000 00000000 00000000  ................\n"
-"    00001030 00000000 00000000 00000000 00000000  ................\n"
-"    00001040 00000000 00000000 00000000 00000000  ................\n"
-"    00001050 00000000 00000000 00000000 00000000  ................\n"
-"    00001060 00000000 00000000 00000000 00000000  ................\n"
-"    00001070 00000000 00000000 00000000 00000000  ................\n"
-"    00001080 00000000 00000000 00000000 00000000  ................\n"
-"    00001090 00000000 00000000 00000000 00000000  ................\n"
-"    000010a0 00000000 00000000 00000000 00000000  ................\n"
-"    000010b0 00000000 00000000 00000000 00000000  ................\n"
-"    000010c0 00000000 00000000 00000000 00000000  ................\n"
-"    000010d0 00000000 00000000 00000000 00000000  ................\n"
-"    000010e0 00000000 00000000 00000000 00000000  ................\n"
-"    000010f0 00000000 00000000 00000000 00000000  ................\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+  std::string expected_dump = "\nmemory near r1:\n";
+  expected_dump += GetMemoryString(0x1000, std::vector<uint64_t>(32, 0UL));
+  ASSERT_EQ(expected_dump, tombstone_contents);
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -414,61 +384,17 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory near r4:\n"
+  std::string expected_dump = "\nmemory near r4:\n";
+  uintptr_t addr;
 #if defined(__aarch64__)
-"    00ffffffffffff00 0706050403020100 0f0e0d0c0b0a0908  ................\n"
-"    00ffffffffffff10 1716151413121110 1f1e1d1c1b1a1918  ................\n"
-"    00ffffffffffff20 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
-"    00ffffffffffff30 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
-"    00ffffffffffff40 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    00ffffffffffff50 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
-"    00ffffffffffff60 6766656463626160 6f6e6d6c6b6a6968  `abcdefghijklmno\n"
-"    00ffffffffffff70 7776757473727170 7f7e7d7c7b7a7978  pqrstuvwxyz{|}~.\n"
-"    00ffffffffffff80 8786858483828180 8f8e8d8c8b8a8988  ................\n"
-"    00ffffffffffff90 9796959493929190 9f9e9d9c9b9a9998  ................\n"
-"    00ffffffffffffa0 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
-"    00ffffffffffffb0 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
-"    00ffffffffffffc0 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    00ffffffffffffd0 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    00ffffffffffffe0 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
-"    00fffffffffffff0 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
+  addr = 0x00ffffffffffff00UL;
 #elif defined(__LP64__)
-"    ffffffffffffff00 0706050403020100 0f0e0d0c0b0a0908  ................\n"
-"    ffffffffffffff10 1716151413121110 1f1e1d1c1b1a1918  ................\n"
-"    ffffffffffffff20 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
-"    ffffffffffffff30 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
-"    ffffffffffffff40 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    ffffffffffffff50 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
-"    ffffffffffffff60 6766656463626160 6f6e6d6c6b6a6968  `abcdefghijklmno\n"
-"    ffffffffffffff70 7776757473727170 7f7e7d7c7b7a7978  pqrstuvwxyz{|}~.\n"
-"    ffffffffffffff80 8786858483828180 8f8e8d8c8b8a8988  ................\n"
-"    ffffffffffffff90 9796959493929190 9f9e9d9c9b9a9998  ................\n"
-"    ffffffffffffffa0 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................\n"
-"    ffffffffffffffb0 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................\n"
-"    ffffffffffffffc0 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    ffffffffffffffd0 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    ffffffffffffffe0 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................\n"
-"    fffffffffffffff0 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................\n";
+  addr = 0xffffffffffffff00UL;
 #else
-"    ffffff00 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
-"    ffffff10 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
-"    ffffff20 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
-"    ffffff30 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
-"    ffffff40 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
-"    ffffff50 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
-"    ffffff60 63626160 67666564 6b6a6968 6f6e6d6c  `abcdefghijklmno\n"
-"    ffffff70 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.\n"
-"    ffffff80 83828180 87868584 8b8a8988 8f8e8d8c  ................\n"
-"    ffffff90 93929190 97969594 9b9a9998 9f9e9d9c  ................\n"
-"    ffffffa0 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................\n"
-"    ffffffb0 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................\n"
-"    ffffffc0 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    ffffffd0 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
-"    ffffffe0 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................\n"
-"    fffffff0 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................\n";
+  addr = 0xffffff00UL;
 #endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+  expected_dump += GetMemoryString(addr, GetDefaultData());
+  ASSERT_EQ(expected_dump, tombstone_contents);
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -490,30 +416,15 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory near r4:\n"
-#if defined(__LP64__)
-R"(    0000000010001000 8786858483828180 8f8e8d8c8b8a8988  ................
-    0000000010001010 9796959493929190 9f9e9d9c9b9a9998  ................
-    0000000010001020 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................
-    0000000010001030 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................
-    0000000010001040 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................
-    0000000010001050 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................
-    0000000010001060 e7e6e5e4e3e2e1e0 efeeedecebeae9e8  ................
-    0000000010001070 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................
-)";
-#else
-R"(    10001000 83828180 87868584 8b8a8988 8f8e8d8c  ................
-    10001010 93929190 97969594 9b9a9998 9f9e9d9c  ................
-    10001020 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................
-    10001030 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................
-    10001040 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................
-    10001050 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................
-    10001060 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec  ................
-    10001070 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc  ................
-)";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+  std::string expected_dump = "\nmemory near r4:\n";
+  expected_dump += GetMemoryString(
+      0x10000000 + page_size,
+      std::vector<uint64_t>{
+          0x8786858483828180UL, 0x8f8e8d8c8b8a8988UL, 0x9796959493929190UL, 0x9f9e9d9c9b9a9998UL,
+          0xa7a6a5a4a3a2a1a0UL, 0xafaeadacabaaa9a8UL, 0xb7b6b5b4b3b2b1b0UL, 0xbfbebdbcbbbab9b8UL,
+          0xc7c6c5c4c3c2c1c0UL, 0xcfcecdcccbcac9c8UL, 0xd7d6d5d4d3d2d1d0UL, 0xdfdedddcdbdad9d8UL,
+          0xe7e6e5e4e3e2e1e0UL, 0xefeeedecebeae9e8UL, 0xf7f6f5f4f3f2f1f0UL, 0xfffefdfcfbfaf9f8UL});
+  ASSERT_EQ(expected_dump, tombstone_contents);
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -535,16 +446,11 @@
   std::string tombstone_contents;
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory near r4:\n"
-#if defined(__LP64__)
-"    0000000010001000 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n";
-#else
-"    10001000 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+  std::string expected_dump = "\nmemory near r4:\n";
+  expected_dump += GetMemoryString(
+      0x10000000 + page_size, std::vector<uint64_t>{0xc7c6c5c4c3c2c1c0UL, 0xcfcecdcccbcac9c8UL,
+                                                    0xd7d6d5d4d3d2d1d0UL, 0xdfdedddcdbdad9d8UL});
+  ASSERT_EQ(expected_dump, tombstone_contents);
 
   // Verify that the log buf is empty, and no error messages.
   ASSERT_STREQ("", getFakeLogBuf().c_str());
diff --git a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
index ac92ac0..a4c08a4 100644
--- a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
@@ -118,3 +118,19 @@
                "LOG pac_enabled_keys: 0000000000001009 \\(PR_PAC_APIAKEY, PR_PAC_APDBKEY, unknown "
                "0x1000\\)\\n");
 }
+
+TEST_F(TombstoneProtoToTextTest, crash_detail_string) {
+  auto* crash_detail = tombstone_->add_crash_details();
+  crash_detail->set_name("CRASH_DETAIL_NAME");
+  crash_detail->set_data("crash_detail_value");
+  ProtoToString();
+  EXPECT_MATCH(text_, "(CRASH_DETAIL_NAME: 'crash_detail_value')");
+}
+
+TEST_F(TombstoneProtoToTextTest, crash_detail_bytes) {
+  auto* crash_detail = tombstone_->add_crash_details();
+  crash_detail->set_name("CRASH_DETAIL_NAME");
+  crash_detail->set_data("helloworld\1\255\3");
+  ProtoToString();
+  EXPECT_MATCH(text_, R"(CRASH_DETAIL_NAME: 'helloworld\\1\\255\\3')");
+}
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 375ed8a..0ce5573 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -76,7 +76,7 @@
   threads[target_tid] = ThreadInfo {
     .registers = std::move(regs), .uid = uid, .tid = target_tid,
     .thread_name = std::move(thread_name), .pid = pid, .command_line = std::move(command_line),
-    .selinux_label = std::move(selinux_label), .siginfo = siginfo,
+    .selinux_label = std::move(selinux_label), .siginfo = siginfo, .signo = siginfo->si_signo,
     // Only supported on aarch64 for now.
 #if defined(__aarch64__)
     .tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0),
@@ -125,10 +125,12 @@
                        unwindstack::AndroidUnwinder* unwinder,
                        const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
                        const ProcessInfo& process_info, OpenFilesList* open_files,
-                       std::string* amfd_data) {
+                       std::string* amfd_data, const Architecture* guest_arch,
+                       unwindstack::AndroidUnwinder* guest_unwinder) {
   // Don't copy log messages to tombstone unless this is a development device.
   Tombstone tombstone;
-  engrave_tombstone_proto(&tombstone, unwinder, threads, target_thread, process_info, open_files);
+  engrave_tombstone_proto(&tombstone, unwinder, threads, target_thread, process_info, open_files,
+                          guest_arch, guest_unwinder);
 
   if (proto_fd != -1) {
     if (!tombstone.SerializeToFileDescriptor(proto_fd.get())) {
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 744bfab..3e8ab6e 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -48,8 +48,10 @@
 #include <android-base/unique_fd.h>
 
 #include <android/log.h>
+#include <android/set_abort_message.h>
 #include <bionic/macros.h>
 #include <bionic/reserved_signals.h>
+#include <bionic/crash_detail_internal.h>
 #include <log/log.h>
 #include <log/log_read.h>
 #include <log/logprint.h>
@@ -94,6 +96,11 @@
 
 static std::optional<std::string> get_stack_overflow_cause(uint64_t fault_addr, uint64_t sp,
                                                            unwindstack::Maps* maps) {
+  // Under stack MTE the stack pointer and/or the fault address can be tagged.
+  // In order to calculate deltas between them, strip off the tags off both
+  // addresses.
+  fault_addr = untag_address(fault_addr);
+  sp = untag_address(sp);
   static constexpr uint64_t kMaxDifferenceBytes = 256;
   uint64_t difference;
   if (sp >= fault_addr) {
@@ -196,7 +203,7 @@
 }
 
 static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder,
-                                const ProcessInfo& process_info, const ThreadInfo& main_thread) {
+                                const ProcessInfo& process_info, const ThreadInfo& target_thread) {
 #if defined(USE_SCUDO)
   ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
   if (scudo_crash_data.CrashIsMine()) {
@@ -206,13 +213,13 @@
 #endif
 
   GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
-                                       main_thread);
+                                       target_thread);
   if (gwp_asan_crash_data.CrashIsMine()) {
     gwp_asan_crash_data.AddCauseProtos(tombstone, unwinder);
     return;
   }
 
-  const siginfo *si = main_thread.siginfo;
+  const siginfo *si = target_thread.siginfo;
   auto fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
   unwindstack::Maps* maps = unwinder->GetMaps();
 
@@ -231,14 +238,14 @@
     } else if (fault_addr == 0xffff0f60) {
       cause = "call to kuser_cmpxchg64";
     } else {
-      cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
+      cause = get_stack_overflow_cause(fault_addr, target_thread.registers->sp(), maps);
     }
   } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
     auto map_info = maps->Find(fault_addr);
     if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
       cause = "execute-only (no-read) memory access error; likely due to data in .text.";
     } else {
-      cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
+      cause = get_stack_overflow_cause(fault_addr, target_thread.registers->sp(), maps);
     }
   } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
     cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
@@ -251,6 +258,46 @@
   }
 }
 
+static void dump_crash_details(Tombstone* tombstone,
+                               std::shared_ptr<unwindstack::Memory>& process_memory,
+                               const ProcessInfo& process_info) {
+  uintptr_t address = process_info.crash_detail_page;
+  while (address) {
+    struct crash_detail_page_t page;
+    if (!process_memory->ReadFully(address, &page, sizeof(page))) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read crash detail page: %m");
+      break;
+    }
+    if (page.used > kNumCrashDetails) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "crash detail: page corrupted");
+      break;
+    }
+    for (size_t i = 0; i < page.used; ++i) {
+      const crash_detail_t& crash_detail = page.crash_details[i];
+      if (!crash_detail.data) {
+        continue;
+      }
+      std::string name(crash_detail.name_size, '\0');
+      if (!process_memory->ReadFully(reinterpret_cast<uintptr_t>(crash_detail.name), name.data(),
+                                     crash_detail.name_size)) {
+        async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "crash detail: failed to read name: %m");
+        continue;
+      }
+      std::string data(crash_detail.data_size, '\0');
+      if (!process_memory->ReadFully(reinterpret_cast<uintptr_t>(crash_detail.data), data.data(),
+                                     crash_detail.data_size)) {
+        async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                              "crash detail: failed to read data for %s: %m", name.c_str());
+        continue;
+      }
+      auto* proto_detail = tombstone->add_crash_details();
+      proto_detail->set_name(name);
+      proto_detail->set_data(data);
+    }
+    address = reinterpret_cast<uintptr_t>(page.prev);
+  }
+}
+
 static void dump_abort_message(Tombstone* tombstone,
                                std::shared_ptr<unwindstack::Memory>& process_memory,
                                const ProcessInfo& process_info) {
@@ -435,7 +482,8 @@
 }
 
 static void dump_thread(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder,
-                        const ThreadInfo& thread_info, bool memory_dump = false) {
+                        const ThreadInfo& thread_info, bool memory_dump = false,
+                        unwindstack::AndroidUnwinder* guest_unwinder = nullptr) {
   Thread thread;
 
   thread.set_id(thread_info.tid);
@@ -462,6 +510,27 @@
 
   auto& threads = *tombstone->mutable_threads();
   threads[thread_info.tid] = thread;
+
+  if (guest_unwinder) {
+    if (!thread_info.guest_registers) {
+      async_safe_format_log(ANDROID_LOG_INFO, LOG_TAG,
+                            "No guest state registers information for tid %d", thread_info.tid);
+      return;
+    }
+    Thread guest_thread;
+    unwindstack::AndroidUnwinderData guest_data;
+    guest_data.saved_initial_regs = std::make_optional<std::unique_ptr<unwindstack::Regs>>();
+    if (guest_unwinder->Unwind(thread_info.guest_registers.get(), guest_data)) {
+      dump_thread_backtrace(guest_data.frames, guest_thread);
+    } else {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                            "Unwind guest state registers failed for tid %d: Error %s",
+                            thread_info.tid, guest_data.GetErrorString().c_str());
+    }
+    dump_registers(guest_unwinder, *guest_data.saved_initial_regs, guest_thread, memory_dump);
+    auto& guest_threads = *tombstone->mutable_guest_threads();
+    guest_threads[thread_info.tid] = guest_thread;
+  }
 }
 
 static void dump_mappings(Tombstone* tombstone, unwindstack::Maps* maps,
@@ -638,28 +707,35 @@
 }
 
 void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder,
-                             const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
-                             const ProcessInfo& process_info, const OpenFilesList* open_files) {
+                             const std::map<pid_t, ThreadInfo>& threads, pid_t target_tid,
+                             const ProcessInfo& process_info, const OpenFilesList* open_files,
+                             const Architecture* guest_arch,
+                             unwindstack::AndroidUnwinder* guest_unwinder) {
   Tombstone result;
 
   result.set_arch(get_arch());
+  if (guest_arch != nullptr) {
+    result.set_guest_arch(*guest_arch);
+  } else {
+    result.set_guest_arch(Architecture::NONE);
+  }
   result.set_build_fingerprint(android::base::GetProperty("ro.build.fingerprint", "unknown"));
   result.set_revision(android::base::GetProperty("ro.revision", "unknown"));
   result.set_timestamp(get_timestamp());
 
-  const ThreadInfo& main_thread = threads.at(target_thread);
-  result.set_pid(main_thread.pid);
-  result.set_tid(main_thread.tid);
-  result.set_uid(main_thread.uid);
-  result.set_selinux_label(main_thread.selinux_label);
+  const ThreadInfo& target_thread = threads.at(target_tid);
+  result.set_pid(target_thread.pid);
+  result.set_tid(target_thread.tid);
+  result.set_uid(target_thread.uid);
+  result.set_selinux_label(target_thread.selinux_label);
   // The main thread must have a valid siginfo.
-  CHECK(main_thread.siginfo != nullptr);
+  CHECK(target_thread.siginfo != nullptr);
 
   struct sysinfo si;
   sysinfo(&si);
   android::procinfo::ProcessInfo proc_info;
   std::string error;
-  if (android::procinfo::GetProcessInfo(main_thread.pid, &proc_info, &error)) {
+  if (android::procinfo::GetProcessInfo(target_thread.pid, &proc_info, &error)) {
     uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK);
     result.set_process_uptime(si.uptime - starttime);
   } else {
@@ -667,25 +743,28 @@
                           error.c_str());
   }
 
+  result.set_page_size(getpagesize());
+  result.set_has_been_16kb_mode(android::base::GetBoolProperty("ro.misctrl.16kb_before", false));
+
   auto cmd_line = result.mutable_command_line();
-  for (const auto& arg : main_thread.command_line) {
+  for (const auto& arg : target_thread.command_line) {
     *cmd_line->Add() = arg;
   }
 
-  if (!main_thread.siginfo) {
+  if (!target_thread.siginfo) {
     async_safe_fatal("siginfo missing");
   }
 
   Signal sig;
-  sig.set_number(main_thread.signo);
-  sig.set_name(get_signame(main_thread.siginfo));
-  sig.set_code(main_thread.siginfo->si_code);
-  sig.set_code_name(get_sigcode(main_thread.siginfo));
+  sig.set_number(target_thread.signo);
+  sig.set_name(get_signame(target_thread.siginfo));
+  sig.set_code(target_thread.siginfo->si_code);
+  sig.set_code_name(get_sigcode(target_thread.siginfo));
 
-  if (signal_has_sender(main_thread.siginfo, main_thread.pid)) {
+  if (signal_has_sender(target_thread.siginfo, target_thread.pid)) {
     sig.set_has_sender(true);
-    sig.set_sender_uid(main_thread.siginfo->si_uid);
-    sig.set_sender_pid(main_thread.siginfo->si_pid);
+    sig.set_sender_uid(target_thread.siginfo->si_uid);
+    sig.set_sender_pid(target_thread.siginfo->si_pid);
   }
 
   if (process_info.has_fault_address) {
@@ -698,29 +777,29 @@
   *result.mutable_signal_info() = sig;
 
   dump_abort_message(&result, unwinder->GetProcessMemory(), process_info);
-
-  // Dump the main thread, but save the memory around the registers.
-  dump_thread(&result, unwinder, main_thread, /* memory_dump */ true);
+  dump_crash_details(&result, unwinder->GetProcessMemory(), process_info);
+  // Dump the target thread, but save the memory around the registers.
+  dump_thread(&result, unwinder, target_thread, /* memory_dump */ true, guest_unwinder);
 
   for (const auto& [tid, thread_info] : threads) {
-    if (tid != target_thread) {
-      dump_thread(&result, unwinder, thread_info);
+    if (tid != target_tid) {
+      dump_thread(&result, unwinder, thread_info, /* memory_dump */ false, guest_unwinder);
     }
   }
 
-  dump_probable_cause(&result, unwinder, process_info, main_thread);
+  dump_probable_cause(&result, unwinder, process_info, target_thread);
 
   dump_mappings(&result, unwinder->GetMaps(), unwinder->GetProcessMemory());
 
   // Only dump logs on debuggable devices.
   if (android::base::GetBoolProperty("ro.debuggable", false)) {
     // Get the thread that corresponds to the main pid of the process.
-    const ThreadInfo& thread = threads.at(main_thread.pid);
+    const ThreadInfo& thread = threads.at(target_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_logcat(&result, target_thread.pid);
     }
   }
 
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index ad91320..1900719 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -18,7 +18,9 @@
 
 #include <inttypes.h>
 
+#include <charconv>
 #include <functional>
+#include <limits>
 #include <set>
 #include <string>
 #include <unordered_set>
@@ -77,8 +79,8 @@
   return describe_end(value, desc);
 }
 
-static const char* abi_string(const Tombstone& tombstone) {
-  switch (tombstone.arch()) {
+static const char* abi_string(const Architecture& arch) {
+  switch (arch) {
     case Architecture::ARM32:
       return "arm";
     case Architecture::ARM64:
@@ -425,6 +427,27 @@
   }
 }
 
+static std::string oct_encode(const std::string& data) {
+  std::string oct_encoded;
+  oct_encoded.reserve(data.size());
+
+  // N.B. the unsigned here is very important, otherwise e.g. \255 would render as
+  // \-123 (and overflow our buffer).
+  for (unsigned char c : data) {
+    if (isprint(c)) {
+      oct_encoded += c;
+    } else {
+      std::string oct_digits("\\\0\0\0", 4);
+      // char is encodable in 3 oct digits
+      static_assert(std::numeric_limits<unsigned char>::max() <= 8 * 8 * 8);
+      auto [ptr, ec] = std::to_chars(oct_digits.data() + 1, oct_digits.data() + 4, c, 8);
+      oct_digits.resize(ptr - oct_digits.data());
+      oct_encoded += oct_digits;
+    }
+  }
+  return oct_encoded;
+}
+
 static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
                               const Thread& thread) {
   print_thread_header(callback, tombstone, thread, true);
@@ -468,6 +491,12 @@
     CBL("Abort message: '%s'", tombstone.abort_message().c_str());
   }
 
+  for (const auto& crash_detail : tombstone.crash_details()) {
+    std::string oct_encoded_name = oct_encode(crash_detail.name());
+    std::string oct_encoded_data = oct_encode(crash_detail.data());
+    CBL("Extra crash detail: %s: '%s'", oct_encoded_name.c_str(), oct_encoded_data.c_str());
+  }
+
   print_thread_registers(callback, tombstone, thread, true);
   if (is_async_mte_crash) {
     CBL("Note: This crash is a delayed async MTE crash. Memory corruption has occurred");
@@ -549,14 +578,38 @@
   }
 }
 
+static void print_guest_thread(CallbackType callback, const Tombstone& tombstone,
+                               const Thread& guest_thread, pid_t tid, bool should_log) {
+  CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---");
+  CBS("Guest thread information for tid: %d", tid);
+  print_thread_registers(callback, tombstone, guest_thread, should_log);
+
+  CBS("");
+  CB(true, "%d total frames", guest_thread.current_backtrace().size());
+  CB(true, "backtrace:");
+  print_backtrace(callback, tombstone, guest_thread.current_backtrace(), should_log);
+
+  print_thread_memory_dump(callback, tombstone, guest_thread);
+}
+
 bool tombstone_proto_to_text(const Tombstone& tombstone, CallbackType callback) {
   CBL("*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***");
   CBL("Build fingerprint: '%s'", tombstone.build_fingerprint().c_str());
   CBL("Revision: '%s'", tombstone.revision().c_str());
-  CBL("ABI: '%s'", abi_string(tombstone));
+  CBL("ABI: '%s'", abi_string(tombstone.arch()));
+  if (tombstone.guest_arch() != Architecture::NONE) {
+    CBL("Guest architecture: '%s'", abi_string(tombstone.guest_arch()));
+  }
   CBL("Timestamp: %s", tombstone.timestamp().c_str());
   CBL("Process uptime: %ds", tombstone.process_uptime());
 
+  // only print this info if the page size is not 4k or has been in 16k mode
+  if (tombstone.page_size() != 4096) {
+    CBL("Page size: %d bytes", tombstone.page_size());
+  } else if (tombstone.has_been_16kb_mode()) {
+    CBL("Has been in 16kb mode: yes");
+  }
+
   // Process header
   const auto& threads = tombstone.threads();
   auto main_thread_it = threads.find(tombstone.tid());
@@ -571,6 +624,12 @@
 
   print_logs(callback, tombstone, 50);
 
+  const auto& guest_threads = tombstone.guest_threads();
+  auto main_guest_thread_it = guest_threads.find(tombstone.tid());
+  if (main_guest_thread_it != threads.end()) {
+    print_guest_thread(callback, tombstone, main_guest_thread_it->second, tombstone.tid(), true);
+  }
+
   // protobuf's map is unordered, so sort the keys first.
   std::set<int> thread_ids;
   for (const auto& [tid, _] : threads) {
@@ -582,6 +641,10 @@
   for (const auto& tid : thread_ids) {
     CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---");
     print_thread(callback, tombstone, threads.find(tid)->second);
+    auto guest_thread_it = guest_threads.find(tid);
+    if (guest_thread_it != guest_threads.end()) {
+      print_guest_thread(callback, tombstone, guest_thread_it->second, tid, false);
+    }
   }
 
   if (tombstone.open_fds().size() > 0) {
diff --git a/debuggerd/proto/Android.bp b/debuggerd/proto/Android.bp
index 7be5d61..7b9e780 100644
--- a/debuggerd/proto/Android.bp
+++ b/debuggerd/proto/Android.bp
@@ -52,4 +52,3 @@
     sdk_version: "current",
     static_libs: ["libprotobuf-java-lite"],
 }
-
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index 49865a2..b662d36 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -15,8 +15,16 @@
 // NOTE TO OEMS:
 // If you add custom fields to this proto, do not use numbers in the reserved range.
 
+message CrashDetail {
+  bytes name = 1;
+  bytes data = 2;
+
+  reserved 3 to 999;
+}
+
 message Tombstone {
   Architecture arch = 1;
+  Architecture guest_arch = 24;
   string build_fingerprint = 2;
   string revision = 3;
   string timestamp = 4;
@@ -33,14 +41,19 @@
 
   Signal signal_info = 10;
   string abort_message = 14;
+  repeated CrashDetail crash_details = 21;
   repeated Cause causes = 15;
 
   map<uint32, Thread> threads = 16;
+  map<uint32, Thread> guest_threads = 25;
   repeated MemoryMapping memory_mappings = 17;
   repeated LogBuffer log_buffers = 18;
   repeated FD open_fds = 19;
 
-  reserved 21 to 999;
+  uint32 page_size = 22;
+  bool has_been_16kb_mode = 23;
+
+  reserved 26 to 999;
 }
 
 enum Architecture {
@@ -49,8 +62,9 @@
   X86 = 2;
   X86_64 = 3;
   RISCV64 = 4;
+  NONE = 5;
 
-  reserved 5 to 999;
+  reserved 6 to 999;
 }
 
 message Signal {
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index 793428a..9af7377 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -100,7 +100,8 @@
   uintptr_t scudo_ring_buffer;
   size_t scudo_ring_buffer_size;
   size_t scudo_stack_depot_size;
-  bool recoverable_gwp_asan_crash;
+  bool recoverable_crash;
+  uintptr_t crash_detail_page;
 };
 
 struct __attribute__((__packed__)) CrashInfo {
diff --git a/debuggerd/rust/tombstoned_client/Android.bp b/debuggerd/rust/tombstoned_client/Android.bp
index 2007f39..bf19bb7 100644
--- a/debuggerd/rust/tombstoned_client/Android.bp
+++ b/debuggerd/rust/tombstoned_client/Android.bp
@@ -8,7 +8,7 @@
         "wrapper.cpp",
     ],
     generated_sources: [
-        "libtombstoned_client_rust_bridge_code"
+        "libtombstoned_client_rust_bridge_code",
     ],
     header_libs: [
         "libbase_headers",
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index adf8738..c5d10d6 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -28,11 +28,11 @@
 rt_tgsigqueueinfo: 1
 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS || arg0 == 56 || arg0 == 61
 madvise: 1
-mprotect: arg2 in 0x1|0x2
+mprotect: arg2 in 0x1|0x2|0x20
 munmap: 1
 getuid: 1
 fstat: 1
-mmap: arg2 in 0x1|0x2
+mmap: arg2 in 0x1|0x2|0x20
 geteuid: 1
 getgid: 1
 getegid: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index 972a575..dc751da 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -25,8 +25,8 @@
 faccessat: 1
 recvmsg: 1
 recvfrom: 1
-sysinfo: 1
 setsockopt: 1
+sysinfo: 1
 
 process_vm_readv: 1
 
@@ -53,20 +53,29 @@
 
 #if 0
 libminijail on vendor partitions older than P does not have constants from <sys/mman.h>.
-Define the values of PROT_READ and PROT_WRITE ourselves to maintain backwards compatibility.
+Define values for PROT_READ, PROT_WRITE and PROT_MTE ourselves to maintain backwards compatibility.
 #else
 #define PROT_READ 0x1
 #define PROT_WRITE 0x2
+#define PROT_MTE 0x20
 #endif
 
 madvise: 1
+#if defined(__aarch64__)
+mprotect: arg2 in PROT_READ|PROT_WRITE|PROT_MTE
+#else
 mprotect: arg2 in PROT_READ|PROT_WRITE
+#endif
 munmap: 1
 
 #if defined(__LP64__)
 getuid: 1
 fstat: 1
+#if defined(__aarch64__)
+mmap: arg2 in PROT_READ|PROT_WRITE|PROT_MTE
+#else
 mmap: arg2 in PROT_READ|PROT_WRITE
+#endif
 #else
 getuid32: 1
 fstat64: 1
diff --git a/debuggerd/test_permissive_mte/Android.bp b/debuggerd/test_permissive_mte/Android.bp
index d3f7520..0ad3243 100644
--- a/debuggerd/test_permissive_mte/Android.bp
+++ b/debuggerd/test_permissive_mte/Android.bp
@@ -17,22 +17,28 @@
 }
 
 cc_binary {
-  name: "mte_crash",
-  tidy: false,
-  srcs: ["mte_crash.cpp"],
-  sanitize: {
-    memtag_heap: true,
-    diag: {
-      memtag_heap: true,
+    name: "mte_crash",
+    tidy: false,
+    srcs: ["mte_crash.cpp"],
+    sanitize: {
+        memtag_heap: true,
+        diag: {
+            memtag_heap: true,
+        },
     },
-  },
 }
 
 java_test_host {
     name: "permissive_mte_test",
     libs: ["tradefed"],
-    static_libs: ["frameworks-base-hostutils", "cts-install-lib-host"],
-    srcs:  ["src/**/PermissiveMteTest.java", ":libtombstone_proto-src"],
+    static_libs: [
+        "frameworks-base-hostutils",
+        "cts-install-lib-host",
+    ],
+    srcs: [
+        "src/**/PermissiveMteTest.java",
+        ":libtombstone_proto-src",
+    ],
     data: [":mte_crash"],
     test_config: "AndroidTest.xml",
     test_suites: ["general-tests"],
diff --git a/debuggerd/test_permissive_mte/AndroidTest.xml b/debuggerd/test_permissive_mte/AndroidTest.xml
index bd3d018..db5f5b8 100644
--- a/debuggerd/test_permissive_mte/AndroidTest.xml
+++ b/debuggerd/test_permissive_mte/AndroidTest.xml
@@ -17,8 +17,6 @@
     <option name="test-suite-tag" value="init_test_upgrade_mte" />
     <option name="test-suite-tag" value="apct" />
 
-    <!-- For tombstone inspection. -->
-    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
       <option name="cleanup" value="true" />
       <option name="push" value="mte_crash->/data/local/tmp/mte_crash" />
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index cf7904f..fa67d46 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -61,7 +61,6 @@
 
 struct CrashArtifact {
   unique_fd fd;
-  std::optional<std::string> temporary_path;
 
   static CrashArtifact devnull() {
     CrashArtifact result;
@@ -97,7 +96,7 @@
 class CrashQueue {
  public:
   CrashQueue(const std::string& dir_path, const std::string& file_name_prefix, size_t max_artifacts,
-             size_t max_concurrent_dumps, bool supports_proto)
+             size_t max_concurrent_dumps, bool supports_proto, bool world_readable)
       : file_name_prefix_(file_name_prefix),
         dir_path_(dir_path),
         dir_fd_(open(dir_path.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)),
@@ -105,7 +104,8 @@
         next_artifact_(0),
         max_concurrent_dumps_(max_concurrent_dumps),
         num_concurrent_dumps_(0),
-        supports_proto_(supports_proto) {
+        supports_proto_(supports_proto),
+        world_readable_(world_readable) {
     if (dir_fd_ == -1) {
       PLOG(FATAL) << "failed to open directory: " << dir_path;
     }
@@ -128,14 +128,16 @@
   static CrashQueue* for_tombstones() {
     static CrashQueue queue("/data/tombstones", "tombstone_" /* file_name_prefix */,
                             GetIntProperty("tombstoned.max_tombstone_count", 32),
-                            1 /* max_concurrent_dumps */, true /* supports_proto */);
+                            1 /* max_concurrent_dumps */, true /* supports_proto */,
+                            true /* world_readable */);
     return &queue;
   }
 
   static CrashQueue* for_anrs() {
     static CrashQueue queue("/data/anr", "trace_" /* file_name_prefix */,
                             GetIntProperty("tombstoned.max_anr_count", 64),
-                            4 /* max_concurrent_dumps */, false /* supports_proto */);
+                            4 /* max_concurrent_dumps */, false /* supports_proto */,
+                            false /* world_readable */);
     return &queue;
   }
 
@@ -145,16 +147,15 @@
     std::optional<std::string> path;
     result.fd.reset(openat(dir_fd_, ".", O_WRONLY | O_APPEND | O_TMPFILE | O_CLOEXEC, 0660));
     if (result.fd == -1) {
-      // We might not have O_TMPFILE. Try creating with an arbitrary filename instead.
-      static size_t counter = 0;
-      std::string tmp_filename = StringPrintf(".temporary%zu", counter++);
-      result.fd.reset(openat(dir_fd_, tmp_filename.c_str(),
-                             O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_CLOEXEC, 0660));
-      if (result.fd == -1) {
-        PLOG(FATAL) << "failed to create temporary tombstone in " << dir_path_;
-      }
+      PLOG(FATAL) << "failed to create temporary tombstone in " << dir_path_;
+    }
 
-      result.temporary_path = std::move(tmp_filename);
+    if (world_readable_) {
+      // We need to fchmodat after creating to avoid getting the umask applied.
+      std::string fd_path = StringPrintf("/proc/self/fd/%d", result.fd.get());
+      if (fchmodat(dir_fd_, fd_path.c_str(), 0664, 0) != 0) {
+        PLOG(ERROR) << "Failed to make tombstone world-readable";
+      }
     }
 
     return std::move(result);
@@ -266,6 +267,7 @@
   size_t num_concurrent_dumps_;
 
   bool supports_proto_;
+  bool world_readable_;
 
   std::deque<std::unique_ptr<Crash>> queued_requests_;
 
@@ -417,6 +419,7 @@
     return false;
   }
 
+  // This fd is created inside of dirfd in CrashQueue::create_temporary_file.
   std::string fd_path = StringPrintf("/proc/self/fd/%d", fd.get());
   rc = linkat(AT_FDCWD, fd_path.c_str(), dirfd.get(), path.c_str(), AT_SYMLINK_FOLLOW);
   if (rc != 0) {
@@ -453,17 +456,6 @@
 
   CrashArtifactPaths paths = queue->get_next_artifact_paths();
 
-  if (rename_tombstone_fd(crash->output.text.fd, queue->dir_fd(), paths.text)) {
-    if (crash->crash_type == kDebuggerdJavaBacktrace) {
-      LOG(ERROR) << "Traces for pid " << crash->crash_pid << " written to: " << paths.text;
-    } else {
-      // NOTE: Several tools parse this log message to figure out where the
-      // tombstone associated with a given native crash was written. Any changes
-      // to this message must be carefully considered.
-      LOG(ERROR) << "Tombstone written to: " << paths.text;
-    }
-  }
-
   if (crash->output.proto && crash->output.proto->fd != -1) {
     if (!paths.proto) {
       LOG(ERROR) << "missing path for proto tombstone";
@@ -472,17 +464,14 @@
     }
   }
 
-  // If we don't have O_TMPFILE, we need to clean up after ourselves.
-  if (crash->output.text.temporary_path) {
-    rc = unlinkat(queue->dir_fd().get(), crash->output.text.temporary_path->c_str(), 0);
-    if (rc != 0) {
-      PLOG(ERROR) << "failed to unlink temporary tombstone at " << paths.text;
-    }
-  }
-  if (crash->output.proto && crash->output.proto->temporary_path) {
-    rc = unlinkat(queue->dir_fd().get(), crash->output.proto->temporary_path->c_str(), 0);
-    if (rc != 0) {
-      PLOG(ERROR) << "failed to unlink temporary proto tombstone";
+  if (rename_tombstone_fd(crash->output.text.fd, queue->dir_fd(), paths.text)) {
+    if (crash->crash_type == kDebuggerdJavaBacktrace) {
+      LOG(ERROR) << "Traces for pid " << crash->crash_pid << " written to: " << paths.text;
+    } else {
+      // NOTE: Several tools parse this log message to figure out where the
+      // tombstone associated with a given native crash was written. Any changes
+      // to this message must be carefully considered.
+      LOG(ERROR) << "Tombstone written to: " << paths.text;
     }
   }
 }
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index c0445f3..774af28 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -190,7 +190,6 @@
 
     static_libs: [
         "android.hardware.health-translate-ndk",
-        "libc++fs",
         "libhealthhalutils",
         "libhealthshim",
         "libfastbootshim",
@@ -416,7 +415,6 @@
     srcs: ["vendor_boot_img_utils_test.cpp"],
     static_libs: [
         "libbase",
-        "libc++fs",
         "libfastboot",
         "libgmock",
         "liblog",
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 12a1ddc..6b9e493 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -1547,9 +1547,14 @@
 
 void reboot_to_userspace_fastboot() {
     fb->RebootTo("fastboot");
+    if (fb->WaitForDisconnect() != fastboot::SUCCESS) {
+        die("Error waiting for USB disconnect.");
+    }
     fb->set_transport(nullptr);
 
-    // Give the current connection time to close.
+    // Not all platforms support WaitForDisconnect. There also isn't a great way to tell whether
+    // or not WaitForDisconnect is supported. So, just wait a bit extra for everyone, in order to
+    // make sure that the device has had time to initiate its reboot and disconnect itself.
     std::this_thread::sleep_for(std::chrono::seconds(1));
 
     fb->set_transport(open_device());
diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp
index a898070..59533fa 100644
--- a/fastboot/fuzzer/Android.bp
+++ b/fastboot/fuzzer/Android.bp
@@ -55,7 +55,10 @@
     ],
     fuzz_config: {
         cc: [
-            "android-media-fuzzing-reports@google.com",
+            "dvander@google.com",
+            "elsk@google.com",
+            "enh@google.com",
+            "zhangkelvin@google.com",
         ],
         componentid: 533764,
         hotlists: [
diff --git a/fastboot/usb_linux.cpp b/fastboot/usb_linux.cpp
index 37bb304..03af8f7 100644
--- a/fastboot/usb_linux.cpp
+++ b/fastboot/usb_linux.cpp
@@ -83,7 +83,18 @@
 // be reliable.
 // 256KiB seems to work, but 1MiB bulk transfers lock up my z620 with a 3.13
 // kernel.
-#define MAX_USBFS_BULK_SIZE (16 * 1024)
+// 128KiB was experimentally found to be enough to saturate the bus at
+// SuperSpeed+, so we first try double that for writes. If the operation fails
+// due to a lack of contiguous regions (or an ancient kernel), try smaller sizes
+// until we find one that works (see LinuxUsbTransport::Write). Reads are less
+// performance critical so for now just use a known good size.
+#define MAX_USBFS_BULK_WRITE_SIZE (256 * 1024)
+#define MAX_USBFS_BULK_READ_SIZE (16 * 1024)
+
+// This size should pretty much always work (it's compatible with pre-3.3
+// kernels and it's what we used to use historically), so if it doesn't work
+// something has gone badly wrong.
+#define MIN_USBFS_BULK_WRITE_SIZE (16 * 1024)
 
 struct usb_handle
 {
@@ -108,6 +119,7 @@
   private:
     std::unique_ptr<usb_handle> handle_;
     const uint32_t ms_timeout_;
+    size_t max_usbfs_bulk_write_size_ = MAX_USBFS_BULK_WRITE_SIZE;
 
     DISALLOW_COPY_AND_ASSIGN(LinuxUsbTransport);
 };
@@ -269,6 +281,9 @@
         auto path = android::base::StringPrintf("/sys/bus/usb/devices/%s/%s:1.%d/interface",
                                                 sysfs_name, sysfs_name, ifc->bInterfaceNumber);
         if (android::base::ReadFileToString(path, &interface)) {
+            if (!interface.empty() && interface.back() == '\n') {
+                interface.pop_back();
+            }
             snprintf(info.interface, sizeof(info.interface), "%s", interface.c_str());
         }
 
@@ -404,34 +419,90 @@
 {
     unsigned char *data = (unsigned char*) _data;
     unsigned count = 0;
-    struct usbdevfs_bulktransfer bulk;
-    int n;
+    struct usbdevfs_urb urb[2] = {};
+    bool pending[2] = {};
 
     if (handle_->ep_out == 0 || handle_->desc == -1) {
         return -1;
     }
 
-    do {
-        int xfer;
-        xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len;
+    auto submit_urb = [&](size_t i) {
+        while (true) {
+            int xfer = (len > max_usbfs_bulk_write_size_) ? max_usbfs_bulk_write_size_ : len;
 
-        bulk.ep = handle_->ep_out;
-        bulk.len = xfer;
-        bulk.data = data;
-        bulk.timeout = ms_timeout_;
+            urb[i].type = USBDEVFS_URB_TYPE_BULK;
+            urb[i].endpoint = handle_->ep_out;
+            urb[i].buffer_length = xfer;
+            urb[i].buffer = data;
+            urb[i].usercontext = (void *)i;
 
-        n = ioctl(handle_->desc, USBDEVFS_BULK, &bulk);
-        if(n != xfer) {
-            DBG("ERROR: n = %d, errno = %d (%s)\n",
-                n, errno, strerror(errno));
+            int n = ioctl(handle_->desc, USBDEVFS_SUBMITURB, &urb[i]);
+            if (n != 0) {
+                if (errno == ENOMEM && max_usbfs_bulk_write_size_ > MIN_USBFS_BULK_WRITE_SIZE) {
+                    max_usbfs_bulk_write_size_ /= 2;
+                    continue;
+                }
+                DBG("ioctl(USBDEVFS_SUBMITURB) failed\n");
+                return false;
+            }
+
+            pending[i] = true;
+            count += xfer;
+            len -= xfer;
+            data += xfer;
+
+            return true;
+        }
+    };
+
+    auto reap_urb = [&](size_t i) {
+        while (pending[i]) {
+            struct usbdevfs_urb *urbp;
+            int res = ioctl(handle_->desc, USBDEVFS_REAPURB, &urbp);
+            if (res != 0) {
+                DBG("ioctl(USBDEVFS_REAPURB) failed\n");
+                return false;
+            }
+            size_t done = (size_t)urbp->usercontext;
+            if (done >= 2 || !pending[done]) {
+                DBG("unexpected urb\n");
+                return false;
+            }
+            if (urbp->status != 0 || urbp->actual_length != urbp->buffer_length) {
+                DBG("urb returned error\n");
+                return false;
+            }
+            pending[done] = false;
+        }
+        return true;
+    };
+
+    if (!submit_urb(0)) {
+        return -1;
+    }
+    while (len > 0) {
+        if (!submit_urb(1)) {
             return -1;
         }
-
-        count += xfer;
-        len -= xfer;
-        data += xfer;
-    } while(len > 0);
-
+        if (!reap_urb(0)) {
+            return -1;
+        }
+        if (len <= 0) {
+            if (!reap_urb(1)) {
+                return -1;
+            }
+            return count;
+        }
+        if (!submit_urb(0)) {
+            return -1;
+        }
+        if (!reap_urb(1)) {
+            return -1;
+        }
+    }
+    if (!reap_urb(0)) {
+        return -1;
+    }
     return count;
 }
 
@@ -447,7 +518,7 @@
     }
 
     while (len > 0) {
-        int xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len;
+        int xfer = (len > MAX_USBFS_BULK_READ_SIZE) ? MAX_USBFS_BULK_READ_SIZE : len;
 
         bulk.ep = handle_->ep_in;
         bulk.len = xfer;
diff --git a/fastboot/util.cpp b/fastboot/util.cpp
index e03012a..5966aea 100644
--- a/fastboot/util.cpp
+++ b/fastboot/util.cpp
@@ -31,7 +31,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
-#include <sys/time.h>
+#include <time.h>
 
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
@@ -43,9 +43,9 @@
 static bool g_verbose = false;
 
 double now() {
-    struct timeval tv;
-    gettimeofday(&tv, NULL);
-    return (double)tv.tv_sec + (double)tv.tv_usec / 1000000;
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    return (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000;
 }
 
 void die(const char* fmt, ...) {
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index 1989a5c..32e8b88 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -25,8 +25,6 @@
     {
       "name": "vab_legacy_tests"
     },
-    // TODO(b/279009697):
-    //{"name": "vabc_legacy_tests"},
     {
       "name": "cow_api_test"
     },
@@ -44,8 +42,6 @@
     {
       "name": "vab_legacy_tests"
     },
-    // TODO(b/279009697):
-    //{"name": "vabc_legacy_tests"}
     {
       "name": "snapuserd_test"
     }
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 8c0c1ef..7f41cea 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -40,6 +40,7 @@
 #include <functional>
 #include <map>
 #include <memory>
+#include <numeric>
 #include <string>
 #include <string_view>
 #include <thread>
@@ -1553,7 +1554,9 @@
                     fs_mgr_set_blk_ro(attempted_entry.blk_device, false);
                     if (!call_vdc({"cryptfs", "encryptFstab", attempted_entry.blk_device,
                                    attempted_entry.mount_point, wiped ? "true" : "false",
-                                   attempted_entry.fs_type, attempted_entry.zoned_device},
+                                   attempted_entry.fs_type,
+                                   attempted_entry.fs_mgr_flags.is_zoned ? "true" : "false",
+                                   android::base::Join(attempted_entry.user_devices, ' ')},
                                   nullptr)) {
                         LERROR << "Encryption failed";
                         set_type_property(encryptable);
@@ -1596,7 +1599,9 @@
 
                 if (!call_vdc({"cryptfs", "encryptFstab", current_entry.blk_device,
                                current_entry.mount_point, "true" /* shouldFormat */,
-                               current_entry.fs_type, current_entry.zoned_device},
+                               current_entry.fs_type,
+                               current_entry.fs_mgr_flags.is_zoned ? "true" : "false",
+                               android::base::Join(current_entry.user_devices, ' ')},
                               nullptr)) {
                     LERROR << "Encryption failed";
                 } else {
@@ -1621,7 +1626,9 @@
         if (mount_errno != EBUSY && mount_errno != EACCES &&
             should_use_metadata_encryption(attempted_entry)) {
             if (!call_vdc({"cryptfs", "mountFstab", attempted_entry.blk_device,
-                           attempted_entry.mount_point, attempted_entry.zoned_device},
+                           attempted_entry.mount_point,
+                           current_entry.fs_mgr_flags.is_zoned ? "true" : "false",
+                           android::base::Join(current_entry.user_devices, ' ')},
                           nullptr)) {
                 ++error_count;
             } else if (current_entry.mount_point == "/data") {
@@ -1649,6 +1656,19 @@
             continue;
         }
     }
+    if (userdata_mounted) {
+        Fstab mounted_fstab;
+        if (!ReadFstabFromFile("/proc/mounts", &mounted_fstab)) {
+            LOG(ERROR) << "Could't load fstab from /proc/mounts , unable to set ro.fstype.data . "
+                          "init.rc actions depending on this prop would not run, boot might fail.";
+        } else {
+            for (const auto& entry : mounted_fstab) {
+                if (entry.mount_point == "/data") {
+                    android::base::SetProperty("ro.fstype.data", entry.fs_type);
+                }
+            }
+        }
+    }
 
     set_type_property(encryptable);
 
diff --git a/fs_mgr/fs_mgr_format.cpp b/fs_mgr/fs_mgr_format.cpp
index 8e76150..0dde1d3 100644
--- a/fs_mgr/fs_mgr_format.cpp
+++ b/fs_mgr/fs_mgr_format.cpp
@@ -125,7 +125,8 @@
 }
 
 static int format_f2fs(const std::string& fs_blkdev, uint64_t dev_sz, bool needs_projid,
-                       bool needs_casefold, bool fs_compress, const std::string& zoned_device) {
+                       bool needs_casefold, bool fs_compress, bool is_zoned,
+                       const std::vector<std::string>& user_devices) {
     if (!dev_sz) {
         int rc = get_dev_sz(fs_blkdev, &dev_sz);
         if (rc) {
@@ -159,16 +160,21 @@
     args.push_back(block_size.c_str());
     args.push_back("-b");
     args.push_back(block_size.c_str());
-    if (!zoned_device.empty()) {
-        args.push_back("-c");
-        args.push_back(zoned_device.c_str());
+
+    if (is_zoned) {
         args.push_back("-m");
-        args.push_back(fs_blkdev.c_str());
-    } else {
-        args.push_back(fs_blkdev.c_str());
-        args.push_back(size_str.c_str());
+    }
+    for (auto& device : user_devices) {
+        args.push_back("-c");
+        args.push_back(device.c_str());
     }
 
+    if (user_devices.empty()) {
+        args.push_back(fs_blkdev.c_str());
+        args.push_back(size_str.c_str());
+    } else {
+        args.push_back(fs_blkdev.c_str());
+    }
     return logwrap_fork_execvp(args.size(), args.data(), nullptr, false, LOG_KLOG, false, nullptr);
 }
 
@@ -184,7 +190,8 @@
 
     if (entry.fs_type == "f2fs") {
         return format_f2fs(entry.blk_device, entry.length, needs_projid, needs_casefold,
-                           entry.fs_mgr_flags.fs_compress, entry.zoned_device);
+                           entry.fs_mgr_flags.fs_compress, entry.fs_mgr_flags.is_zoned,
+                           entry.user_devices);
     } else if (entry.fs_type == "ext4") {
         return format_ext4(entry.blk_device, entry.mount_point, needs_projid,
                            entry.fs_mgr_flags.ext_meta_csum);
diff --git a/fs_mgr/fs_mgr_overlayfs_mount.cpp b/fs_mgr/fs_mgr_overlayfs_mount.cpp
index a1ec63b..bd0fcfd 100644
--- a/fs_mgr/fs_mgr_overlayfs_mount.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_mount.cpp
@@ -412,6 +412,8 @@
     bool retval = true;
     bool move_dir_shared = true;
     bool parent_shared = true;
+    bool parent_have_parent = false;
+    bool parent_made_private = false;
     bool root_shared = true;
     bool root_made_private = false;
 
@@ -443,6 +445,10 @@
         if (entry.mount_point == "/") {
             root_shared = entry.shared_flag;
         }
+        // Ignore "/" as we don't overlay "/" directly.
+        if (entry.mount_point != "/") {
+            parent_have_parent |= android::base::StartsWith(mount_point, entry.mount_point + "/");
+        }
     }
 
     // Precondition is that kMoveMountTempDir is MS_PRIVATE, otherwise don't try to move any
@@ -453,11 +459,13 @@
 
     // Need to make the original mountpoint MS_PRIVATE, so that the overlayfs can be MS_MOVE.
     // This could happen if its parent mount is remounted later.
-    if (!fs_mgr_overlayfs_set_shared_mount(mount_point, false)) {
-        // If failed to set "/system" mount type, it might be due to "/system" not being a valid
-        // mountpoint after switch root. Retry with "/" in this case.
-        if (errno == EINVAL && mount_point == "/system") {
-            root_made_private = fs_mgr_overlayfs_set_shared_mount("/", false);
+    if (parent_have_parent) {
+        parent_made_private |= fs_mgr_overlayfs_set_shared_mount(mount_point, false);
+        if (!parent_made_private && errno == EINVAL && mount_point == "/system") {
+            // If failed to set "/system" mount type, it might be due to "/system" not being a valid
+            // mountpoint after switch root. Retry with "/" in this case.
+            parent_made_private |= fs_mgr_overlayfs_set_shared_mount("/", false);
+            root_made_private |= parent_made_private;
         }
     }
 
@@ -496,6 +504,15 @@
                 continue;
             }
         }
+        if (!parent_made_private) {
+            parent_made_private |= fs_mgr_overlayfs_set_shared_mount(mount_point, false);
+            if (!parent_made_private && errno == EINVAL && mount_point == "/system") {
+                // If failed to set "/system" mount type, it might be due to "/system" not being a
+                // valid mountpoint after switch root. Retry with "/" in this case.
+                parent_made_private |= fs_mgr_overlayfs_set_shared_mount("/", false);
+                root_made_private |= parent_made_private;
+            }
+        }
 
         if (new_entry.shared_flag) {
             new_entry.shared_flag = fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, false);
@@ -524,7 +541,7 @@
         rmdir(entry.dir.c_str());
     }
     // If the original (overridden) mount was MS_SHARED, then set the overlayfs mount to MS_SHARED.
-    if (parent_shared) {
+    if (parent_shared && parent_made_private) {
         fs_mgr_overlayfs_set_shared_mount(mount_point, true);
     }
     if (root_shared && root_made_private) {
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 733ba2f..f91d232 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -42,6 +42,7 @@
 #include <fstab/fstab.h>
 #include <libavb_user/libavb_user.h>
 #include <libgsi/libgsid.h>
+#include <private/android_filesystem_config.h>
 
 #include "fs_mgr_overlayfs_control.h"
 #include "fs_mgr_overlayfs_mount.h"
@@ -170,11 +171,12 @@
         }
         if (show_help) {
             show_help = false;
-            std::cerr << "WARNING: Userdata checkpoint is in progress. To force end checkpointing, "
-                         "call 'vdc checkpoint commitChanges'. This can lead to data corruption if "
-                         "rolled back."
+            std::cerr << "WARNING: Userdata checkpoint is in progress. "
+                         "To forcibly end checkpointing, "
+                         "call 'vdc checkpoint commitChanges'. "
+                         "This can lead to data corruption if rolled back."
                       << std::endl;
-            LOG(INFO) << "Waiting for checkpoint to complete and then continue remount.";
+            LOG(INFO) << "Waiting for checkpoint to complete before remounting...";
         }
         std::this_thread::sleep_for(4s);
     }
@@ -608,7 +610,19 @@
     }
 
     // Make sure we are root.
-    if (::getuid() != 0) {
+    if (const auto uid = ::getuid(); uid != AID_ROOT) {
+        // If requesting auto reboot, also try to auto gain root.
+        if (auto_reboot && uid == AID_SHELL && access("/system/xbin/su", F_OK) == 0) {
+            std::vector<char*> args{const_cast<char*>("/system/xbin/su"),
+                                    const_cast<char*>("root")};
+            for (int i = 0; i < argc; ++i) {
+                args.push_back(argv[i]);
+            }
+            args.push_back(nullptr);
+            LOG(INFO) << "Attempting to gain root with \"su root\"";
+            execv(args[0], args.data());
+            PLOG(ERROR) << "Failed to execute \"su root\"";
+        }
         LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
         return EXIT_FAILURE;
     }
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index 5cc0346..c3ca758 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -71,6 +71,9 @@
         "libbase",
         "liblog",
     ],
+    header_libs: [
+        "libstorage_literals_headers",
+    ],
     srcs: [":libdm_test_srcs"],
     auto_gen_config: true,
     require_root: true,
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index e261aa3..a963322 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -39,6 +39,9 @@
 #ifndef DM_DEFERRED_REMOVE
 #define DM_DEFERRED_REMOVE (1 << 17)
 #endif
+#ifndef DM_IMA_MEASUREMENT_FLAG
+#define DM_IMA_MEASUREMENT_FLAG (1 << 19)
+#endif
 
 namespace android {
 namespace dm {
@@ -540,6 +543,10 @@
     return GetTable(name, 0, table);
 }
 
+bool DeviceMapper::GetTableStatusIma(const std::string& name, std::vector<TargetInfo>* table) {
+    return GetTable(name, DM_IMA_MEASUREMENT_FLAG, table);
+}
+
 bool DeviceMapper::GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) {
     return GetTable(name, DM_STATUS_TABLE_FLAG, table);
 }
@@ -762,5 +769,25 @@
     return true;
 }
 
+bool DeviceMapper::SendMessage(const std::string& name, uint64_t sector,
+                               const std::string& message) {
+    std::string ioctl_buffer(sizeof(struct dm_ioctl) + sizeof(struct dm_target_msg), 0);
+    ioctl_buffer += message;
+    ioctl_buffer.push_back('\0');
+
+    struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(&ioctl_buffer[0]);
+    InitIo(io, name);
+    io->data_size = ioctl_buffer.size();
+    io->data_start = sizeof(struct dm_ioctl);
+    struct dm_target_msg* msg =
+            reinterpret_cast<struct dm_target_msg*>(&ioctl_buffer[sizeof(struct dm_ioctl)]);
+    msg->sector = sector;
+    if (ioctl(fd_, DM_TARGET_MSG, io)) {
+        PLOG(ERROR) << "DM_TARGET_MSG failed";
+        return false;
+    }
+    return true;
+}
+
 }  // namespace dm
 }  // namespace android
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index 90d91a0..b5cc9aa 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -61,6 +61,10 @@
     return block_device_ + " " + std::to_string(physical_sector_);
 }
 
+std::string DmTargetStripe::GetParameterString() const {
+    return "2 " + std::to_string(chunksize) + " " + block_device0_ + " 0 " + block_device1_ + " 0";
+}
+
 DmTargetVerity::DmTargetVerity(uint64_t start, uint64_t length, uint32_t version,
                                const std::string& block_device, const std::string& hash_device,
                                uint32_t data_block_size, uint32_t hash_block_size,
@@ -294,5 +298,43 @@
     return android::base::Join(argv, " ");
 }
 
+DmTargetThinPool::DmTargetThinPool(uint64_t start, uint64_t length, const std::string& metadata_dev,
+                                   const std::string& data_dev, uint64_t data_block_size,
+                                   uint64_t low_water_mark)
+    : DmTarget(start, length),
+      metadata_dev_(metadata_dev),
+      data_dev_(data_dev),
+      data_block_size_(data_block_size),
+      low_water_mark_(low_water_mark) {}
+
+std::string DmTargetThinPool::GetParameterString() const {
+    std::vector<std::string> args{
+            metadata_dev_,
+            data_dev_,
+            std::to_string(data_block_size_),
+            std::to_string(low_water_mark_),
+    };
+    return android::base::Join(args, " ");
+}
+
+bool DmTargetThinPool::Valid() const {
+    // data_block_size: must be between 128 (64KB) and 2097152 (1GB) and a multiple of 128 (64KB)
+    if (data_block_size_ < 128 || data_block_size_ > 2097152) return false;
+    if (data_block_size_ % 128) return false;
+    return true;
+}
+
+DmTargetThin::DmTargetThin(uint64_t start, uint64_t length, const std::string& pool_dev,
+                           uint64_t dev_id)
+    : DmTarget(start, length), pool_dev_(pool_dev), dev_id_(dev_id) {}
+
+std::string DmTargetThin::GetParameterString() const {
+    std::vector<std::string> args{
+            pool_dev_,
+            std::to_string(dev_id_),
+    };
+    return android::base::Join(args, " ");
+}
+
 }  // namespace dm
 }  // namespace android
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index a0129c2..b890f47 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -37,12 +37,14 @@
 #include <gtest/gtest.h>
 #include <libdm/dm.h>
 #include <libdm/loop_control.h>
+#include <storage_literals/storage_literals.h>
 #include "test_util.h"
 #include "utility.h"
 
 using namespace std;
 using namespace std::chrono_literals;
 using namespace android::dm;
+using namespace android::storage_literals;
 using android::base::make_scope_guard;
 using android::base::unique_fd;
 
@@ -181,6 +183,13 @@
     ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE);
 }
 
+TEST_F(DmTest, StripeArgs) {
+    DmTargetStripe target(0, 4096, 1024, "/dev/loop0", "/dev/loop1");
+    ASSERT_EQ(target.name(), "striped");
+    ASSERT_TRUE(target.Valid());
+    ASSERT_EQ(target.GetParameterString(), "2 1024 /dev/loop0 0 /dev/loop1 0");
+}
+
 TEST_F(DmTest, DmVerityArgsAvb2) {
     std::string device = "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a";
     std::string algorithm = "sha1";
@@ -766,3 +775,42 @@
     ASSERT_EQ(name, test_name_);
     ASSERT_FALSE(uuid.empty());
 }
+
+TEST_F(DmTest, ThinProvisioning) {
+    if (!DeviceMapper::Instance().GetTargetByName("thin-pool", nullptr)) GTEST_SKIP();
+
+    constexpr uint64_t MetaSize = 2_MiB;
+    constexpr uint64_t DataSize = 64_MiB;
+    constexpr uint64_t ThinSize = 1_TiB;
+
+    // Prepare two loop devices for meta and data devices.
+    TemporaryFile meta;
+    ASSERT_GE(meta.fd, 0);
+    ASSERT_EQ(0, ftruncate64(meta.fd, MetaSize));
+    TemporaryFile data;
+    ASSERT_GE(data.fd, 0);
+    ASSERT_EQ(0, ftruncate64(data.fd, DataSize));
+
+    LoopDevice loop_meta(meta.fd, 10s);
+    ASSERT_TRUE(loop_meta.valid());
+    LoopDevice loop_data(data.fd, 10s);
+    ASSERT_TRUE(loop_data.valid());
+
+    // Create a thin-pool
+    DmTable poolTable;
+    poolTable.Emplace<DmTargetThinPool>(0, DataSize / kSectorSize, loop_meta.device(),
+                                        loop_data.device(), 128, 0);
+    TempDevice pool("pool", poolTable);
+    ASSERT_TRUE(pool.valid());
+
+    // Create a thin volume
+    uint64_t thin_volume_id = 0;
+    ASSERT_TRUE(DeviceMapper::Instance().SendMessage(
+            "pool", 0, "create_thin " + std::to_string(thin_volume_id)));
+
+    // Use a thin volume to create a 1T device
+    DmTable thinTable;
+    thinTable.Emplace<DmTargetThin>(0, ThinSize / kSectorSize, pool.path(), thin_volume_id);
+    TempDevice thin("thin", thinTable);
+    ASSERT_TRUE(thin.valid());
+}
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 22c475f..43d84f9 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -78,6 +78,7 @@
     virtual bool LoadTable(const std::string& name, const DmTable& table) = 0;
     virtual bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) = 0;
     virtual bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) = 0;
+    virtual bool GetTableStatusIma(const std::string& name, std::vector<TargetInfo>* table) = 0;
     virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) = 0;
     virtual bool GetDeviceString(const std::string& name, std::string* dev) = 0;
     virtual bool DeleteDeviceIfExists(const std::string& name) = 0;
@@ -267,6 +268,12 @@
     // false.
     bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) override;
 
+    // Query the status of a table, given a device name. The output vector will
+    // contain IMA TargetInfo for each target in the table. If the device does
+    // not exist, or there were too many targets, the call will fail and return
+    // false.
+    bool GetTableStatusIma(const std::string& name, std::vector<TargetInfo>* table) override;
+
     // Identical to GetTableStatus, except also retrives the active table for the device
     // mapper device from the kernel.
     bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) override;
@@ -300,6 +307,9 @@
 
     bool GetDeviceNameAndUuid(dev_t dev, std::string* name, std::string* uuid);
 
+    // Send |message| to target, pointed by |name| and |sector|. Use 0 if |sector| is not needed.
+    bool SendMessage(const std::string& name, uint64_t sector, const std::string& message);
+
   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/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
index 09fe200..c49fc5e 100644
--- a/fs_mgr/libdm/include/libdm/dm_target.h
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -116,6 +116,24 @@
     uint64_t physical_sector_;
 };
 
+class DmTargetStripe final : public DmTarget {
+  public:
+    DmTargetStripe(uint64_t start, uint64_t length, uint64_t chunksize,
+                   const std::string& block_device0, const std::string& block_device1)
+        : DmTarget(start, length),
+          chunksize(chunksize),
+          block_device0_(block_device0),
+          block_device1_(block_device1) {}
+
+    std::string name() const override { return "striped"; }
+    std::string GetParameterString() const override;
+
+  private:
+    uint64_t chunksize;
+    std::string block_device0_;
+    std::string block_device1_;
+};
+
 class DmTargetVerity final : public DmTarget {
   public:
     DmTargetVerity(uint64_t start, uint64_t length, uint32_t version,
@@ -331,6 +349,35 @@
     std::string GetParameterString() const override { return ""; }
 };
 
+class DmTargetThinPool final : public DmTarget {
+  public:
+    DmTargetThinPool(uint64_t start, uint64_t length, const std::string& metadata_dev,
+                     const std::string& data_dev, uint64_t data_block_size,
+                     uint64_t low_water_mark);
+
+    std::string name() const override { return "thin-pool"; }
+    std::string GetParameterString() const override;
+    bool Valid() const override;
+
+  private:
+    std::string metadata_dev_;
+    std::string data_dev_;
+    uint64_t data_block_size_;
+    uint64_t low_water_mark_;
+};
+
+class DmTargetThin final : public DmTarget {
+  public:
+    DmTargetThin(uint64_t start, uint64_t length, const std::string& pool_dev, uint64_t dev_id);
+
+    std::string name() const override { return "thin"; }
+    std::string GetParameterString() const override;
+
+  private:
+    std::string pool_dev_;
+    uint64_t dev_id_;
+};
+
 }  // namespace dm
 }  // namespace android
 
diff --git a/fs_mgr/libfstab/fstab.cpp b/fs_mgr/libfstab/fstab.cpp
index 6fa22fe..f00e0dc 100644
--- a/fs_mgr/libfstab/fstab.cpp
+++ b/fs_mgr/libfstab/fstab.cpp
@@ -147,6 +147,29 @@
     entry->fs_options = std::move(fs_options);
 }
 
+void ParseUserDevices(const std::string& arg, FstabEntry* entry) {
+    auto param = Split(arg, ":");
+    if (param.size() != 2) {
+        LWARNING << "Warning: device= malformed: " << arg;
+        return;
+    }
+
+    if (access(param[1].c_str(), F_OK) != 0) {
+        LWARNING << "Warning: device does not exist : " << param[1];
+        return;
+    }
+
+    if (param[0] == "zoned") {
+        // atgc in f2fs does not support a zoned device
+        auto options = Split(entry->fs_options, ",");
+        options.erase(std::remove(options.begin(), options.end(), "atgc"), options.end());
+        entry->fs_options = android::base::Join(options, ",");
+        LINFO << "Removed ATGC in fs_options as " << entry->fs_options << " for zoned device";
+        entry->fs_mgr_flags.is_zoned = true;
+    }
+    entry->user_devices.push_back(param[1]);
+}
+
 bool ParseFsMgrFlags(const std::string& flags, FstabEntry* entry) {
     for (const auto& flag : Split(flags, ",")) {
         if (flag.empty() || flag == "defaults") continue;
@@ -311,17 +334,8 @@
             if (!ParseByteCount(arg, &entry->zram_backingdev_size)) {
                 LWARNING << "Warning: zram_backingdev_size= flag malformed: " << arg;
             }
-        } else if (flag == "zoned_device") {
-            if (access("/dev/block/by-name/zoned_device", F_OK) == 0) {
-                entry->zoned_device = "/dev/block/by-name/zoned_device";
-
-                // atgc in f2fs does not support a zoned device
-                auto options = Split(entry->fs_options, ",");
-                options.erase(std::remove(options.begin(), options.end(), "atgc"), options.end());
-                entry->fs_options = android::base::Join(options, ",");
-                LINFO << "Removed ATGC in fs_options as " << entry->fs_options
-                      << " for zoned device=" << entry->zoned_device;
-            }
+        } else if (StartsWith(flag, "device=")) {
+            ParseUserDevices(arg, entry);
         } else {
             LWARNING << "Warning: unknown flag: " << flag;
         }
@@ -849,6 +863,14 @@
                             [&path](const FstabEntry& entry) { return entry.mount_point == path; });
 }
 
+FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string_view path,
+                                  const std::string_view fstype) {
+    auto&& vec = GetEntriesByPred(fstab, [&path, fstype](const FstabEntry& entry) {
+        return entry.mount_point == path && entry.fs_type == fstype;
+    });
+    return vec.empty() ? nullptr : vec.front();
+}
+
 std::vector<const FstabEntry*> GetEntriesForMountPoint(const Fstab* fstab,
                                                        const std::string& path) {
     return GetEntriesByPred(fstab,
diff --git a/fs_mgr/libfstab/include/fstab/fstab.h b/fs_mgr/libfstab/include/fstab/fstab.h
index 5e4019c..1696daf 100644
--- a/fs_mgr/libfstab/include/fstab/fstab.h
+++ b/fs_mgr/libfstab/include/fstab/fstab.h
@@ -32,7 +32,7 @@
 
 struct FstabEntry {
     std::string blk_device;
-    std::string zoned_device;
+    std::vector<std::string> user_devices;
     std::string logical_partition_name;
     std::string mount_point;
     std::string fs_type;
@@ -85,6 +85,7 @@
         bool ext_meta_csum : 1;
         bool fs_compress : 1;
         bool overlayfs_remove_missing_lowerdir : 1;
+        bool is_zoned : 1;
     } fs_mgr_flags = {};
 
     bool is_encryptable() const { return fs_mgr_flags.crypt; }
@@ -108,6 +109,9 @@
 FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string& path);
 const FstabEntry* GetEntryForMountPoint(const Fstab* fstab, const std::string& path);
 
+FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string_view path,
+                                  const std::string_view fstype);
+
 // This method builds DSU fstab entries and transfer the fstab.
 //
 // fstab points to the unmodified fstab.
diff --git a/fs_mgr/liblp/fuzzer/Android.bp b/fs_mgr/liblp/fuzzer/Android.bp
index a9e3509..46bd031 100644
--- a/fs_mgr/liblp/fuzzer/Android.bp
+++ b/fs_mgr/liblp/fuzzer/Android.bp
@@ -15,6 +15,10 @@
  *
  */
 
+package {
+    default_team: "trendy_team_android_kernel",
+}
+
 cc_defaults {
     name: "liblp_fuzz_defaults",
     header_libs: [
@@ -33,7 +37,7 @@
     ],
     fuzz_config: {
         cc: [
-            "android-media-fuzzing-reports@google.com",
+            "android-systems-storage@google.com",
         ],
         componentid: 59148,
         hotlists: ["4593311"],
@@ -41,8 +45,8 @@
         vector: "local_no_privileges_required",
         service_privilege: "privileged",
         users: "multi_user",
-        fuzzed_code_usage: "shipped"
-    }
+        fuzzed_code_usage: "shipped",
+    },
 }
 
 cc_fuzz {
@@ -196,6 +200,6 @@
         ":test_vendor_boot_v4_with_frag",
     ],
     cflags: [
-      "-Wno-unused-parameter",
-   ],
+        "-Wno-unused-parameter",
+    ],
 }
diff --git a/fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp b/fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp
index 7f09ac8..162c9fc 100644
--- a/fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp
+++ b/fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp
@@ -28,7 +28,7 @@
 static constexpr uint64_t kBlockDeviceInfoSize = 1024 * 1024;
 static constexpr uint64_t kValidBlockDeviceInfoSize = 8_GiB;
 static constexpr uint64_t kValidMaxGroupSize = 40960;
-static constexpr uint64_t kMinBlockDevValue = 0;
+static constexpr uint64_t kMinBlockDevValue = 1;
 static constexpr uint64_t kMaxBlockDevValue = 100000;
 static constexpr uint64_t kMinSectorValue = 1;
 static constexpr uint64_t kMaxSectorValue = 1000000;
@@ -149,12 +149,16 @@
 
 void BuilderFuzzer::setupBuilder(string superBlockDeviceName) {
     uint64_t blockDeviceInfoSize =
-            mFdp.ConsumeBool() ? mFdp.ConsumeIntegral<uint64_t>() : kValidBlockDeviceInfoSize;
+            mFdp.ConsumeBool()
+                    ? mFdp.ConsumeIntegralInRange<uint64_t>(kMinBlockDevValue, kMaxBlockDevValue)
+                    : kValidBlockDeviceInfoSize;
     uint32_t alignment = mFdp.ConsumeBool() ? mFdp.ConsumeIntegral<uint32_t>() : kValidAlignment;
     uint32_t alignmentOffset =
             mFdp.ConsumeBool() ? mFdp.ConsumeIntegral<uint32_t>() : kValidAlignmentOffset;
-    uint32_t logicalBlockSize =
-            mFdp.ConsumeBool() ? mFdp.ConsumeIntegral<uint32_t>() : kValidLogicalBlockSize;
+    uint32_t logicalBlockSize = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange<uint32_t>(
+                                                             kMinBlockDevValue, kMaxBlockDevValue)
+                                                   : kValidLogicalBlockSize;
+
     BlockDeviceInfo super(superBlockDeviceName, blockDeviceInfoSize, alignment, alignmentOffset,
                           logicalBlockSize);
     mBlockDevices.push_back(super);
@@ -176,13 +180,16 @@
             mFdp.ConsumeBool() ? kDeviceInfoName : mFdp.ConsumeRandomLengthString(kMaxBytes);
     BlockDeviceInfo changePartitionDeviceInfo(
             changePartitionDeviceInfoName,
-            mFdp.ConsumeBool() ? mFdp.ConsumeIntegral<uint64_t>() : kBlockDeviceInfoSize /* size */,
+            mFdp.ConsumeBool()
+                    ? mFdp.ConsumeIntegralInRange<uint64_t>(kMinBlockDevValue, kMaxBlockDevValue)
+                    : kBlockDeviceInfoSize /* size */,
             mFdp.ConsumeBool() ? mFdp.ConsumeIntegral<uint32_t>()
                                : kZeroAlignmentOffset /* alignment */,
             mFdp.ConsumeBool() ? mFdp.ConsumeIntegral<uint32_t>()
                                : kZeroAlignmentOffset /* alignment_offset */,
-            mFdp.ConsumeBool() ? mFdp.ConsumeIntegral<uint32_t>()
-                               : kValidLogicalBlockSize /* logical_block_size */);
+            mFdp.ConsumeBool()
+                    ? mFdp.ConsumeIntegralInRange<uint32_t>(kMinBlockDevValue, kMaxBlockDevValue)
+                    : kValidLogicalBlockSize);
     mBlockDevices.push_back(changePartitionDeviceInfo);
 }
 
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index a8a7716..cc6db35 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -108,7 +108,7 @@
     ],
     srcs: [":libsnapshot_sources"],
     static_libs: [
-        "libfs_mgr_binder"
+        "libfs_mgr_binder",
     ],
 }
 
@@ -126,14 +126,13 @@
         "libprotobuf-cpp-lite",
     ],
     static_libs: [
-        "libc++fs",
         "libsnapshot_cow",
-    ]
+    ],
 }
 
 cc_library_static {
     name: "libsnapshot_init",
-    native_coverage : true,
+    native_coverage: true,
     defaults: ["libsnapshot_defaults"],
     srcs: [":libsnapshot_sources"],
     ramdisk_available: true,
@@ -204,6 +203,10 @@
         "libsnapshot_cow/writer_v2.cpp",
         "libsnapshot_cow/writer_v3.cpp",
     ],
+
+    header_libs: [
+        "libstorage_literals_headers",
+    ],
     export_include_dirs: ["include"],
     host_supported: true,
     recovery_available: true,
@@ -243,7 +246,10 @@
 
 cc_defaults {
     name: "libsnapshot_test_defaults",
-    defaults: ["libsnapshot_defaults", "libsnapshot_cow_defaults"],
+    defaults: [
+        "libsnapshot_defaults",
+        "libsnapshot_cow_defaults",
+    ],
     srcs: [
         "partition_cow_creator_test.cpp",
         "snapshot_metadata_updater_test.cpp",
@@ -262,7 +268,6 @@
         "android.hardware.boot@1.1",
         "android.hardware.boot-V1-ndk",
         "libbrotli",
-        "libc++fs",
         "libfs_mgr_binder",
         "libgflags",
         "libgsi",
@@ -283,10 +288,13 @@
 
 cc_test {
     name: "vts_libsnapshot_test",
-    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
+    defaults: [
+        "libsnapshot_test_defaults",
+        "libsnapshot_hal_deps",
+    ],
     test_suites: [
         "vts",
-        "device-tests"
+        "device-tests",
     ],
     test_options: {
         min_shipping_api_level: 30,
@@ -295,31 +303,25 @@
 
 cc_test {
     name: "vab_legacy_tests",
-    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
+    defaults: [
+        "libsnapshot_test_defaults",
+        "libsnapshot_hal_deps",
+    ],
     cppflags: [
         "-DLIBSNAPSHOT_TEST_VAB_LEGACY",
     ],
     test_suites: [
-        "device-tests"
+        "device-tests",
     ],
     test_options: {
         // Legacy VAB launched in Android R.
         min_shipping_api_level: 30,
-    },
-}
-
-cc_test {
-    name: "vabc_legacy_tests",
-    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
-    cppflags: [
-        "-DLIBSNAPSHOT_TEST_VABC_LEGACY",
-    ],
-    test_suites: [
-        "device-tests"
-    ],
-    test_options: {
-        // Legacy VABC launched in Android S.
-        min_shipping_api_level: 31,
+        test_runner_options: [
+            {
+                name: "force-no-test-error",
+                value: "false",
+            },
+        ],
     },
 }
 
@@ -343,13 +345,15 @@
 
 cc_binary {
     name: "snapshotctl",
-    defaults: ["libsnapshot_cow_defaults", "libsnapshot_hal_deps"],
+    defaults: [
+        "libsnapshot_cow_defaults",
+        "libsnapshot_hal_deps",
+    ],
     srcs: [
         "snapshotctl.cpp",
     ],
     static_libs: [
         "libbrotli",
-        "libc++fs",
         "libfstab",
         "libz",
         "update_metadata-protos",
@@ -412,8 +416,11 @@
         "libgtest",
         "libsnapshot_cow",
     ],
+    header_libs: [
+        "libstorage_literals_headers",
+    ],
     test_suites: [
-        "device-tests"
+        "device-tests",
     ],
     test_options: {
         min_shipping_api_level: 30,
@@ -499,6 +506,8 @@
             enabled: false,
         },
     },
+    stl: "libc++_static",
+    static_executable: true,
 }
 
 python_library_host {
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 7e97dc0..2e948dd 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -103,7 +103,7 @@
     // The old partition size (if none existed, this will be zero).
     uint64 old_partition_size = 10;
 
-    // Compression algorithm (none, gz, or brotli).
+    // Compression algorithm (none, lz4, zstd).
     string compression_algorithm = 11;
 
     // Estimated COW size from OTA manifest.
@@ -117,6 +117,20 @@
 
     // Size of v3 operation buffer. Needs to be determined during writer initialization
     uint64 estimated_ops_buffer_size = 15;
+
+    // Max bytes to be compressed at once (4k, 8k, 16k, 32k, 64k, 128k)
+    uint64 compression_factor = 16;
+
+    // Default value is 32, can be set lower for low mem devices
+    uint32 read_ahead_size = 17;
+
+    reserved 18;
+
+    // Blocks size to be verified at once
+    uint64 verify_block_size = 19;
+
+    // Default value is 2, configures threads to do verification phase
+    uint32 num_verify_threads = 20;
 }
 
 // Next: 8
@@ -209,6 +223,12 @@
 
     // io_uring support
     bool io_uring_enabled = 10;
+
+    // legacy dm-snapshot based snapuserd
+    bool legacy_snapuserd = 11;
+
+    // Enable direct reads from source device
+    bool o_direct = 12;
 }
 
 // Next: 10
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h
index 8191d61..635a38c 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h
@@ -17,6 +17,8 @@
 #pragma once
 
 #include <memory>
+#include <vector>
+
 #include "libsnapshot/cow_format.h"
 
 namespace android {
@@ -24,28 +26,30 @@
 
 class ICompressor {
   public:
-    explicit ICompressor(uint32_t compression_level, uint32_t block_size)
+    explicit ICompressor(const int32_t compression_level, const uint32_t block_size)
         : compression_level_(compression_level), block_size_(block_size) {}
 
     virtual ~ICompressor() {}
     // Factory methods for compression methods.
-    static std::unique_ptr<ICompressor> Gz(uint32_t compression_level, const int32_t block_size);
-    static std::unique_ptr<ICompressor> Brotli(uint32_t compression_level,
-                                               const int32_t block_size);
-    static std::unique_ptr<ICompressor> Lz4(uint32_t compression_level, const int32_t block_size);
-    static std::unique_ptr<ICompressor> Zstd(uint32_t compression_level, const int32_t block_size);
+    static std::unique_ptr<ICompressor> Gz(const int32_t compression_level,
+                                           const uint32_t block_size);
+    static std::unique_ptr<ICompressor> Brotli(const int32_t compression_level,
+                                               const uint32_t block_size);
+    static std::unique_ptr<ICompressor> Lz4(const int32_t compression_level,
+                                            const uint32_t block_size);
+    static std::unique_ptr<ICompressor> Zstd(const int32_t compression_level,
+                                             const uint32_t block_size);
 
     static std::unique_ptr<ICompressor> Create(CowCompression compression,
-                                               const int32_t block_size);
+                                               const uint32_t block_size);
 
-    uint32_t GetCompressionLevel() const { return compression_level_; }
+    int32_t GetCompressionLevel() const { return compression_level_; }
     uint32_t GetBlockSize() const { return block_size_; }
-    [[nodiscard]] virtual std::basic_string<uint8_t> Compress(const void* data,
-                                                              size_t length) const = 0;
+    [[nodiscard]] virtual std::vector<uint8_t> Compress(const void* data, size_t length) const = 0;
 
   private:
-    uint32_t compression_level_;
-    uint32_t block_size_;
+    const int32_t compression_level_;
+    const uint32_t block_size_;
 };
 }  // namespace snapshot
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 9401c66..991e17c 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -31,15 +31,9 @@
 static constexpr uint32_t kCowVersionMajor = 2;
 static constexpr uint32_t kCowVersionMinor = 0;
 
-static constexpr uint32_t kCowVersionManifest = 2;
-
 static constexpr uint32_t kMinCowVersion = 1;
 static constexpr uint32_t kMaxCowVersion = 3;
 
-// Normally, this should be kMaxCowVersion. When a new version is under testing
-// it may be the previous value of kMaxCowVersion.
-static constexpr uint32_t kDefaultCowVersion = 2;
-
 // This header appears as the first sequence of bytes in the COW. All fields
 // in the layout are little-endian encoded. The on-disk layout is:
 //
@@ -101,7 +95,7 @@
     // monotonically increasing value used by update_engine
     uint64_t label;
     // Index of last CowOperation guaranteed to be resumable
-    uint32_t op_index;
+    uint64_t op_index;
 } __attribute__((packed));
 
 static constexpr uint8_t kNumResumePoints = 4;
@@ -115,10 +109,12 @@
     uint32_t resume_point_max;
     // Number of CowOperationV3 structs in the operation buffer, currently and total
     // region size.
-    uint32_t op_count;
-    uint32_t op_count_max;
+    uint64_t op_count;
+    uint64_t op_count_max;
     // Compression Algorithm
     uint32_t compression_algorithm;
+    // Max compression size supported
+    uint32_t max_compression_size;
 } __attribute__((packed));
 
 enum class CowOperationType : uint8_t {
@@ -199,18 +195,24 @@
 static constexpr uint64_t kCowOpSourceInfoTypeBit = 60;
 static constexpr uint64_t kCowOpSourceInfoTypeNumBits = 4;
 static constexpr uint64_t kCowOpSourceInfoTypeMask = (1ULL << kCowOpSourceInfoTypeNumBits) - 1;
+
+static constexpr uint64_t kCowOpSourceInfoCompressionBit = 57;
+static constexpr uint64_t kCowOpSourceInfoCompressionNumBits = 3;
+static constexpr uint64_t kCowOpSourceInfoCompressionMask =
+        ((1ULL << kCowOpSourceInfoCompressionNumBits) - 1);
+
 // The on disk format of cow (currently ==  CowOperation)
 struct CowOperationV3 {
     // If this operation reads from the data section of the COW, this contains
     // the length.
-    uint16_t data_length;
+    uint32_t data_length;
 
     // The block of data in the new image that this operation modifies.
     uint32_t new_block;
 
     // source_info with have the following layout
-    // |---4 bits ---| ---12 bits---| --- 48 bits ---|
-    // |--- type --- | -- unused -- | --- source --- |
+    // |--- 4 bits -- | --------- 3 bits ------ | --- 9 bits --- | --- 48 bits ---|
+    // |--- type ---  | -- compression factor --| --- unused --- | --- source --- |
     //
     // The value of |source| depends on the operation code.
     //
@@ -223,6 +225,17 @@
     // For ops other than Label:
     //  Bits 47-62 are reserved and must be zero.
     // A block is compressed if it’s data is < block_sz
+    //
+    // Bits [57-59] represents the compression factor.
+    //
+    //       Compression - factor
+    // ==========================
+    // 000 -  4k
+    // 001 -  8k
+    // 010 -  16k
+    // ...
+    // 110 -  256k
+    //
     uint64_t source_info_;
     constexpr uint64_t source() const { return source_info_ & kCowOpSourceInfoDataMask; }
     constexpr void set_source(uint64_t source) {
@@ -243,6 +256,20 @@
         source_info_ |= (static_cast<uint64_t>(type) & kCowOpSourceInfoTypeMask)
                         << kCowOpSourceInfoTypeBit;
     }
+    constexpr void set_compression_bits(uint8_t compression_factor) {
+        // Clear the 3 bits from bit 57 - [57-59]
+        source_info_ &= ~(kCowOpSourceInfoCompressionMask << kCowOpSourceInfoCompressionBit);
+        // Set the actual compression factor
+        source_info_ |=
+                (static_cast<uint64_t>(compression_factor) & kCowOpSourceInfoCompressionMask)
+                << kCowOpSourceInfoCompressionBit;
+    }
+    constexpr uint8_t compression_bits() const {
+        // Grab the 3 bits from [57-59]
+        const auto compression_factor =
+                (source_info_ >> kCowOpSourceInfoCompressionBit) & kCowOpSourceInfoCompressionMask;
+        return static_cast<uint8_t>(compression_factor);
+    }
 } __attribute__((packed));
 
 // Ensure that getters/setters added to CowOperationV3 does not increases size
@@ -260,7 +287,7 @@
 };
 struct CowCompression {
     CowCompressionAlgorithm algorithm = kCowCompressNone;
-    uint32_t compression_level = 0;
+    int32_t compression_level = 0;
 };
 
 static constexpr uint8_t kCowReadAheadNotStarted = 0;
@@ -324,5 +351,11 @@
 // Convert compression name to internal value.
 std::optional<CowCompressionAlgorithm> CompressionAlgorithmFromString(std::string_view name);
 
+// Return block size used for compression
+size_t CowOpCompressionSize(const CowOperation* op, size_t block_size);
+
+// Return the relative offset of the I/O block which the CowOperation
+// multi-block compression
+bool GetBlockOffset(const CowOperation* op, uint64_t io_block, size_t block_size, off_t* offset);
 }  // 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 bf4c79f..3389f58 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -19,6 +19,7 @@
 #include <memory>
 #include <optional>
 #include <unordered_map>
+#include <vector>
 
 #include <android-base/unique_fd.h>
 #include <libsnapshot/cow_format.h>
@@ -162,6 +163,9 @@
     // Creates a clone of the current CowReader without the file handlers
     std::unique_ptr<CowReader> CloneCowReader();
 
+    // Get the max compression size
+    uint32_t GetMaxCompressionSize();
+
     void UpdateMergeOpsCompleted(int num_merge_ops) { header_.num_merge_ops += num_merge_ops; }
 
   private:
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 7df976d..2c6eefb 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -45,7 +45,7 @@
     std::optional<uint64_t> max_blocks;
 
     // Number of CowOperations in a cluster. 0 for no clustering. Cannot be 1.
-    uint32_t cluster_ops = 200;
+    uint32_t cluster_ops = 1024;
 
     bool scratch_space = true;
 
@@ -53,13 +53,16 @@
     uint64_t num_merge_ops = 0;
 
     // Number of threads for compression
-    int num_compress_threads = 0;
+    uint16_t num_compress_threads = 0;
 
     // Batch write cluster ops
     bool batch_write = false;
 
     // Size of the cow operation buffer; used in v3 only.
     uint64_t op_count_max = 0;
+
+    // Compression factor
+    uint64_t compression_factor = 4096;
 };
 
 // Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -116,36 +119,38 @@
 
 class CompressWorker {
   public:
-    CompressWorker(std::unique_ptr<ICompressor>&& compressor, uint32_t block_size);
+    CompressWorker(std::unique_ptr<ICompressor>&& compressor);
     bool RunThread();
-    void EnqueueCompressBlocks(const void* buffer, size_t num_blocks);
-    bool GetCompressedBuffers(std::vector<std::basic_string<uint8_t>>* compressed_buf);
+    void EnqueueCompressBlocks(const void* buffer, size_t block_size, size_t num_blocks);
+    bool GetCompressedBuffers(std::vector<std::vector<uint8_t>>* compressed_buf);
     void Finalize();
     static uint32_t GetDefaultCompressionLevel(CowCompressionAlgorithm compression);
 
     static bool CompressBlocks(ICompressor* compressor, size_t block_size, const void* buffer,
                                size_t num_blocks,
-                               std::vector<std::basic_string<uint8_t>>* compressed_data);
+                               std::vector<std::vector<uint8_t>>* compressed_data);
 
   private:
     struct CompressWork {
         const void* buffer;
         size_t num_blocks;
+        size_t block_size;
         bool compression_status = false;
-        std::vector<std::basic_string<uint8_t>> compressed_data;
+        std::vector<std::vector<uint8_t>> compressed_data;
     };
 
     std::unique_ptr<ICompressor> compressor_;
-    uint32_t block_size_;
 
     std::queue<CompressWork> work_queue_;
     std::queue<CompressWork> compressed_queue_;
     std::mutex lock_;
     std::condition_variable cv_;
     bool stopped_ = false;
+    size_t total_submitted_ = 0;
+    size_t total_processed_ = 0;
 
-    bool CompressBlocks(const void* buffer, size_t num_blocks,
-                        std::vector<std::basic_string<uint8_t>>* compressed_data);
+    bool CompressBlocks(const void* buffer, size_t num_blocks, size_t block_size,
+                        std::vector<std::vector<uint8_t>>* compressed_data);
 };
 
 // Create an ICowWriter not backed by any file. This is useful for estimating
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index d102863..6d422c6 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -410,6 +410,7 @@
     FRIEND_TEST(SnapshotTest, CreateSnapshot);
     FRIEND_TEST(SnapshotTest, FirstStageMountAfterRollback);
     FRIEND_TEST(SnapshotTest, FirstStageMountAndMerge);
+    FRIEND_TEST(SnapshotTest, FlagCheck);
     FRIEND_TEST(SnapshotTest, FlashSuperDuringMerge);
     FRIEND_TEST(SnapshotTest, FlashSuperDuringUpdate);
     FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
@@ -425,6 +426,7 @@
     FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeWithStaleSnapshots);
+    FRIEND_TEST(SnapshotUpdateTest, FlagCheck);
     FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
     FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
@@ -822,6 +824,9 @@
     // Check if io_uring API's need to be used
     bool UpdateUsesIouring(LockedFile* lock);
 
+    // Check if direct reads are enabled for the source image
+    bool UpdateUsesODirect(LockedFile* lock);
+
     // Wrapper around libdm, with diagnostics.
     bool DeleteDeviceIfExists(const std::string& name,
                               const std::chrono::milliseconds& timeout_ms = {});
@@ -829,6 +834,9 @@
     // Set read-ahead size during OTA
     void SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb);
 
+    // Returns true post OTA reboot if legacy snapuserd is required
+    bool IsLegacySnapuserdPostReboot();
+
     android::dm::IDeviceMapper& dm_;
     std::unique_ptr<IDeviceInfo> device_;
     std::string metadata_dir_;
@@ -839,6 +847,7 @@
     std::unique_ptr<SnapuserdClient> snapuserd_client_;
     std::unique_ptr<LpMetadata> old_partition_metadata_;
     std::optional<bool> is_snapshot_userspace_;
+    std::optional<bool> is_legacy_snapuserd_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 5e9f049..90813fe 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -155,6 +155,9 @@
     virtual bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) {
         return impl_.GetTableStatus(name, table);
     }
+    virtual bool GetTableStatusIma(const std::string& name, std::vector<TargetInfo>* table) {
+        return impl_.GetTableStatusIma(name, table);
+    }
     virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) {
         return impl_.GetDmDevicePathByName(name, path);
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
index abc7d33..bff5257 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
@@ -17,9 +17,11 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <cstdint>
 #include <limits>
 #include <memory>
 #include <queue>
+#include <vector>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -56,7 +58,7 @@
 }
 
 std::unique_ptr<ICompressor> ICompressor::Create(CowCompression compression,
-                                                 const int32_t block_size) {
+                                                 const uint32_t block_size) {
     switch (compression.algorithm) {
         case kCowCompressLz4:
             return ICompressor::Lz4(compression.compression_level, block_size);
@@ -100,12 +102,12 @@
 
 class GzCompressor final : public ICompressor {
   public:
-    GzCompressor(uint32_t compression_level, const uint32_t block_size)
+    GzCompressor(int32_t compression_level, const uint32_t block_size)
         : ICompressor(compression_level, block_size){};
 
-    std::basic_string<uint8_t> Compress(const void* data, size_t length) const override {
+    std::vector<uint8_t> Compress(const void* data, size_t length) const override {
         const auto bound = compressBound(length);
-        std::basic_string<uint8_t> buffer(bound, '\0');
+        std::vector<uint8_t> buffer(bound, '\0');
 
         uLongf dest_len = bound;
         auto rv = compress2(buffer.data(), &dest_len, reinterpret_cast<const Bytef*>(data), length,
@@ -121,16 +123,16 @@
 
 class Lz4Compressor final : public ICompressor {
   public:
-    Lz4Compressor(uint32_t compression_level, const uint32_t block_size)
+    Lz4Compressor(int32_t compression_level, const uint32_t block_size)
         : ICompressor(compression_level, block_size){};
 
-    std::basic_string<uint8_t> Compress(const void* data, size_t length) const override {
+    std::vector<uint8_t> Compress(const void* data, size_t length) const override {
         const auto bound = LZ4_compressBound(length);
         if (!bound) {
             LOG(ERROR) << "LZ4_compressBound returned 0";
             return {};
         }
-        std::basic_string<uint8_t> buffer(bound, '\0');
+        std::vector<uint8_t> buffer(bound, '\0');
 
         const auto compressed_size =
                 LZ4_compress_default(static_cast<const char*>(data),
@@ -153,16 +155,16 @@
 
 class BrotliCompressor final : public ICompressor {
   public:
-    BrotliCompressor(uint32_t compression_level, const uint32_t block_size)
+    BrotliCompressor(int32_t compression_level, const uint32_t block_size)
         : ICompressor(compression_level, block_size){};
 
-    std::basic_string<uint8_t> Compress(const void* data, size_t length) const override {
+    std::vector<uint8_t> Compress(const void* data, size_t length) const override {
         const auto bound = BrotliEncoderMaxCompressedSize(length);
         if (!bound) {
             LOG(ERROR) << "BrotliEncoderMaxCompressedSize returned 0";
             return {};
         }
-        std::basic_string<uint8_t> buffer(bound, '\0');
+        std::vector<uint8_t> buffer(bound, '\0');
 
         size_t encoded_size = bound;
         auto rv = BrotliEncoderCompress(
@@ -179,15 +181,15 @@
 
 class ZstdCompressor final : public ICompressor {
   public:
-    ZstdCompressor(uint32_t compression_level, const uint32_t block_size)
+    ZstdCompressor(int32_t compression_level, const uint32_t block_size)
         : ICompressor(compression_level, block_size),
           zstd_context_(ZSTD_createCCtx(), ZSTD_freeCCtx) {
         ZSTD_CCtx_setParameter(zstd_context_.get(), ZSTD_c_compressionLevel, compression_level);
         ZSTD_CCtx_setParameter(zstd_context_.get(), ZSTD_c_windowLog, log2(GetBlockSize()));
     };
 
-    std::basic_string<uint8_t> Compress(const void* data, size_t length) const override {
-        std::basic_string<uint8_t> buffer(ZSTD_compressBound(length), '\0');
+    std::vector<uint8_t> Compress(const void* data, size_t length) const override {
+        std::vector<uint8_t> buffer(ZSTD_compressBound(length), '\0');
         const auto compressed_size =
                 ZSTD_compress2(zstd_context_.get(), buffer.data(), buffer.size(), data, length);
         if (compressed_size <= 0) {
@@ -208,14 +210,14 @@
     std::unique_ptr<ZSTD_CCtx, decltype(&ZSTD_freeCCtx)> zstd_context_;
 };
 
-bool CompressWorker::CompressBlocks(const void* buffer, size_t num_blocks,
-                                    std::vector<std::basic_string<uint8_t>>* compressed_data) {
-    return CompressBlocks(compressor_.get(), block_size_, buffer, num_blocks, compressed_data);
+bool CompressWorker::CompressBlocks(const void* buffer, size_t num_blocks, size_t block_size,
+                                    std::vector<std::vector<uint8_t>>* compressed_data) {
+    return CompressBlocks(compressor_.get(), block_size, buffer, num_blocks, compressed_data);
 }
 
 bool CompressWorker::CompressBlocks(ICompressor* compressor, size_t block_size, const void* buffer,
                                     size_t num_blocks,
-                                    std::vector<std::basic_string<uint8_t>>* compressed_data) {
+                                    std::vector<std::vector<uint8_t>>* compressed_data) {
     const uint8_t* iter = reinterpret_cast<const uint8_t*>(buffer);
     while (num_blocks) {
         auto data = compressor->Compress(iter, block_size);
@@ -223,7 +225,7 @@
             PLOG(ERROR) << "CompressBlocks: Compression failed";
             return false;
         }
-        if (data.size() > std::numeric_limits<uint16_t>::max()) {
+        if (data.size() > std::numeric_limits<uint32_t>::max()) {
             LOG(ERROR) << "Compressed block is too large: " << data.size();
             return false;
         }
@@ -254,7 +256,8 @@
         }
 
         // Compress blocks
-        bool ret = CompressBlocks(blocks.buffer, blocks.num_blocks, &blocks.compressed_data);
+        bool ret = CompressBlocks(blocks.buffer, blocks.num_blocks, blocks.block_size,
+                                  &blocks.compressed_data);
         blocks.compression_status = ret;
         {
             std::lock_guard<std::mutex> lock(lock_);
@@ -273,35 +276,31 @@
     return true;
 }
 
-void CompressWorker::EnqueueCompressBlocks(const void* buffer, size_t num_blocks) {
+void CompressWorker::EnqueueCompressBlocks(const void* buffer, size_t block_size,
+                                           size_t num_blocks) {
     {
         std::lock_guard<std::mutex> lock(lock_);
 
         CompressWork blocks = {};
         blocks.buffer = buffer;
+        blocks.block_size = block_size;
         blocks.num_blocks = num_blocks;
         work_queue_.push(std::move(blocks));
+        total_submitted_ += 1;
     }
     cv_.notify_all();
 }
 
-bool CompressWorker::GetCompressedBuffers(std::vector<std::basic_string<uint8_t>>* compressed_buf) {
-    {
+bool CompressWorker::GetCompressedBuffers(std::vector<std::vector<uint8_t>>* compressed_buf) {
+    while (true) {
         std::unique_lock<std::mutex> lock(lock_);
-        while (compressed_queue_.empty() && !stopped_) {
+        while ((total_submitted_ != total_processed_) && compressed_queue_.empty() && !stopped_) {
             cv_.wait(lock);
         }
-
-        if (stopped_) {
-            return true;
-        }
-    }
-
-    {
-        std::lock_guard<std::mutex> lock(lock_);
         while (compressed_queue_.size() > 0) {
             CompressWork blocks = std::move(compressed_queue_.front());
             compressed_queue_.pop();
+            total_processed_ += 1;
 
             if (blocks.compression_status) {
                 compressed_buf->insert(compressed_buf->end(),
@@ -312,27 +311,31 @@
                 return false;
             }
         }
+        if ((total_submitted_ == total_processed_) || stopped_) {
+            total_submitted_ = 0;
+            total_processed_ = 0;
+            return true;
+        }
     }
-
-    return true;
 }
 
-std::unique_ptr<ICompressor> ICompressor::Brotli(uint32_t compression_level,
-                                                 const int32_t block_size) {
+std::unique_ptr<ICompressor> ICompressor::Brotli(const int32_t compression_level,
+                                                 const uint32_t block_size) {
     return std::make_unique<BrotliCompressor>(compression_level, block_size);
 }
 
-std::unique_ptr<ICompressor> ICompressor::Gz(uint32_t compression_level, const int32_t block_size) {
+std::unique_ptr<ICompressor> ICompressor::Gz(const int32_t compression_level,
+                                             const uint32_t block_size) {
     return std::make_unique<GzCompressor>(compression_level, block_size);
 }
 
-std::unique_ptr<ICompressor> ICompressor::Lz4(uint32_t compression_level,
-                                              const int32_t block_size) {
+std::unique_ptr<ICompressor> ICompressor::Lz4(const int32_t compression_level,
+                                              const uint32_t block_size) {
     return std::make_unique<Lz4Compressor>(compression_level, block_size);
 }
 
-std::unique_ptr<ICompressor> ICompressor::Zstd(uint32_t compression_level,
-                                               const int32_t block_size) {
+std::unique_ptr<ICompressor> ICompressor::Zstd(const int32_t compression_level,
+                                               const uint32_t block_size) {
     return std::make_unique<ZstdCompressor>(compression_level, block_size);
 }
 
@@ -344,8 +347,8 @@
     cv_.notify_all();
 }
 
-CompressWorker::CompressWorker(std::unique_ptr<ICompressor>&& compressor, uint32_t block_size)
-    : compressor_(std::move(compressor)), block_size_(block_size) {}
+CompressWorker::CompressWorker(std::unique_ptr<ICompressor>&& compressor)
+    : compressor_(std::move(compressor)) {}
 
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index 8d1786c..19014c0 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -21,6 +21,7 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <libsnapshot/cow_format.h>
+#include <storage_literals/storage_literals.h>
 #include "writer_v2.h"
 #include "writer_v3.h"
 
@@ -28,6 +29,7 @@
 namespace snapshot {
 
 using android::base::unique_fd;
+using namespace android::storage_literals;
 
 std::ostream& EmitCowTypeString(std::ostream& os, CowOperationType cow_type) {
     switch (cow_type) {
@@ -174,5 +176,36 @@
     return CreateCowWriter(version, options, unique_fd{-1}, std::nullopt);
 }
 
+size_t CowOpCompressionSize(const CowOperation* op, size_t block_size) {
+    uint8_t compression_bits = op->compression_bits();
+    return (block_size << compression_bits);
+}
+
+bool GetBlockOffset(const CowOperation* op, uint64_t io_block, size_t block_size, off_t* offset) {
+    const uint64_t new_block = op->new_block;
+
+    if (op->type() != kCowReplaceOp || io_block < new_block) {
+        LOG(VERBOSE) << "Invalid IO request for block: " << io_block
+                     << " CowOperation: new_block: " << new_block;
+        return false;
+    }
+
+    // Get the actual compression size
+    const size_t compression_size = CowOpCompressionSize(op, block_size);
+    // Find the number of blocks spanned
+    const size_t num_blocks = compression_size / block_size;
+    // Find the distance of the I/O block which this
+    // CowOperation encompasses
+    const size_t block_distance = io_block - new_block;
+    // Check if this block is within this range;
+    // if so, return the relative offset
+    if (block_distance < num_blocks) {
+        *offset = block_distance * block_size;
+        return true;
+    }
+
+    return false;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 1b4a971..6516499 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -26,6 +26,7 @@
 #include <android-base/logging.h>
 #include <libsnapshot/cow_format.h>
 #include <libsnapshot/cow_reader.h>
+#include <storage_literals/storage_literals.h>
 #include <zlib.h>
 
 #include "cow_decompress.h"
@@ -35,6 +36,8 @@
 namespace android {
 namespace snapshot {
 
+using namespace android::storage_literals;
+
 bool ReadCowHeader(android::base::borrowed_fd fd, CowHeaderV3* header) {
     if (lseek(fd.get(), 0, SEEK_SET) < 0) {
         PLOG(ERROR) << "lseek header failed";
@@ -161,6 +164,21 @@
     return PrepMergeOps();
 }
 
+uint32_t CowReader::GetMaxCompressionSize() {
+    switch (header_.prefix.major_version) {
+        case 1:
+        case 2:
+            // Old versions supports only 4KB compression.
+            return header_.block_size;
+            ;
+        case 3:
+            return header_.max_compression_size;
+        default:
+            LOG(ERROR) << "Unknown version: " << header_.prefix.major_version;
+            return 0;
+    }
+}
+
 //
 // This sets up the data needed for MergeOpIter. MergeOpIter presents
 // data in the order we intend to merge in.
@@ -705,6 +723,11 @@
 ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
                             size_t ignore_bytes) {
     std::unique_ptr<IDecompressor> decompressor;
+    const size_t op_buf_size = CowOpCompressionSize(op, header_.block_size);
+    if (!op_buf_size) {
+        LOG(ERROR) << "Compression size is zero. op: " << *op;
+        return -1;
+    }
     switch (GetCompressionType()) {
         case kCowCompressNone:
             break;
@@ -715,12 +738,12 @@
             decompressor = IDecompressor::Brotli();
             break;
         case kCowCompressZstd:
-            if (header_.block_size != op->data_length) {
+            if (op_buf_size != op->data_length) {
                 decompressor = IDecompressor::Zstd();
             }
             break;
         case kCowCompressLz4:
-            if (header_.block_size != op->data_length) {
+            if (op_buf_size != op->data_length) {
                 decompressor = IDecompressor::Lz4();
             }
             break;
@@ -736,14 +759,14 @@
         offset = op->source();
     }
     if (!decompressor ||
-        ((op->data_length == header_.block_size) && (header_.prefix.major_version == 3))) {
+        ((op->data_length == op_buf_size) && (header_.prefix.major_version == 3))) {
         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);
-    return decompressor->Decompress(buffer, buffer_size, header_.block_size, ignore_bytes);
+    return decompressor->Decompress(buffer, buffer_size, op_buf_size, ignore_bytes);
 }
 
 bool CowReader::GetSourceOffset(const CowOperation* op, uint64_t* source_offset) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp
index efb1035..5497b72 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp
@@ -71,6 +71,8 @@
 
     const int kNumThreads = 6;
     const size_t kBlockSizeToRead = 1_MiB;
+    const size_t compression_factor_ = 64_KiB;
+    size_t replace_ops_ = 0, copy_ops_ = 0, zero_ops_ = 0, in_place_ops_ = 0;
 
     std::unordered_map<std::string, int> source_block_hash_;
     std::mutex source_block_hash_lock_;
@@ -81,7 +83,12 @@
     std::unique_ptr<uint8_t[]> zblock_;
 
     std::string compression_ = "lz4";
-    unique_fd fd_;
+    unique_fd cow_fd_;
+    unique_fd target_fd_;
+
+    std::vector<uint64_t> zero_blocks_;
+    std::vector<uint64_t> replace_blocks_;
+    std::unordered_map<uint64_t, uint64_t> copy_blocks_;
 
     const int BLOCK_SZ = 4_KiB;
     void SHA256(const void* data, size_t length, uint8_t out[32]);
@@ -93,7 +100,14 @@
     bool FindSourceBlockHash();
     bool PrepareParse(std::string& parsing_file, const bool createSnapshot);
     bool ParsePartition();
-    bool WriteSnapshot(const void* buffer, uint64_t block, std::string& block_hash);
+    void PrepareMergeBlock(const void* buffer, uint64_t block, std::string& block_hash);
+    bool WriteV3Snapshots();
+    size_t PrepareWrite(size_t* pending_ops, size_t start_index);
+
+    bool CreateSnapshotWriter();
+    bool WriteOrderedSnapshots();
+    bool WriteNonOrderedSnapshots();
+    bool VerifyMergeOrder();
 };
 
 void CreateSnapshotLogger(android::base::LogId, android::base::LogSeverity severity, const char*,
@@ -118,21 +132,19 @@
     create_snapshot_patch_ = createSnapshot;
 
     if (createSnapshot) {
-        fd_.reset(open(patch_file_.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666));
-        if (fd_ < 0) {
+        cow_fd_.reset(open(patch_file_.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666));
+        if (cow_fd_ < 0) {
             PLOG(ERROR) << "Failed to open the snapshot-patch file: " << patch_file_;
             return false;
         }
 
+        target_fd_.reset((open(parsing_file_.c_str(), O_RDONLY)));
+        if (target_fd_ < 0) {
+            LOG(ERROR) << "open failed: " << parsing_file_;
+            return false;
+        }
         zblock_ = std::make_unique<uint8_t[]>(BLOCK_SZ);
         std::memset(zblock_.get(), 0, BLOCK_SZ);
-
-        CowOptions options;
-        options.compression = compression_;
-        options.num_compress_threads = 2;
-        options.batch_write = true;
-        options.cluster_ops = 600;
-        writer_ = CreateCowWriter(2, options, std::move(fd_));
     }
     return true;
 }
@@ -187,19 +199,158 @@
     return out;
 }
 
-bool CreateSnapshot::WriteSnapshot(const void* buffer, uint64_t block, std::string& block_hash) {
+void CreateSnapshot::PrepareMergeBlock(const void* buffer, uint64_t block,
+                                       std::string& block_hash) {
     if (std::memcmp(zblock_.get(), buffer, BLOCK_SZ) == 0) {
         std::lock_guard<std::mutex> lock(write_lock_);
-        return writer_->AddZeroBlocks(block, 1);
+        zero_blocks_.push_back(block);
+        return;
     }
 
     auto iter = source_block_hash_.find(block_hash);
     if (iter != source_block_hash_.end()) {
         std::lock_guard<std::mutex> lock(write_lock_);
-        return writer_->AddCopy(block, iter->second, 1);
+        // In-place copy is skipped
+        if (block != iter->second) {
+            copy_blocks_[block] = iter->second;
+        } else {
+            in_place_ops_ += 1;
+        }
+        return;
     }
     std::lock_guard<std::mutex> lock(write_lock_);
-    return writer_->AddRawBlocks(block, buffer, BLOCK_SZ);
+    replace_blocks_.push_back(block);
+}
+
+size_t CreateSnapshot::PrepareWrite(size_t* pending_ops, size_t start_index) {
+    size_t num_ops = *pending_ops;
+    uint64_t start_block = replace_blocks_[start_index];
+    size_t nr_consecutive = 1;
+    num_ops -= 1;
+    while (num_ops) {
+        uint64_t next_block = replace_blocks_[start_index + nr_consecutive];
+        if (next_block != start_block + nr_consecutive) {
+            break;
+        }
+        nr_consecutive += 1;
+        num_ops -= 1;
+    }
+    return nr_consecutive;
+}
+
+bool CreateSnapshot::CreateSnapshotWriter() {
+    uint64_t dev_sz = lseek(target_fd_.get(), 0, SEEK_END);
+    CowOptions options;
+    options.compression = compression_;
+    options.num_compress_threads = 2;
+    options.batch_write = true;
+    options.cluster_ops = 600;
+    options.compression_factor = compression_factor_;
+    options.max_blocks = {dev_sz / options.block_size};
+    writer_ = CreateCowWriter(3, options, std::move(cow_fd_));
+    return true;
+}
+
+bool CreateSnapshot::WriteNonOrderedSnapshots() {
+    zero_ops_ = zero_blocks_.size();
+    for (auto it = zero_blocks_.begin(); it != zero_blocks_.end(); it++) {
+        if (!writer_->AddZeroBlocks(*it, 1)) {
+            return false;
+        }
+    }
+    std::string buffer(compression_factor_, '\0');
+
+    replace_ops_ = replace_blocks_.size();
+    size_t blocks_to_compress = replace_blocks_.size();
+    size_t num_ops = 0;
+    size_t block_index = 0;
+    while (blocks_to_compress) {
+        num_ops = std::min((compression_factor_ / BLOCK_SZ), blocks_to_compress);
+        auto linear_blocks = PrepareWrite(&num_ops, block_index);
+        if (!android::base::ReadFullyAtOffset(target_fd_.get(), buffer.data(),
+                                              (linear_blocks * BLOCK_SZ),
+                                              replace_blocks_[block_index] * BLOCK_SZ)) {
+            LOG(ERROR) << "Failed to read at offset: " << replace_blocks_[block_index] * BLOCK_SZ
+                       << " size: " << linear_blocks * BLOCK_SZ;
+            return false;
+        }
+        if (!writer_->AddRawBlocks(replace_blocks_[block_index], buffer.data(),
+                                   linear_blocks * BLOCK_SZ)) {
+            LOG(ERROR) << "AddRawBlocks failed";
+            return false;
+        }
+
+        block_index += linear_blocks;
+        blocks_to_compress -= linear_blocks;
+    }
+    if (!writer_->Finalize()) {
+        return false;
+    }
+    return true;
+}
+
+bool CreateSnapshot::WriteOrderedSnapshots() {
+    std::unordered_map<uint64_t, uint64_t> overwritten_blocks;
+    std::vector<std::pair<uint64_t, uint64_t>> merge_sequence;
+    for (auto it = copy_blocks_.begin(); it != copy_blocks_.end(); it++) {
+        if (overwritten_blocks.count(it->second)) {
+            replace_blocks_.push_back(it->first);
+            continue;
+        }
+        overwritten_blocks[it->first] = it->second;
+        merge_sequence.emplace_back(std::make_pair(it->first, it->second));
+    }
+    // Sort the blocks so that if the blocks are contiguous, it would help
+    // compress multiple blocks in one shot based on the compression factor.
+    std::sort(replace_blocks_.begin(), replace_blocks_.end());
+
+    copy_ops_ = merge_sequence.size();
+    for (auto it = merge_sequence.begin(); it != merge_sequence.end(); it++) {
+        if (!writer_->AddCopy(it->first, it->second, 1)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool CreateSnapshot::VerifyMergeOrder() {
+    unique_fd read_fd;
+    read_fd.reset(open(patch_file_.c_str(), O_RDONLY));
+    if (read_fd < 0) {
+        PLOG(ERROR) << "Failed to open the snapshot-patch file: " << patch_file_;
+        return false;
+    }
+    CowReader reader;
+    if (!reader.Parse(read_fd)) {
+        LOG(ERROR) << "Parse failed";
+        return false;
+    }
+
+    if (!reader.VerifyMergeOps()) {
+        LOG(ERROR) << "MergeOps Order is wrong";
+        return false;
+    }
+    return true;
+}
+
+bool CreateSnapshot::WriteV3Snapshots() {
+    if (!CreateSnapshotWriter()) {
+        return false;
+    }
+    if (!WriteOrderedSnapshots()) {
+        return false;
+    }
+    if (!WriteNonOrderedSnapshots()) {
+        return false;
+    }
+    if (!VerifyMergeOrder()) {
+        return false;
+    }
+
+    LOG(INFO) << "In-place: " << in_place_ops_ << " Zero: " << zero_ops_
+              << " Replace: " << replace_ops_ << " copy: " << copy_ops_;
+    return true;
 }
 
 bool CreateSnapshot::ReadBlocks(off_t offset, const int skip_blocks, const uint64_t dev_sz) {
@@ -241,10 +392,7 @@
             std::string hash = ToHexString(checksum, sizeof(checksum));
 
             if (create_snapshot_patch_) {
-                if (!WriteSnapshot(bufptr, blkindex, hash)) {
-                    LOG(ERROR) << "WriteSnapshot failed for block: " << blkindex;
-                    return false;
-                }
+                PrepareMergeBlock(bufptr, blkindex, hash);
             } else {
                 std::lock_guard<std::mutex> lock(source_block_hash_lock_);
                 {
@@ -306,8 +454,8 @@
         ret = t.get() && ret;
     }
 
-    if (ret && create_snapshot_patch_ && !writer_->Finalize()) {
-        LOG(ERROR) << "Finzalize failed";
+    if (ret && create_snapshot_patch_ && !WriteV3Snapshots()) {
+        LOG(ERROR) << "Snapshot Write failed";
         return false;
     }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
index fe977b7..a35b614 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
@@ -206,6 +206,8 @@
 
         auto& new_op = out->ops->at(i);
         new_op.set_type(v2_op.type);
+        // v2 ops always have 4k compression
+        new_op.set_compression_bits(0);
         new_op.data_length = v2_op.data_length;
 
         if (v2_op.new_block > std::numeric_limits<uint32_t>::max()) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
index 4e90a0f..12073fc 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
@@ -42,10 +42,21 @@
             op_iter_->Next();
             continue;
         }
-        if (op->new_block >= ops_.size()) {
-            ops_.resize(op->new_block + 1, nullptr);
+
+        size_t num_blocks = 1;
+        if (op->type() == kCowReplaceOp) {
+            num_blocks = (CowOpCompressionSize(op, block_size_) / block_size_);
         }
-        ops_[op->new_block] = op;
+        if (op->new_block >= ops_.size()) {
+            ops_.resize(op->new_block + num_blocks, nullptr);
+        }
+
+        size_t vec_index = op->new_block;
+        while (num_blocks) {
+            ops_[vec_index] = op;
+            num_blocks -= 1;
+            vec_index += 1;
+        }
         op_iter_->Next();
     }
 }
@@ -172,11 +183,20 @@
     } else if (op->type() == kCowZeroOp) {
         memset(buffer, 0, bytes_to_read);
     } else if (op->type() == kCowReplaceOp) {
-        if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
-            LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
+        size_t buffer_size = CowOpCompressionSize(op, block_size_);
+        uint8_t temp_buffer[buffer_size];
+        if (cow_->ReadData(op, temp_buffer, buffer_size, 0) < buffer_size) {
+            LOG(ERROR) << "CompressedSnapshotReader failed to read replace op: buffer_size: "
+                       << buffer_size << "start_offset: " << start_offset;
             errno = EIO;
             return -1;
         }
+        off_t block_offset{};
+        if (!GetBlockOffset(op, chunk, block_size_, &block_offset)) {
+            LOG(ERROR) << "GetBlockOffset failed";
+            return -1;
+        }
+        std::memcpy(buffer, (char*)temp_buffer + block_offset + start_offset, bytes_to_read);
     } else if (op->type() == kCowXorOp) {
         borrowed_fd fd = GetSourceFd();
         if (fd < 0) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader_test.cpp
index 10cb06d..f00f236 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader_test.cpp
@@ -184,7 +184,7 @@
     unique_fd cow_fd(dup(cow_->fd));
     ASSERT_GE(cow_fd, 0);
 
-    auto writer = CreateCowWriter(kDefaultCowVersion, options, std::move(cow_fd));
+    auto writer = CreateCowWriter(2, options, std::move(cow_fd));
     ASSERT_NO_FATAL_FAILURE(WriteCow(writer.get()));
     ASSERT_NO_FATAL_FAILURE(TestReads(writer.get()));
 }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
index 1d1d24c..ce80cd7 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
@@ -18,6 +18,7 @@
 #include <iostream>
 #include <memory>
 #include <string_view>
+#include <vector>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -429,7 +430,7 @@
 template <typename T>
 class HorribleStream : public IByteStream {
   public:
-    HorribleStream(const std::basic_string<T>& input) : input_(input) {}
+    HorribleStream(const std::vector<T>& input) : input_(input) {}
 
     ssize_t Read(void* buffer, size_t length) override {
         if (pos_ >= input_.size()) {
@@ -444,16 +445,17 @@
     size_t Size() const override { return input_.size(); }
 
   private:
-    std::basic_string<T> input_;
+    std::vector<T> input_;
     size_t pos_ = 0;
 };
 
 TEST(HorribleStream, ReadFully) {
-    std::string expected = "this is some data";
+    std::string expected_str = "this is some data";
+    std::vector<char> expected{expected_str.begin(), expected_str.end()};
 
     HorribleStream<char> stream(expected);
 
-    std::string buffer(expected.size(), '\0');
+    std::vector<char> buffer(expected.size(), '\0');
     ASSERT_TRUE(stream.ReadFully(buffer.data(), buffer.size()));
     ASSERT_EQ(buffer, expected);
 }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index 8cf46f4..4456b26 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -18,6 +18,7 @@
 #include <libsnapshot/cow_format.h>
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
+#include <storage_literals/storage_literals.h>
 #include "writer_v2.h"
 #include "writer_v3.h"
 
@@ -29,6 +30,9 @@
 namespace android {
 namespace snapshot {
 
+using namespace android::storage_literals;
+using ::testing::TestWithParam;
+
 class CowTestV3 : public ::testing::Test {
   protected:
     virtual void SetUp() override {
@@ -72,6 +76,7 @@
 
 TEST_F(CowTestV3, Header) {
     CowOptions options;
+    options.op_count_max = 15;
     auto writer = CreateCowWriter(3, options, GetCowFd());
     ASSERT_TRUE(writer->Finalize());
 
@@ -104,6 +109,40 @@
     ASSERT_EQ(reader.header_v3().op_count, 20);
 }
 
+TEST_F(CowTestV3, MaxOpSingleThreadCompression) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.num_compress_threads = 1;
+    options.compression_factor = 4096 * 8;
+    options.compression = "lz4";
+
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->AddZeroBlocks(1, 20));
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_FALSE(writer->AddRawBlocks(5, data.data(), data.size()));
+
+    ASSERT_TRUE(writer->Finalize());
+}
+
+TEST_F(CowTestV3, MaxOpMultiThreadCompression) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.num_compress_threads = 2;
+    options.compression_factor = 4096 * 8;
+    options.compression = "lz4";
+
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->AddZeroBlocks(1, 20));
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_FALSE(writer->AddRawBlocks(5, data.data(), data.size()));
+
+    ASSERT_TRUE(writer->Finalize());
+}
+
 TEST_F(CowTestV3, ZeroOp) {
     CowOptions options;
     options.op_count_max = 20;
@@ -170,6 +209,48 @@
     ASSERT_EQ(sink, data);
 }
 
+TEST_F(CowTestV3, BigReplaceOp) {
+    CowOptions options;
+    options.op_count_max = 10000;
+    options.batch_write = true;
+    options.cluster_ops = 2048;
+
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size * 4096, '\0');
+    for (int i = 0; i < data.size(); i++) {
+        data[i] = static_cast<char>('A' + i / options.block_size);
+    }
+    ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.op_count, 4096);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    size_t i = 0;
+
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+        std::string sink(options.block_size, '\0');
+        ASSERT_EQ(op->type(), kCowReplaceOp);
+        ASSERT_EQ(op->data_length, options.block_size);
+        ASSERT_EQ(op->new_block, 5 + i);
+        ASSERT_TRUE(ReadData(reader, op, sink.data(), options.block_size));
+        ASSERT_EQ(std::string_view(sink),
+                  std::string_view(data).substr(i * options.block_size, options.block_size))
+                << " readback data for " << i << "th block does not match";
+        iter->Next();
+        i++;
+    }
+}
+
 TEST_F(CowTestV3, ConsecutiveReplaceOp) {
     CowOptions options;
     options.op_count_max = 20;
@@ -483,14 +564,14 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
     auto header = reader.header_v3();
-    ASSERT_EQ(header.sequence_data_count, 0);
+    ASSERT_EQ(header.sequence_data_count, static_cast<uint64_t>(0));
     ASSERT_EQ(header.resume_point_count, 0);
     ASSERT_EQ(header.resume_point_max, 4);
 
     writer->AddLabel(0);
     ASSERT_TRUE(reader.Parse(cow_->fd));
     header = reader.header_v3();
-    ASSERT_EQ(header.sequence_data_count, 0);
+    ASSERT_EQ(header.sequence_data_count, static_cast<uint64_t>(0));
     ASSERT_EQ(header.resume_point_count, 1);
     ASSERT_EQ(header.resume_point_max, 4);
 
@@ -698,5 +779,189 @@
     ASSERT_FALSE(writer->AddZeroBlocks(0, 19));
 }
 
+struct TestParam {
+    std::string compression;
+    int block_size;
+    int num_threads;
+    size_t cluster_ops;
+};
+
+class VariableBlockTest : public ::testing::TestWithParam<TestParam> {
+  protected:
+    virtual void SetUp() override {
+        cow_ = std::make_unique<TemporaryFile>();
+        ASSERT_GE(cow_->fd, 0) << strerror(errno);
+    }
+
+    virtual void TearDown() override { cow_ = nullptr; }
+
+    unique_fd GetCowFd() { return unique_fd{dup(cow_->fd)}; }
+
+    std::unique_ptr<TemporaryFile> cow_;
+};
+
+// Helper to check read sizes.
+static inline void ReadBlockData(CowReader& reader, const CowOperation* op, void* buffer,
+                                 size_t size) {
+    size_t block_size = CowOpCompressionSize(op, 4096);
+    std::string data(block_size, '\0');
+    size_t value = reader.ReadData(op, data.data(), block_size);
+    ASSERT_TRUE(value == block_size);
+    std::memcpy(buffer, data.data(), size);
+}
+
+TEST_P(VariableBlockTest, VariableBlockCompressionTest) {
+    const TestParam params = GetParam();
+
+    CowOptions options;
+    options.op_count_max = 100000;
+    options.compression = params.compression;
+    options.num_compress_threads = params.num_threads;
+    options.batch_write = true;
+    options.compression_factor = params.block_size;
+    options.cluster_ops = params.cluster_ops;
+
+    CowWriterV3 writer(options, GetCowFd());
+
+    ASSERT_TRUE(writer.Initialize());
+
+    std::string xor_data = "This is test data-1. Testing xor";
+    xor_data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer.AddXorBlocks(50, xor_data.data(), xor_data.size(), 24, 10));
+
+    // Large number of blocks
+    std::string data = "This is test data-2. Testing replace ops";
+    data.resize(options.block_size * 2048, '\0');
+    ASSERT_TRUE(writer.AddRawBlocks(100, data.data(), data.size()));
+
+    std::string data2 = "This is test data-3. Testing replace ops";
+    data2.resize(options.block_size * 259, '\0');
+    ASSERT_TRUE(writer.AddRawBlocks(6000, data2.data(), data2.size()));
+
+    // Test data size is smaller than block size
+
+    // 4k block
+    std::string data3 = "This is test data-4. Testing replace ops";
+    data3.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer.AddRawBlocks(9000, data3.data(), data3.size()));
+
+    // 8k block
+    std::string data4;
+    data4.resize(options.block_size * 2, '\0');
+    for (size_t i = 0; i < data4.size(); i++) {
+        data4[i] = static_cast<char>('A' + i / options.block_size);
+    }
+    ASSERT_TRUE(writer.AddRawBlocks(10000, data4.data(), data4.size()));
+
+    // 16k block
+    std::string data5;
+    data.resize(options.block_size * 4, '\0');
+    for (int i = 0; i < data5.size(); i++) {
+        data5[i] = static_cast<char>('C' + i / options.block_size);
+    }
+    ASSERT_TRUE(writer.AddRawBlocks(11000, data5.data(), data5.size()));
+
+    // 64k Random buffer which cannot be compressed
+    unique_fd rnd_fd(open("/dev/random", O_RDONLY));
+    ASSERT_GE(rnd_fd, 0);
+    std::string random_buffer;
+    random_buffer.resize(65536, '\0');
+    ASSERT_EQ(android::base::ReadFullyAtOffset(rnd_fd, random_buffer.data(), 65536, 0), true);
+    ASSERT_TRUE(writer.AddRawBlocks(12000, random_buffer.data(), 65536));
+
+    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);
+
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+
+        if (op->type() == kCowXorOp) {
+            std::string sink(xor_data.size(), '\0');
+            ASSERT_EQ(op->new_block, 50);
+            ASSERT_EQ(op->source(), 98314);  // 4096 * 24 + 10
+            ReadBlockData(reader, op, sink.data(), sink.size());
+            ASSERT_EQ(sink, xor_data);
+        }
+        if (op->type() == kCowReplaceOp) {
+            if (op->new_block == 100) {
+                data.resize(options.block_size);
+                std::string sink(data.size(), '\0');
+                ReadBlockData(reader, op, sink.data(), sink.size());
+                ASSERT_EQ(sink.size(), data.size());
+                ASSERT_EQ(sink, data);
+            }
+            if (op->new_block == 6000) {
+                data2.resize(options.block_size);
+                std::string sink(data2.size(), '\0');
+                ReadBlockData(reader, op, sink.data(), sink.size());
+                ASSERT_EQ(sink, data2);
+            }
+            if (op->new_block == 9000) {
+                std::string sink(data3.size(), '\0');
+                ReadBlockData(reader, op, sink.data(), sink.size());
+                ASSERT_EQ(sink, data3);
+            }
+            if (op->new_block == 10000) {
+                data4.resize(options.block_size);
+                std::string sink(options.block_size, '\0');
+                ReadBlockData(reader, op, sink.data(), sink.size());
+                ASSERT_EQ(sink, data4);
+            }
+            if (op->new_block == 11000) {
+                data5.resize(options.block_size);
+                std::string sink(options.block_size, '\0');
+                ReadBlockData(reader, op, sink.data(), sink.size());
+                ASSERT_EQ(sink, data5);
+            }
+            if (op->new_block == 12000) {
+                random_buffer.resize(options.block_size);
+                std::string sink(options.block_size, '\0');
+                ReadBlockData(reader, op, sink.data(), sink.size());
+                ASSERT_EQ(sink, random_buffer);
+            }
+        }
+
+        iter->Next();
+    }
+}
+
+std::vector<TestParam> GetTestConfigs() {
+    std::vector<TestParam> testParams;
+
+    std::vector<int> block_sizes = {4_KiB, 8_KiB, 16_KiB, 32_KiB, 64_KiB, 128_KiB, 256_KiB};
+    std::vector<std::string> compression_algo = {"none", "lz4", "zstd", "gz"};
+    std::vector<int> threads = {1, 2};
+    // This will also test batch size
+    std::vector<size_t> cluster_ops = {1, 256};
+
+    // This should test 112 combination
+    for (auto block : block_sizes) {
+        for (auto compression : compression_algo) {
+            for (auto thread : threads) {
+                for (auto cluster : cluster_ops) {
+                    TestParam param;
+                    param.block_size = block;
+                    param.compression = compression;
+                    param.num_threads = thread;
+                    param.cluster_ops = cluster;
+                    testParams.push_back(std::move(param));
+                }
+            }
+        }
+    }
+
+    return testParams;
+}
+
+INSTANTIATE_TEST_SUITE_P(CompressorsWithVariableBlocks, VariableBlockTest,
+                         ::testing::ValuesIn(GetTestConfigs()));
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
index 75cd111..0993dba 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
@@ -134,7 +134,7 @@
         return false;
     }
     if (parts.size() > 1) {
-        if (!android::base::ParseUint(parts[1], &compression_.compression_level)) {
+        if (!android::base::ParseInt(parts[1], &compression_.compression_level)) {
             LOG(ERROR) << "failed to parse compression level invalid type: " << parts[1];
             return false;
         }
@@ -185,7 +185,7 @@
     for (int i = 0; i < num_compress_threads_; i++) {
         std::unique_ptr<ICompressor> compressor =
                 ICompressor::Create(compression_, header_.block_size);
-        auto wt = std::make_unique<CompressWorker>(std::move(compressor), header_.block_size);
+        auto wt = std::make_unique<CompressWorker>(std::move(compressor));
         threads_.emplace_back(std::async(std::launch::async, &CompressWorker::RunThread, wt.get()));
         compress_threads_.push_back(std::move(wt));
     }
@@ -353,7 +353,7 @@
         if (i == num_threads - 1) {
             num_blocks_per_thread = num_blocks;
         }
-        worker->EnqueueCompressBlocks(iter, num_blocks_per_thread);
+        worker->EnqueueCompressBlocks(iter, header_.block_size, num_blocks_per_thread);
         iter += (num_blocks_per_thread * header_.block_size);
         num_blocks -= num_blocks_per_thread;
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
index 05de2ad..6a37aa7 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
@@ -81,8 +81,8 @@
     int num_compress_threads_ = 1;
     std::vector<std::unique_ptr<CompressWorker>> compress_threads_;
     std::vector<std::future<bool>> threads_;
-    std::vector<std::basic_string<uint8_t>> compressed_buf_;
-    std::vector<std::basic_string<uint8_t>>::iterator buf_iter_;
+    std::vector<std::vector<uint8_t>> compressed_buf_;
+    std::vector<std::vector<uint8_t>>::iterator buf_iter_;
 
     std::vector<std::unique_ptr<CowOperationV2>> opbuffer_vec_;
     std::vector<std::unique_ptr<uint8_t[]>> databuffer_vec_;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index be6b6da..1117ec9 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -37,6 +37,7 @@
 #include <libsnapshot/cow_compress.h>
 #include <libsnapshot_cow/parser_v3.h>
 #include <linux/fs.h>
+#include <storage_literals/storage_literals.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 #include <numeric>
@@ -54,6 +55,7 @@
 
 static_assert(sizeof(off_t) == sizeof(uint64_t));
 
+using namespace android::storage_literals;
 using android::base::unique_fd;
 
 // Divide |x| by |y| and round up to the nearest integer.
@@ -77,9 +79,9 @@
     threads_.clear();
     for (size_t i = 0; i < num_compress_threads_; i++) {
         std::unique_ptr<ICompressor> compressor =
-                ICompressor::Create(compression_, header_.block_size);
+                ICompressor::Create(compression_, header_.max_compression_size);
         auto&& wt = compress_threads_.emplace_back(
-                std::make_unique<CompressWorker>(std::move(compressor), header_.block_size));
+                std::make_unique<CompressWorker>(std::move(compressor)));
         threads_.emplace_back(std::thread([wt = wt.get()]() { wt->RunThread(); }));
     }
     LOG(INFO) << num_compress_threads_ << " thread used for compression";
@@ -110,11 +112,16 @@
     header_.op_count = 0;
     header_.op_count_max = 0;
     header_.compression_algorithm = kCowCompressNone;
-    return;
+    header_.max_compression_size = options_.compression_factor;
 }
 
 bool CowWriterV3::ParseOptions() {
-    num_compress_threads_ = std::max(options_.num_compress_threads, 1);
+    if (!header_.max_compression_size || !IsBlockAligned(header_.max_compression_size)) {
+        LOG(ERROR) << "Invalid compression factor: " << header_.max_compression_size;
+        return false;
+    }
+
+    num_compress_threads_ = std::max(int(options_.num_compress_threads), 1);
     auto parts = android::base::Split(options_.compression, ",");
     if (parts.size() > 2) {
         LOG(ERROR) << "failed to parse compression parameters: invalid argument count: "
@@ -129,8 +136,20 @@
     header_.compression_algorithm = *algorithm;
     header_.op_count_max = options_.op_count_max;
 
+    if (!IsEstimating() && header_.op_count_max == 0) {
+        if (!options_.max_blocks.has_value()) {
+            LOG(ERROR) << "can't size op buffer size since op_count_max is 0 and max_blocks is not "
+                          "set.";
+            return false;
+        }
+        LOG(INFO) << "op count max is read in as 0. Setting to "
+                     "num blocks in partition "
+                  << options_.max_blocks.value();
+        header_.op_count_max = options_.max_blocks.value();
+    }
+
     if (parts.size() > 1) {
-        if (!android::base::ParseUint(parts[1], &compression_.compression_level)) {
+        if (!android::base::ParseInt(parts[1], &compression_.compression_level)) {
             LOG(ERROR) << "failed to parse compression level invalid type: " << parts[1];
             return false;
         }
@@ -141,20 +160,22 @@
 
     compression_.algorithm = *algorithm;
     if (compression_.algorithm != kCowCompressNone) {
-        compressor_ = ICompressor::Create(compression_, header_.block_size);
+        compressor_ = ICompressor::Create(compression_, header_.max_compression_size);
         if (compressor_ == nullptr) {
             LOG(ERROR) << "Failed to create compressor for " << compression_.algorithm;
             return false;
         }
-        if (options_.cluster_ops &&
-            (android::base::GetBoolProperty("ro.virtual_ab.batch_writes", false) ||
-             options_.batch_write)) {
-            batch_size_ = std::max<size_t>(options_.cluster_ops, 1);
-            data_vec_.reserve(batch_size_);
-            cached_data_.reserve(batch_size_);
-            cached_ops_.reserve(batch_size_);
-        }
     }
+
+    if (options_.cluster_ops &&
+        (android::base::GetBoolProperty("ro.virtual_ab.batch_writes", false) ||
+         options_.batch_write)) {
+        batch_size_ = std::max<size_t>(options_.cluster_ops, 1);
+        data_vec_.reserve(batch_size_);
+        cached_data_.reserve(batch_size_);
+        cached_ops_.reserve(batch_size_ * kNonDataOpBufferSize);
+    }
+
     if (batch_size_ > 1) {
         LOG(INFO) << "Batch writes: enabled with batch size " << batch_size_;
     } else {
@@ -165,6 +186,7 @@
         num_compress_threads_ = options_.num_compress_threads;
     }
     InitWorkers();
+
     return true;
 }
 
@@ -192,7 +214,6 @@
             return false;
         }
     }
-
     return true;
 }
 
@@ -280,6 +301,14 @@
     return true;
 }
 
+size_t CowWriterV3::CachedDataSize() const {
+    size_t size = 0;
+    for (const auto& i : cached_data_) {
+        size += i.size();
+    }
+    return size;
+}
+
 bool CowWriterV3::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
     if (!CheckOpCount(num_blocks)) {
         return false;
@@ -300,17 +329,11 @@
 }
 
 bool CowWriterV3::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
-    if (!CheckOpCount(size / header_.block_size)) {
-        return false;
-    }
     return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp);
 }
 
 bool CowWriterV3::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
                                 uint32_t old_block, uint16_t offset) {
-    if (!CheckOpCount(size / header_.block_size)) {
-        return false;
-    }
     return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp);
 }
 
@@ -318,7 +341,51 @@
     // Allow bigger batch sizes for ops without data. A single CowOperationV3
     // struct uses 14 bytes of memory, even if we cache 200 * 16 ops in memory,
     // it's only ~44K.
-    return cached_data_.size() >= batch_size_ || cached_ops_.size() >= batch_size_ * 16;
+    return CachedDataSize() >= batch_size_ * header_.block_size ||
+           cached_ops_.size() >= batch_size_ * kNonDataOpBufferSize;
+}
+
+bool CowWriterV3::ConstructCowOpCompressedBuffers(uint64_t new_block_start, const void* data,
+                                                  uint64_t old_block, uint16_t offset,
+                                                  CowOperationType type, size_t blocks_to_write) {
+    size_t compressed_bytes = 0;
+    auto&& blocks = CompressBlocks(blocks_to_write, data, type);
+    if (blocks.empty()) {
+        LOG(ERROR) << "Failed to compress blocks " << new_block_start << ", " << blocks_to_write
+                   << ", actual number of blocks received from compressor " << blocks.size();
+        return false;
+    }
+    if (!CheckOpCount(blocks.size())) {
+        return false;
+    }
+    size_t blocks_written = 0;
+    for (size_t blk_index = 0; blk_index < blocks.size(); blk_index++) {
+        CowOperation& op = cached_ops_.emplace_back();
+        auto& vec = data_vec_.emplace_back();
+        CompressedBuffer buffer = std::move(blocks[blk_index]);
+        auto& compressed_data = cached_data_.emplace_back(std::move(buffer.compressed_data));
+        op.new_block = new_block_start + blocks_written;
+
+        op.set_type(type);
+        op.set_compression_bits(std::log2(buffer.compression_factor / header_.block_size));
+
+        if (type == kCowXorOp) {
+            op.set_source((old_block + blocks_written) * header_.block_size + offset);
+        } else {
+            op.set_source(next_data_pos_ + compressed_bytes);
+        }
+
+        vec = {.iov_base = compressed_data.data(), .iov_len = compressed_data.size()};
+        op.data_length = vec.iov_len;
+        compressed_bytes += op.data_length;
+        blocks_written += (buffer.compression_factor / header_.block_size);
+    }
+    if (blocks_written != blocks_to_write) {
+        LOG(ERROR) << "Total compressed blocks: " << blocks_written
+                   << " Expected: " << blocks_to_write;
+        return false;
+    }
+    return true;
 }
 
 bool CowWriterV3::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
@@ -329,42 +396,23 @@
         return false;
     }
     const auto bytes = reinterpret_cast<const uint8_t*>(data);
-    const size_t num_blocks = (size / header_.block_size);
-
-    for (size_t i = 0; i < num_blocks;) {
-        const auto blocks_to_write =
-                std::min<size_t>(batch_size_ - cached_data_.size(), num_blocks - i);
-        size_t compressed_bytes = 0;
-        auto&& blocks = CompressBlocks(blocks_to_write, bytes + header_.block_size * i);
-        if (blocks.size() != blocks_to_write) {
-            LOG(ERROR) << "Failed to compress blocks " << new_block_start + i << ", "
-                       << blocks_to_write << ", actual number of blocks received from compressor "
-                       << blocks.size();
+    size_t num_blocks = (size / header_.block_size);
+    size_t total_written = 0;
+    while (total_written < num_blocks) {
+        size_t chunk = std::min(num_blocks - total_written, batch_size_);
+        if (!ConstructCowOpCompressedBuffers(new_block_start + total_written,
+                                             bytes + header_.block_size * total_written,
+                                             old_block + total_written, offset, type, chunk)) {
             return false;
         }
-        for (size_t j = 0; j < blocks_to_write; j++) {
-            CowOperation& op = cached_ops_.emplace_back();
-            auto& vec = data_vec_.emplace_back();
-            auto& compressed_data = cached_data_.emplace_back(std::move(blocks[j]));
-            op.new_block = new_block_start + i + j;
 
-            op.set_type(type);
-            if (type == kCowXorOp) {
-                op.set_source((old_block + i + j) * header_.block_size + offset);
-            } else {
-                op.set_source(next_data_pos_ + compressed_bytes);
-            }
-            vec = {.iov_base = compressed_data.data(), .iov_len = compressed_data.size()};
-            op.data_length = vec.iov_len;
-            compressed_bytes += op.data_length;
-        }
         if (NeedsFlush() && !FlushCacheOps()) {
             LOG(ERROR) << "EmitBlocks with compression: write failed. new block: "
                        << new_block_start << " compression: " << compression_.algorithm
                        << ", op type: " << type;
             return false;
         }
-        i += blocks_to_write;
+        total_written += chunk;
     }
 
     return true;
@@ -432,7 +480,8 @@
 
     header_.sequence_data_count = num_ops;
 
-    // Ensure next_data_pos_ is updated as previously initialized + the newly added sequence buffer.
+    // Ensure next_data_pos_ is updated as previously initialized + the newly added sequence
+    // buffer.
     CHECK_EQ(next_data_pos_ + header_.sequence_data_count * sizeof(uint32_t),
              GetDataOffset(header_));
     next_data_pos_ = GetDataOffset(header_);
@@ -462,8 +511,7 @@
         }
         bytes_written += op.data_length;
     }
-    if (!WriteOperation({cached_ops_.data(), cached_ops_.size()},
-                        {data_vec_.data(), data_vec_.size()})) {
+    if (!WriteOperation(cached_ops_, data_vec_)) {
         LOG(ERROR) << "Failed to flush " << cached_ops_.size() << " ops to disk";
         return false;
     }
@@ -473,59 +521,175 @@
     return true;
 }
 
-std::vector<std::basic_string<uint8_t>> CowWriterV3::CompressBlocks(const size_t num_blocks,
-                                                                    const void* data) {
-    const size_t num_threads = (num_blocks == 1) ? 1 : num_compress_threads_;
-    const size_t blocks_per_thread = DivRoundUp(num_blocks, num_threads);
-    std::vector<std::basic_string<uint8_t>> compressed_buf;
-    compressed_buf.clear();
-    const uint8_t* const iter = reinterpret_cast<const uint8_t*>(data);
-    if (compression_.algorithm == kCowCompressNone) {
-        for (size_t i = 0; i < num_blocks; i++) {
-            auto& buf = compressed_buf.emplace_back();
-            buf.resize(header_.block_size);
-            std::memcpy(buf.data(), iter + i * header_.block_size, header_.block_size);
-        }
-        return compressed_buf;
-    }
-    if (num_threads <= 1) {
-        if (!CompressWorker::CompressBlocks(compressor_.get(), header_.block_size, data, num_blocks,
-                                            &compressed_buf)) {
-            return {};
-        }
-    } else {
-        // Submit the blocks per thread. The retrieval of
-        // compressed buffers has to be done in the same order.
-        // We should not poll for completed buffers in a different order as the
-        // buffers are tightly coupled with block ordering.
-        for (size_t i = 0; i < num_threads; i++) {
-            CompressWorker* worker = compress_threads_[i].get();
-            const auto blocks_in_batch =
-                    std::min(num_blocks - i * blocks_per_thread, blocks_per_thread);
-            worker->EnqueueCompressBlocks(iter + i * blocks_per_thread * header_.block_size,
-                                          blocks_in_batch);
-        }
-
-        for (size_t i = 0; i < num_threads; i++) {
-            CompressWorker* worker = compress_threads_[i].get();
-            if (!worker->GetCompressedBuffers(&compressed_buf)) {
-                return {};
-            }
-        }
-    }
-    for (size_t i = 0; i < num_blocks; i++) {
-        auto& block = compressed_buf[i];
-        if (block.size() >= header_.block_size) {
-            block.resize(header_.block_size);
-            std::memcpy(block.data(), iter + header_.block_size * i, header_.block_size);
-        }
+size_t CowWriterV3::GetCompressionFactor(const size_t blocks_to_compress,
+                                         CowOperationType type) const {
+    // For XOR ops, we don't support bigger block size compression yet.
+    // For bigger block size support, snapshot-merge also has to changed. We
+    // aren't there yet; hence, just stick to 4k for now until
+    // snapshot-merge is ready for XOR operation.
+    if (type == kCowXorOp) {
+        return header_.block_size;
     }
 
-    return compressed_buf;
+    size_t compression_factor = header_.max_compression_size;
+    while (compression_factor > header_.block_size) {
+        size_t num_blocks = compression_factor / header_.block_size;
+        if (blocks_to_compress >= num_blocks) {
+            return compression_factor;
+        }
+        compression_factor >>= 1;
+    }
+    return header_.block_size;
 }
 
-bool CowWriterV3::WriteOperation(std::basic_string_view<CowOperationV3> ops,
-                                 std::basic_string_view<struct iovec> data) {
+std::vector<CowWriterV3::CompressedBuffer> CowWriterV3::ProcessBlocksWithNoCompression(
+        const size_t num_blocks, const void* data, CowOperationType type) {
+    size_t blocks_to_compress = num_blocks;
+    const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
+    std::vector<CompressedBuffer> compressed_vec;
+
+    while (blocks_to_compress) {
+        CompressedBuffer buffer;
+
+        const size_t compression_factor = GetCompressionFactor(blocks_to_compress, type);
+        size_t num_blocks = compression_factor / header_.block_size;
+
+        buffer.compression_factor = compression_factor;
+        buffer.compressed_data.resize(compression_factor);
+
+        // No compression. Just copy the data as-is.
+        std::memcpy(buffer.compressed_data.data(), iter, compression_factor);
+
+        compressed_vec.push_back(std::move(buffer));
+        blocks_to_compress -= num_blocks;
+        iter += compression_factor;
+    }
+    return compressed_vec;
+}
+
+std::vector<CowWriterV3::CompressedBuffer> CowWriterV3::ProcessBlocksWithCompression(
+        const size_t num_blocks, const void* data, CowOperationType type) {
+    size_t blocks_to_compress = num_blocks;
+    const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
+    std::vector<CompressedBuffer> compressed_vec;
+
+    while (blocks_to_compress) {
+        CompressedBuffer buffer;
+
+        const size_t compression_factor = GetCompressionFactor(blocks_to_compress, type);
+        size_t num_blocks = compression_factor / header_.block_size;
+
+        buffer.compression_factor = compression_factor;
+        // Compress the blocks
+        buffer.compressed_data = compressor_->Compress(iter, compression_factor);
+        if (buffer.compressed_data.empty()) {
+            PLOG(ERROR) << "Compression failed";
+            return {};
+        }
+
+        // Check if the buffer was indeed compressed
+        if (buffer.compressed_data.size() >= compression_factor) {
+            buffer.compressed_data.resize(compression_factor);
+            std::memcpy(buffer.compressed_data.data(), iter, compression_factor);
+        }
+
+        compressed_vec.push_back(std::move(buffer));
+        blocks_to_compress -= num_blocks;
+        iter += compression_factor;
+    }
+    return compressed_vec;
+}
+
+std::vector<CowWriterV3::CompressedBuffer> CowWriterV3::ProcessBlocksWithThreadedCompression(
+        const size_t num_blocks, const void* data, CowOperationType type) {
+    const size_t num_threads = num_compress_threads_;
+    const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
+
+    // We will alternate which thread to send compress work to. E.g. alternate between T1 and T2
+    // until all blocks are processed
+    std::vector<CompressedBuffer> compressed_vec;
+    int iteration = 0;
+    int blocks_to_compress = static_cast<int>(num_blocks);
+    while (blocks_to_compress) {
+        CompressedBuffer buffer;
+        CompressWorker* worker = compress_threads_[iteration % num_threads].get();
+
+        const size_t compression_factor = GetCompressionFactor(blocks_to_compress, type);
+        size_t num_blocks = compression_factor / header_.block_size;
+
+        worker->EnqueueCompressBlocks(iter, compression_factor, 1);
+        buffer.compression_factor = compression_factor;
+        compressed_vec.push_back(std::move(buffer));
+
+        iteration++;
+        iter += compression_factor;
+        blocks_to_compress -= num_blocks;
+    }
+
+    std::vector<std::vector<uint8_t>> compressed_buf;
+    std::vector<std::vector<std::vector<uint8_t>>> worker_buffers(num_threads);
+    compressed_buf.clear();
+    for (size_t i = 0; i < num_threads; i++) {
+        CompressWorker* worker = compress_threads_[i].get();
+        if (!worker->GetCompressedBuffers(&worker_buffers[i])) {
+            return {};
+        }
+    }
+    // compressed_vec | CB 1 | CB 2 | CB 3 | CB 4 | <-compressed buffers
+    //                   t1     t2     t1     t2    <- processed by these threads
+    // Ordering is important here. We need to retrieve the compressed data in the same order we
+    // processed it and assume that that we submit data beginning with the first thread and then
+    // round robin the consecutive data calls. We need to Fetch compressed buffers from the
+    // threads via the same ordering
+    for (size_t i = 0; i < compressed_vec.size(); i++) {
+        compressed_buf.emplace_back(worker_buffers[i % num_threads][i / num_threads]);
+    }
+
+    if (compressed_vec.size() != compressed_buf.size()) {
+        LOG(ERROR) << "Compressed buffer size: " << compressed_buf.size()
+                   << " - Expected: " << compressed_vec.size();
+        return {};
+    }
+
+    iter = reinterpret_cast<const uint8_t*>(data);
+    // Walk through all the compressed buffers
+    for (size_t i = 0; i < compressed_buf.size(); i++) {
+        auto& buffer = compressed_vec[i];
+        auto& block = compressed_buf[i];
+        size_t block_size = buffer.compression_factor;
+        // Check if the blocks was indeed compressed
+        if (block.size() >= block_size) {
+            buffer.compressed_data.resize(block_size);
+            std::memcpy(buffer.compressed_data.data(), iter, block_size);
+        } else {
+            // Compressed block
+            buffer.compressed_data.resize(block.size());
+            std::memcpy(buffer.compressed_data.data(), block.data(), block.size());
+        }
+        iter += block_size;
+    }
+    return compressed_vec;
+}
+
+std::vector<CowWriterV3::CompressedBuffer> CowWriterV3::CompressBlocks(const size_t num_blocks,
+                                                                       const void* data,
+                                                                       CowOperationType type) {
+    if (compression_.algorithm == kCowCompressNone) {
+        return ProcessBlocksWithNoCompression(num_blocks, data, type);
+    }
+
+    const size_t num_threads = (num_blocks == 1) ? 1 : num_compress_threads_;
+
+    // If no threads are required, just compress the blocks inline.
+    if (num_threads <= 1) {
+        return ProcessBlocksWithCompression(num_blocks, data, type);
+    }
+
+    return ProcessBlocksWithThreadedCompression(num_blocks, data, type);
+}
+
+bool CowWriterV3::WriteOperation(std::span<const CowOperationV3> ops,
+                                 std::span<const struct iovec> data) {
     const auto total_data_size =
             std::transform_reduce(data.begin(), data.end(), 0, std::plus<size_t>{},
                                   [](const struct iovec& a) { return a.iov_len; });
@@ -552,13 +716,29 @@
         return false;
     }
     if (!data.empty()) {
-        const auto ret = pwritev(fd_, data.data(), data.size(), next_data_pos_);
-        if (ret != total_data_size) {
-            PLOG(ERROR) << "write failed for data of size: " << data.size()
-                        << " at offset: " << next_data_pos_ << " " << ret;
+        int total_written = 0;
+        int i = 0;
+        while (i < data.size()) {
+            int chunk = std::min(static_cast<int>(data.size() - i), IOV_MAX);
+
+            const auto ret = pwritev(fd_, data.data() + i, chunk, next_data_pos_ + total_written);
+            if (ret < 0) {
+                PLOG(ERROR) << "write failed chunk size of: " << chunk
+                            << " at offset: " << next_data_pos_ + total_written << " " << errno;
+                return false;
+            }
+            total_written += ret;
+            i += chunk;
+        }
+        if (total_written != total_data_size) {
+            PLOG(ERROR) << "write failed for data vector of size: " << data.size()
+                        << " and total data length: " << total_data_size
+                        << " at offset: " << next_data_pos_ << " " << errno
+                        << ", only wrote: " << total_written;
             return false;
         }
     }
+
     header_.op_count += ops.size();
     next_data_pos_ += total_data_size;
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index b19af60..871ed27 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -15,15 +15,23 @@
 #pragma once
 
 #include <android-base/logging.h>
+#include <span>
 #include <string_view>
 #include <thread>
 #include <vector>
 
+#include <libsnapshot/cow_format.h>
+#include <storage_literals/storage_literals.h>
 #include "writer_base.h"
 
 namespace android {
 namespace snapshot {
 
+using namespace android::storage_literals;
+// This is a multiple on top of the number of data ops that can be stored in our cache at once. This
+// is added so that we can cache more non-data ops as it takes up less space.
+static constexpr uint32_t kNonDataOpBufferSize = 16;
+
 class CowWriterV3 : public CowWriterBase {
   public:
     explicit CowWriterV3(const CowOptions& options, android::base::unique_fd&& fd);
@@ -43,20 +51,50 @@
     virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
 
   private:
+    struct CompressedBuffer {
+        size_t compression_factor;
+        std::vector<uint8_t> compressed_data;
+    };
     void SetupHeaders();
     bool NeedsFlush() const;
     bool ParseOptions();
     bool OpenForWrite();
     bool OpenForAppend(uint64_t label);
-    bool WriteOperation(std::basic_string_view<CowOperationV3> op,
-                        std::basic_string_view<struct iovec> data);
+    bool WriteOperation(std::span<const CowOperationV3> op, std::span<const struct iovec> data);
     bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
                     uint16_t offset, CowOperationType type);
+    bool ConstructCowOpCompressedBuffers(uint64_t new_block_start, const void* data,
+                                         uint64_t old_block, uint16_t offset, CowOperationType type,
+                                         size_t blocks_to_write);
     bool CheckOpCount(size_t op_count);
 
   private:
-    std::vector<std::basic_string<uint8_t>> CompressBlocks(const size_t num_blocks,
-                                                           const void* data);
+    std::vector<CompressedBuffer> ProcessBlocksWithNoCompression(const size_t num_blocks,
+                                                                 const void* data,
+                                                                 CowOperationType type);
+    std::vector<CompressedBuffer> ProcessBlocksWithCompression(const size_t num_blocks,
+                                                               const void* data,
+                                                               CowOperationType type);
+    std::vector<CompressedBuffer> ProcessBlocksWithThreadedCompression(const size_t num_blocks,
+                                                                       const void* data,
+                                                                       CowOperationType type);
+    std::vector<CompressedBuffer> CompressBlocks(const size_t num_blocks, const void* data,
+                                                 CowOperationType type);
+    size_t GetCompressionFactor(const size_t blocks_to_compress, CowOperationType type) const;
+
+    constexpr bool IsBlockAligned(const size_t size) {
+        // These are the only block size supported. Block size beyond 256k
+        // may impact random read performance post OTA boot.
+        const size_t values[] = {4_KiB, 8_KiB, 16_KiB, 32_KiB, 64_KiB, 128_KiB, 256_KiB};
+
+        auto it = std::lower_bound(std::begin(values), std::end(values), size);
+
+        if (it != std::end(values) && *it == size) {
+            return true;
+        }
+        return false;
+    }
+    size_t CachedDataSize() const;
     bool ReadBackVerification();
     bool FlushCacheOps();
     void InitWorkers();
@@ -76,7 +114,7 @@
     int num_compress_threads_ = 1;
     size_t batch_size_ = 1;
     std::vector<CowOperationV3> cached_ops_;
-    std::vector<std::basic_string<uint8_t>> cached_data_;
+    std::vector<std::vector<uint8_t>> cached_data_;
     std::vector<struct iovec> data_vec_;
 
     std::vector<std::thread> threads_;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
index bd5c8cb..1adbba2 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.h
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -59,6 +59,11 @@
     // True if snapuserd COWs are enabled.
     bool using_snapuserd = false;
     std::string compression_algorithm;
+    uint64_t compression_factor;
+    uint32_t read_ahead_size;
+
+    // Enable direct reads on source device
+    bool o_direct;
 
     // True if multi-threaded compression should be enabled
     bool enable_threading;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
index cf26a16..a4a2c1a 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -39,6 +39,7 @@
 namespace android {
 namespace snapshot {
 
+// @VsrTest = 3.7.6
 class PartitionCowCreatorTest : public ::testing::Test {
   public:
     void SetUp() override {
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 9eb41b2..c01360e 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -24,7 +24,6 @@
 #include <filesystem>
 #include <optional>
 #include <thread>
-#include <unordered_set>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -46,7 +45,6 @@
 #include <android/snapshot/snapshot.pb.h>
 #include <libsnapshot/snapshot_stats.h>
 #include "device_info.h"
-#include "libsnapshot_cow/parser_v2.h"
 #include "partition_cow_creator.h"
 #include "snapshot_metadata_updater.h"
 #include "utility.h"
@@ -103,8 +101,7 @@
  * time, they could use O_DIRECT functionality wherein the I/O to the source
  * block device will be O_DIRECT.
  */
-static constexpr auto kCowReadAheadSizeKb = 32;
-static constexpr auto kSourceReadAheadSizeKb = 32;
+static constexpr auto kReadAheadSizeKb = 32;
 
 // Note: IImageManager is an incomplete type in the header, so the default
 // destructor doesn't work.
@@ -265,7 +262,6 @@
     auto boot_file = GetSnapshotBootIndicatorPath();
     std::string contents;
     if (!android::base::ReadFileToString(boot_file, &contents)) {
-        PLOG(WARNING) << "Cannot read " << boot_file;
         return {};
     }
     return contents;
@@ -420,6 +416,8 @@
     status->set_metadata_sectors(0);
     status->set_using_snapuserd(cow_creator->using_snapuserd);
     status->set_compression_algorithm(cow_creator->compression_algorithm);
+    status->set_compression_factor(cow_creator->compression_factor);
+    status->set_read_ahead_size(cow_creator->read_ahead_size);
     if (cow_creator->enable_threading) {
         status->set_enable_threading(cow_creator->enable_threading);
     }
@@ -507,8 +505,6 @@
         // When snapshots are on current slot, we determine the size
         // of block device based on the number of COW operations. We cannot
         // use base device as it will be from older image.
-        size_t num_ops = 0;
-        uint64_t dev_sz = 0;
         unique_fd fd(open(cow_file.c_str(), O_RDONLY | O_CLOEXEC));
         if (fd < 0) {
             PLOG(ERROR) << "Failed to open " << cow_file;
@@ -521,13 +517,18 @@
             return false;
         }
 
+        uint64_t dev_sz = 0;
         const auto& header = reader.GetHeader();
-        if (header.prefix.major_version > 2) {
-            LOG(ERROR) << "COW format not supported";
-            return false;
+        if (header.prefix.major_version == 2) {
+            const size_t num_ops = reader.get_num_total_data_ops();
+            dev_sz = (num_ops * header.block_size);
+        } else {
+            // create_snapshot will skip in-place copy ops. Hence, fetch this
+            // information directly from v3 header.
+            const auto& v3_header = reader.header_v3();
+            dev_sz = v3_header.op_count_max * v3_header.block_size;
         }
-        num_ops = reader.get_num_total_data_ops();
-        dev_sz = (num_ops * header.block_size);
+
         base_sectors = dev_sz >> 9;
     } else {
         // For userspace snapshots, the size of the base device is taken as the
@@ -1141,8 +1142,8 @@
     return result;
 }
 
-auto SnapshotManager::CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel)
-        -> MergeResult {
+auto SnapshotManager::CheckMergeState(LockedFile* lock,
+                                      const std::function<bool()>& before_cancel) -> MergeResult {
     SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
     switch (update_status.state()) {
         case UpdateState::None:
@@ -1696,6 +1697,9 @@
         if (UpdateUsesIouring(lock.get())) {
             snapuserd_argv->emplace_back("-io_uring");
         }
+        if (UpdateUsesODirect(lock.get())) {
+            snapuserd_argv->emplace_back("-o_direct");
+        }
     }
 
     size_t num_cows = 0;
@@ -1764,9 +1768,8 @@
                                base_path_merge;
                 snapuserd_argv->emplace_back(std::move(message));
             }
-
-            SetReadAheadSize(cow_image_device, kCowReadAheadSizeKb);
-            SetReadAheadSize(source_device, kSourceReadAheadSizeKb);
+            SetReadAheadSize(cow_image_device, snapshot_status.read_ahead_size());
+            SetReadAheadSize(source_device, snapshot_status.read_ahead_size());
 
             // Do not attempt to connect to the new snapuserd yet, it hasn't
             // been started. We do however want to wait for the misc device
@@ -2114,6 +2117,58 @@
     return update_status.io_uring_enabled();
 }
 
+bool SnapshotManager::UpdateUsesODirect(LockedFile* lock) {
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    return update_status.o_direct();
+}
+
+/*
+ * Please see b/304829384 for more details.
+ *
+ * In Android S, we use dm-snapshot for mounting snapshots and snapshot-merge
+ * process. If the vendor partition continues to be on Android S, then
+ * "snapuserd" binary in first stage ramdisk will be from vendor partition.
+ * Thus, we need to maintain backward compatibility.
+ *
+ * Now, We take a two step approach to maintain the backward compatibility:
+ *
+ * 1: During OTA installation, we will continue to use "user-space" snapshots
+ * for OTA installation as both update-engine and snapuserd binary will be from system partition.
+ * However, during installation, we mark "legacy_snapuserd" in
+ * SnapshotUpdateStatus file to mark that this is a path to support backward compatibility.
+ * Thus, this function will return "false" during OTA installation.
+ *
+ * 2: Post OTA reboot, there are two key steps:
+ *    a: During first stage init, "init" and "snapuserd" could be from vendor
+ *    partition. This could be from Android S. Thus, the snapshot mount path
+ *    will be based off dm-snapshot.
+ *
+ *    b: Post selinux transition, "init" and "update-engine" will be "system"
+ *    partition. Now, since the snapshots are mounted off dm-snapshot,
+ *    update-engine interaction with "snapuserd" should work based off
+ *    dm-snapshots.
+ *
+ *    TL;DR: update-engine will use the "system" snapuserd for installing new
+ *    updates (this is safe as there is no "vendor" snapuserd running during
+ *    installation). Post reboot, update-engine will use the legacy path when
+ *    communicating with "vendor" snapuserd that was started in first-stage
+ *    init. Hence, this function checks:
+ *         i: Are we in post OTA reboot
+ *         ii: Is the Vendor from Android 12
+ *         iii: If both (i) and (ii) are true, then use the dm-snapshot based
+ *         approach.
+ *
+ */
+bool SnapshotManager::IsLegacySnapuserdPostReboot() {
+    if (is_legacy_snapuserd_.has_value() && is_legacy_snapuserd_.value() == true) {
+        auto slot = GetCurrentSlot();
+        if (slot == Slot::Target) {
+            return true;
+        }
+    }
+    return false;
+}
+
 bool SnapshotManager::UpdateUsesUserSnapshots() {
     // This and the following function is constantly
     // invoked during snapshot merge. We want to avoid
@@ -2125,7 +2180,12 @@
     // during merge phase. Hence, once we know that
     // the value is read from disk the very first time,
     // it is safe to read successive checks from memory.
+
     if (is_snapshot_userspace_.has_value()) {
+        // Check if legacy snapuserd is running post OTA reboot
+        if (IsLegacySnapuserdPostReboot()) {
+            return false;
+        }
         return is_snapshot_userspace_.value();
     }
 
@@ -2136,13 +2196,16 @@
 }
 
 bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) {
-    // See UpdateUsesUserSnapshots()
-    if (is_snapshot_userspace_.has_value()) {
-        return is_snapshot_userspace_.value();
+    if (!is_snapshot_userspace_.has_value()) {
+        SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+        is_snapshot_userspace_ = update_status.userspace_snapshots();
+        is_legacy_snapuserd_ = update_status.legacy_snapuserd();
     }
 
-    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
-    is_snapshot_userspace_ = update_status.userspace_snapshots();
+    if (IsLegacySnapuserdPostReboot()) {
+        return false;
+    }
+
     return is_snapshot_userspace_.value();
 }
 
@@ -2796,8 +2859,8 @@
     return true;
 }
 
-auto SnapshotManager::OpenFile(const std::string& file, int lock_flags)
-        -> std::unique_ptr<LockedFile> {
+auto SnapshotManager::OpenFile(const std::string& file,
+                               int lock_flags) -> std::unique_ptr<LockedFile> {
     unique_fd fd(open(file.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
     if (fd < 0) {
         PLOG(ERROR) << "Open failed: " << file;
@@ -2960,6 +3023,8 @@
         status.set_merge_phase(old_status.merge_phase());
         status.set_userspace_snapshots(old_status.userspace_snapshots());
         status.set_io_uring_enabled(old_status.io_uring_enabled());
+        status.set_legacy_snapuserd(old_status.legacy_snapuserd());
+        status.set_o_direct(old_status.o_direct());
     }
     return WriteSnapshotUpdateStatus(lock, status);
 }
@@ -3206,6 +3271,8 @@
     // Deduce supported features.
     bool userspace_snapshots = CanUseUserspaceSnapshots();
     bool legacy_compression = GetLegacyCompressionEnabledProperty();
+    bool is_legacy_snapuserd = IsVendorFromAndroid12();
+
     if (!vabc_disable_reason.empty()) {
         if (userspace_snapshots) {
             LOG(INFO) << "Userspace snapshots disabled: " << vabc_disable_reason;
@@ -3215,6 +3282,7 @@
         }
         userspace_snapshots = false;
         legacy_compression = false;
+        is_legacy_snapuserd = false;
     }
 
     if (legacy_compression || userspace_snapshots) {
@@ -3227,20 +3295,30 @@
         }
     }
 
+    if (!userspace_snapshots && is_legacy_snapuserd && legacy_compression) {
+        userspace_snapshots = true;
+        LOG(INFO) << "Vendor from Android 12. Enabling userspace snapshot for OTA install";
+    }
+
     const bool using_snapuserd = userspace_snapshots || legacy_compression;
     if (!using_snapuserd) {
         LOG(INFO) << "Using legacy Virtual A/B (dm-snapshot)";
     }
 
     std::string compression_algorithm;
+    uint64_t compression_factor{};
     if (using_snapuserd) {
         compression_algorithm = dap_metadata.vabc_compression_param();
+        compression_factor = dap_metadata.compression_factor();
         if (compression_algorithm.empty()) {
             // Older OTAs don't set an explicit compression type, so default to gz.
             compression_algorithm = "gz";
         }
+        LOG(INFO) << "using compression algorithm: " << compression_algorithm
+                  << ", max compressible block size: " << compression_factor;
     }
-
+    auto read_ahead_size =
+            android::base::GetUintProperty<uint>("ro.virtual_ab.read_ahead_size", kReadAheadSizeKb);
     PartitionCowCreator cow_creator{
             .target_metadata = target_metadata.get(),
             .target_suffix = target_suffix,
@@ -3251,7 +3329,10 @@
             .extra_extents = {},
             .using_snapuserd = using_snapuserd,
             .compression_algorithm = compression_algorithm,
+            .compression_factor = compression_factor,
+            .read_ahead_size = read_ahead_size,
     };
+
     if (dap_metadata.vabc_feature_set().has_threaded()) {
         cow_creator.enable_threading = dap_metadata.vabc_feature_set().threaded();
     }
@@ -3318,6 +3399,14 @@
             status.set_io_uring_enabled(true);
             LOG(INFO) << "io_uring for snapshots enabled";
         }
+        if (GetODirectEnabledProperty()) {
+            status.set_o_direct(true);
+            LOG(INFO) << "o_direct for source image enabled";
+        }
+        if (is_legacy_snapuserd) {
+            status.set_legacy_snapuserd(true);
+            LOG(INFO) << "Setting legacy_snapuserd to true";
+        }
     } else if (legacy_compression) {
         LOG(INFO) << "Virtual A/B using legacy snapuserd";
     } else {
@@ -3325,6 +3414,7 @@
     }
 
     is_snapshot_userspace_.emplace(userspace_snapshots);
+    is_legacy_snapuserd_.emplace(is_legacy_snapuserd);
 
     if (!device()->IsTestDevice() && using_snapuserd) {
         // Terminate stale daemon if any
@@ -3553,6 +3643,7 @@
             options.compression = it->second.compression_algorithm();
             if (cow_version >= 3) {
                 options.op_count_max = it->second.estimated_ops_buffer_size();
+                options.max_blocks = {it->second.device_size() / options.block_size};
             }
 
             auto writer = CreateCowWriter(cow_version, options, std::move(fd));
@@ -3666,6 +3757,7 @@
     cow_options.batch_write = status.batched_writes();
     cow_options.num_compress_threads = status.enable_threading() ? 2 : 1;
     cow_options.op_count_max = status.estimated_ops_buffer_size();
+    cow_options.compression_factor = status.compression_factor();
     // Disable scratch space for vts tests
     if (device()->IsTestDevice()) {
         cow_options.scratch_space = false;
@@ -3749,6 +3841,7 @@
     ss << "Using snapuserd: " << update_status.using_snapuserd() << std::endl;
     ss << "Using userspace snapshots: " << update_status.userspace_snapshots() << std::endl;
     ss << "Using io_uring: " << update_status.io_uring_enabled() << std::endl;
+    ss << "Using o_direct: " << update_status.o_direct() << std::endl;
     ss << "Using XOR compression: " << GetXorCompressionEnabledProperty() << std::endl;
     ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
     ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
@@ -3793,6 +3886,7 @@
         ss << "    allocated sectors: " << status.sectors_allocated() << std::endl;
         ss << "    metadata sectors: " << status.metadata_sectors() << std::endl;
         ss << "    compression: " << status.compression_algorithm() << std::endl;
+        ss << "    compression factor: " << status.compression_factor() << std::endl;
         ss << "    merge phase: " << DecideMergePhase(status) << std::endl;
     }
     os << ss.rdbuf();
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index e538d50..3299ec5 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -56,14 +56,12 @@
 
 #if defined(LIBSNAPSHOT_TEST_VAB_LEGACY)
 #define DEFAULT_MODE "vab-legacy"
-#elif defined(LIBSNAPSHOT_TEST_VABC_LEGACY)
-#define DEFAULT_MODE "vabc-legacy"
 #else
 #define DEFAULT_MODE ""
 #endif
 
 DEFINE_string(force_mode, DEFAULT_MODE,
-              "Force testing older modes (vab-legacy, vabc-legacy) ignoring device config.");
+              "Force testing older modes (vab-legacy) ignoring device config.");
 DEFINE_string(force_iouring_disable, "",
               "Force testing mode (iouring_disabled) - disable io_uring");
 DEFINE_string(compression_method, "gz", "Default compression algorithm.");
@@ -104,6 +102,7 @@
 
 void MountMetadata();
 
+// @VsrTest = 3.7.6
 class SnapshotTest : public ::testing::Test {
   public:
     SnapshotTest() : dm_(DeviceMapper::Instance()) {}
@@ -139,17 +138,10 @@
     void SetupProperties() {
         std::unordered_map<std::string, std::string> properties;
 
-        ASSERT_TRUE(android::base::SetProperty("snapuserd.test.dm.snapshots", "0"))
-                << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
         ASSERT_TRUE(android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0"))
                 << "Failed to set property: snapuserd.test.io_uring.disabled";
 
-        if (FLAGS_force_mode == "vabc-legacy") {
-            ASSERT_TRUE(android::base::SetProperty("snapuserd.test.dm.snapshots", "1"))
-                    << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
-            properties["ro.virtual_ab.compression.enabled"] = "true";
-            properties["ro.virtual_ab.userspace.snapshots.enabled"] = "false";
-        } else if (FLAGS_force_mode == "vab-legacy") {
+        if (FLAGS_force_mode == "vab-legacy") {
             properties["ro.virtual_ab.compression.enabled"] = "false";
             properties["ro.virtual_ab.userspace.snapshots.enabled"] = "false";
         }
@@ -2655,6 +2647,41 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 }
 
+TEST_F(SnapshotTest, FlagCheck) {
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "Skipping snapuserd test";
+    }
+    ASSERT_TRUE(AcquireLock());
+
+    SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get());
+
+    // Set flags in proto
+    status.set_o_direct(true);
+    status.set_io_uring_enabled(true);
+    status.set_userspace_snapshots(true);
+
+    sm->WriteSnapshotUpdateStatus(lock_.get(), status);
+    // Ensure a connection to the second-stage daemon, but use the first-stage
+    // code paths thereafter.
+    ASSERT_TRUE(sm->EnsureSnapuserdConnected());
+    sm->set_use_first_stage_snapuserd(true);
+
+    auto init = NewManagerForFirstStageMount("_b");
+    ASSERT_NE(init, nullptr);
+
+    lock_ = nullptr;
+
+    std::vector<std::string> snapuserd_argv;
+    ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SELINUX_DETACH,
+                                            &snapuserd_argv));
+    ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-o_direct") !=
+                snapuserd_argv.end());
+    ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-io_uring") !=
+                snapuserd_argv.end());
+    ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-user_snapshot") !=
+                snapuserd_argv.end());
+}
+
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
                              public WithParamInterface<std::tuple<uint32_t, bool>> {
   public:
@@ -2891,22 +2918,29 @@
     ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
     gflags::ParseCommandLineFlags(&argc, &argv, false);
 
-    android::base::SetProperty("ctl.stop", "snapuserd");
+    bool vab_legacy = false;
+    if (FLAGS_force_mode == "vab-legacy") {
+        vab_legacy = true;
+    }
 
-    std::unordered_set<std::string> modes = {"", "vab-legacy", "vabc-legacy"};
+    if (!vab_legacy) {
+        // This is necessary if the configuration we're testing doesn't match the device.
+        android::base::SetProperty("ctl.stop", "snapuserd");
+        android::snapshot::KillSnapuserd();
+    }
+
+    std::unordered_set<std::string> modes = {"", "vab-legacy"};
     if (modes.count(FLAGS_force_mode) == 0) {
         std::cerr << "Unexpected force_config argument\n";
         return 1;
     }
 
-    // This is necessary if the configuration we're testing doesn't match the device.
-    android::snapshot::KillSnapuserd();
-
     int ret = RUN_ALL_TESTS();
 
-    android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
     android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
 
-    android::snapshot::KillSnapuserd();
+    if (!vab_legacy) {
+        android::snapshot::KillSnapuserd();
+    }
     return ret;
 }
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index 0396a55..0158d4d 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -15,6 +15,7 @@
 //
 
 #include <sysexits.h>
+#include <unistd.h>
 
 #include <chrono>
 #include <filesystem>
@@ -53,6 +54,9 @@
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 using namespace android::storage_literals;
+using android::base::LogdLogger;
+using android::base::StderrLogger;
+using android::base::TeeLogger;
 using android::fs_mgr::CreateLogicalPartitionParams;
 using android::fs_mgr::FindPartition;
 using android::fs_mgr::GetPartitionSize;
@@ -79,13 +83,18 @@
                  "  revert-snapshots\n"
                  "    Prepares devices to boot without snapshots on next boot.\n"
                  "    This does not delete the snapshot. It only removes the indicators\n"
-                 "    so that first stage init will not mount from snapshots.\n";
+                 "    so that first stage init will not mount from snapshots.\n"
+                 "  apply-update\n"
+                 "    Apply the incremental OTA update wherein the snapshots are\n"
+                 "    directly written to COW block device. This will bypass update-engine\n"
+                 "    and the device will be ready to boot from the target build.\n";
     return EX_USAGE;
 }
 
 namespace android {
 namespace snapshot {
 
+#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
 class MapSnapshots {
   public:
     MapSnapshots(std::string path = "");
@@ -96,14 +105,23 @@
     bool DeleteSnapshots();
     bool CleanupSnapshot() { return sm_->PrepareDeviceToBootWithoutSnapshot(); }
     bool BeginUpdate();
+    bool ApplyUpdate();
 
   private:
     std::optional<std::string> GetCowImagePath(std::string& name);
+    bool PrepareUpdate();
+    bool GetCowDevicePath(std::string partition_name, std::string* cow_path);
     bool WriteSnapshotPatch(std::string cow_device, std::string patch);
+    std::string GetGroupName(const android::fs_mgr::LpMetadata& pt,
+                             const std::string& partiton_name);
     std::unique_ptr<SnapshotManager::LockedFile> lock_;
     std::unique_ptr<SnapshotManager> sm_;
     std::vector<std::future<bool>> threads_;
     std::string snapshot_dir_path_;
+    std::unordered_map<std::string, chromeos_update_engine::DynamicPartitionGroup*> group_map_;
+
+    std::vector<std::string> patchfiles_;
+    chromeos_update_engine::DeltaArchiveManifest manifest_;
 };
 
 MapSnapshots::MapSnapshots(std::string path) {
@@ -115,6 +133,193 @@
     snapshot_dir_path_ = path + "/";
 }
 
+std::string MapSnapshots::GetGroupName(const android::fs_mgr::LpMetadata& pt,
+                                       const std::string& partition_name) {
+    std::string group_name;
+    for (const auto& partition : pt.partitions) {
+        std::string name = android::fs_mgr::GetPartitionName(partition);
+        auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
+        std::string pname = name.substr(0, name.size() - suffix.size());
+        if (pname == partition_name) {
+            std::string group_name =
+                    android::fs_mgr::GetPartitionGroupName(pt.groups[partition.group_index]);
+            return group_name.substr(0, group_name.size() - suffix.size());
+        }
+    }
+    return "";
+}
+
+bool MapSnapshots::PrepareUpdate() {
+    auto source_slot = fs_mgr_get_slot_suffix();
+    auto source_slot_number = SlotNumberForSlotSuffix(source_slot);
+    auto super_source = fs_mgr_get_super_partition_name(source_slot_number);
+
+    // Get current partition information.
+    PartitionOpener opener;
+    auto source_metadata = ReadMetadata(opener, super_source, source_slot_number);
+    if (!source_metadata) {
+        LOG(ERROR) << "Could not read source partition metadata.\n";
+        return false;
+    }
+
+    auto dap = manifest_.mutable_dynamic_partition_metadata();
+    dap->set_snapshot_enabled(true);
+    dap->set_vabc_enabled(true);
+    dap->set_vabc_compression_param("lz4");
+    dap->set_cow_version(3);
+
+    for (const auto& entry : std::filesystem::directory_iterator(snapshot_dir_path_)) {
+        if (android::base::EndsWith(entry.path().generic_string(), ".patch")) {
+            patchfiles_.push_back(android::base::Basename(entry.path().generic_string()));
+        }
+    }
+
+    for (auto& patchfile : patchfiles_) {
+        std::string parsing_file = snapshot_dir_path_ + patchfile;
+        android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(parsing_file.c_str(), O_RDONLY)));
+        if (fd < 0) {
+            LOG(ERROR) << "Failed to open file: " << parsing_file;
+            return false;
+        }
+        uint64_t dev_sz = lseek(fd.get(), 0, SEEK_END);
+        if (!dev_sz) {
+            LOG(ERROR) << "Could not determine block device size: " << parsing_file;
+            return false;
+        }
+
+        const int block_sz = 4_KiB;
+        dev_sz += block_sz - 1;
+        dev_sz &= ~(block_sz - 1);
+
+        auto npos = patchfile.rfind(".patch");
+        auto partition_name = patchfile.substr(0, npos);
+
+        chromeos_update_engine::DynamicPartitionGroup* group = nullptr;
+        std::string group_name = GetGroupName(*source_metadata.get(), partition_name);
+        if (group_map_.find(group_name) != group_map_.end()) {
+            group = group_map_[group_name];
+        } else {
+            group = dap->add_groups();
+            group->set_name(group_name);
+            group_map_[group_name] = group;
+        }
+        group->add_partition_names(partition_name);
+
+        auto pu = manifest_.mutable_partitions()->Add();
+        pu->set_partition_name(partition_name);
+        pu->set_estimate_cow_size(dev_sz);
+
+        CowReader reader;
+        if (!reader.Parse(fd)) {
+            LOG(ERROR) << "COW reader parse failed";
+            return false;
+        }
+
+        uint64_t new_device_size = 0;
+        const auto& header = reader.GetHeader();
+        if (header.prefix.major_version == 2) {
+            size_t num_ops = reader.get_num_total_data_ops();
+            new_device_size = (num_ops * header.block_size);
+        } else {
+            const auto& v3_header = reader.header_v3();
+            new_device_size = v3_header.op_count_max * v3_header.block_size;
+        }
+
+        LOG(INFO) << "Partition: " << partition_name << " Group_name: " << group_name
+                  << " size: " << new_device_size << " COW-size: " << dev_sz;
+        pu->mutable_new_partition_info()->set_size(new_device_size);
+    }
+    return true;
+}
+
+bool MapSnapshots::GetCowDevicePath(std::string partition_name, std::string* cow_path) {
+    auto& dm = android::dm::DeviceMapper::Instance();
+    std::string cow_device = partition_name + "-cow";
+    if (dm.GetDmDevicePathByName(cow_device, cow_path)) {
+        return true;
+    }
+
+    LOG(INFO) << "Failed to find cow path: " << cow_device << " Checking the device for -img path";
+    // If the COW device exists only on /data
+    cow_device = partition_name + "-cow-img";
+    if (!dm.GetDmDevicePathByName(cow_device, cow_path)) {
+        LOG(ERROR) << "Failed to cow path: " << cow_device;
+        return false;
+    }
+    return true;
+}
+
+bool MapSnapshots::ApplyUpdate() {
+    if (!PrepareUpdate()) {
+        LOG(ERROR) << "PrepareUpdate failed";
+        return false;
+    }
+    if (!sm_->BeginUpdate()) {
+        LOG(ERROR) << "BeginUpdate failed";
+        return false;
+    }
+    if (!sm_->CreateUpdateSnapshots(manifest_)) {
+        LOG(ERROR) << "Could not apply snapshots";
+        return false;
+    }
+
+    LOG(INFO) << "CreateUpdateSnapshots success";
+    if (!sm_->MapAllSnapshots(10s)) {
+        LOG(ERROR) << "MapAllSnapshots failed";
+        return false;
+    }
+
+    LOG(INFO) << "MapAllSnapshots success";
+
+    auto target_slot = fs_mgr_get_other_slot_suffix();
+    for (auto& patchfile : patchfiles_) {
+        auto npos = patchfile.rfind(".patch");
+        auto partition_name = patchfile.substr(0, npos) + target_slot;
+        std::string cow_path;
+        if (!GetCowDevicePath(partition_name, &cow_path)) {
+            LOG(ERROR) << "Failed to find cow path";
+            return false;
+        }
+        threads_.emplace_back(std::async(std::launch::async, &MapSnapshots::WriteSnapshotPatch,
+                                         this, cow_path, patchfile));
+    }
+
+    bool ret = true;
+    for (auto& t : threads_) {
+        ret = t.get() && ret;
+    }
+    if (!ret) {
+        LOG(ERROR) << "Snapshot writes failed";
+        return false;
+    }
+    if (!sm_->UnmapAllSnapshots()) {
+        LOG(ERROR) << "UnmapAllSnapshots failed";
+        return false;
+    }
+
+    LOG(INFO) << "Pre-created snapshots successfully copied";
+    // All snapshots have been written.
+    if (!sm_->FinishedSnapshotWrites(false /* wipe */)) {
+        LOG(ERROR) << "Could not finalize snapshot writes.\n";
+        return false;
+    }
+
+    auto hal = hal::BootControlClient::WaitForService();
+    if (!hal) {
+        LOG(ERROR) << "Could not find IBootControl HAL.\n";
+        return false;
+    }
+    auto target_slot_number = SlotNumberForSlotSuffix(target_slot);
+    auto cr = hal->SetActiveBootSlot(target_slot_number);
+    if (!cr.IsOk()) {
+        LOG(ERROR) << "Could not set active boot slot: " << cr.errMsg;
+        return false;
+    }
+
+    LOG(INFO) << "ApplyUpdate success";
+    return true;
+}
+
 bool MapSnapshots::BeginUpdate() {
     lock_ = sm_->LockExclusive();
     std::vector<std::string> snapshots;
@@ -227,11 +432,10 @@
         if (file_offset >= dev_sz) {
             break;
         }
-
-        if (fsync(cfd.get()) < 0) {
-            PLOG(ERROR) << "Fsync failed at offset: " << file_offset << " size: " << to_read;
-            return false;
-        }
+    }
+    if (fsync(cfd.get()) < 0) {
+        PLOG(ERROR) << "Fsync failed";
+        return false;
     }
     return true;
 }
@@ -277,29 +481,31 @@
     }
     return true;
 }
+#endif
 
 bool DumpCmdHandler(int /*argc*/, char** argv) {
-    android::base::InitLogging(argv, &android::base::StderrLogger);
+    android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger));
     return SnapshotManager::New()->Dump(std::cout);
 }
 
 bool MapCmdHandler(int, char** argv) {
-    android::base::InitLogging(argv, &android::base::StderrLogger);
+    android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger));
     using namespace std::chrono_literals;
     return SnapshotManager::New()->MapAllSnapshots(5000ms);
 }
 
 bool UnmapCmdHandler(int, char** argv) {
-    android::base::InitLogging(argv, &android::base::StderrLogger);
+    android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger));
     return SnapshotManager::New()->UnmapAllSnapshots();
 }
 
 bool MergeCmdHandler(int /*argc*/, char** argv) {
-    android::base::InitLogging(argv, &android::base::StderrLogger);
+    android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger));
     LOG(WARNING) << "Deprecated. Call update_engine_client --merge instead.";
     return false;
 }
 
+#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
 bool GetVerityPartitions(std::vector<std::string>& partitions) {
     auto& dm = android::dm::DeviceMapper::Instance();
     auto dm_block_devices = dm.FindDmPartitions();
@@ -367,6 +573,30 @@
     return snapshot.DeleteSnapshots();
 }
 
+bool ApplyUpdate(int argc, char** argv) {
+    android::base::InitLogging(argv, &android::base::KernelLogger);
+
+    // Make sure we are root.
+    if (::getuid() != 0) {
+        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
+        return EXIT_FAILURE;
+    }
+
+    if (argc < 3) {
+        std::cerr << " apply-update <directory location where snapshot patches are present>"
+                     "    Apply the snapshots to the COW block device\n";
+        return false;
+    }
+
+    std::string path = std::string(argv[2]);
+    MapSnapshots cow(path);
+    if (!cow.ApplyUpdate()) {
+        return false;
+    }
+    LOG(INFO) << "Apply update success. Please reboot the device";
+    return true;
+}
+
 bool MapPrecreatedSnapshots(int argc, char** argv) {
     android::base::InitLogging(argv, &android::base::KernelLogger);
 
@@ -428,7 +658,6 @@
     return cow.FinishSnapshotWrites();
 }
 
-#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
 bool CreateTestUpdate(SnapshotManager* sm) {
     chromeos_update_engine::DeltaArchiveManifest manifest;
 
@@ -552,12 +781,13 @@
         {"map", MapCmdHandler},
 #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
         {"test-blank-ota", TestOtaHandler},
-#endif
-        {"unmap", UnmapCmdHandler},
+        {"apply-update", ApplyUpdate},
         {"map-snapshots", MapPrecreatedSnapshots},
         {"unmap-snapshots", UnMapPrecreatedSnapshots},
         {"delete-snapshots", DeletePrecreatedSnapshots},
         {"revert-snapshots", RemovePrecreatedSnapshots},
+#endif
+        {"unmap", UnmapCmdHandler},
         // clang-format on
 };
 
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index bd296a3..d83524a 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -59,9 +59,6 @@
     ],
     local_include_dirs: ["include/"],
     srcs: [
-        "dm-snapshot-merge/snapuserd.cpp",
-        "dm-snapshot-merge/snapuserd_readahead.cpp",
-        "dm-snapshot-merge/snapuserd_worker.cpp",
         "dm_user_block_server.cpp",
         "snapuserd_buffer.cpp",
         "user-space-merge/handler_manager.cpp",
@@ -109,7 +106,6 @@
         "fs_mgr_defaults",
     ],
     srcs: [
-        "dm-snapshot-merge/snapuserd_server.cpp",
         "snapuserd_daemon.cpp",
         "user-space-merge/snapuserd_server.cpp",
     ],
@@ -172,7 +168,7 @@
     vendor_ramdisk_available: true,
 }
 
-// This target will install to /system/bin/snapuserd_ramdisk 
+// This target will install to /system/bin/snapuserd_ramdisk
 // It will also create a symblink on /system/bin/snapuserd that point to
 // /system/bin/snapuserd_ramdisk .
 // This way, init can check if generic ramdisk copy exists.
@@ -190,45 +186,6 @@
     symlinks: ["snapuserd"],
 }
 
-cc_test {
-    name: "snapuserd_test_legacy",
-    defaults: [
-        "fs_mgr_defaults",
-        "libsnapshot_cow_defaults",
-    ],
-    srcs: [
-        "dm-snapshot-merge/cow_snapuserd_test.cpp",
-        "dm-snapshot-merge/snapuserd.cpp",
-        "dm-snapshot-merge/snapuserd_worker.cpp",
-        "snapuserd_buffer.cpp",
-    ],
-    shared_libs: [
-        "libbase",
-        "liblog",
-    ],
-    static_libs: [
-        "libbrotli",
-        "libgtest",
-        "libsnapshot_cow",
-        "libsnapuserd_client",
-        "libcutils_sockets",
-        "libz",
-        "libdm",
-        "libext2_uuid",
-        "libext4_utils",
-        "libfs_mgr_file_wait",
-    ],
-    header_libs: [
-        "libstorage_literals_headers",
-        "libfiemap_headers",
-    ],
-    test_options: {
-        min_shipping_api_level: 30,
-    },
-    auto_gen_config: true,
-    require_root: false,
-}
-
 cc_defaults {
     name: "snapuserd_test_defaults",
     defaults: [
@@ -292,6 +249,14 @@
     test_suites: [
         "device-tests",
     ],
+    test_options: {
+        test_runner_options: [
+            {
+                name: "force-no-test-error",
+                value: "false",
+            },
+        ],
+    },
 }
 
 // vts tests cannot be host_supported.
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
deleted file mode 100644
index 737c480..0000000
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
+++ /dev/null
@@ -1,1238 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <linux/fs.h>
-#include <linux/memfd.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <chrono>
-#include <iostream>
-#include <memory>
-#include <string_view>
-
-#include <android-base/file.h>
-#include <android-base/unique_fd.h>
-#include <fs_mgr/file_wait.h>
-#include <gtest/gtest.h>
-#include <libdm/dm.h>
-#include <libdm/loop_control.h>
-#include <libsnapshot/cow_writer.h>
-#include <snapuserd/snapuserd_buffer.h>
-#include <snapuserd/snapuserd_client.h>
-#include <storage_literals/storage_literals.h>
-
-#include "snapuserd.h"
-
-namespace android {
-namespace snapshot {
-
-using namespace android::storage_literals;
-using android::base::unique_fd;
-using LoopDevice = android::dm::LoopDevice;
-using namespace std::chrono_literals;
-using namespace android::dm;
-using namespace std;
-
-static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
-
-class TempDevice {
-  public:
-    TempDevice(const std::string& name, const DmTable& table)
-        : dm_(DeviceMapper::Instance()), name_(name), valid_(false) {
-        valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5));
-    }
-    TempDevice(TempDevice&& other) noexcept
-        : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) {
-        other.valid_ = false;
-    }
-    ~TempDevice() {
-        if (valid_) {
-            dm_.DeleteDevice(name_);
-        }
-    }
-    bool Destroy() {
-        if (!valid_) {
-            return false;
-        }
-        valid_ = false;
-        return dm_.DeleteDevice(name_);
-    }
-    const std::string& path() const { return path_; }
-    const std::string& name() const { return name_; }
-    bool valid() const { return valid_; }
-
-    TempDevice(const TempDevice&) = delete;
-    TempDevice& operator=(const TempDevice&) = delete;
-
-    TempDevice& operator=(TempDevice&& other) noexcept {
-        name_ = other.name_;
-        valid_ = other.valid_;
-        other.valid_ = false;
-        return *this;
-    }
-
-  private:
-    DeviceMapper& dm_;
-    std::string name_;
-    std::string path_;
-    bool valid_;
-};
-
-class CowSnapuserdTest final {
-  public:
-    bool Setup();
-    bool SetupOrderedOps();
-    bool SetupOrderedOpsInverted();
-    bool SetupCopyOverlap_1();
-    bool SetupCopyOverlap_2();
-    bool Merge();
-    void ValidateMerge();
-    void ReadSnapshotDeviceAndValidate();
-    void Shutdown();
-    void MergeInterrupt();
-    void MergeInterruptFixed(int duration);
-    void MergeInterruptRandomly(int max_duration);
-    void ReadDmUserBlockWithoutDaemon();
-    void ReadLastBlock();
-
-    std::string snapshot_dev() const { return snapshot_dev_->path(); }
-
-    static const uint64_t kSectorSize = 512;
-
-  private:
-    void SetupImpl();
-
-    void MergeImpl();
-    void SimulateDaemonRestart();
-    void StartMerge();
-
-    std::unique_ptr<ICowWriter> CreateCowDeviceInternal();
-    void CreateCowDevice();
-    void CreateCowDeviceOrderedOps();
-    void CreateCowDeviceOrderedOpsInverted();
-    void CreateCowDeviceWithCopyOverlap_1();
-    void CreateCowDeviceWithCopyOverlap_2();
-    bool SetupDaemon();
-    void CreateBaseDevice();
-    void InitCowDevice();
-    void SetDeviceControlName();
-    void InitDaemon();
-    void CreateDmUserDevice();
-    void StartSnapuserdDaemon();
-    void CreateSnapshotDevice();
-
-    unique_ptr<LoopDevice> base_loop_;
-    unique_ptr<TempDevice> dmuser_dev_;
-    unique_ptr<TempDevice> snapshot_dev_;
-
-    std::string system_device_ctrl_name_;
-    std::string system_device_name_;
-
-    unique_fd base_fd_;
-    std::unique_ptr<TemporaryFile> cow_system_;
-    std::unique_ptr<SnapuserdClient> client_;
-    std::unique_ptr<uint8_t[]> orig_buffer_;
-    std::unique_ptr<uint8_t[]> merged_buffer_;
-    bool setup_ok_ = false;
-    bool merge_ok_ = false;
-    size_t size_ = 50_MiB;
-    int cow_num_sectors_;
-    int total_base_size_;
-};
-
-class CowSnapuserdMetadataTest final {
-  public:
-    void Setup();
-    void SetupPartialArea();
-    void ValidateMetadata();
-    void ValidatePartialFilledArea();
-
-  private:
-    void InitMetadata();
-    std::unique_ptr<ICowWriter> CreateCowDeviceInternal();
-    void CreateCowDevice();
-    void CreateCowPartialFilledArea();
-
-    std::unique_ptr<Snapuserd> snapuserd_;
-    std::unique_ptr<TemporaryFile> cow_system_;
-    size_t size_ = 1_MiB;
-};
-
-static unique_fd CreateTempFile(const std::string& name, size_t size) {
-    unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING));
-    if (fd < 0) {
-        return {};
-    }
-    if (size) {
-        if (ftruncate(fd, size) < 0) {
-            perror("ftruncate");
-            return {};
-        }
-        if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
-            perror("fcntl");
-            return {};
-        }
-    }
-    return fd;
-}
-
-void CowSnapuserdTest::Shutdown() {
-    ASSERT_TRUE(snapshot_dev_->Destroy());
-    ASSERT_TRUE(dmuser_dev_->Destroy());
-
-    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
-    ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
-    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
-    ASSERT_TRUE(client_->DetachSnapuserd());
-}
-
-bool CowSnapuserdTest::Setup() {
-    SetupImpl();
-    return setup_ok_;
-}
-
-bool CowSnapuserdTest::SetupOrderedOps() {
-    CreateBaseDevice();
-    CreateCowDeviceOrderedOps();
-    return SetupDaemon();
-}
-
-bool CowSnapuserdTest::SetupOrderedOpsInverted() {
-    CreateBaseDevice();
-    CreateCowDeviceOrderedOpsInverted();
-    return SetupDaemon();
-}
-
-bool CowSnapuserdTest::SetupCopyOverlap_1() {
-    CreateBaseDevice();
-    CreateCowDeviceWithCopyOverlap_1();
-    return SetupDaemon();
-}
-
-bool CowSnapuserdTest::SetupCopyOverlap_2() {
-    CreateBaseDevice();
-    CreateCowDeviceWithCopyOverlap_2();
-    return SetupDaemon();
-}
-
-bool CowSnapuserdTest::SetupDaemon() {
-    SetDeviceControlName();
-
-    StartSnapuserdDaemon();
-    InitCowDevice();
-
-    CreateDmUserDevice();
-    InitDaemon();
-
-    CreateSnapshotDevice();
-    setup_ok_ = true;
-
-    return setup_ok_;
-}
-
-void CowSnapuserdTest::StartSnapuserdDaemon() {
-    pid_t pid = fork();
-    ASSERT_GE(pid, 0);
-    if (pid == 0) {
-        std::string arg0 = "/system/bin/snapuserd";
-        std::string arg1 = "-socket="s + kSnapuserdSocketTest;
-        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
-        ASSERT_GE(execv(arg0.c_str(), argv), 0);
-    } else {
-        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
-        ASSERT_NE(client_, nullptr);
-    }
-}
-
-std::unique_ptr<ICowWriter> CowSnapuserdTest::CreateCowDeviceInternal() {
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
-
-    CowOptions options;
-    options.compression = "gz";
-
-    unique_fd fd(cow_system_->fd);
-    cow_system_->fd = -1;
-
-    return CreateCowWriter(kDefaultCowVersion, options, std::move(fd));
-}
-
-void CowSnapuserdTest::ReadLastBlock() {
-    unique_fd rnd_fd;
-    total_base_size_ = BLOCK_SZ * 2;
-
-    base_fd_ = CreateTempFile("base_device", total_base_size_);
-    ASSERT_GE(base_fd_, 0);
-
-    rnd_fd.reset(open("/dev/random", O_RDONLY));
-    ASSERT_TRUE(rnd_fd > 0);
-
-    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
-
-    for (size_t j = 0; j < ((total_base_size_) / BLOCK_SZ); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), BLOCK_SZ, 0), true);
-        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), BLOCK_SZ), true);
-    }
-
-    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
-
-    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
-    ASSERT_TRUE(base_loop_->valid());
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(total_base_size_);
-    loff_t offset = 0;
-
-    // Fill random data
-    for (size_t j = 0; j < (total_base_size_ / BLOCK_SZ); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, BLOCK_SZ, 0),
-                  true);
-
-        offset += BLOCK_SZ;
-    }
-
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    ASSERT_TRUE(writer->AddRawBlocks(0, random_buffer_1_.get(), BLOCK_SZ));
-    ASSERT_TRUE(writer->AddRawBlocks(1, (char*)random_buffer_1_.get() + BLOCK_SZ, BLOCK_SZ));
-
-    ASSERT_TRUE(writer->Finalize());
-
-    SetDeviceControlName();
-
-    StartSnapuserdDaemon();
-    InitCowDevice();
-
-    CreateDmUserDevice();
-    InitDaemon();
-
-    CreateSnapshotDevice();
-
-    unique_fd snapshot_fd(open(snapshot_dev_->path().c_str(), O_RDONLY));
-    ASSERT_TRUE(snapshot_fd > 0);
-
-    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
-
-    offset = 7680;
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), 512, offset), true);
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)random_buffer_1_.get() + offset, 512), 0);
-}
-
-void CowSnapuserdTest::CreateBaseDevice() {
-    unique_fd rnd_fd;
-
-    total_base_size_ = (size_ * 5);
-    base_fd_ = CreateTempFile("base_device", total_base_size_);
-    ASSERT_GE(base_fd_, 0);
-
-    rnd_fd.reset(open("/dev/random", O_RDONLY));
-    ASSERT_TRUE(rnd_fd > 0);
-
-    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
-
-    for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
-        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true);
-    }
-
-    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
-
-    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
-    ASSERT_TRUE(base_loop_->valid());
-}
-
-void CowSnapuserdTest::ReadSnapshotDeviceAndValidate() {
-    unique_fd snapshot_fd(open(snapshot_dev_->path().c_str(), O_RDONLY));
-    ASSERT_TRUE(snapshot_fd > 0);
-
-    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
-
-    // COPY
-    loff_t offset = 0;
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0);
-
-    // REPLACE
-    offset += size_;
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0);
-
-    // ZERO
-    offset += size_;
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0);
-
-    // REPLACE
-    offset += size_;
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
-
-    // XOR
-    offset += size_;
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
-}
-
-void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    size_t num_blocks = size_ / writer->GetBlockSize();
-    size_t x = num_blocks;
-    size_t blk_src_copy = 0;
-
-    // Create overlapping copy operations
-    while (1) {
-        ASSERT_TRUE(writer->AddCopy(blk_src_copy, blk_src_copy + 1));
-        x -= 1;
-        if (x == 1) {
-            break;
-        }
-        blk_src_copy += 1;
-    }
-
-    // Flush operations
-    ASSERT_TRUE(writer->Finalize());
-
-    // Construct the buffer required for validation
-    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
-
-    // Read the entire base device
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
-              true);
-
-    // Merged operations required for validation
-    int block_size = 4096;
-    x = num_blocks;
-    loff_t src_offset = block_size;
-    loff_t dest_offset = 0;
-
-    while (1) {
-        memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset,
-                block_size);
-        x -= 1;
-        if (x == 1) {
-            break;
-        }
-        src_offset += block_size;
-        dest_offset += block_size;
-    }
-}
-
-void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    size_t num_blocks = size_ / writer->GetBlockSize();
-    size_t x = num_blocks;
-    size_t blk_src_copy = num_blocks - 1;
-
-    // Create overlapping copy operations
-    while (1) {
-        ASSERT_TRUE(writer->AddCopy(blk_src_copy + 1, blk_src_copy));
-        x -= 1;
-        if (x == 0) {
-            ASSERT_EQ(blk_src_copy, 0);
-            break;
-        }
-        blk_src_copy -= 1;
-    }
-
-    // Flush operations
-    ASSERT_TRUE(writer->Finalize());
-
-    // Construct the buffer required for validation
-    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
-
-    // Read the entire base device
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
-              true);
-
-    // Merged operations
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), writer->GetBlockSize(),
-                                               0),
-              true);
-    ASSERT_EQ(android::base::ReadFullyAtOffset(
-                      base_fd_, (char*)orig_buffer_.get() + writer->GetBlockSize(), size_, 0),
-              true);
-}
-
-void CowSnapuserdTest::CreateCowDeviceOrderedOpsInverted() {
-    unique_fd rnd_fd;
-    loff_t offset = 0;
-
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    rnd_fd.reset(open("/dev/random", O_RDONLY));
-    ASSERT_TRUE(rnd_fd > 0);
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
-
-    // Fill random data
-    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
-                  true);
-
-        offset += 1_MiB;
-    }
-
-    size_t num_blocks = size_ / writer->GetBlockSize();
-    size_t blk_end_copy = num_blocks * 3;
-    size_t source_blk = num_blocks - 1;
-    size_t blk_src_copy = blk_end_copy - 1;
-    uint16_t xor_offset = 5;
-
-    size_t x = num_blocks;
-    while (1) {
-        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
-        x -= 1;
-        if (x == 0) {
-            break;
-        }
-        source_blk -= 1;
-        blk_src_copy -= 1;
-    }
-
-    for (size_t i = num_blocks; i > 0; i--) {
-        ASSERT_TRUE(writer->AddXorBlocks(
-                num_blocks + i - 1, &random_buffer_1_.get()[writer->GetBlockSize() * (i - 1)],
-                writer->GetBlockSize(), 2 * num_blocks + i - 1, xor_offset));
-    }
-    // Flush operations
-    ASSERT_TRUE(writer->Finalize());
-    // Construct the buffer required for validation
-    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
-    // Read the entire base device
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
-              true);
-    // Merged Buffer
-    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
-    memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
-    for (int i = 0; i < size_; i++) {
-        orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
-    }
-}
-
-void CowSnapuserdTest::CreateCowDeviceOrderedOps() {
-    unique_fd rnd_fd;
-    loff_t offset = 0;
-
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    rnd_fd.reset(open("/dev/random", O_RDONLY));
-    ASSERT_TRUE(rnd_fd > 0);
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
-
-    // Fill random data
-    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
-                  true);
-
-        offset += 1_MiB;
-    }
-    memset(random_buffer_1_.get(), 0, size_);
-
-    size_t num_blocks = size_ / writer->GetBlockSize();
-    size_t x = num_blocks;
-    size_t source_blk = 0;
-    size_t blk_src_copy = 2 * num_blocks;
-    uint16_t xor_offset = 5;
-
-    while (1) {
-        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
-
-        x -= 1;
-        if (x == 0) {
-            break;
-        }
-        source_blk += 1;
-        blk_src_copy += 1;
-    }
-
-    ASSERT_TRUE(writer->AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
-                                     xor_offset));
-    // Flush operations
-    ASSERT_TRUE(writer->Finalize());
-    // Construct the buffer required for validation
-    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
-    // Read the entire base device
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
-              true);
-    // Merged Buffer
-    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
-    memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
-    for (int i = 0; i < size_; i++) {
-        orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
-    }
-}
-
-void CowSnapuserdTest::CreateCowDevice() {
-    unique_fd rnd_fd;
-    loff_t offset = 0;
-
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    rnd_fd.reset(open("/dev/random", O_RDONLY));
-    ASSERT_TRUE(rnd_fd > 0);
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
-
-    // Fill random data
-    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
-                  true);
-
-        offset += 1_MiB;
-    }
-
-    size_t num_blocks = size_ / writer->GetBlockSize();
-    size_t blk_end_copy = num_blocks * 2;
-    size_t source_blk = num_blocks - 1;
-    size_t blk_src_copy = blk_end_copy - 1;
-
-    uint32_t sequence[num_blocks * 2];
-    // Sequence for Copy ops
-    for (int i = 0; i < num_blocks; i++) {
-        sequence[i] = num_blocks - 1 - i;
-    }
-    // Sequence for Xor ops
-    for (int i = 0; i < num_blocks; i++) {
-        sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
-    }
-    ASSERT_TRUE(writer->AddSequenceData(2 * num_blocks, sequence));
-
-    size_t x = num_blocks;
-    while (1) {
-        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
-        x -= 1;
-        if (x == 0) {
-            break;
-        }
-        source_blk -= 1;
-        blk_src_copy -= 1;
-    }
-
-    source_blk = num_blocks;
-    blk_src_copy = blk_end_copy;
-
-    ASSERT_TRUE(writer->AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
-
-    size_t blk_zero_copy_start = source_blk + num_blocks;
-    size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
-
-    ASSERT_TRUE(writer->AddZeroBlocks(blk_zero_copy_start, num_blocks));
-
-    size_t blk_random2_replace_start = blk_zero_copy_end;
-
-    ASSERT_TRUE(writer->AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
-
-    size_t blk_xor_start = blk_random2_replace_start + num_blocks;
-    size_t xor_offset = BLOCK_SZ / 2;
-    ASSERT_TRUE(writer->AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
-                                     xor_offset));
-
-    // Flush operations
-    ASSERT_TRUE(writer->Finalize());
-    // Construct the buffer required for validation
-    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
-    std::string zero_buffer(size_, 0);
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true);
-    memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
-    memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
-    memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_);
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_,
-                                               size_ + xor_offset),
-              true);
-    for (int i = 0; i < size_; i++) {
-        orig_buffer_.get()[(size_ * 4) + i] =
-                (uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]);
-    }
-}
-
-void CowSnapuserdTest::InitCowDevice() {
-    cow_num_sectors_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
-                                              base_loop_->device());
-    ASSERT_NE(cow_num_sectors_, 0);
-}
-
-void CowSnapuserdTest::SetDeviceControlName() {
-    system_device_name_.clear();
-    system_device_ctrl_name_.clear();
-
-    std::string str(cow_system_->path);
-    std::size_t found = str.find_last_of("/\\");
-    ASSERT_NE(found, std::string::npos);
-    system_device_name_ = str.substr(found + 1);
-
-    system_device_ctrl_name_ = system_device_name_ + "-ctrl";
-}
-
-void CowSnapuserdTest::CreateDmUserDevice() {
-    DmTable dmuser_table;
-    ASSERT_TRUE(dmuser_table.AddTarget(
-            std::make_unique<DmTargetUser>(0, cow_num_sectors_, system_device_ctrl_name_)));
-    ASSERT_TRUE(dmuser_table.valid());
-
-    dmuser_dev_ = std::make_unique<TempDevice>(system_device_name_, dmuser_table);
-    ASSERT_TRUE(dmuser_dev_->valid());
-    ASSERT_FALSE(dmuser_dev_->path().empty());
-
-    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
-    ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
-}
-
-void CowSnapuserdTest::ReadDmUserBlockWithoutDaemon() {
-    DmTable dmuser_table;
-    std::string dm_user_name = "dm-test-device";
-    unique_fd fd;
-
-    // Create a dm-user block device
-    ASSERT_TRUE(dmuser_table.AddTarget(std::make_unique<DmTargetUser>(0, 123456, dm_user_name)));
-    ASSERT_TRUE(dmuser_table.valid());
-
-    dmuser_dev_ = std::make_unique<TempDevice>(dm_user_name, dmuser_table);
-    ASSERT_TRUE(dmuser_dev_->valid());
-    ASSERT_FALSE(dmuser_dev_->path().empty());
-
-    fd.reset(open(dmuser_dev_->path().c_str(), O_RDONLY));
-    ASSERT_GE(fd, 0);
-
-    std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(1_MiB);
-
-    loff_t offset = 0;
-    // Every IO should fail as there is no daemon to process the IO
-    for (size_t j = 0; j < 10; j++) {
-        ASSERT_EQ(ReadFullyAtOffset(fd, (char*)buffer.get() + offset, BLOCK_SZ, offset), false);
-
-        offset += BLOCK_SZ;
-    }
-
-    fd = {};
-    ASSERT_TRUE(dmuser_dev_->Destroy());
-}
-
-void CowSnapuserdTest::InitDaemon() {
-    bool ok = client_->AttachDmUser(system_device_ctrl_name_);
-    ASSERT_TRUE(ok);
-}
-
-void CowSnapuserdTest::CreateSnapshotDevice() {
-    DmTable snap_table;
-    ASSERT_TRUE(snap_table.AddTarget(std::make_unique<DmTargetSnapshot>(
-            0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
-            SnapshotStorageMode::Persistent, 8)));
-    ASSERT_TRUE(snap_table.valid());
-
-    snap_table.set_readonly(true);
-
-    snapshot_dev_ = std::make_unique<TempDevice>("cowsnapuserd-test-dm-snapshot", snap_table);
-    ASSERT_TRUE(snapshot_dev_->valid());
-    ASSERT_FALSE(snapshot_dev_->path().empty());
-}
-
-void CowSnapuserdTest::SetupImpl() {
-    CreateBaseDevice();
-    CreateCowDevice();
-
-    SetDeviceControlName();
-
-    StartSnapuserdDaemon();
-    InitCowDevice();
-
-    CreateDmUserDevice();
-    InitDaemon();
-
-    CreateSnapshotDevice();
-    setup_ok_ = true;
-}
-
-bool CowSnapuserdTest::Merge() {
-    MergeImpl();
-    return merge_ok_;
-}
-
-void CowSnapuserdTest::StartMerge() {
-    DmTable merge_table;
-    ASSERT_TRUE(merge_table.AddTarget(std::make_unique<DmTargetSnapshot>(
-            0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
-            SnapshotStorageMode::Merge, 8)));
-    ASSERT_TRUE(merge_table.valid());
-    ASSERT_EQ(total_base_size_ / kSectorSize, merge_table.num_sectors());
-
-    DeviceMapper& dm = DeviceMapper::Instance();
-    ASSERT_TRUE(dm.LoadTableAndActivate("cowsnapuserd-test-dm-snapshot", merge_table));
-}
-
-void CowSnapuserdTest::MergeImpl() {
-    StartMerge();
-    DeviceMapper& dm = DeviceMapper::Instance();
-
-    while (true) {
-        vector<DeviceMapper::TargetInfo> status;
-        ASSERT_TRUE(dm.GetTableStatus("cowsnapuserd-test-dm-snapshot", &status));
-        ASSERT_EQ(status.size(), 1);
-        ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")),
-                  0);
-
-        DmTargetSnapshot::Status merge_status;
-        ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(status[0].data, &merge_status));
-        ASSERT_TRUE(merge_status.error.empty());
-        if (merge_status.sectors_allocated == merge_status.metadata_sectors) {
-            break;
-        }
-
-        std::this_thread::sleep_for(250ms);
-    }
-
-    merge_ok_ = true;
-}
-
-void CowSnapuserdTest::ValidateMerge() {
-    merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
-              true);
-    ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
-}
-
-void CowSnapuserdTest::SimulateDaemonRestart() {
-    Shutdown();
-    std::this_thread::sleep_for(500ms);
-    SetDeviceControlName();
-    StartSnapuserdDaemon();
-    InitCowDevice();
-    CreateDmUserDevice();
-    InitDaemon();
-    CreateSnapshotDevice();
-}
-
-void CowSnapuserdTest::MergeInterruptRandomly(int max_duration) {
-    std::srand(std::time(nullptr));
-    StartMerge();
-
-    for (int i = 0; i < 20; i++) {
-        int duration = std::rand() % max_duration;
-        std::this_thread::sleep_for(std::chrono::milliseconds(duration));
-        SimulateDaemonRestart();
-        StartMerge();
-    }
-
-    SimulateDaemonRestart();
-    ASSERT_TRUE(Merge());
-}
-
-void CowSnapuserdTest::MergeInterruptFixed(int duration) {
-    StartMerge();
-
-    for (int i = 0; i < 25; i++) {
-        std::this_thread::sleep_for(std::chrono::milliseconds(duration));
-        SimulateDaemonRestart();
-        StartMerge();
-    }
-
-    SimulateDaemonRestart();
-    ASSERT_TRUE(Merge());
-}
-
-void CowSnapuserdTest::MergeInterrupt() {
-    // Interrupt merge at various intervals
-    StartMerge();
-    std::this_thread::sleep_for(250ms);
-    SimulateDaemonRestart();
-
-    StartMerge();
-    std::this_thread::sleep_for(250ms);
-    SimulateDaemonRestart();
-
-    StartMerge();
-    std::this_thread::sleep_for(150ms);
-    SimulateDaemonRestart();
-
-    StartMerge();
-    std::this_thread::sleep_for(100ms);
-    SimulateDaemonRestart();
-
-    StartMerge();
-    std::this_thread::sleep_for(800ms);
-    SimulateDaemonRestart();
-
-    StartMerge();
-    std::this_thread::sleep_for(600ms);
-    SimulateDaemonRestart();
-
-    ASSERT_TRUE(Merge());
-}
-
-std::unique_ptr<ICowWriter> CowSnapuserdMetadataTest::CreateCowDeviceInternal() {
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
-
-    CowOptions options;
-    options.compression = "gz";
-
-    unique_fd fd(cow_system_->fd);
-    cow_system_->fd = -1;
-
-    return CreateCowWriter(kDefaultCowVersion, options, std::move(fd));
-}
-
-void CowSnapuserdMetadataTest::CreateCowPartialFilledArea() {
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    // Area 0 is completely filled with 256 exceptions
-    for (int i = 0; i < 256; i++) {
-        ASSERT_TRUE(writer->AddCopy(i, 256 + i));
-    }
-
-    // Area 1 is partially filled with 2 copy ops and 10 zero ops
-    ASSERT_TRUE(writer->AddCopy(500, 1000));
-    ASSERT_TRUE(writer->AddCopy(501, 1001));
-
-    ASSERT_TRUE(writer->AddZeroBlocks(300, 10));
-
-    // Flush operations
-    ASSERT_TRUE(writer->Finalize());
-}
-
-void CowSnapuserdMetadataTest::ValidatePartialFilledArea() {
-    int area_sz = snapuserd_->GetMetadataAreaSize();
-
-    ASSERT_EQ(area_sz, 2);
-
-    // Verify the partially filled area
-    void* buffer = snapuserd_->GetExceptionBuffer(1);
-    loff_t offset = 0;
-    struct disk_exception* de;
-    for (int i = 11; i >= 0; i--) {
-        de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-        ASSERT_EQ(de->old_chunk, i);
-        offset += sizeof(struct disk_exception);
-    }
-
-    de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-    ASSERT_EQ(de->old_chunk, 0);
-    ASSERT_EQ(de->new_chunk, 0);
-}
-
-void CowSnapuserdMetadataTest::SetupPartialArea() {
-    CreateCowPartialFilledArea();
-    InitMetadata();
-}
-
-void CowSnapuserdMetadataTest::CreateCowDevice() {
-    unique_fd rnd_fd;
-    loff_t offset = 0;
-
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    rnd_fd.reset(open("/dev/random", O_RDONLY));
-    ASSERT_TRUE(rnd_fd > 0);
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
-
-    // Fill random data
-    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
-                  true);
-
-        offset += 1_MiB;
-    }
-
-    size_t num_blocks = size_ / writer->GetBlockSize();
-
-    // Overlapping region. This has to be split
-    // into two batch operations
-    ASSERT_TRUE(writer->AddCopy(23, 20));
-    ASSERT_TRUE(writer->AddCopy(22, 19));
-    ASSERT_TRUE(writer->AddCopy(21, 18));
-    ASSERT_TRUE(writer->AddCopy(20, 17));
-    ASSERT_TRUE(writer->AddCopy(19, 16));
-    ASSERT_TRUE(writer->AddCopy(18, 15));
-
-    // Contiguous region but blocks in ascending order
-    // Daemon has to ensure that these blocks are merged
-    // in a batch
-    ASSERT_TRUE(writer->AddCopy(50, 75));
-    ASSERT_TRUE(writer->AddCopy(51, 76));
-    ASSERT_TRUE(writer->AddCopy(52, 77));
-    ASSERT_TRUE(writer->AddCopy(53, 78));
-
-    // Dis-contiguous region
-    ASSERT_TRUE(writer->AddCopy(110, 130));
-    ASSERT_TRUE(writer->AddCopy(105, 125));
-    ASSERT_TRUE(writer->AddCopy(100, 120));
-
-    // Overlap
-    ASSERT_TRUE(writer->AddCopy(25, 30));
-    ASSERT_TRUE(writer->AddCopy(30, 31));
-
-    size_t source_blk = num_blocks;
-
-    ASSERT_TRUE(writer->AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
-
-    size_t blk_zero_copy_start = source_blk + num_blocks;
-
-    ASSERT_TRUE(writer->AddZeroBlocks(blk_zero_copy_start, num_blocks));
-
-    // Flush operations
-    ASSERT_TRUE(writer->Finalize());
-}
-
-void CowSnapuserdMetadataTest::InitMetadata() {
-    snapuserd_ = std::make_unique<Snapuserd>("", cow_system_->path, "");
-    ASSERT_TRUE(snapuserd_->InitCowDevice());
-}
-
-void CowSnapuserdMetadataTest::Setup() {
-    CreateCowDevice();
-    InitMetadata();
-}
-
-void CowSnapuserdMetadataTest::ValidateMetadata() {
-    int area_sz = snapuserd_->GetMetadataAreaSize();
-    ASSERT_EQ(area_sz, 3);
-
-    size_t old_chunk;
-    size_t new_chunk;
-
-    for (int i = 0; i < area_sz; i++) {
-        void* buffer = snapuserd_->GetExceptionBuffer(i);
-        loff_t offset = 0;
-        if (i == 0) {
-            old_chunk = 256;
-            new_chunk = 2;
-        } else if (i == 1) {
-            old_chunk = 512;
-            new_chunk = 259;
-        }
-        for (int j = 0; j < 256; j++) {
-            struct disk_exception* de =
-                    reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-
-            if (i != 2) {
-                ASSERT_EQ(de->old_chunk, old_chunk);
-                ASSERT_EQ(de->new_chunk, new_chunk);
-                old_chunk += 1;
-                new_chunk += 1;
-            } else {
-                break;
-            }
-            offset += sizeof(struct disk_exception);
-        }
-
-        if (i == 2) {
-            // The first 5 copy operation is not batch merged
-            // as the sequence is discontiguous
-            struct disk_exception* de =
-                    reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 30);
-            ASSERT_EQ(de->new_chunk, 518);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 25);
-            ASSERT_EQ(de->new_chunk, 520);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 100);
-            ASSERT_EQ(de->new_chunk, 521);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 105);
-            ASSERT_EQ(de->new_chunk, 522);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 110);
-            ASSERT_EQ(de->new_chunk, 523);
-            offset += sizeof(struct disk_exception);
-
-            // The next 4 operations are batch merged as
-            // both old and new chunk are contiguous
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 53);
-            ASSERT_EQ(de->new_chunk, 524);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 52);
-            ASSERT_EQ(de->new_chunk, 525);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 51);
-            ASSERT_EQ(de->new_chunk, 526);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 50);
-            ASSERT_EQ(de->new_chunk, 527);
-            offset += sizeof(struct disk_exception);
-
-            // This is handling overlap operation with
-            // two batch merge operations.
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 18);
-            ASSERT_EQ(de->new_chunk, 528);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 19);
-            ASSERT_EQ(de->new_chunk, 529);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 20);
-            ASSERT_EQ(de->new_chunk, 530);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 21);
-            ASSERT_EQ(de->new_chunk, 532);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 22);
-            ASSERT_EQ(de->new_chunk, 533);
-            offset += sizeof(struct disk_exception);
-
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 23);
-            ASSERT_EQ(de->new_chunk, 534);
-            offset += sizeof(struct disk_exception);
-
-            // End of metadata
-            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
-            ASSERT_EQ(de->old_chunk, 0);
-            ASSERT_EQ(de->new_chunk, 0);
-            offset += sizeof(struct disk_exception);
-        }
-    }
-}
-
-TEST(Snapuserd_Test, Snapshot_Metadata) {
-    CowSnapuserdMetadataTest harness;
-    harness.Setup();
-    harness.ValidateMetadata();
-}
-
-TEST(Snapuserd_Test, Snapshot_Metadata_Overlap) {
-    CowSnapuserdMetadataTest harness;
-    harness.SetupPartialArea();
-    harness.ValidatePartialFilledArea();
-}
-
-TEST(Snapuserd_Test, Snapshot_Merge_Resume) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.Setup());
-    harness.MergeInterrupt();
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, Snapshot_IO_TEST) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.Setup());
-    harness.ReadSnapshotDeviceAndValidate();
-    ASSERT_TRUE(harness.Merge());
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, Snapshot_END_IO_TEST) {
-    CowSnapuserdTest harness;
-    harness.ReadLastBlock();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.SetupCopyOverlap_1());
-    ASSERT_TRUE(harness.Merge());
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.SetupCopyOverlap_2());
-    ASSERT_TRUE(harness.Merge());
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.SetupCopyOverlap_1());
-    harness.MergeInterrupt();
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, ReadDmUserBlockWithoutDaemon) {
-    CowSnapuserdTest harness;
-    harness.ReadDmUserBlockWithoutDaemon();
-}
-
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Ordered) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.SetupOrderedOps());
-    harness.MergeInterruptFixed(300);
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Ordered) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.SetupOrderedOps());
-    harness.MergeInterruptRandomly(500);
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Inverted) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.SetupOrderedOpsInverted());
-    harness.MergeInterruptFixed(50);
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Inverted) {
-    CowSnapuserdTest harness;
-    ASSERT_TRUE(harness.SetupOrderedOpsInverted());
-    harness.MergeInterruptRandomly(50);
-    harness.ValidateMerge();
-    harness.Shutdown();
-}
-
-}  // namespace snapshot
-}  // namespace android
-
-int main(int argc, char** argv) {
-    ::testing::InitGoogleTest(&argc, argv);
-    return RUN_ALL_TESTS();
-}
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
deleted file mode 100644
index 93bb0b2..0000000
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ /dev/null
@@ -1,870 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "snapuserd.h"
-
-#include <dirent.h>
-#include <fcntl.h>
-#include <linux/fs.h>
-#include <unistd.h>
-#include <algorithm>
-
-#include <csignal>
-#include <optional>
-#include <set>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/parseint.h>
-#include <android-base/properties.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <snapuserd/snapuserd_client.h>
-
-namespace android {
-namespace snapshot {
-
-using namespace android;
-using namespace android::dm;
-using android::base::unique_fd;
-
-#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
-#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
-
-Snapuserd::Snapuserd(const std::string& misc_name, const std::string& cow_device,
-                     const std::string& backing_device) {
-    misc_name_ = misc_name;
-    cow_device_ = cow_device;
-    backing_store_device_ = backing_device;
-    control_device_ = "/dev/dm-user/" + misc_name;
-}
-
-bool Snapuserd::InitializeWorkers() {
-    for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
-        std::unique_ptr<WorkerThread> wt = std::make_unique<WorkerThread>(
-                cow_device_, backing_store_device_, control_device_, misc_name_, GetSharedPtr());
-
-        worker_threads_.push_back(std::move(wt));
-    }
-
-    read_ahead_thread_ = std::make_unique<ReadAheadThread>(cow_device_, backing_store_device_,
-                                                           misc_name_, GetSharedPtr());
-    return true;
-}
-
-std::unique_ptr<CowReader> Snapuserd::CloneReaderForWorker() {
-    return reader_->CloneCowReader();
-}
-
-bool Snapuserd::CommitMerge(int num_merge_ops) {
-    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
-    ch->num_merge_ops += num_merge_ops;
-
-    if (read_ahead_feature_ && read_ahead_ops_.size() > 0) {
-        struct BufferState* ra_state = GetBufferState();
-        ra_state->read_ahead_state = kCowReadAheadInProgress;
-    }
-
-    int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
-    if (ret < 0) {
-        SNAP_PLOG(ERROR) << "msync header failed: " << ret;
-        return false;
-    }
-
-    merge_initiated_ = true;
-
-    return true;
-}
-
-void Snapuserd::PrepareReadAhead() {
-    if (!read_ahead_feature_) {
-        return;
-    }
-
-    struct BufferState* ra_state = GetBufferState();
-    // Check if the data has to be re-constructed from COW device
-    if (ra_state->read_ahead_state == kCowReadAheadDone) {
-        populate_data_from_cow_ = true;
-    } else {
-        populate_data_from_cow_ = false;
-    }
-
-    StartReadAhead();
-}
-
-bool Snapuserd::GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer) {
-    if (!lock->owns_lock()) {
-        SNAP_LOG(ERROR) << "GetRABuffer - Lock not held";
-        return false;
-    }
-    std::unordered_map<uint64_t, void*>::iterator it = read_ahead_buffer_map_.find(block);
-
-    // This will be true only for IO's generated as part of reading a root
-    // filesystem. IO's related to merge should always be in read-ahead cache.
-    if (it == read_ahead_buffer_map_.end()) {
-        return false;
-    }
-
-    // Theoretically, we can send the data back from the read-ahead buffer
-    // all the way to the kernel without memcpy. However, if the IO is
-    // un-aligned, the wrapper function will need to touch the read-ahead
-    // buffers and transitions will be bit more complicated.
-    memcpy(buffer, it->second, BLOCK_SZ);
-    return true;
-}
-
-// ========== State transition functions for read-ahead operations ===========
-
-bool Snapuserd::GetReadAheadPopulatedBuffer(uint64_t block, void* buffer) {
-    if (!read_ahead_feature_) {
-        return false;
-    }
-
-    {
-        std::unique_lock<std::mutex> lock(lock_);
-        if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
-            return false;
-        }
-
-        if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS) {
-            return GetRABuffer(&lock, block, buffer);
-        }
-    }
-
-    {
-        // Read-ahead thread IO is in-progress. Wait for it to complete
-        std::unique_lock<std::mutex> lock(lock_);
-        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE ||
-                 io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS)) {
-            cv.wait(lock);
-        }
-
-        return GetRABuffer(&lock, block, buffer);
-    }
-}
-
-// This is invoked by read-ahead thread waiting for merge IO's
-// to complete
-bool Snapuserd::WaitForMergeToComplete() {
-    {
-        std::unique_lock<std::mutex> lock(lock_);
-        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN ||
-                 io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED)) {
-            cv.wait(lock);
-        }
-
-        if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED) {
-            return false;
-        }
-
-        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_IN_PROGRESS;
-        return true;
-    }
-}
-
-// This is invoked during the launch of worker threads. We wait
-// for read-ahead thread to by fully up before worker threads
-// are launched; else we will have a race between worker threads
-// and read-ahead thread specifically during re-construction.
-bool Snapuserd::WaitForReadAheadToStart() {
-    {
-        std::unique_lock<std::mutex> lock(lock_);
-        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS ||
-                 io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE)) {
-            cv.wait(lock);
-        }
-
-        if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
-            return false;
-        }
-
-        return true;
-    }
-}
-
-// Invoked by worker threads when a sequence of merge operation
-// is complete notifying read-ahead thread to make forward
-// progress.
-void Snapuserd::StartReadAhead() {
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN;
-    }
-
-    cv.notify_one();
-}
-
-void Snapuserd::MergeCompleted() {
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        io_state_ = READ_AHEAD_IO_TRANSITION::IO_TERMINATED;
-    }
-
-    cv.notify_one();
-}
-
-bool Snapuserd::ReadAheadIOCompleted(bool sync) {
-    if (sync) {
-        // Flush the entire buffer region
-        int ret = msync(mapped_addr_, total_mapped_addr_length_, MS_SYNC);
-        if (ret < 0) {
-            PLOG(ERROR) << "msync failed after ReadAheadIOCompleted: " << ret;
-            return false;
-        }
-
-        // Metadata and data are synced. Now, update the state.
-        // We need to update the state after flushing data; if there is a crash
-        // when read-ahead IO is in progress, the state of data in the COW file
-        // is unknown. kCowReadAheadDone acts as a checkpoint wherein the data
-        // in the scratch space is good and during next reboot, read-ahead thread
-        // can safely re-construct the data.
-        struct BufferState* ra_state = GetBufferState();
-        ra_state->read_ahead_state = kCowReadAheadDone;
-
-        ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
-        if (ret < 0) {
-            PLOG(ERROR) << "msync failed to flush Readahead completion state...";
-            return false;
-        }
-    }
-
-    // Notify the worker threads
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        io_state_ = READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS;
-    }
-
-    cv.notify_all();
-    return true;
-}
-
-void Snapuserd::ReadAheadIOFailed() {
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE;
-    }
-
-    cv.notify_all();
-}
-
-//========== End of state transition functions ====================
-
-bool Snapuserd::IsChunkIdMetadata(chunk_t chunk) {
-    uint32_t stride = exceptions_per_area_ + 1;
-    lldiv_t divresult = lldiv(chunk, stride);
-
-    return (divresult.rem == NUM_SNAPSHOT_HDR_CHUNKS);
-}
-
-// Find the next free chunk-id to be assigned. Check if the next free
-// chunk-id represents a metadata page. If so, skip it.
-chunk_t Snapuserd::GetNextAllocatableChunkId(chunk_t chunk) {
-    chunk_t next_chunk = chunk + 1;
-
-    if (IsChunkIdMetadata(next_chunk)) {
-        next_chunk += 1;
-    }
-    return next_chunk;
-}
-
-void Snapuserd::CheckMergeCompletionStatus() {
-    if (!merge_initiated_) {
-        SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: "
-                       << reader_->get_num_total_data_ops();
-        return;
-    }
-
-    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
-
-    SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops
-                   << " Total-data-ops: " << reader_->get_num_total_data_ops();
-}
-
-/*
- * Read the metadata from COW device and
- * construct the metadata as required by the kernel.
- *
- * Please see design on kernel COW format
- *
- * 1: Read the metadata from internal COW device
- * 2: There are 3 COW operations:
- *     a: Replace op
- *     b: Copy op
- *     c: Zero op
- * 3: For each of the 3 operations, op->new_block
- *    represents the block number in the base device
- *    for which one of the 3 operations have to be applied.
- *    This represents the old_chunk in the kernel COW format
- * 4: We need to assign new_chunk for a corresponding old_chunk
- * 5: The algorithm is similar to how kernel assigns chunk number
- *    while creating exceptions. However, there are few cases
- *    which needs to be addressed here:
- *      a: During merge process, kernel scans the metadata page
- *      from backwards when merge is initiated. Since, we need
- *      to make sure that the merge ordering follows our COW format,
- *      we read the COW operation from backwards and populate the
- *      metadata so that when kernel starts the merging from backwards,
- *      those ops correspond to the beginning of our COW format.
- *      b: Kernel can merge successive operations if the two chunk IDs
- *      are contiguous. This can be problematic when there is a crash
- *      during merge; specifically when the merge operation has dependency.
- *      These dependencies can only happen during copy operations.
- *
- *      To avoid this problem, we make sure overlap copy operations
- *      are not batch merged.
- * 6: Use a monotonically increasing chunk number to assign the
- *    new_chunk
- * 7: Each chunk-id represents either
- *        a: Metadata page or
- *        b: Data page
- * 8: Chunk-id representing a data page is stored in a map.
- * 9: Chunk-id representing a metadata page is converted into a vector
- *    index. We store this in vector as kernel requests metadata during
- *    two stage:
- *       a: When initial dm-snapshot device is created, kernel requests
- *          all the metadata and stores it in its internal data-structures.
- *       b: During merge, kernel once again requests the same metadata
- *          once-again.
- *    In both these cases, a quick lookup based on chunk-id is done.
- * 10: When chunk number is incremented, we need to make sure that
- *    if the chunk is representing a metadata page and skip.
- * 11: Each 4k page will contain 256 disk exceptions. We call this
- *    exceptions_per_area_
- * 12: Kernel will stop issuing metadata IO request when new-chunk ID is 0.
- */
-bool Snapuserd::ReadMetadata() {
-    reader_ = std::make_unique<CowReader>();
-    CowOptions options;
-    bool metadata_found = false;
-    int replace_ops = 0, zero_ops = 0, copy_ops = 0;
-
-    SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
-
-    if (!reader_->Parse(cow_fd_)) {
-        SNAP_LOG(ERROR) << "Failed to parse";
-        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;
-    }
-
-    SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
-
-    if (!MmapMetadata()) {
-        SNAP_LOG(ERROR) << "mmap failed";
-        return false;
-    }
-
-    // Initialize the iterator for reading metadata
-    std::unique_ptr<ICowOpIter> cowop_rm_iter = reader_->GetRevMergeOpIter();
-
-    exceptions_per_area_ = (CHUNK_SIZE << SECTOR_SHIFT) / sizeof(struct disk_exception);
-
-    // Start from chunk number 2. Chunk 0 represents header and chunk 1
-    // represents first metadata page.
-    chunk_t data_chunk_id = NUM_SNAPSHOT_HDR_CHUNKS + 1;
-    size_t num_ops = 0;
-
-    loff_t offset = 0;
-    std::unique_ptr<uint8_t[]> de_ptr =
-            std::make_unique<uint8_t[]>(exceptions_per_area_ * sizeof(struct disk_exception));
-
-    // This memset is important. Kernel will stop issuing IO when new-chunk ID
-    // is 0. When Area is not filled completely with all 256 exceptions,
-    // 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->AtEnd()) {
-        const CowOperation* cow_op = cowop_rm_iter->Get();
-        struct disk_exception* de =
-                reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
-
-        metadata_found = true;
-        // This loop will handle all the replace and zero ops.
-        // We will handle the copy ops later as it requires special
-        // handling of assigning chunk-id's. Furthermore, we make
-        // sure that replace/zero and copy ops are not batch merged; hence,
-        // the bump in the chunk_id before break of this loop
-        if (IsOrderedOp(*cow_op)) {
-            data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
-            break;
-        }
-
-        if (cow_op->type() == kCowReplaceOp) {
-            replace_ops++;
-        } else if (cow_op->type() == kCowZeroOp) {
-            zero_ops++;
-        }
-
-        // Construct the disk-exception
-        de->old_chunk = cow_op->new_block;
-        de->new_chunk = data_chunk_id;
-
-        // Store operation pointer.
-        chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
-        num_ops += 1;
-        offset += sizeof(struct disk_exception);
-        cowop_rm_iter->Next();
-
-        SNAP_LOG(DEBUG) << num_ops << ":"
-                        << " Old-chunk: " << de->old_chunk << " New-chunk: " << de->new_chunk;
-
-        if (num_ops == exceptions_per_area_) {
-            // Store it in vector at the right index. This maps the chunk-id to
-            // vector index.
-            vec_.push_back(std::move(de_ptr));
-            offset = 0;
-            num_ops = 0;
-
-            // Create buffer for next area
-            de_ptr = std::make_unique<uint8_t[]>(exceptions_per_area_ *
-                                                 sizeof(struct disk_exception));
-            memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
-
-            if (cowop_rm_iter->AtEnd()) {
-                vec_.push_back(std::move(de_ptr));
-            }
-        }
-
-        data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
-    }
-
-    int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
-    std::optional<chunk_t> prev_id = {};
-    std::vector<const CowOperation*> vec;
-    std::set<uint64_t> dest_blocks;
-    std::set<uint64_t> source_blocks;
-    size_t pending_ordered_ops = exceptions_per_area_ - num_ops;
-    uint64_t total_ordered_ops = reader_->get_num_ordered_ops_to_merge();
-
-    SNAP_LOG(DEBUG) << " Processing copy-ops at Area: " << vec_.size()
-                    << " Number of replace/zero ops completed in this area: " << num_ops
-                    << " Pending copy ops for this area: " << pending_ordered_ops;
-
-    while (!cowop_rm_iter->AtEnd()) {
-        do {
-            const CowOperation* cow_op = cowop_rm_iter->Get();
-
-            // We have two cases specific cases:
-            //
-            // =====================================================
-            // Case 1: Overlapping copy regions
-            //
-            // Ex:
-            //
-            // Source -> Destination
-            //
-            // 1: 15 -> 18
-            // 2: 16 -> 19
-            // 3: 17 -> 20
-            // 4: 18 -> 21
-            // 5: 19 -> 22
-            // 6: 20 -> 23
-            //
-            // We have 6 copy operations to be executed in OTA and there is a overlap. Update-engine
-            // will write to COW file as follows:
-            //
-            // Op-1: 20 -> 23
-            // Op-2: 19 -> 22
-            // Op-3: 18 -> 21
-            // Op-4: 17 -> 20
-            // Op-5: 16 -> 19
-            // Op-6: 15 -> 18
-            //
-            // Note that the blocks numbers are contiguous. Hence, all 6 copy
-            // operations can be batch merged. However, that will be
-            // problematic if we have a crash as block 20, 19, 18 would have
-            // been overwritten and hence subsequent recovery may end up with
-            // a silent data corruption when op-1, op-2 and op-3 are
-            // re-executed.
-            //
-            // To address the above problem, read-ahead thread will
-            // read all the 6 source blocks, cache them in the scratch
-            // space of the COW file. During merge, read-ahead
-            // thread will serve the blocks from the read-ahead cache.
-            // If there is a crash during merge; on subsequent reboot,
-            // read-ahead thread will recover the data from the
-            // scratch space and re-construct it thereby there
-            // is no loss of data.
-            //
-            // Note that we will follow the same order of COW operations
-            // as present in the COW file. This will make sure that
-            // the merge of operations are done based on the ops present
-            // in the file.
-            //===========================================================
-            uint64_t block_source = cow_op->source();
-            if (prev_id.has_value()) {
-                if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source)) {
-                    break;
-                }
-            }
-            metadata_found = true;
-            pending_ordered_ops -= 1;
-            vec.push_back(cow_op);
-            dest_blocks.insert(block_source);
-            source_blocks.insert(cow_op->new_block);
-            prev_id = cow_op->new_block;
-            cowop_rm_iter->Next();
-        } 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()
-                        << " Area: " << vec_.size() << " Area offset: " << offset
-                        << " Pending-ordered-ops in this area: " << pending_ordered_ops;
-
-        for (size_t i = 0; i < vec.size(); i++) {
-            struct disk_exception* de =
-                    reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
-            const CowOperation* cow_op = vec[i];
-
-            de->old_chunk = cow_op->new_block;
-            de->new_chunk = data_chunk_id;
-
-            // Store operation pointer.
-            chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
-            offset += sizeof(struct disk_exception);
-            num_ops += 1;
-            if (cow_op->type() == kCowCopyOp) {
-                copy_ops++;
-            }
-
-            if (read_ahead_feature_) {
-                read_ahead_ops_.push_back(cow_op);
-            }
-
-            SNAP_LOG(DEBUG) << num_ops << ":"
-                            << " Ordered-op: "
-                            << " Old-chunk: " << de->old_chunk << " New-chunk: " << de->new_chunk;
-
-            if (num_ops == exceptions_per_area_) {
-                // Store it in vector at the right index. This maps the chunk-id to
-                // vector index.
-                vec_.push_back(std::move(de_ptr));
-                num_ops = 0;
-                offset = 0;
-
-                // Create buffer for next area
-                de_ptr = std::make_unique<uint8_t[]>(exceptions_per_area_ *
-                                                     sizeof(struct disk_exception));
-                memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
-
-                if (cowop_rm_iter->AtEnd()) {
-                    vec_.push_back(std::move(de_ptr));
-                    SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
-                }
-
-                if (!(pending_ordered_ops == 0)) {
-                    SNAP_LOG(ERROR) << "Invalid pending_ordered_ops: expected: 0 found: "
-                                    << pending_ordered_ops;
-                    return false;
-                }
-                pending_ordered_ops = exceptions_per_area_;
-            }
-
-            data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
-            total_ordered_ops -= 1;
-            /*
-             * Split the number of ops based on the size of read-ahead buffer
-             * region. We need to ensure that kernel doesn't issue IO on blocks
-             * which are not read by the read-ahead thread.
-             */
-            if (read_ahead_feature_ && (total_ordered_ops % num_ra_ops_per_iter == 0)) {
-                data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
-            }
-        }
-        vec.clear();
-        dest_blocks.clear();
-        source_blocks.clear();
-        prev_id.reset();
-    }
-
-    // Partially filled area or there is no metadata
-    // If there is no metadata, fill with zero so that kernel
-    // is aware that merge is completed.
-    if (num_ops || !metadata_found) {
-        vec_.push_back(std::move(de_ptr));
-        SNAP_LOG(DEBUG) << "ReadMetadata() completed. Partially filled area num_ops: " << num_ops
-                        << "Areas : " << vec_.size();
-    }
-
-    chunk_vec_.shrink_to_fit();
-    vec_.shrink_to_fit();
-    read_ahead_ops_.shrink_to_fit();
-
-    // Sort the vector based on sectors as we need this during un-aligned access
-    std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare);
-
-    SNAP_LOG(INFO) << "ReadMetadata completed. Final-chunk-id: " << data_chunk_id
-                   << " Num Sector: " << ChunkToSector(data_chunk_id)
-                   << " Replace-ops: " << replace_ops << " Zero-ops: " << zero_ops
-                   << " Copy-ops: " << copy_ops << " Areas: " << vec_.size()
-                   << " Num-ops-merged: " << header.num_merge_ops
-                   << " Total-data-ops: " << reader_->get_num_total_data_ops();
-
-    // Total number of sectors required for creating dm-user device
-    num_sectors_ = ChunkToSector(data_chunk_id);
-    merge_initiated_ = false;
-    PrepareReadAhead();
-
-    return true;
-}
-
-bool Snapuserd::MmapMetadata() {
-    const auto& header = reader_->GetHeader();
-
-    if (header.prefix.major_version >= 2 && header.buffer_size > 0) {
-        total_mapped_addr_length_ = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE;
-        read_ahead_feature_ = true;
-    } else {
-        // mmap the first 4k page - older COW format
-        total_mapped_addr_length_ = BLOCK_SZ;
-        read_ahead_feature_ = false;
-    }
-
-    mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED,
-                        cow_fd_.get(), 0);
-    if (mapped_addr_ == MAP_FAILED) {
-        SNAP_LOG(ERROR) << "mmap metadata failed";
-        return false;
-    }
-
-    return true;
-}
-
-void Snapuserd::UnmapBufferRegion() {
-    int ret = munmap(mapped_addr_, total_mapped_addr_length_);
-    if (ret < 0) {
-        SNAP_PLOG(ERROR) << "munmap failed";
-    }
-}
-
-void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
-              unsigned int, const char* message) {
-    if (severity == android::base::ERROR) {
-        fprintf(stderr, "%s\n", message);
-    } else {
-        fprintf(stdout, "%s\n", message);
-    }
-}
-
-bool Snapuserd::InitCowDevice() {
-    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
-    if (cow_fd_ < 0) {
-        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
-        return false;
-    }
-
-    return ReadMetadata();
-}
-
-void Snapuserd::ReadBlocksToCache(const std::string& dm_block_device,
-                                  const std::string& partition_name, off_t offset, size_t size) {
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
-    if (fd.get() == -1) {
-        SNAP_PLOG(ERROR) << "Error reading " << dm_block_device
-                         << " partition-name: " << partition_name;
-        return;
-    }
-
-    size_t remain = size;
-    off_t file_offset = offset;
-    // We pick 4M I/O size based on the fact that the current
-    // update_verifier has a similar I/O size.
-    size_t read_sz = 1024 * BLOCK_SZ;
-    std::vector<uint8_t> buf(read_sz);
-
-    while (remain > 0) {
-        size_t to_read = std::min(remain, read_sz);
-
-        if (!android::base::ReadFullyAtOffset(fd.get(), buf.data(), to_read, file_offset)) {
-            SNAP_PLOG(ERROR) << "Failed to read block from block device: " << dm_block_device
-                             << " at offset: " << file_offset
-                             << " partition-name: " << partition_name << " total-size: " << size
-                             << " remain_size: " << remain;
-            return;
-        }
-
-        file_offset += to_read;
-        remain -= to_read;
-    }
-
-    SNAP_LOG(INFO) << "Finished reading block-device: " << dm_block_device
-                   << " partition: " << partition_name << " size: " << size
-                   << " offset: " << offset;
-}
-
-void Snapuserd::ReadBlocks(const std::string& partition_name, const std::string& dm_block_device) {
-    SNAP_LOG(DEBUG) << "Reading partition: " << partition_name
-                    << " Block-Device: " << dm_block_device;
-
-    uint64_t dev_sz = 0;
-
-    unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_CLOEXEC)));
-    if (fd < 0) {
-        SNAP_LOG(ERROR) << "Cannot open block device";
-        return;
-    }
-
-    dev_sz = get_block_device_size(fd.get());
-    if (!dev_sz) {
-        SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
-        return;
-    }
-
-    int num_threads = 2;
-    size_t num_blocks = dev_sz >> BLOCK_SHIFT;
-    size_t num_blocks_per_thread = num_blocks / num_threads;
-    size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
-    off_t offset = 0;
-
-    for (int i = 0; i < num_threads; i++) {
-        (void)std::async(std::launch::async, &Snapuserd::ReadBlocksToCache, this, dm_block_device,
-                         partition_name, offset, read_sz_per_thread);
-
-        offset += read_sz_per_thread;
-    }
-}
-
-/*
- * Entry point to launch threads
- */
-bool Snapuserd::Start() {
-    std::vector<std::future<bool>> threads;
-    std::future<bool> ra_thread;
-    bool rathread = (read_ahead_feature_ && (read_ahead_ops_.size() > 0));
-
-    // Start the read-ahead thread and wait
-    // for it as the data has to be re-constructed
-    // from COW device.
-    if (rathread) {
-        ra_thread = std::async(std::launch::async, &ReadAheadThread::RunThread,
-                               read_ahead_thread_.get());
-        if (!WaitForReadAheadToStart()) {
-            SNAP_LOG(ERROR) << "Failed to start Read-ahead thread...";
-            return false;
-        }
-
-        SNAP_LOG(INFO) << "Read-ahead thread started...";
-    }
-
-    // Launch worker threads
-    for (int i = 0; i < worker_threads_.size(); i++) {
-        threads.emplace_back(
-                std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get()));
-    }
-
-    bool second_stage_init = true;
-
-    // We don't want to read the blocks during first stage init.
-    if (android::base::EndsWith(misc_name_, "-init") || is_socket_present_) {
-        second_stage_init = false;
-    }
-
-    if (second_stage_init) {
-        SNAP_LOG(INFO) << "Reading blocks to cache....";
-        auto& dm = DeviceMapper::Instance();
-        auto dm_block_devices = dm.FindDmPartitions();
-        if (dm_block_devices.empty()) {
-            SNAP_LOG(ERROR) << "No dm-enabled block device is found.";
-        } else {
-            auto parts = android::base::Split(misc_name_, "-");
-            std::string partition_name = parts[0];
-
-            const char* suffix_b = "_b";
-            const char* suffix_a = "_a";
-
-            partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1);
-            partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1);
-
-            if (dm_block_devices.find(partition_name) == dm_block_devices.end()) {
-                SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name;
-            } else {
-                ReadBlocks(partition_name, dm_block_devices.at(partition_name));
-            }
-        }
-    } else {
-        SNAP_LOG(INFO) << "Not reading block device into cache";
-    }
-
-    bool ret = true;
-    for (auto& t : threads) {
-        ret = t.get() && ret;
-    }
-
-    if (rathread) {
-        // Notify the read-ahead thread that all worker threads
-        // are done. We need this explicit notification when
-        // there is an IO failure or there was a switch
-        // of dm-user table; thus, forcing the read-ahead
-        // thread to wake up.
-        MergeCompleted();
-        ret = ret && ra_thread.get();
-    }
-
-    return ret;
-}
-
-uint64_t Snapuserd::GetBufferMetadataOffset() {
-    const auto& header = reader_->GetHeader();
-
-    size_t size = header.prefix.header_size + sizeof(BufferState);
-    return size;
-}
-
-/*
- * Metadata for read-ahead is 16 bytes. For a 2 MB region, we will
- * end up with 8k (2 PAGE) worth of metadata. Thus, a 2MB buffer
- * region is split into:
- *
- * 1: 8k metadata
- *
- */
-size_t Snapuserd::GetBufferMetadataSize() {
-    const auto& header = reader_->GetHeader();
-
-    size_t metadata_bytes = (header.buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ;
-    return metadata_bytes;
-}
-
-size_t Snapuserd::GetBufferDataOffset() {
-    const auto& header = reader_->GetHeader();
-
-    return (header.prefix.header_size + GetBufferMetadataSize());
-}
-
-/*
- * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
- */
-size_t Snapuserd::GetBufferDataSize() {
-    const auto& header = reader_->GetHeader();
-
-    size_t size = header.buffer_size - GetBufferMetadataSize();
-    return size;
-}
-
-struct BufferState* Snapuserd::GetBufferState() {
-    const auto& header = reader_->GetHeader();
-
-    struct BufferState* ra_state =
-            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.prefix.header_size);
-    return ra_state;
-}
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
deleted file mode 100644
index beb6004..0000000
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
+++ /dev/null
@@ -1,332 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <linux/types.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <sys/mman.h>
-
-#include <bitset>
-#include <condition_variable>
-#include <csignal>
-#include <cstring>
-#include <future>
-#include <iostream>
-#include <limits>
-#include <map>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <unordered_map>
-#include <unordered_set>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <ext4_utils/ext4_utils.h>
-#include <libdm/dm.h>
-#include <libsnapshot/cow_reader.h>
-#include <libsnapshot/cow_writer.h>
-#include <snapuserd/snapuserd_buffer.h>
-#include <snapuserd/snapuserd_kernel.h>
-
-namespace android {
-namespace snapshot {
-
-using android::base::unique_fd;
-using namespace std::chrono_literals;
-
-static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
-static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
-
-/*
- * With 4 threads, we get optimal performance
- * when update_verifier reads the partition during
- * boot.
- */
-static constexpr int NUM_THREADS_PER_PARTITION = 4;
-
-/*
- * State transitions between worker threads and read-ahead
- * threads.
- *
- * READ_AHEAD_BEGIN: Worker threads initiates the read-ahead
- *                   thread to begin reading the copy operations
- *                   for each bounded region.
- *
- * READ_AHEAD_IN_PROGRESS: When read ahead thread is in-flight
- *                         and reading the copy operations.
- *
- * IO_IN_PROGRESS: Merge operation is in-progress by worker threads.
- *
- * IO_TERMINATED: When all the worker threads are done, request the
- *                read-ahead thread to terminate
- *
- * READ_AHEAD_FAILURE: If there are any IO failures when read-ahead
- *                     thread is reading from COW device.
- *
- * The transition of each states is described in snapuserd_readahead.cpp
- */
-enum class READ_AHEAD_IO_TRANSITION {
-    READ_AHEAD_BEGIN,
-    READ_AHEAD_IN_PROGRESS,
-    IO_IN_PROGRESS,
-    IO_TERMINATED,
-    READ_AHEAD_FAILURE,
-};
-
-class Snapuserd;
-
-class ReadAheadThread {
-  public:
-    ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
-                    const std::string& misc_name, std::shared_ptr<Snapuserd> snapuserd);
-    bool RunThread();
-
-  private:
-    void InitializeRAIter();
-    bool RAIterDone();
-    void RAIterNext();
-    const CowOperation* GetRAOpIter();
-    void InitializeBuffer();
-
-    bool InitializeFds();
-    void CloseFds() {
-        cow_fd_ = {};
-        backing_store_fd_ = {};
-    }
-
-    bool ReadAheadIOStart();
-    void PrepareReadAhead(uint64_t* source_offset, int* pending_ops, std::vector<uint64_t>& blocks);
-    bool ReconstructDataFromCow();
-    void CheckOverlap(const CowOperation* cow_op);
-
-    void* read_ahead_buffer_;
-    void* metadata_buffer_;
-    std::vector<const CowOperation*>::reverse_iterator read_ahead_iter_;
-    std::string cow_device_;
-    std::string backing_store_device_;
-    std::string misc_name_;
-
-    unique_fd cow_fd_;
-    unique_fd backing_store_fd_;
-
-    std::shared_ptr<Snapuserd> snapuserd_;
-
-    std::unordered_set<uint64_t> dest_blocks_;
-    std::unordered_set<uint64_t> source_blocks_;
-    bool overlap_;
-};
-
-class WorkerThread {
-  public:
-    WorkerThread(const std::string& cow_device, const std::string& backing_device,
-                 const std::string& control_device, const std::string& misc_name,
-                 std::shared_ptr<Snapuserd> snapuserd);
-    bool RunThread();
-
-  private:
-    // Initialization
-    void InitializeBufsink();
-    bool InitializeFds();
-    bool InitReader();
-    void CloseFds() {
-        ctrl_fd_ = {};
-        backing_store_fd_ = {};
-    }
-
-    // Functions interacting with dm-user
-    bool ReadDmUserHeader();
-    bool DmuserReadRequest();
-    bool DmuserWriteRequest();
-    bool ReadDmUserPayload(void* buffer, size_t size);
-    bool WriteDmUserPayload(size_t size, bool header_response);
-
-    bool ReadDiskExceptions(chunk_t chunk, size_t size);
-    bool ZerofillDiskExceptions(size_t read_size);
-    void ConstructKernelCowHeader();
-
-    // IO Path
-    bool ProcessIORequest();
-    int ReadData(sector_t sector, size_t size);
-    int ReadUnalignedSector(sector_t sector, size_t size,
-                            std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
-
-    // Processing COW operations
-    bool ProcessCowOp(const CowOperation* cow_op);
-    bool ProcessReplaceOp(const CowOperation* cow_op);
-    // Handles Copy
-    bool ProcessCopyOp(const CowOperation* cow_op);
-    bool ProcessZeroOp();
-
-    bool ReadFromBaseDevice(const CowOperation* cow_op);
-    bool GetReadAheadPopulatedBuffer(const CowOperation* cow_op);
-
-    // Merge related functions
-    bool ProcessMergeComplete(chunk_t chunk, void* buffer);
-    loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
-                               int* unmerged_exceptions);
-
-    int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
-                             int unmerged_exceptions, bool* copy_op, bool* commit);
-
-    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
-    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
-
-    std::unique_ptr<CowReader> reader_;
-    BufferSink bufsink_;
-
-    std::string cow_device_;
-    std::string backing_store_device_;
-    std::string control_device_;
-    std::string misc_name_;
-
-    unique_fd cow_fd_;
-    unique_fd backing_store_fd_;
-    unique_fd ctrl_fd_;
-
-    std::shared_ptr<Snapuserd> snapuserd_;
-    uint32_t exceptions_per_area_;
-};
-
-class Snapuserd : public std::enable_shared_from_this<Snapuserd> {
-  public:
-    Snapuserd(const std::string& misc_name, const std::string& cow_device,
-              const std::string& backing_device);
-    bool InitCowDevice();
-    bool Start();
-    const std::string& GetControlDevicePath() { return control_device_; }
-    const std::string& GetMiscName() { return misc_name_; }
-    uint64_t GetNumSectors() { return num_sectors_; }
-    bool IsAttached() const { return attached_; }
-    void AttachControlDevice() { attached_ = true; }
-
-    void CheckMergeCompletionStatus();
-    bool CommitMerge(int num_merge_ops);
-
-    void CloseFds() { cow_fd_ = {}; }
-    void FreeResources() {
-        worker_threads_.clear();
-        read_ahead_thread_ = nullptr;
-    }
-    size_t GetMetadataAreaSize() { return vec_.size(); }
-    void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
-
-    bool InitializeWorkers();
-    std::unique_ptr<CowReader> CloneReaderForWorker();
-    std::shared_ptr<Snapuserd> GetSharedPtr() { return shared_from_this(); }
-
-    std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; }
-    const std::vector<std::unique_ptr<uint8_t[]>>& GetMetadataVec() const { return vec_; }
-
-    static bool compare(std::pair<sector_t, const CowOperation*> p1,
-                        std::pair<sector_t, const CowOperation*> p2) {
-        return p1.first < p2.first;
-    }
-
-    void UnmapBufferRegion();
-    bool MmapMetadata();
-
-    // Read-ahead related functions
-    std::vector<const CowOperation*>& GetReadAheadOpsVec() { return read_ahead_ops_; }
-    std::unordered_map<uint64_t, void*>& GetReadAheadMap() { return read_ahead_buffer_map_; }
-    void* GetMappedAddr() { return mapped_addr_; }
-    bool IsReadAheadFeaturePresent() { return read_ahead_feature_; }
-    void PrepareReadAhead();
-    void StartReadAhead();
-    void MergeCompleted();
-    bool ReadAheadIOCompleted(bool sync);
-    void ReadAheadIOFailed();
-    bool WaitForMergeToComplete();
-    bool GetReadAheadPopulatedBuffer(uint64_t block, void* buffer);
-    bool ReconstructDataFromCow() { return populate_data_from_cow_; }
-    void ReconstructDataFromCowFinish() { populate_data_from_cow_ = false; }
-    bool WaitForReadAheadToStart();
-
-    uint64_t GetBufferMetadataOffset();
-    size_t GetBufferMetadataSize();
-    size_t GetBufferDataOffset();
-    size_t GetBufferDataSize();
-
-    // Final block to be merged in a given read-ahead buffer region
-    void SetFinalBlockMerged(uint64_t x) { final_block_merged_ = x; }
-    uint64_t GetFinalBlockMerged() { return final_block_merged_; }
-    // Total number of blocks to be merged in a given read-ahead buffer region
-    void SetTotalRaBlocksMerged(int x) { total_ra_blocks_merged_ = x; }
-    int GetTotalRaBlocksMerged() { return total_ra_blocks_merged_; }
-    void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
-
-  private:
-    bool IsChunkIdMetadata(chunk_t chunk);
-    chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
-
-    bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
-    bool ReadMetadata();
-    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
-    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
-    bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
-    struct BufferState* GetBufferState();
-
-    void ReadBlocks(const std::string& partition_name, const std::string& dm_block_device);
-    void ReadBlocksToCache(const std::string& dm_block_device, const std::string& partition_name,
-                           off_t offset, size_t size);
-
-    std::string cow_device_;
-    std::string backing_store_device_;
-    std::string control_device_;
-    std::string misc_name_;
-
-    unique_fd cow_fd_;
-
-    uint32_t exceptions_per_area_;
-    uint64_t num_sectors_;
-
-    std::unique_ptr<CowReader> reader_;
-
-    // Vector of disk exception which is a
-    // mapping of old-chunk to new-chunk
-    std::vector<std::unique_ptr<uint8_t[]>> vec_;
-
-    // chunk_vec stores the pseudo mapping of sector
-    // to COW operations.
-    std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
-
-    std::mutex lock_;
-    std::condition_variable cv;
-
-    void* mapped_addr_;
-    size_t total_mapped_addr_length_;
-
-    std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
-    // Read-ahead related
-    std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
-    std::vector<const CowOperation*> read_ahead_ops_;
-    bool populate_data_from_cow_ = false;
-    bool read_ahead_feature_;
-    uint64_t final_block_merged_;
-    int total_ra_blocks_merged_ = 0;
-    READ_AHEAD_IO_TRANSITION io_state_;
-    std::unique_ptr<ReadAheadThread> read_ahead_thread_;
-
-    bool merge_initiated_ = false;
-    bool attached_ = false;
-    bool is_socket_present_;
-};
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
deleted file mode 100644
index f1b9245..0000000
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "snapuserd.h"
-
-#include <csignal>
-#include <optional>
-#include <set>
-
-#include <snapuserd/snapuserd_client.h>
-
-namespace android {
-namespace snapshot {
-
-using namespace android;
-using namespace android::dm;
-using android::base::unique_fd;
-
-#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
-#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
-
-/*
- * Merging a copy operation involves the following flow:
- *
- * 1: dm-snapshot layer requests merge for a 4k block. dm-user sends the request
- *    to the daemon
- * 2: daemon reads the source block
- * 3: daemon copies the source data
- * 4: IO completion sent back to dm-user (a switch from user space to kernel)
- * 5: dm-snapshot merges the data to base device
- * 6: dm-snapshot sends the merge-completion IO to dm-user
- * 7: dm-user re-directs the merge completion IO to daemon (one more switch)
- * 8: daemon updates the COW file about the completed merge request (a write syscall) and followed
- * by a fysnc. 9: Send the IO completion back to dm-user
- *
- * The above sequence is a significant overhead especially when merging one 4k
- * block at a time.
- *
- * Read-ahead layer will optimize the above path by reading the data from base
- * device in the background so that merging thread can retrieve the data from
- * the read-ahead cache. Additionally, syncing of merged data is deferred to
- * read-ahead thread threadby the IO path is not bottlenecked.
- *
- * We create a scratch space of 2MB to store the read-ahead data in the COW
- * device.
- *
- *      +-----------------------+
- *      |     Header (fixed)    |
- *      +-----------------------+
- *      |    Scratch space      |  <-- 2MB
- *      +-----------------------+
- *
- *      Scratch space is as follows:
- *
- *      +-----------------------+
- *      |       Metadata        | <- 4k page
- *      +-----------------------+
- *      |       Metadata        | <- 4k page
- *      +-----------------------+
- *      |                       |
- *      |    Read-ahead data    |
- *      |                       |
- *      +-----------------------+
- *
- * State transitions and communication between read-ahead thread and worker
- * thread during merge:
- * =====================================================================
- *
- *   Worker Threads                                 Read-Ahead thread
- *   ------------------------------------------------------------------
- *
- *      |
- *      |
- *  --> -----------------READ_AHEAD_BEGIN------------->|
- *  |   |                                              | READ_AHEAD_IN_PROGRESS
- *  |  WAIT                                            |
- *  |   |                                              |
- *  |   |<-----------------IO_IN_PROGRESS---------------
- *  |   |                                              |
- *  |   | IO_IN_PRGRESS                               WAIT
- *  |   |                                              |
- *  |<--|                                              |
- *      |                                              |
- *      ------------------IO_TERMINATED--------------->|
- *                                                     END
- *
- *
- * ===================================================================
- *
- * Example:
- *
- * We have 6 copy operations to be executed in OTA and there is a overlap. Update-engine
- * will write to COW file as follows:
- *
- * Op-1: 20 -> 23
- * Op-2: 19 -> 22
- * Op-3: 18 -> 21
- * Op-4: 17 -> 20
- * Op-5: 16 -> 19
- * Op-6: 15 -> 18
- *
- * Read-ahead thread will read all the 6 source blocks and store the data in the
- * scratch space. Metadata will contain the destination block numbers. Thus,
- * scratch space will look something like this:
- *
- * +--------------+
- * | Block   23   |
- * | offset - 1   |
- * +--------------+
- * | Block   22   |
- * | offset - 2   |
- * +--------------+
- * | Block   21   |
- * | offset - 3   |
- * +--------------+
- *    ...
- *    ...
- * +--------------+
- * | Data-Block 20| <-- offset - 1
- * +--------------+
- * | Data-Block 19| <-- offset - 2
- * +--------------+
- * | Data-Block 18| <-- offset - 3
- * +--------------+
- *     ...
- *     ...
- *
- * ====================================================================
- * IO Path:
- *
- * Read-ahead will serve the data to worker threads during merge only
- * after metadata and data are persisted to the scratch space. Worker
- * threads during merge will always retrieve the data from cache; if the
- * cache is not populated, it will wait for the read-ahead thread to finish.
- * Furthermore, the number of operations merged will by synced to the header
- * only when all the blocks in the read-ahead cache are merged. In the above
- * case, when all 6 operations are merged, COW Header is updated with
- * num_merge_ops = 6.
- *
- * Merge resume after crash:
- *
- * Let's say we have a crash after 5 operations are merged. i.e. after
- * Op-5: 16->19 is completed but before the Op-6 is merged. Thus, COW Header
- * num_merge_ops will be 0 as the all the ops were not merged yet. During next
- * reboot, read-ahead thread will re-construct the data in-memory from the
- * scratch space; when merge resumes, Op-1 will be re-exectued. However,
- * data will be served from read-ahead cache safely even though, block 20
- * was over-written by Op-4.
- *
- */
-
-ReadAheadThread::ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
-                                 const std::string& misc_name,
-                                 std::shared_ptr<Snapuserd> snapuserd) {
-    cow_device_ = cow_device;
-    backing_store_device_ = backing_device;
-    misc_name_ = misc_name;
-    snapuserd_ = snapuserd;
-}
-
-void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
-    uint64_t source_block = cow_op->source();
-    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block)) {
-        overlap_ = true;
-    }
-
-    dest_blocks_.insert(source_block);
-    source_blocks_.insert(cow_op->new_block);
-}
-
-void ReadAheadThread::PrepareReadAhead(uint64_t* source_offset, int* pending_ops,
-                                       std::vector<uint64_t>& blocks) {
-    int num_ops = *pending_ops;
-    int nr_consecutive = 0;
-    CHECK_NE(source_offset, nullptr);
-
-    if (!RAIterDone() && num_ops) {
-        // Get the first block with offset
-        const CowOperation* cow_op = GetRAOpIter();
-        CHECK_NE(cow_op, nullptr);
-        *source_offset = cow_op->source();
-        if (cow_op->type() == kCowCopyOp) {
-            *source_offset *= BLOCK_SZ;
-        }
-        RAIterNext();
-        num_ops -= 1;
-        nr_consecutive = 1;
-        blocks.push_back(cow_op->new_block);
-
-        if (!overlap_) {
-            CheckOverlap(cow_op);
-        }
-
-        /*
-         * Find number of consecutive blocks working backwards.
-         */
-        while (!RAIterDone() && num_ops) {
-            const CowOperation* op = GetRAOpIter();
-            CHECK_NE(op, nullptr);
-            uint64_t next_offset = op->source();
-            if (op->type() == kCowCopyOp) {
-                next_offset *= BLOCK_SZ;
-            }
-            if (next_offset + nr_consecutive * BLOCK_SZ != *source_offset) {
-                break;
-            }
-            nr_consecutive += 1;
-            num_ops -= 1;
-            blocks.push_back(op->new_block);
-            RAIterNext();
-
-            if (!overlap_) {
-                CheckOverlap(op);
-            }
-        }
-    }
-}
-
-bool ReadAheadThread::ReconstructDataFromCow() {
-    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
-    read_ahead_buffer_map.clear();
-    loff_t metadata_offset = 0;
-    loff_t start_data_offset = snapuserd_->GetBufferDataOffset();
-    int num_ops = 0;
-    int total_blocks_merged = 0;
-
-    // This memcpy is important as metadata_buffer_ will be an unaligned address and will fault
-    // on 32-bit systems
-    std::unique_ptr<uint8_t[]> metadata_buffer =
-            std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
-    memcpy(metadata_buffer.get(), metadata_buffer_, snapuserd_->GetBufferMetadataSize());
-
-    while (true) {
-        struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
-                (char*)metadata_buffer.get() + metadata_offset);
-
-        // Done reading metadata
-        if (bm->new_block == 0 && bm->file_offset == 0) {
-            break;
-        }
-
-        loff_t buffer_offset = bm->file_offset - start_data_offset;
-        void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + buffer_offset);
-        read_ahead_buffer_map[bm->new_block] = bufptr;
-        num_ops += 1;
-        total_blocks_merged += 1;
-
-        metadata_offset += sizeof(struct ScratchMetadata);
-    }
-
-    // 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.
-    while (!RAIterDone()) {
-        const CowOperation* op = GetRAOpIter();
-        if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
-            num_ops -= 1;
-            snapuserd_->SetFinalBlockMerged(op->new_block);
-            RAIterNext();
-        } else {
-            // Verify that we have covered all the ops which were re-constructed
-            // from COW device - These are the ops which are being
-            // re-constructed after crash.
-            if (!(num_ops == 0)) {
-                SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
-                                << " Pending ops: " << num_ops;
-                snapuserd_->ReadAheadIOFailed();
-                return false;
-            }
-            break;
-        }
-    }
-
-    snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
-
-    snapuserd_->ReconstructDataFromCowFinish();
-
-    if (!snapuserd_->ReadAheadIOCompleted(true)) {
-        SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
-        snapuserd_->ReadAheadIOFailed();
-        return false;
-    }
-
-    SNAP_LOG(INFO) << "ReconstructDataFromCow success";
-    return true;
-}
-
-bool ReadAheadThread::ReadAheadIOStart() {
-    // Check if the data has to be constructed from the COW file.
-    // This will be true only once during boot up after a crash
-    // during merge.
-    if (snapuserd_->ReconstructDataFromCow()) {
-        return ReconstructDataFromCow();
-    }
-
-    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
-    read_ahead_buffer_map.clear();
-
-    int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
-    loff_t metadata_offset = 0;
-
-    struct ScratchMetadata* bm =
-            reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
-
-    bm->new_block = 0;
-    bm->file_offset = 0;
-
-    std::vector<uint64_t> blocks;
-
-    loff_t buffer_offset = 0;
-    loff_t offset = 0;
-    loff_t file_offset = snapuserd_->GetBufferDataOffset();
-    int total_blocks_merged = 0;
-    overlap_ = false;
-    dest_blocks_.clear();
-    source_blocks_.clear();
-
-    while (true) {
-        uint64_t source_offset;
-        int linear_blocks;
-
-        PrepareReadAhead(&source_offset, &num_ops, blocks);
-        linear_blocks = blocks.size();
-        if (linear_blocks == 0) {
-            // No more blocks to read
-            SNAP_LOG(DEBUG) << " Read-ahead completed....";
-            break;
-        }
-
-        // Get the first block in the consecutive set of blocks
-        source_offset = source_offset - (linear_blocks - 1) * BLOCK_SZ;
-        size_t io_size = (linear_blocks * BLOCK_SZ);
-        num_ops -= linear_blocks;
-        total_blocks_merged += linear_blocks;
-
-        // Mark the block number as the one which will
-        // be the final block to be merged in this entire region.
-        // Read-ahead thread will get
-        // notified when this block is merged to make
-        // forward progress
-        snapuserd_->SetFinalBlockMerged(blocks.back());
-
-        while (linear_blocks) {
-            uint64_t new_block = blocks.back();
-            blocks.pop_back();
-            // Assign the mapping
-            void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
-            read_ahead_buffer_map[new_block] = bufptr;
-            offset += BLOCK_SZ;
-
-            bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ +
-                                                           metadata_offset);
-            bm->new_block = new_block;
-            bm->file_offset = file_offset;
-
-            metadata_offset += sizeof(struct ScratchMetadata);
-            file_offset += BLOCK_SZ;
-
-            linear_blocks -= 1;
-        }
-
-        // Read from the base device consecutive set of blocks in one shot
-        if (!android::base::ReadFullyAtOffset(backing_store_fd_,
-                                              (char*)read_ahead_buffer_ + buffer_offset, io_size,
-                                              source_offset)) {
-            SNAP_PLOG(ERROR) << "Ordered-op failed. Read from backing store: "
-                             << backing_store_device_ << "at block :" << source_offset / BLOCK_SZ
-                             << " offset :" << source_offset % BLOCK_SZ
-                             << " buffer_offset : " << buffer_offset << " io_size : " << io_size
-                             << " buf-addr : " << read_ahead_buffer_;
-
-            snapuserd_->ReadAheadIOFailed();
-            return false;
-        }
-
-        // This is important - explicitly set the contents to zero. This is used
-        // when re-constructing the data after crash. This indicates end of
-        // reading metadata contents when re-constructing the data
-        bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
-        bm->new_block = 0;
-        bm->file_offset = 0;
-
-        buffer_offset += io_size;
-    }
-
-    snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
-
-    // Flush the data only if we have a overlapping blocks in the region
-    if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
-        SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
-        snapuserd_->ReadAheadIOFailed();
-        return false;
-    }
-
-    return true;
-}
-
-bool ReadAheadThread::RunThread() {
-    if (!InitializeFds()) {
-        return false;
-    }
-
-    InitializeRAIter();
-    InitializeBuffer();
-
-    while (!RAIterDone()) {
-        if (!ReadAheadIOStart()) {
-            return false;
-        }
-
-        bool status = snapuserd_->WaitForMergeToComplete();
-
-        if (status && !snapuserd_->CommitMerge(snapuserd_->GetTotalRaBlocksMerged())) {
-            return false;
-        }
-
-        if (!status) break;
-    }
-
-    CloseFds();
-    SNAP_LOG(INFO) << " ReadAhead thread terminating....";
-    return true;
-}
-
-// Initialization
-bool ReadAheadThread::InitializeFds() {
-    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
-    if (backing_store_fd_ < 0) {
-        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
-        return false;
-    }
-
-    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
-    if (cow_fd_ < 0) {
-        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
-        return false;
-    }
-
-    return true;
-}
-
-void ReadAheadThread::InitializeRAIter() {
-    std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
-    read_ahead_iter_ = read_ahead_ops.rbegin();
-}
-
-bool ReadAheadThread::RAIterDone() {
-    std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
-    return read_ahead_iter_ == read_ahead_ops.rend();
-}
-
-void ReadAheadThread::RAIterNext() {
-    read_ahead_iter_++;
-}
-
-const CowOperation* ReadAheadThread::GetRAOpIter() {
-    return *read_ahead_iter_;
-}
-
-void ReadAheadThread::InitializeBuffer() {
-    void* mapped_addr = snapuserd_->GetMappedAddr();
-    // Map the scratch space region into memory
-    metadata_buffer_ =
-            static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
-    read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
-}
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
deleted file mode 100644
index 577b09d..0000000
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
+++ /dev/null
@@ -1,559 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <arpa/inet.h>
-#include <cutils/sockets.h>
-#include <errno.h>
-#include <netinet/in.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <android-base/cmsg.h>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/scopeguard.h>
-#include <fs_mgr/file_wait.h>
-#include <snapuserd/snapuserd_client.h>
-
-#include "snapuserd_server.h"
-
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
-
-namespace android {
-namespace snapshot {
-
-using namespace std::string_literals;
-
-using android::base::borrowed_fd;
-using android::base::unique_fd;
-
-DaemonOperations SnapuserdServer::Resolveop(std::string& input) {
-    if (input == "init") return DaemonOperations::INIT;
-    if (input == "start") return DaemonOperations::START;
-    if (input == "stop") return DaemonOperations::STOP;
-    if (input == "query") return DaemonOperations::QUERY;
-    if (input == "delete") return DaemonOperations::DELETE;
-    if (input == "detach") return DaemonOperations::DETACH;
-    if (input == "supports") return DaemonOperations::SUPPORTS;
-
-    return DaemonOperations::INVALID;
-}
-
-SnapuserdServer::~SnapuserdServer() {
-    // Close any client sockets that were added via AcceptClient().
-    for (size_t i = 1; i < watched_fds_.size(); i++) {
-        close(watched_fds_[i].fd);
-    }
-}
-
-std::string SnapuserdServer::GetDaemonStatus() {
-    std::string msg = "";
-
-    if (IsTerminating())
-        msg = "passive";
-    else
-        msg = "active";
-
-    return msg;
-}
-
-void SnapuserdServer::Parsemsg(std::string const& msg, const char delim,
-                               std::vector<std::string>& out) {
-    std::stringstream ss(msg);
-    std::string s;
-
-    while (std::getline(ss, s, delim)) {
-        out.push_back(s);
-    }
-}
-
-void SnapuserdServer::ShutdownThreads() {
-    StopThreads();
-    JoinAllThreads();
-}
-
-DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd)
-    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
-
-bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
-    ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
-    if (ret < 0) {
-        PLOG(ERROR) << "Snapuserd:server: send() failed";
-        return false;
-    }
-
-    if (ret < msg.size()) {
-        LOG(ERROR) << "Partial send; expected " << msg.size() << " bytes, sent " << ret;
-        return false;
-    }
-    return true;
-}
-
-bool SnapuserdServer::Recv(android::base::borrowed_fd fd, std::string* data) {
-    char msg[MAX_PACKET_SIZE];
-    ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
-    if (rv < 0) {
-        PLOG(ERROR) << "recv failed";
-        return false;
-    }
-    *data = std::string(msg, rv);
-    return true;
-}
-
-bool SnapuserdServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
-    const char delim = ',';
-
-    std::vector<std::string> out;
-    Parsemsg(str, delim, out);
-    DaemonOperations op = Resolveop(out[0]);
-
-    switch (op) {
-        case DaemonOperations::INIT: {
-            // Message format:
-            // init,<misc_name>,<cow_device_path>,<backing_device>
-            //
-            // Reads the metadata and send the number of sectors
-            if (out.size() != 4) {
-                LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-
-            auto handler = AddHandler(out[1], out[2], out[3]);
-            if (!handler) {
-                return Sendmsg(fd, "fail");
-            }
-
-            auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
-            return Sendmsg(fd, retval);
-        }
-        case DaemonOperations::START: {
-            // Message format:
-            // start,<misc_name>
-            //
-            // Start the new thread which binds to dm-user misc device
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-
-            std::lock_guard<std::mutex> lock(lock_);
-            auto iter = FindHandler(&lock, out[1]);
-            if (iter == dm_users_.end()) {
-                LOG(ERROR) << "Could not find handler: " << out[1];
-                return Sendmsg(fd, "fail");
-            }
-            if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) {
-                LOG(ERROR) << "Tried to re-attach control device: " << out[1];
-                return Sendmsg(fd, "fail");
-            }
-            if (!StartHandler(*iter)) {
-                return Sendmsg(fd, "fail");
-            }
-            return Sendmsg(fd, "success");
-        }
-        case DaemonOperations::STOP: {
-            // Message format: stop
-            //
-            // Stop all the threads gracefully and then shutdown the
-            // main thread
-            SetTerminating();
-            ShutdownThreads();
-            return true;
-        }
-        case DaemonOperations::QUERY: {
-            // Message format: query
-            //
-            // As part of transition, Second stage daemon will be
-            // created before terminating the first stage daemon. Hence,
-            // for a brief period client may have to distiguish between
-            // first stage daemon and second stage daemon.
-            //
-            // Second stage daemon is marked as active and hence will
-            // be ready to receive control message.
-            return Sendmsg(fd, GetDaemonStatus());
-        }
-        case DaemonOperations::DELETE: {
-            // Message format:
-            // delete,<misc_name>
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-            if (!RemoveAndJoinHandler(out[1])) {
-                return Sendmsg(fd, "fail");
-            }
-            return Sendmsg(fd, "success");
-        }
-        case DaemonOperations::DETACH: {
-            terminating_ = true;
-            return true;
-        }
-        case DaemonOperations::SUPPORTS: {
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-            if (out[1] == "second_stage_socket_handoff") {
-                return Sendmsg(fd, "success");
-            }
-            return Sendmsg(fd, "fail");
-        }
-        default: {
-            LOG(ERROR) << "Received unknown message type from client";
-            Sendmsg(fd, "fail");
-            return false;
-        }
-    }
-}
-
-void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
-    LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
-
-    handler->snapuserd()->SetSocketPresent(is_socket_present_);
-    if (!handler->snapuserd()->Start()) {
-        LOG(ERROR) << " Failed to launch all worker threads";
-    }
-
-    handler->snapuserd()->CloseFds();
-    handler->snapuserd()->CheckMergeCompletionStatus();
-    handler->snapuserd()->UnmapBufferRegion();
-
-    auto misc_name = handler->misc_name();
-    LOG(INFO) << "Handler thread about to exit: " << misc_name;
-
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        auto iter = FindHandler(&lock, handler->misc_name());
-        if (iter == dm_users_.end()) {
-            // RemoveAndJoinHandler() already removed us from the list, and is
-            // now waiting on a join(), so just return. Additionally, release
-            // all the resources held by snapuserd object which are shared
-            // by worker threads. This should be done when the last reference
-            // of "handler" is released; but we will explicitly release here
-            // to make sure snapuserd object is freed as it is the biggest
-            // consumer of memory in the daemon.
-            handler->FreeResources();
-            LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
-            return;
-        }
-
-        LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name;
-
-        if (handler->snapuserd()->IsAttached()) {
-            handler->thread().detach();
-        }
-
-        // Important: free resources within the lock. This ensures that if
-        // WaitForDelete() is called, the handler is either in the list, or
-        // it's not and its resources are guaranteed to be freed.
-        handler->FreeResources();
-    }
-}
-
-bool SnapuserdServer::Start(const std::string& socketname) {
-    bool start_listening = true;
-
-    sockfd_.reset(android_get_control_socket(socketname.c_str()));
-    if (sockfd_ < 0) {
-        sockfd_.reset(socket_local_server(socketname.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                          SOCK_STREAM));
-        if (sockfd_ < 0) {
-            PLOG(ERROR) << "Failed to create server socket " << socketname;
-            return false;
-        }
-        start_listening = false;
-    }
-    return StartWithSocket(start_listening);
-}
-
-bool SnapuserdServer::StartWithSocket(bool start_listening) {
-    if (start_listening && listen(sockfd_.get(), 4) < 0) {
-        PLOG(ERROR) << "listen socket failed";
-        return false;
-    }
-
-    AddWatchedFd(sockfd_, POLLIN);
-    is_socket_present_ = true;
-
-    // If started in first-stage init, the property service won't be online.
-    if (access("/dev/socket/property_service", F_OK) == 0) {
-        if (!android::base::SetProperty("snapuserd.ready", "true")) {
-            LOG(ERROR) << "Failed to set snapuserd.ready property";
-            return false;
-        }
-    }
-
-    LOG(DEBUG) << "Snapuserd server now accepting connections";
-    return true;
-}
-
-bool SnapuserdServer::Run() {
-    LOG(INFO) << "Now listening on snapuserd socket";
-
-    while (!IsTerminating()) {
-        int rv = TEMP_FAILURE_RETRY(poll(watched_fds_.data(), watched_fds_.size(), -1));
-        if (rv < 0) {
-            PLOG(ERROR) << "poll failed";
-            return false;
-        }
-        if (!rv) {
-            continue;
-        }
-
-        if (watched_fds_[0].revents) {
-            AcceptClient();
-        }
-
-        auto iter = watched_fds_.begin() + 1;
-        while (iter != watched_fds_.end()) {
-            if (iter->revents && !HandleClient(iter->fd, iter->revents)) {
-                close(iter->fd);
-                iter = watched_fds_.erase(iter);
-            } else {
-                iter++;
-            }
-        }
-    }
-
-    JoinAllThreads();
-    return true;
-}
-
-void SnapuserdServer::JoinAllThreads() {
-    // Acquire the thread list within the lock.
-    std::vector<std::shared_ptr<DmUserHandler>> dm_users;
-    {
-        std::lock_guard<std::mutex> guard(lock_);
-        dm_users = std::move(dm_users_);
-    }
-
-    for (auto& client : dm_users) {
-        auto& th = client->thread();
-
-        if (th.joinable()) th.join();
-    }
-}
-
-void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
-    struct pollfd p = {};
-    p.fd = fd.get();
-    p.events = events;
-    watched_fds_.emplace_back(std::move(p));
-}
-
-void SnapuserdServer::AcceptClient() {
-    int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC));
-    if (fd < 0) {
-        PLOG(ERROR) << "accept4 failed";
-        return;
-    }
-
-    AddWatchedFd(fd, POLLIN);
-}
-
-bool SnapuserdServer::HandleClient(android::base::borrowed_fd fd, int revents) {
-    if (revents & POLLHUP) {
-        LOG(DEBUG) << "Snapuserd client disconnected";
-        return false;
-    }
-
-    std::string str;
-    if (!Recv(fd, &str)) {
-        return false;
-    }
-    if (!Receivemsg(fd, str)) {
-        LOG(ERROR) << "Encountered error handling client message, revents: " << revents;
-        return false;
-    }
-    return true;
-}
-
-void SnapuserdServer::Interrupt() {
-    // Force close the socket so poll() fails.
-    sockfd_ = {};
-    SetTerminating();
-}
-
-std::shared_ptr<DmUserHandler> SnapuserdServer::AddHandler(const std::string& misc_name,
-                                                           const std::string& cow_device_path,
-                                                           const std::string& backing_device) {
-    auto snapuserd = std::make_shared<Snapuserd>(misc_name, cow_device_path, backing_device);
-    if (!snapuserd->InitCowDevice()) {
-        LOG(ERROR) << "Failed to initialize Snapuserd";
-        return nullptr;
-    }
-
-    if (!snapuserd->InitializeWorkers()) {
-        LOG(ERROR) << "Failed to initialize workers";
-        return nullptr;
-    }
-
-    auto handler = std::make_shared<DmUserHandler>(snapuserd);
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        if (FindHandler(&lock, misc_name) != dm_users_.end()) {
-            LOG(ERROR) << "Handler already exists: " << misc_name;
-            return nullptr;
-        }
-        dm_users_.push_back(handler);
-    }
-    return handler;
-}
-
-bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
-    if (handler->snapuserd()->IsAttached()) {
-        LOG(ERROR) << "Handler already attached";
-        return false;
-    }
-
-    handler->snapuserd()->AttachControlDevice();
-
-    handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler));
-    return true;
-}
-
-auto SnapuserdServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                  const std::string& misc_name) -> HandlerList::iterator {
-    CHECK(proof_of_lock);
-
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        if ((*iter)->misc_name() == misc_name) {
-            return iter;
-        }
-    }
-    return dm_users_.end();
-}
-
-bool SnapuserdServer::RemoveAndJoinHandler(const std::string& misc_name) {
-    std::shared_ptr<DmUserHandler> handler;
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-
-        auto iter = FindHandler(&lock, misc_name);
-        if (iter == dm_users_.end()) {
-            // Client already deleted.
-            return true;
-        }
-        handler = std::move(*iter);
-        dm_users_.erase(iter);
-    }
-
-    auto& th = handler->thread();
-    if (th.joinable()) {
-        th.join();
-    }
-    return true;
-}
-
-bool SnapuserdServer::WaitForSocket() {
-    auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
-
-    auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
-
-    if (!android::fs_mgr::WaitForFile(socket_path, std::chrono::milliseconds::max())) {
-        LOG(ERROR)
-                << "Failed to wait for proxy socket, second-stage snapuserd will fail to connect";
-        return false;
-    }
-
-    // We must re-initialize property service access, since we launched before
-    // second-stage init.
-    __system_properties_init();
-
-    if (!android::base::WaitForProperty("snapuserd.proxy_ready", "true")) {
-        LOG(ERROR)
-                << "Failed to wait for proxy property, second-stage snapuserd will fail to connect";
-        return false;
-    }
-
-    unique_fd fd(socket_local_client(kSnapuserdSocketProxy, ANDROID_SOCKET_NAMESPACE_RESERVED,
-                                     SOCK_SEQPACKET));
-    if (fd < 0) {
-        PLOG(ERROR) << "Failed to connect to socket proxy";
-        return false;
-    }
-
-    char code[1];
-    std::vector<unique_fd> fds;
-    ssize_t rv = android::base::ReceiveFileDescriptorVector(fd, code, sizeof(code), 1, &fds);
-    if (rv < 0) {
-        PLOG(ERROR) << "Failed to receive server socket over proxy";
-        return false;
-    }
-    if (fds.empty()) {
-        LOG(ERROR) << "Expected at least one file descriptor from proxy";
-        return false;
-    }
-
-    // We don't care if the ACK is received.
-    code[0] = 'a';
-    if (TEMP_FAILURE_RETRY(send(fd, code, sizeof(code), MSG_NOSIGNAL)) < 0) {
-        PLOG(ERROR) << "Failed to send ACK to proxy";
-        return false;
-    }
-
-    sockfd_ = std::move(fds[0]);
-    if (!StartWithSocket(true)) {
-        return false;
-    }
-    return Run();
-}
-
-bool SnapuserdServer::RunForSocketHandoff() {
-    unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
-    if (proxy_fd < 0) {
-        PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;
-    }
-    borrowed_fd server_fd(android_get_control_socket(kSnapuserdSocket));
-    if (server_fd < 0) {
-        PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocket;
-    }
-
-    if (listen(proxy_fd.get(), 4) < 0) {
-        PLOG(FATAL) << "Proxy listen socket failed";
-    }
-
-    if (!android::base::SetProperty("snapuserd.proxy_ready", "true")) {
-        LOG(FATAL) << "Proxy failed to set ready property";
-    }
-
-    unique_fd client_fd(
-            TEMP_FAILURE_RETRY(accept4(proxy_fd.get(), nullptr, nullptr, SOCK_CLOEXEC)));
-    if (client_fd < 0) {
-        PLOG(FATAL) << "Proxy accept failed";
-    }
-
-    char code[1] = {'a'};
-    std::vector<int> fds = {server_fd.get()};
-    ssize_t rv = android::base::SendFileDescriptorVector(client_fd, code, sizeof(code), fds);
-    if (rv < 0) {
-        PLOG(FATAL) << "Proxy could not send file descriptor to snapuserd";
-    }
-    // Wait for an ACK - results don't matter, we just don't want to risk closing
-    // the proxy socket too early.
-    if (recv(client_fd, code, sizeof(code), 0) < 0) {
-        PLOG(FATAL) << "Proxy could not receive terminating code from snapuserd";
-    }
-    return true;
-}
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h
deleted file mode 100644
index 3b6ff15..0000000
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <poll.h>
-
-#include <cstdio>
-#include <cstring>
-#include <functional>
-#include <future>
-#include <iostream>
-#include <mutex>
-#include <sstream>
-#include <string>
-#include <thread>
-#include <vector>
-
-#include <android-base/unique_fd.h>
-#include "snapuserd.h"
-
-namespace android {
-namespace snapshot {
-
-static constexpr uint32_t MAX_PACKET_SIZE = 512;
-
-enum class DaemonOperations {
-    INIT,
-    START,
-    QUERY,
-    STOP,
-    DELETE,
-    DETACH,
-    SUPPORTS,
-    INVALID,
-};
-
-class DmUserHandler {
-  public:
-    explicit DmUserHandler(std::shared_ptr<Snapuserd> snapuserd);
-
-    void FreeResources() {
-        // Each worker thread holds a reference to snapuserd.
-        // Clear them so that all the resources
-        // held by snapuserd is released
-        if (snapuserd_) {
-            snapuserd_->FreeResources();
-            snapuserd_ = nullptr;
-        }
-    }
-    const std::shared_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
-    std::thread& thread() { return thread_; }
-
-    const std::string& misc_name() const { return misc_name_; }
-
-  private:
-    std::thread thread_;
-    std::shared_ptr<Snapuserd> snapuserd_;
-    std::string misc_name_;
-};
-
-class Stoppable {
-    std::promise<void> exitSignal_;
-    std::future<void> futureObj_;
-
-  public:
-    Stoppable() : futureObj_(exitSignal_.get_future()) {}
-
-    virtual ~Stoppable() {}
-
-    bool StopRequested() {
-        // checks if value in future object is available
-        if (futureObj_.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout) {
-            return false;
-        }
-        return true;
-    }
-    // Request the thread to stop by setting value in promise object
-    void StopThreads() { exitSignal_.set_value(); }
-};
-
-class SnapuserdServer : public Stoppable {
-  private:
-    android::base::unique_fd sockfd_;
-    bool terminating_;
-    volatile bool received_socket_signal_ = false;
-    std::vector<struct pollfd> watched_fds_;
-    bool is_socket_present_ = false;
-
-    std::mutex lock_;
-
-    using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
-    HandlerList dm_users_;
-
-    void AddWatchedFd(android::base::borrowed_fd fd, int events);
-    void AcceptClient();
-    bool HandleClient(android::base::borrowed_fd fd, int revents);
-    bool Recv(android::base::borrowed_fd fd, std::string* data);
-    bool Sendmsg(android::base::borrowed_fd fd, const std::string& msg);
-    bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
-
-    void ShutdownThreads();
-    bool RemoveAndJoinHandler(const std::string& control_device);
-    DaemonOperations Resolveop(std::string& input);
-    std::string GetDaemonStatus();
-    void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
-
-    bool IsTerminating() { return terminating_; }
-
-    void RunThread(std::shared_ptr<DmUserHandler> handler);
-    void JoinAllThreads();
-    bool StartWithSocket(bool start_listening);
-
-    // Find a DmUserHandler within a lock.
-    HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                      const std::string& misc_name);
-
-  public:
-    SnapuserdServer() { terminating_ = false; }
-    ~SnapuserdServer();
-
-    bool Start(const std::string& socketname);
-    bool Run();
-    void Interrupt();
-    bool RunForSocketHandoff();
-    bool WaitForSocket();
-
-    std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
-                                              const std::string& cow_device_path,
-                                              const std::string& backing_device);
-    bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
-
-    void SetTerminating() { terminating_ = true; }
-    void ReceivedSocketSignal() { received_socket_signal_ = true; }
-};
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
deleted file mode 100644
index 1f5d568..0000000
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ /dev/null
@@ -1,869 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "snapuserd.h"
-
-#include <csignal>
-#include <optional>
-#include <set>
-
-#include <snapuserd/snapuserd_client.h>
-
-namespace android {
-namespace snapshot {
-
-using namespace android;
-using namespace android::dm;
-using android::base::unique_fd;
-
-#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
-#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
-
-WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device,
-                           const std::string& control_device, const std::string& misc_name,
-                           std::shared_ptr<Snapuserd> snapuserd) {
-    cow_device_ = cow_device;
-    backing_store_device_ = backing_device;
-    control_device_ = control_device;
-    misc_name_ = misc_name;
-    snapuserd_ = snapuserd;
-    exceptions_per_area_ = (CHUNK_SIZE << SECTOR_SHIFT) / sizeof(struct disk_exception);
-}
-
-bool WorkerThread::InitializeFds() {
-    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
-    if (backing_store_fd_ < 0) {
-        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
-        return false;
-    }
-
-    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
-    if (cow_fd_ < 0) {
-        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
-        return false;
-    }
-
-    ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
-    if (ctrl_fd_ < 0) {
-        SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
-        return false;
-    }
-
-    return true;
-}
-
-bool WorkerThread::InitReader() {
-    reader_ = snapuserd_->CloneReaderForWorker();
-
-    if (!reader_->InitForMerge(std::move(cow_fd_))) {
-        return false;
-    }
-    return true;
-}
-
-// Construct kernel COW header in memory
-// This header will be in sector 0. The IO
-// request will always be 4k. After constructing
-// the header, zero out the remaining block.
-void WorkerThread::ConstructKernelCowHeader() {
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-
-    memset(buffer, 0, BLOCK_SZ);
-
-    struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer);
-
-    dh->magic = SNAP_MAGIC;
-    dh->valid = SNAPSHOT_VALID;
-    dh->version = SNAPSHOT_DISK_VERSION;
-    dh->chunk_size = CHUNK_SIZE;
-}
-
-// Start the replace operation. This will read the
-// internal COW format and if the block is compressed,
-// it will be de-compressed.
-bool WorkerThread::ProcessReplaceOp(const CowOperation* cow_op) {
-    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 << ", COW operation = " << *cow_op;
-        return false;
-    }
-    return true;
-}
-
-bool WorkerThread::ReadFromBaseDevice(const CowOperation* cow_op) {
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-    if (buffer == nullptr) {
-        SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
-        return false;
-    }
-    uint64_t offset;
-    if (!reader_->GetSourceOffset(cow_op, &offset)) {
-        SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get source offset";
-        return false;
-    }
-    SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
-                    << " Source: " << offset;
-    if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
-        SNAP_PLOG(ERROR) << "Copy op failed. Read from backing store: " << backing_store_device_
-                         << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ;
-        return false;
-    }
-
-    return true;
-}
-
-bool WorkerThread::GetReadAheadPopulatedBuffer(const CowOperation* cow_op) {
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-    if (buffer == nullptr) {
-        SNAP_LOG(ERROR) << "GetReadAheadPopulatedBuffer: Failed to get payload buffer";
-        return false;
-    }
-
-    if (!snapuserd_->GetReadAheadPopulatedBuffer(cow_op->new_block, buffer)) {
-        return false;
-    }
-
-    return true;
-}
-
-// Start the copy operation. This will read the backing
-// block device which is represented by cow_op->source.
-bool WorkerThread::ProcessCopyOp(const CowOperation* cow_op) {
-    if (!GetReadAheadPopulatedBuffer(cow_op)) {
-        SNAP_LOG(DEBUG) << " GetReadAheadPopulatedBuffer failed..."
-                        << " new_block: " << cow_op->new_block;
-        if (!ReadFromBaseDevice(cow_op)) {
-            return false;
-        }
-    }
-
-    return true;
-}
-
-bool WorkerThread::ProcessZeroOp() {
-    // Zero out the entire block
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-    if (buffer == nullptr) {
-        SNAP_LOG(ERROR) << "ProcessZeroOp: Failed to get payload buffer";
-        return false;
-    }
-
-    memset(buffer, 0, BLOCK_SZ);
-    return true;
-}
-
-bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) {
-    if (cow_op == nullptr) {
-        SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op";
-        return false;
-    }
-
-    switch (cow_op->type()) {
-        case kCowReplaceOp: {
-            return ProcessReplaceOp(cow_op);
-        }
-
-        case kCowZeroOp: {
-            return ProcessZeroOp();
-        }
-
-        case kCowCopyOp: {
-            return ProcessCopyOp(cow_op);
-        }
-
-        default: {
-            SNAP_LOG(ERROR) << "Unsupported operation-type found: "
-                            << static_cast<uint8_t>(cow_op->type());
-        }
-    }
-    return false;
-}
-
-int WorkerThread::ReadUnalignedSector(
-        sector_t sector, size_t size,
-        std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) {
-    size_t skip_sector_size = 0;
-
-    SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
-                    << " Aligned sector: " << it->first;
-
-    if (!ProcessCowOp(it->second)) {
-        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size
-                        << " Aligned sector: " << it->first;
-        return -1;
-    }
-
-    int num_sectors_skip = sector - it->first;
-
-    if (num_sectors_skip > 0) {
-        skip_sector_size = num_sectors_skip << SECTOR_SHIFT;
-        char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr());
-        struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
-
-        if (skip_sector_size == BLOCK_SZ) {
-            SNAP_LOG(ERROR) << "Invalid un-aligned IO request at sector: " << sector
-                            << " Base-sector: " << it->first;
-            return -1;
-        }
-
-        memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
-                (BLOCK_SZ - skip_sector_size));
-    }
-
-    bufsink_.ResetBufferOffset();
-    return std::min(size, (BLOCK_SZ - skip_sector_size));
-}
-
-/*
- * Read the data for a given COW Operation.
- *
- * Kernel can issue IO at a sector granularity.
- * Hence, an IO may end up with reading partial
- * data from a COW operation or we may also
- * end up with interspersed request between
- * two COW operations.
- *
- */
-int WorkerThread::ReadData(sector_t sector, size_t size) {
-    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
-    std::vector<std::pair<sector_t, const CowOperation*>>::iterator it;
-    /*
-     * chunk_map stores COW operation at 4k granularity.
-     * If the requested IO with the sector falls on the 4k
-     * boundary, then we can read the COW op directly without
-     * any issue.
-     *
-     * However, if the requested sector is not 4K aligned,
-     * then we will have the find the nearest COW operation
-     * and chop the 4K block to fetch the requested sector.
-     */
-    it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
-                          Snapuserd::compare);
-
-    bool read_end_of_device = false;
-    if (it == chunk_vec.end()) {
-        // |-------|-------|-------|
-        // 0       1       2       3
-        //
-        // Block 0 - op 1
-        // Block 1 - op 2
-        // Block 2 - op 3
-        //
-        // chunk_vec will have block 0, 1, 2 which maps to relavant COW ops.
-        //
-        // Each block is 4k bytes. Thus, the last block will span 8 sectors
-        // ranging till block 3 (However, block 3 won't be in chunk_vec as
-        // it doesn't have any mapping to COW ops. Now, if we get an I/O request for a sector
-        // spanning between block 2 and block 3, we need to step back
-        // and get hold of the last element.
-        //
-        // Additionally, dm-snapshot makes sure that I/O request beyond block 3
-        // will not be routed to the daemon. Hence, it is safe to assume that
-        // if a sector is not available in the chunk_vec, the I/O falls in the
-        // end of region.
-        it = std::prev(chunk_vec.end());
-        read_end_of_device = true;
-    }
-
-    // We didn't find the required sector; hence find the previous sector
-    // as lower_bound will gives us the value greater than
-    // the requested sector
-    if (it->first != sector) {
-        if (it != chunk_vec.begin() && !read_end_of_device) {
-            --it;
-        }
-
-        /*
-         * If the IO is spanned between two COW operations,
-         * split the IO into two parts:
-         *
-         * 1: Read the first part from the single COW op
-         * 2: Read the second part from the next COW op.
-         *
-         * Ex: Let's say we have a 1024 Bytes IO request.
-         *
-         * 0       COW OP-1  4096     COW OP-2  8192
-         * |******************|*******************|
-         *              |*****|*****|
-         *           3584           4608
-         *              <- 1024B - >
-         *
-         * We have two COW operations which are 4k blocks.
-         * The IO is requested for 1024 Bytes which are spanned
-         * between two COW operations. We will split this IO
-         * into two parts:
-         *
-         * 1: IO of size 512B from offset 3584 bytes (COW OP-1)
-         * 2: IO of size 512B from offset 4096 bytes (COW OP-2)
-         */
-        return ReadUnalignedSector(sector, size, it);
-    }
-
-    int num_ops = DIV_ROUND_UP(size, BLOCK_SZ);
-    sector_t read_sector = sector;
-    while (num_ops) {
-        // We have to make sure that the reads are
-        // sequential; there shouldn't be a data
-        // request merged with a metadata IO.
-        if (it->first != read_sector) {
-            SNAP_LOG(ERROR) << "Invalid IO request: read_sector: " << read_sector
-                            << " cow-op sector: " << it->first;
-            return -1;
-        } else if (!ProcessCowOp(it->second)) {
-            return -1;
-        }
-        num_ops -= 1;
-        read_sector += (BLOCK_SZ >> SECTOR_SHIFT);
-
-        it++;
-
-        if (it == chunk_vec.end() && num_ops) {
-            SNAP_LOG(ERROR) << "Invalid IO request at sector " << sector
-                            << " COW ops completed; pending read-request: " << num_ops;
-            return -1;
-        }
-        // Update the buffer offset
-        bufsink_.UpdateBufferOffset(BLOCK_SZ);
-    }
-
-    // Reset the buffer offset
-    bufsink_.ResetBufferOffset();
-    return size;
-}
-
-/*
- * dm-snap does prefetch reads while reading disk-exceptions.
- * By default, prefetch value is set to 12; this means that
- * dm-snap will issue 12 areas wherein each area is a 4k page
- * of disk-exceptions.
- *
- * If during prefetch, if the chunk-id seen is beyond the
- * actual number of metadata page, fill the buffer with zero.
- * When dm-snap starts parsing the buffer, it will stop
- * reading metadata page once the buffer content is zero.
- */
-bool WorkerThread::ZerofillDiskExceptions(size_t read_size) {
-    size_t size = exceptions_per_area_ * sizeof(struct disk_exception);
-
-    if (read_size > size) {
-        return false;
-    }
-
-    void* buffer = bufsink_.GetPayloadBuffer(size);
-    if (buffer == nullptr) {
-        SNAP_LOG(ERROR) << "ZerofillDiskExceptions: Failed to get payload buffer";
-        return false;
-    }
-
-    memset(buffer, 0, size);
-    return true;
-}
-
-/*
- * A disk exception is a simple mapping of old_chunk to new_chunk.
- * When dm-snapshot device is created, kernel requests these mapping.
- *
- * Each disk exception is of size 16 bytes. Thus a single 4k page can
- * have:
- *
- * exceptions_per_area_ = 4096/16 = 256. This entire 4k page
- * is considered a metadata page and it is represented by chunk ID.
- *
- * Convert the chunk ID to index into the vector which gives us
- * the metadata page.
- */
-bool WorkerThread::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
-    uint32_t stride = exceptions_per_area_ + 1;
-    size_t size;
-    const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
-
-    // ChunkID to vector index
-    lldiv_t divresult = lldiv(chunk, stride);
-
-    if (divresult.quot < vec.size()) {
-        size = exceptions_per_area_ * sizeof(struct disk_exception);
-
-        if (read_size != size) {
-            SNAP_LOG(ERROR) << "ReadDiskExceptions: read_size: " << read_size
-                            << " does not match with size: " << size;
-            return false;
-        }
-
-        void* buffer = bufsink_.GetPayloadBuffer(size);
-        if (buffer == nullptr) {
-            SNAP_LOG(ERROR) << "ReadDiskExceptions: Failed to get payload buffer of size: " << size;
-            return false;
-        }
-
-        memcpy(buffer, vec[divresult.quot].get(), size);
-    } else {
-        return ZerofillDiskExceptions(read_size);
-    }
-
-    return true;
-}
-
-loff_t WorkerThread::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
-                                         int* unmerged_exceptions) {
-    loff_t offset = 0;
-    *unmerged_exceptions = 0;
-
-    while (*unmerged_exceptions <= exceptions_per_area_) {
-        struct disk_exception* merged_de =
-                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
-        struct disk_exception* cow_de =
-                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
-
-        // Unmerged op by the kernel
-        if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) {
-            if (!(merged_de->old_chunk == cow_de->old_chunk)) {
-                SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->old_chunk: "
-                                << merged_de->old_chunk
-                                << "cow_de->old_chunk: " << cow_de->old_chunk;
-                return -1;
-            }
-
-            if (!(merged_de->new_chunk == cow_de->new_chunk)) {
-                SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->new_chunk: "
-                                << merged_de->new_chunk
-                                << "cow_de->new_chunk: " << cow_de->new_chunk;
-                return -1;
-            }
-
-            offset += sizeof(struct disk_exception);
-            *unmerged_exceptions += 1;
-            continue;
-        }
-
-        break;
-    }
-
-    SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
-    return offset;
-}
-
-int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
-                                       int unmerged_exceptions, bool* ordered_op, bool* commit) {
-    int merged_ops_cur_iter = 0;
-    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
-    *ordered_op = false;
-    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
-
-    // Find the operations which are merged in this cycle.
-    while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
-        struct disk_exception* merged_de =
-                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
-        struct disk_exception* cow_de =
-                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
-
-        if (!(merged_de->new_chunk == 0)) {
-            SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid new-chunk: " << merged_de->new_chunk;
-            return -1;
-        }
-
-        if (!(merged_de->old_chunk == 0)) {
-            SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid old-chunk: " << merged_de->old_chunk;
-            return -1;
-        }
-
-        if (cow_de->new_chunk != 0) {
-            merged_ops_cur_iter += 1;
-            offset += sizeof(struct disk_exception);
-            auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
-                                       std::make_pair(ChunkToSector(cow_de->new_chunk), nullptr),
-                                       Snapuserd::compare);
-
-            if (!(it != chunk_vec.end())) {
-                SNAP_LOG(ERROR) << "Sector not found: " << ChunkToSector(cow_de->new_chunk);
-                return -1;
-            }
-
-            if (!(it->first == ChunkToSector(cow_de->new_chunk))) {
-                SNAP_LOG(ERROR) << "Invalid sector: " << ChunkToSector(cow_de->new_chunk);
-                return -1;
-            }
-            const CowOperation* cow_op = it->second;
-
-            if (snapuserd_->IsReadAheadFeaturePresent() && IsOrderedOp(*cow_op)) {
-                *ordered_op = true;
-                // Every single ordered operation has to come from read-ahead
-                // cache.
-                if (read_ahead_buffer_map.find(cow_op->new_block) == read_ahead_buffer_map.end()) {
-                    SNAP_LOG(ERROR)
-                            << " Block: " << cow_op->new_block << " not found in read-ahead cache"
-                            << " Op: " << *cow_op;
-                    return -1;
-                }
-                // If this is a final block merged in the read-ahead buffer
-                // region, notify the read-ahead thread to make forward
-                // progress
-                if (cow_op->new_block == snapuserd_->GetFinalBlockMerged()) {
-                    *commit = true;
-                }
-            }
-
-            // zero out to indicate that operation is merged.
-            cow_de->old_chunk = 0;
-            cow_de->new_chunk = 0;
-        } else if (cow_de->old_chunk == 0) {
-            // Already merged op in previous iteration or
-            // This could also represent a partially filled area.
-            //
-            // If the op was merged in previous cycle, we don't have
-            // to count them.
-            break;
-        } else {
-            SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
-                            << " merged_de-old-chunk: " << merged_de->old_chunk
-                            << " merged_de-new-chunk: " << merged_de->new_chunk
-                            << " cow_de-old-chunk: " << cow_de->old_chunk
-                            << " cow_de-new-chunk: " << cow_de->new_chunk
-                            << " unmerged_exceptions: " << unmerged_exceptions
-                            << " merged_ops_cur_iter: " << merged_ops_cur_iter
-                            << " offset: " << offset;
-            return -1;
-        }
-    }
-    return merged_ops_cur_iter;
-}
-
-bool WorkerThread::ProcessMergeComplete(chunk_t chunk, void* buffer) {
-    uint32_t stride = exceptions_per_area_ + 1;
-    const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
-    bool ordered_op = false;
-    bool commit = false;
-
-    // ChunkID to vector index
-    lldiv_t divresult = lldiv(chunk, stride);
-
-    if (!(divresult.quot < vec.size())) {
-        SNAP_LOG(ERROR) << "ProcessMergeComplete: Invalid chunk: " << chunk
-                        << " Metadata-Index: " << divresult.quot << " Area-size: " << vec.size();
-        return false;
-    }
-
-    SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
-                    << " Metadata-Index: " << divresult.quot;
-
-    int unmerged_exceptions = 0;
-    loff_t offset = GetMergeStartOffset(buffer, vec[divresult.quot].get(), &unmerged_exceptions);
-
-    if (offset < 0) {
-        SNAP_LOG(ERROR) << "GetMergeStartOffset failed: unmerged_exceptions: "
-                        << unmerged_exceptions;
-        return false;
-    }
-
-    int merged_ops_cur_iter = GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset,
-                                                   unmerged_exceptions, &ordered_op, &commit);
-
-    // There should be at least one operation merged in this cycle
-    if (!(merged_ops_cur_iter > 0)) {
-        SNAP_LOG(ERROR) << "Merge operation failed: " << merged_ops_cur_iter;
-        return false;
-    }
-
-    if (ordered_op) {
-        if (commit) {
-            // Push the flushing logic to read-ahead thread so that merge thread
-            // can make forward progress. Sync will happen in the background
-            snapuserd_->StartReadAhead();
-        }
-    } else {
-        // Non-copy ops and all ops in older COW format
-        if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
-            SNAP_LOG(ERROR) << "CommitMerge failed...";
-            return false;
-        }
-    }
-
-    SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
-    return true;
-}
-
-// Read Header from dm-user misc device. This gives
-// us the sector number for which IO is issued by dm-snapshot device
-bool WorkerThread::ReadDmUserHeader() {
-    if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
-        if (errno != ENOTBLK) {
-            SNAP_PLOG(ERROR) << "Control-read failed";
-        }
-
-        return false;
-    }
-
-    return true;
-}
-
-// Send the payload/data back to dm-user misc device.
-bool WorkerThread::WriteDmUserPayload(size_t size, bool header_response) {
-    size_t payload_size = size;
-    void* buf = bufsink_.GetPayloadBufPtr();
-    if (header_response) {
-        payload_size += sizeof(struct dm_user_header);
-        buf = bufsink_.GetBufPtr();
-    }
-
-    if (!android::base::WriteFully(ctrl_fd_, buf, payload_size)) {
-        SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << payload_size;
-        return false;
-    }
-
-    return true;
-}
-
-bool WorkerThread::ReadDmUserPayload(void* buffer, size_t size) {
-    if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
-        SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size;
-        return false;
-    }
-
-    return true;
-}
-
-bool WorkerThread::DmuserWriteRequest() {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
-
-    // device mapper has the capability to allow
-    // targets to flush the cache when writes are completed. This
-    // is controlled by each target by a flag "flush_supported".
-    // This flag is set by dm-user. When flush is supported,
-    // a number of zero-length bio's will be submitted to
-    // the target for the purpose of flushing cache. It is the
-    // responsibility of the target driver - which is dm-user in this
-    // case, to remap these bio's to the underlying device. Since,
-    // there is no underlying device for dm-user, this zero length
-    // bio's gets routed to daemon.
-    //
-    // Flush operations are generated post merge by dm-snap by having
-    // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
-    // to flush per se; hence, just respond back with a success message.
-    if (header->sector == 0) {
-        if (!(header->len == 0)) {
-            SNAP_LOG(ERROR) << "Invalid header length received from sector 0: " << header->len;
-            header->type = DM_USER_RESP_ERROR;
-        } else {
-            header->type = DM_USER_RESP_SUCCESS;
-        }
-
-        if (!WriteDmUserPayload(0, true)) {
-            return false;
-        }
-        return true;
-    }
-
-    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
-    size_t remaining_size = header->len;
-    size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
-
-    chunk_t chunk = SectorToChunk(header->sector);
-    auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
-                               std::make_pair(header->sector, nullptr), Snapuserd::compare);
-
-    bool not_found = (it == chunk_vec.end() || it->first != header->sector);
-
-    if (not_found) {
-        void* buffer = bufsink_.GetPayloadBuffer(read_size);
-        if (buffer == nullptr) {
-            SNAP_LOG(ERROR) << "DmuserWriteRequest: Failed to get payload buffer of size: "
-                            << read_size;
-            header->type = DM_USER_RESP_ERROR;
-        } else {
-            header->type = DM_USER_RESP_SUCCESS;
-
-            if (!ReadDmUserPayload(buffer, read_size)) {
-                SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
-                                << "Sector: " << header->sector;
-                header->type = DM_USER_RESP_ERROR;
-            }
-
-            if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
-                SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
-                                << "Sector: " << header->sector;
-                header->type = DM_USER_RESP_ERROR;
-            }
-        }
-    } else {
-        SNAP_LOG(ERROR) << "DmuserWriteRequest: Invalid sector received: header->sector";
-        header->type = DM_USER_RESP_ERROR;
-    }
-
-    if (!WriteDmUserPayload(0, true)) {
-        return false;
-    }
-
-    return true;
-}
-
-bool WorkerThread::DmuserReadRequest() {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
-    size_t remaining_size = header->len;
-    loff_t offset = 0;
-    sector_t sector = header->sector;
-    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
-    bool header_response = true;
-    do {
-        size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
-
-        int ret = read_size;
-        header->type = DM_USER_RESP_SUCCESS;
-        chunk_t chunk = SectorToChunk(header->sector);
-
-        // Request to sector 0 is always for kernel
-        // representation of COW header. This IO should be only
-        // once during dm-snapshot device creation. We should
-        // never see multiple IO requests. Additionally this IO
-        // will always be a single 4k.
-        if (header->sector == 0) {
-            if (read_size == BLOCK_SZ) {
-                ConstructKernelCowHeader();
-                SNAP_LOG(DEBUG) << "Kernel header constructed";
-            } else {
-                SNAP_LOG(ERROR) << "Invalid read_size: " << read_size << " for sector 0";
-                header->type = DM_USER_RESP_ERROR;
-            }
-        } else {
-            auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
-                                       std::make_pair(header->sector, nullptr), Snapuserd::compare);
-            bool not_found = (it == chunk_vec.end() || it->first != header->sector);
-            if (!offset && (read_size == BLOCK_SZ) && not_found) {
-                if (!ReadDiskExceptions(chunk, read_size)) {
-                    SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
-                                    << "Sector: " << header->sector;
-                    header->type = DM_USER_RESP_ERROR;
-                } else {
-                    SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk
-                                    << "Sector: " << header->sector;
-                }
-            } else {
-                chunk_t num_sectors_read = (offset >> SECTOR_SHIFT);
-
-                ret = ReadData(sector + num_sectors_read, read_size);
-                if (ret < 0) {
-                    SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
-                                    << " Sector: " << (sector + num_sectors_read)
-                                    << " size: " << read_size << " header-len: " << header->len;
-                    header->type = DM_USER_RESP_ERROR;
-                } else {
-                    SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk
-                                    << "Sector: " << header->sector;
-                }
-            }
-        }
-
-        // Just return the header if it is an error
-        if (header->type == DM_USER_RESP_ERROR) {
-            SNAP_LOG(ERROR) << "IO read request failed...";
-            ret = 0;
-        }
-
-        if (!header_response) {
-            CHECK(header->type == DM_USER_RESP_SUCCESS)
-                    << " failed for sector: " << sector << " header->len: " << header->len
-                    << " remaining_size: " << remaining_size;
-        }
-
-        // Daemon will not be terminated if there is any error. We will
-        // just send the error back to dm-user.
-        if (!WriteDmUserPayload(ret, header_response)) {
-            return false;
-        }
-
-        if (header->type == DM_USER_RESP_ERROR) {
-            break;
-        }
-
-        remaining_size -= ret;
-        offset += ret;
-        header_response = false;
-    } while (remaining_size > 0);
-
-    return true;
-}
-
-void WorkerThread::InitializeBufsink() {
-    // Allocate the buffer which is used to communicate between
-    // daemon and dm-user. The buffer comprises of header and a fixed payload.
-    // If the dm-user requests a big IO, the IO will be broken into chunks
-    // of PAYLOAD_SIZE.
-    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
-    bufsink_.Initialize(buf_size);
-}
-
-bool WorkerThread::RunThread() {
-    InitializeBufsink();
-
-    if (!InitializeFds()) {
-        return false;
-    }
-
-    if (!InitReader()) {
-        return false;
-    }
-
-    // Start serving IO
-    while (true) {
-        if (!ProcessIORequest()) {
-            break;
-        }
-    }
-
-    CloseFds();
-    reader_->CloseCowFd();
-
-    return true;
-}
-
-bool WorkerThread::ProcessIORequest() {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
-
-    if (!ReadDmUserHeader()) {
-        return false;
-    }
-
-    SNAP_LOG(DEBUG) << "Daemon: msg->seq: " << std::dec << header->seq;
-    SNAP_LOG(DEBUG) << "Daemon: msg->len: " << std::dec << header->len;
-    SNAP_LOG(DEBUG) << "Daemon: msg->sector: " << std::dec << header->sector;
-    SNAP_LOG(DEBUG) << "Daemon: msg->type: " << std::dec << header->type;
-    SNAP_LOG(DEBUG) << "Daemon: msg->flags: " << std::dec << header->flags;
-
-    switch (header->type) {
-        case DM_USER_REQ_MAP_READ: {
-            if (!DmuserReadRequest()) {
-                return false;
-            }
-            break;
-        }
-
-        case DM_USER_REQ_MAP_WRITE: {
-            if (!DmuserWriteRequest()) {
-                return false;
-            }
-            break;
-        }
-    }
-
-    return true;
-}
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index 36dad33..67e9e52 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -29,6 +29,7 @@
             "If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
 DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
 DEFINE_bool(io_uring, false, "If true, io_uring feature is enabled");
+DEFINE_bool(o_direct, false, "If true, enable direct reads on source device");
 
 namespace android {
 namespace snapshot {
@@ -38,20 +39,20 @@
     const std::string vendor_release =
             android::base::GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN);
 
-    // No user-space snapshots if vendor partition is on Android 12
+    // If the vendor is on Android S, install process will forcefully take the
+    // userspace snapshots path.
+    //
+    // We will not reach here post OTA reboot as the binary will be from vendor
+    // ramdisk which is on Android S.
     if (vendor_release.find("12") != std::string::npos) {
-        LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
+        LOG(INFO) << "Userspace snapshots enabled as vendor partition is on Android: "
                   << vendor_release;
-        return false;
+        return true;
     }
 
     return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
 }
 
-bool Daemon::IsDmSnapshotTestingEnabled() {
-    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
-}
-
 bool Daemon::StartDaemon(int argc, char** argv) {
     int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true);
 
@@ -65,16 +66,15 @@
     // stage init and hence use the command line flags to get the information.
     bool user_snapshots = FLAGS_user_snapshot;
     if (!user_snapshots) {
-        user_snapshots = (!IsDmSnapshotTestingEnabled() && IsUserspaceSnapshotsEnabled());
+        user_snapshots = IsUserspaceSnapshotsEnabled();
     }
-
     if (user_snapshots) {
         LOG(INFO) << "Starting daemon for user-space snapshots.....";
         return StartServerForUserspaceSnapshots(arg_start, argc, argv);
     } else {
-        LOG(INFO) << "Starting daemon for dm-snapshots.....";
-        return StartServerForDmSnapshot(arg_start, argc, argv);
+        LOG(ERROR) << "Userspace snapshots not enabled. No support for legacy snapshots";
     }
+    return false;
 }
 
 bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv) {
@@ -109,11 +109,13 @@
 
     for (int i = arg_start; i < argc; i++) {
         auto parts = android::base::Split(argv[i], ",");
+
         if (parts.size() != 4) {
-            LOG(ERROR) << "Malformed message, expected four sub-arguments.";
+            LOG(ERROR) << "Malformed message, expected at least four sub-arguments.";
             return false;
         }
-        auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
+        auto handler =
+                user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3], FLAGS_o_direct);
         if (!handler || !user_server_.StartHandler(parts[0])) {
             return false;
         }
@@ -130,48 +132,6 @@
     return user_server_.WaitForSocket();
 }
 
-bool Daemon::StartServerForDmSnapshot(int arg_start, int argc, char** argv) {
-    sigfillset(&signal_mask_);
-    sigdelset(&signal_mask_, SIGINT);
-    sigdelset(&signal_mask_, SIGTERM);
-    sigdelset(&signal_mask_, SIGUSR1);
-
-    // Masking signals here ensure that after this point, we won't handle INT/TERM
-    // until after we call into ppoll()
-    signal(SIGINT, Daemon::SignalHandler);
-    signal(SIGTERM, Daemon::SignalHandler);
-    signal(SIGPIPE, Daemon::SignalHandler);
-    signal(SIGUSR1, Daemon::SignalHandler);
-
-    MaskAllSignalsExceptIntAndTerm();
-
-    if (FLAGS_socket_handoff) {
-        return server_.RunForSocketHandoff();
-    }
-    if (!FLAGS_no_socket) {
-        if (!server_.Start(FLAGS_socket)) {
-            return false;
-        }
-        return server_.Run();
-    }
-
-    for (int i = arg_start; i < argc; i++) {
-        auto parts = android::base::Split(argv[i], ",");
-        if (parts.size() != 3) {
-            LOG(ERROR) << "Malformed message, expected three sub-arguments.";
-            return false;
-        }
-        auto handler = server_.AddHandler(parts[0], parts[1], parts[2]);
-        if (!handler || !server_.StartHandler(handler)) {
-            return false;
-        }
-    }
-
-    // Skip the accept() call to avoid spurious log spam. The server will still
-    // run until all handlers have completed.
-    return server_.WaitForSocket();
-}
-
 void Daemon::MaskAllSignalsExceptIntAndTerm() {
     sigset_t signal_mask;
     sigfillset(&signal_mask);
@@ -198,16 +158,12 @@
     // and verify it through a temp variable.
     if (user_server_.IsServerRunning()) {
         user_server_.Interrupt();
-    } else {
-        server_.Interrupt();
     }
 }
 
 void Daemon::ReceivedSocketSignal() {
     if (user_server_.IsServerRunning()) {
         user_server_.ReceivedSocketSignal();
-    } else {
-        server_.ReceivedSocketSignal();
     }
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
index cf3b917..303e394 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
@@ -19,7 +19,6 @@
 #include <string>
 #include <vector>
 
-#include "dm-snapshot-merge/snapuserd_server.h"
 #include "user-space-merge/snapuserd_server.h"
 
 namespace android {
@@ -36,12 +35,10 @@
         return instance;
     }
 
-    bool StartServerForDmSnapshot(int arg_start, int argc, char** argv);
     bool StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv);
     void Interrupt();
     void ReceivedSocketSignal();
     bool IsUserspaceSnapshotsEnabled();
-    bool IsDmSnapshotTestingEnabled();
     bool StartDaemon(int argc, char** argv);
 
   private:
@@ -51,7 +48,6 @@
     Daemon(Daemon const&) = delete;
     void operator=(Daemon const&) = delete;
 
-    SnapuserdServer server_;
     UserSnapshotServer user_server_;
     void MaskAllSignalsExceptIntAndTerm();
     void MaskAllSignals();
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
index 711e704..ea11f0e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -19,6 +19,7 @@
 
 #include <android-base/logging.h>
 
+#include "android-base/properties.h"
 #include "merge_worker.h"
 #include "read_worker.h"
 #include "snapuserd_core.h"
@@ -235,8 +236,10 @@
 
         LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_;
         {
+            auto num_merge_threads = android::base::GetUintProperty<uint>(
+                    "ro.virtual_ab.num_merge_threads", kMaxMergeThreads);
             std::lock_guard<std::mutex> lock(lock_);
-            while (active_merge_threads_ < kMaxMergeThreads && merge_handlers_.size() > 0) {
+            while (active_merge_threads_ < num_merge_threads && merge_handlers_.size() > 0) {
                 auto handler = merge_handlers_.front();
                 merge_handlers_.pop();
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
index 1e7d0c0..e6a9a29 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
@@ -13,10 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include "merge_worker.h"
 
+#include <libsnapshot/cow_format.h>
 #include <pthread.h>
 
+#include <android-base/properties.h>
+
+#include "merge_worker.h"
 #include "snapuserd_core.h"
 #include "utility.h"
 
@@ -37,6 +40,7 @@
     int num_ops = *pending_ops;
     int nr_consecutive = 0;
     bool checkOrderedOp = (replace_zero_vec == nullptr);
+    size_t num_blocks = 1;
 
     do {
         if (!cowop_iter_->AtEnd() && num_ops) {
@@ -48,11 +52,15 @@
             *source_offset = cow_op->new_block * BLOCK_SZ;
             if (!checkOrderedOp) {
                 replace_zero_vec->push_back(cow_op);
+                if (cow_op->type() == kCowReplaceOp) {
+                    // Get the number of blocks this op has compressed
+                    num_blocks = (CowOpCompressionSize(cow_op, BLOCK_SZ) / BLOCK_SZ);
+                }
             }
 
             cowop_iter_->Next();
-            num_ops -= 1;
-            nr_consecutive = 1;
+            num_ops -= num_blocks;
+            nr_consecutive = num_blocks;
 
             while (!cowop_iter_->AtEnd() && num_ops) {
                 const CowOperation* op = cowop_iter_->Get();
@@ -66,11 +74,20 @@
                 }
 
                 if (!checkOrderedOp) {
+                    if (op->type() == kCowReplaceOp) {
+                        num_blocks = (CowOpCompressionSize(op, BLOCK_SZ) / BLOCK_SZ);
+                        if (num_ops < num_blocks) {
+                            break;
+                        }
+                    } else {
+                        // zero op
+                        num_blocks = 1;
+                    }
                     replace_zero_vec->push_back(op);
                 }
 
-                nr_consecutive += 1;
-                num_ops -= 1;
+                nr_consecutive += num_blocks;
+                num_ops -= num_blocks;
                 cowop_iter_->Next();
             }
         }
@@ -108,18 +125,24 @@
 
         for (size_t i = 0; i < replace_zero_vec.size(); i++) {
             const CowOperation* cow_op = replace_zero_vec[i];
-
-            void* buffer = bufsink_.AcquireBuffer(BLOCK_SZ);
-            if (!buffer) {
-                SNAP_LOG(ERROR) << "AcquireBuffer failed in MergeReplaceOps";
-                return false;
-            }
             if (cow_op->type() == kCowReplaceOp) {
-                if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
+                size_t buffer_size = CowOpCompressionSize(cow_op, BLOCK_SZ);
+                void* buffer = bufsink_.AcquireBuffer(buffer_size);
+                if (!buffer) {
+                    SNAP_LOG(ERROR) << "AcquireBuffer failed in MergeReplaceOps";
+                    return false;
+                }
+                // Read the entire compressed buffer spanning multiple blocks
+                if (!reader_->ReadData(cow_op, buffer, buffer_size)) {
                     SNAP_LOG(ERROR) << "Failed to read COW in merge";
                     return false;
                 }
             } else {
+                void* buffer = bufsink_.AcquireBuffer(BLOCK_SZ);
+                if (!buffer) {
+                    SNAP_LOG(ERROR) << "AcquireBuffer failed in MergeReplaceOps";
+                    return false;
+                }
                 CHECK(cow_op->type() == kCowZeroOp);
                 memset(buffer, 0, BLOCK_SZ);
             }
@@ -137,7 +160,7 @@
             return false;
         }
 
-        num_ops_merged += linear_blocks;
+        num_ops_merged += replace_zero_vec.size();
 
         if (num_ops_merged >= total_ops_merged_per_commit) {
             // Flush the data
@@ -158,8 +181,8 @@
         bufsink_.ResetBufferOffset();
 
         if (snapuserd_->IsIOTerminated()) {
-            SNAP_LOG(ERROR)
-                    << "MergeReplaceZeroOps: MergeWorker threads terminated - shutting down merge";
+            SNAP_LOG(ERROR) << "MergeReplaceZeroOps: MergeWorker threads terminated - shutting "
+                               "down merge";
             return false;
         }
     }
@@ -556,8 +579,10 @@
         SNAP_LOG(ERROR) << "Merge terminated early...";
         return true;
     }
+    auto merge_thread_priority = android::base::GetUintProperty<uint32_t>(
+            "ro.virtual_ab.merge_thread_priority", ANDROID_PRIORITY_BACKGROUND);
 
-    if (!SetThreadPriority(ANDROID_PRIORITY_BACKGROUND)) {
+    if (!SetThreadPriority(merge_thread_priority)) {
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
index f1d4065..ef311d4 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-#include "read_worker.h"
+#include <android-base/properties.h>
 
+#include <libsnapshot/cow_format.h>
 #include <pthread.h>
 
+#include "read_worker.h"
 #include "snapuserd_core.h"
+#include "user-space-merge/worker.h"
 #include "utility.h"
 
 namespace android {
@@ -48,9 +51,10 @@
 // Start the replace operation. This will read the
 // internal COW format and if the block is compressed,
 // it will be de-compressed.
-bool ReadWorker::ProcessReplaceOp(const CowOperation* cow_op, void* buffer) {
-    if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
-        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+bool ReadWorker::ProcessReplaceOp(const CowOperation* cow_op, void* buffer, size_t buffer_size) {
+    if (!reader_->ReadData(cow_op, buffer, buffer_size)) {
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block
+                        << " buffer_size: " << buffer_size;
         return false;
     }
     return true;
@@ -183,7 +187,13 @@
 
     switch (cow_op->type()) {
         case kCowReplaceOp: {
-            return ProcessReplaceOp(cow_op, buffer);
+            size_t buffer_size = CowOpCompressionSize(cow_op, BLOCK_SZ);
+            uint8_t chunk[buffer_size];
+            if (!ProcessReplaceOp(cow_op, chunk, buffer_size)) {
+                return false;
+            }
+            std::memcpy(buffer, chunk, BLOCK_SZ);
+            return true;
         }
 
         case kCowZeroOp: {
@@ -209,6 +219,13 @@
         return false;
     }
 
+    const size_t compression_factor = reader_->GetMaxCompressionSize();
+    if (!compression_factor) {
+        SNAP_LOG(ERROR) << "Compression factor is set to 0 which is invalid.";
+        return false;
+    }
+    decompressed_buffer_ = std::make_unique<uint8_t[]>(compression_factor);
+
     backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
     if (backing_store_fd_ < 0) {
         SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
@@ -245,8 +262,10 @@
     SNAP_LOG(INFO) << "Processing snapshot I/O requests....";
 
     pthread_setname_np(pthread_self(), "ReadWorker");
+    auto worker_thread_priority = android::base::GetUintProperty<uint32_t>(
+            "ro.virtual_ab.worker_thread_priority", ANDROID_PRIORITY_NORMAL);
 
-    if (!SetThreadPriority(ANDROID_PRIORITY_NORMAL)) {
+    if (!SetThreadPriority(worker_thread_priority)) {
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
 
@@ -276,6 +295,20 @@
     return true;
 }
 
+bool ReadWorker::GetCowOpBlockOffset(const CowOperation* cow_op, uint64_t io_block,
+                                     off_t* block_offset) {
+    // If this is a replace op, get the block offset of this I/O
+    // block. Multi-block compression is supported only for
+    // Replace ops.
+    //
+    // Note: This can be extended when we support COPY and XOR ops down the
+    // line as the blocks are mostly contiguous.
+    if (cow_op && cow_op->type() == kCowReplaceOp) {
+        return GetBlockOffset(cow_op, io_block, BLOCK_SZ, block_offset);
+    }
+    return false;
+}
+
 bool ReadWorker::ReadAlignedSector(sector_t sector, size_t sz) {
     size_t remaining_size = sz;
     std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
@@ -286,7 +319,7 @@
         size_t read_size = std::min(PAYLOAD_BUFFER_SZ, remaining_size);
 
         size_t total_bytes_read = 0;
-
+        const CowOperation* prev_op = nullptr;
         while (read_size) {
             // We need to check every 4k block to verify if it is
             // present in the mapping.
@@ -294,7 +327,7 @@
 
             auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
                                        std::make_pair(sector, nullptr), SnapshotHandler::compare);
-            bool not_found = (it == chunk_vec.end() || it->first != sector);
+            const bool sector_not_found = (it == chunk_vec.end() || it->first != sector);
 
             void* buffer = block_server_->GetResponseBuffer(BLOCK_SZ, size);
             if (!buffer) {
@@ -302,15 +335,88 @@
                 return false;
             }
 
-            if (not_found) {
-                // Block not found in map - which means this block was not
-                // changed as per the OTA. Just route the I/O to the base
-                // device.
-                if (!ReadDataFromBaseDevice(sector, buffer, size)) {
-                    SNAP_LOG(ERROR) << "ReadDataFromBaseDevice failed";
-                    return false;
+            if (sector_not_found) {
+                // Find the 4k block
+                uint64_t io_block = SectorToChunk(sector);
+                // Get the previous iterator. Since the vector is sorted, the
+                // lookup of this sector can fall in a range of blocks if
+                // CowOperation has compressed multiple blocks.
+                if (it != chunk_vec.begin()) {
+                    std::advance(it, -1);
                 }
 
+                bool is_mapping_present = true;
+
+                // Vector itself is empty. This can happen if the block was not
+                // changed per the OTA or if the merge was already complete but
+                // snapshot table was not yet collapsed.
+                if (it == chunk_vec.end()) {
+                    is_mapping_present = false;
+                }
+
+                const CowOperation* cow_op = nullptr;
+                // Relative offset within the compressed multiple blocks
+                off_t block_offset = 0;
+                if (is_mapping_present) {
+                    // Get the nearest operation found in the vector
+                    cow_op = it->second;
+                    is_mapping_present = GetCowOpBlockOffset(cow_op, io_block, &block_offset);
+                }
+
+                // Thus, we have a case wherein sector was not found in the sorted
+                // vector; however, we indeed have a mapping of this sector
+                // embedded in one of the CowOperation which spans multiple
+                // block size.
+                if (is_mapping_present) {
+                    // block_offset = 0 would mean that the CowOperation should
+                    // already be in the sorted vector. Hence, lookup should
+                    // have already found it. If not, this is a bug.
+                    if (block_offset == 0) {
+                        SNAP_LOG(ERROR)
+                                << "GetBlockOffset returned offset 0 for io_block: " << io_block;
+                        return false;
+                    }
+
+                    // Get the CowOperation actual compression size
+                    size_t compression_size = CowOpCompressionSize(cow_op, BLOCK_SZ);
+                    // Offset cannot be greater than the compression size
+                    if (block_offset > compression_size) {
+                        SNAP_LOG(ERROR) << "Invalid I/O block found. io_block: " << io_block
+                                        << " CowOperation-new-block: " << cow_op->new_block
+                                        << " compression-size: " << compression_size;
+                        return false;
+                    }
+
+                    // Cached copy of the previous iteration. Just retrieve the
+                    // data
+                    if (prev_op && prev_op->new_block == cow_op->new_block) {
+                        std::memcpy(buffer, (char*)decompressed_buffer_.get() + block_offset, size);
+                    } else {
+                        // Get the data from the disk based on the compression
+                        // size
+                        if (!ProcessReplaceOp(cow_op, decompressed_buffer_.get(),
+                                              compression_size)) {
+                            return false;
+                        }
+                        // Copy the data from the decompressed buffer relative
+                        // to the i/o block offset.
+                        std::memcpy(buffer, (char*)decompressed_buffer_.get() + block_offset, size);
+                        // Cache this CowOperation pointer for successive I/O
+                        // operation. Since the request is sequential and the
+                        // block is already decompressed, subsequest I/O blocks
+                        // can fetch the data directly from this decompressed
+                        // buffer.
+                        prev_op = cow_op;
+                    }
+                } else {
+                    // Block not found in map - which means this block was not
+                    // changed as per the OTA. Just route the I/O to the base
+                    // device.
+                    if (!ReadDataFromBaseDevice(sector, buffer, size)) {
+                        SNAP_LOG(ERROR) << "ReadDataFromBaseDevice failed";
+                        return false;
+                    }
+                }
                 ret = size;
             } else {
                 // We found the sector in mapping. Check the type of COW OP and
@@ -341,12 +447,50 @@
     return true;
 }
 
+bool ReadWorker::IsMappingPresent(const CowOperation* cow_op, loff_t requested_offset,
+                                  loff_t cow_op_offset) {
+    const bool replace_op = (cow_op->type() == kCowReplaceOp);
+    if (replace_op) {
+        size_t max_compressed_size = CowOpCompressionSize(cow_op, BLOCK_SZ);
+        if ((requested_offset >= cow_op_offset) &&
+            (requested_offset < (cow_op_offset + max_compressed_size))) {
+            return true;
+        }
+    }
+    return false;
+}
+
 int ReadWorker::ReadUnalignedSector(
         sector_t sector, size_t size,
         std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) {
     SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
                     << " Aligned sector: " << it->first;
 
+    loff_t requested_offset = sector << SECTOR_SHIFT;
+    loff_t final_offset = (it->first) << SECTOR_SHIFT;
+
+    const CowOperation* cow_op = it->second;
+    if (IsMappingPresent(cow_op, requested_offset, final_offset)) {
+        size_t buffer_size = CowOpCompressionSize(cow_op, BLOCK_SZ);
+        uint8_t chunk[buffer_size];
+        // Read the entire decompressed buffer based on the block-size
+        if (!ProcessReplaceOp(cow_op, chunk, buffer_size)) {
+            return -1;
+        }
+        size_t skip_offset = (requested_offset - final_offset);
+        size_t write_sz = std::min(size, buffer_size - skip_offset);
+
+        auto buffer =
+                reinterpret_cast<uint8_t*>(block_server_->GetResponseBuffer(BLOCK_SZ, write_sz));
+        if (!buffer) {
+            SNAP_LOG(ERROR) << "ReadUnalignedSector failed to allocate buffer";
+            return -1;
+        }
+
+        std::memcpy(buffer, (char*)chunk + skip_offset, write_sz);
+        return write_sz;
+    }
+
     int num_sectors_skip = sector - it->first;
     size_t skip_size = num_sectors_skip << SECTOR_SHIFT;
     size_t write_size = std::min(size, BLOCK_SZ - skip_size);
@@ -445,8 +589,11 @@
 
     size_t remaining_size = size;
     int ret = 0;
+
+    const CowOperation* cow_op = it->second;
     if (!merge_complete && (requested_offset >= final_offset) &&
-        (requested_offset - final_offset) < BLOCK_SZ) {
+        (((requested_offset - final_offset) < BLOCK_SZ) ||
+         IsMappingPresent(cow_op, requested_offset, final_offset))) {
         // Read the partial un-aligned data
         ret = ReadUnalignedSector(sector, remaining_size, it);
         if (ret < 0) {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
index 1aff50c..04b2736 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
@@ -44,9 +44,12 @@
     bool ProcessXorOp(const CowOperation* cow_op, void* buffer);
     bool ProcessOrderedOp(const CowOperation* cow_op, void* buffer);
     bool ProcessCopyOp(const CowOperation* cow_op, void* buffer);
-    bool ProcessReplaceOp(const CowOperation* cow_op, void* buffer);
+    bool ProcessReplaceOp(const CowOperation* cow_op, void* buffer, size_t buffer_size);
     bool ProcessZeroOp(void* buffer);
 
+    bool IsMappingPresent(const CowOperation* cow_op, loff_t requested_offset,
+                          loff_t cow_op_offset);
+    bool GetCowOpBlockOffset(const CowOperation* cow_op, uint64_t io_block, off_t* block_offset);
     bool ReadAlignedSector(sector_t sector, size_t sz);
     bool ReadUnalignedSector(sector_t sector, size_t size);
     int ReadUnalignedSector(sector_t sector, size_t size,
@@ -56,6 +59,7 @@
 
     constexpr bool IsBlockAligned(size_t size) { return ((size & (BLOCK_SZ - 1)) == 0); }
     constexpr sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+    constexpr chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
 
     std::string backing_store_device_;
     unique_fd backing_store_fd_;
@@ -65,8 +69,9 @@
     std::shared_ptr<IBlockServerOpener> block_server_opener_;
     std::unique_ptr<IBlockServer> block_server_;
 
-    std::basic_string<uint8_t> xor_buffer_;
+    std::vector<uint8_t> xor_buffer_;
     std::unique_ptr<void, decltype(&::free)> aligned_buffer_;
+    std::unique_ptr<uint8_t[]> decompressed_buffer_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index 3013c47..d9cf97f 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -39,7 +39,7 @@
 namespace snapshot {
 
 static constexpr uint32_t kMaxPacketSize = 512;
-static constexpr uint8_t kMaxMergeThreads = 2;
+
 static constexpr char kBootSnapshotsWithoutSlotSwitch[] =
         "/metadata/ota/snapshot-boot-without-slot-switch";
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index 8ddb0f4..56f7b59 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -64,6 +64,9 @@
 struct TestParam {
     bool io_uring;
     bool o_direct;
+    std::string compression;
+    int block_size;
+    int num_threads;
 };
 
 class SnapuserdTestBase : public ::testing::TestWithParam<TestParam> {
@@ -74,6 +77,7 @@
     void CreateCowDevice();
     void SetDeviceControlName();
     std::unique_ptr<ICowWriter> CreateCowDeviceInternal();
+    std::unique_ptr<ICowWriter> CreateV3Cow();
 
     std::unique_ptr<ITestHarness> harness_;
     size_t size_ = 10_MiB;
@@ -122,7 +126,8 @@
 }
 
 std::unique_ptr<ICowWriter> SnapuserdTestBase::CreateCowDeviceInternal() {
-    cow_system_ = std::make_unique<TemporaryFile>();
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
 
     CowOptions options;
     options.compression = "gz";
@@ -130,7 +135,26 @@
     unique_fd fd(cow_system_->fd);
     cow_system_->fd = -1;
 
-    return CreateCowWriter(kDefaultCowVersion, options, std::move(fd));
+    return CreateCowWriter(2, options, std::move(fd));
+}
+
+std::unique_ptr<ICowWriter> SnapuserdTestBase::CreateV3Cow() {
+    const TestParam params = GetParam();
+
+    CowOptions options;
+    options.op_count_max = 100000;
+    options.compression = params.compression;
+    options.num_compress_threads = params.num_threads;
+    options.batch_write = true;
+    options.compression_factor = params.block_size;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    unique_fd fd(cow_system_->fd);
+    cow_system_->fd = -1;
+
+    return CreateCowWriter(3, options, std::move(fd));
 }
 
 void SnapuserdTestBase::CreateCowDevice() {
@@ -236,6 +260,7 @@
     void SetupOrderedOpsInverted();
     void SetupCopyOverlap_1();
     void SetupCopyOverlap_2();
+    void SetupDeviceForPassthrough();
     bool Merge();
     void ValidateMerge();
     void ReadSnapshotDeviceAndValidate();
@@ -258,6 +283,9 @@
 
     void SimulateDaemonRestart();
 
+    void CreateCowDeviceWithNoBlockChanges();
+    void ValidateDeviceWithNoBlockChanges();
+
     void CreateCowDeviceOrderedOps();
     void CreateCowDeviceOrderedOpsInverted();
     void CreateCowDeviceWithCopyOverlap_1();
@@ -307,6 +335,12 @@
     ASSERT_NO_FATAL_FAILURE(SetupDaemon());
 }
 
+void SnapuserdTest::SetupDeviceForPassthrough() {
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(CreateCowDeviceWithNoBlockChanges());
+    ASSERT_NO_FATAL_FAILURE(SetupDaemon());
+}
+
 void SnapuserdTest::SetupOrderedOpsInverted() {
     ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
     ASSERT_NO_FATAL_FAILURE(CreateCowDeviceOrderedOpsInverted());
@@ -480,6 +514,47 @@
     }
 }
 
+void SnapuserdTest::CreateCowDeviceWithNoBlockChanges() {
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
+
+    std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
+    std::memset(buffer.get(), 'A', BLOCK_SZ);
+
+    // This test focusses on not changing all the blocks thereby validating
+    // the pass-through I/O
+
+    // Replace the first block
+    ASSERT_TRUE(writer->AddRawBlocks(1, buffer.get(), BLOCK_SZ));
+
+    // Set zero block of Block 3
+    ASSERT_TRUE(writer->AddZeroBlocks(3, 1));
+
+    ASSERT_TRUE(writer->Finalize());
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    off_t offset = BLOCK_SZ;
+    std::memcpy(orig_buffer_.get() + offset, buffer.get(), BLOCK_SZ);
+    offset = 3 * BLOCK_SZ;
+    std::memset(orig_buffer_.get() + offset, 0, BLOCK_SZ);
+}
+
+void SnapuserdTest::ValidateDeviceWithNoBlockChanges() {
+    unique_fd fd(open(dmuser_dev_->GetPath().c_str(), O_RDONLY));
+    ASSERT_GE(fd, 0);
+    std::unique_ptr<uint8_t[]> snapshot_buffer = std::make_unique<uint8_t[]>(size_);
+    std::memset(snapshot_buffer.get(), 'B', size_);
+
+    // All the I/O request should be a pass through to base device except for
+    // Block 1 and Block 3.
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), size_, 0), true);
+    ASSERT_EQ(memcmp(snapshot_buffer.get(), orig_buffer_.get(), size_), 0);
+}
+
 void SnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
     auto writer = CreateCowDeviceInternal();
     ASSERT_NE(writer, nullptr);
@@ -781,6 +856,20 @@
     ASSERT_TRUE(Merge());
 }
 
+TEST_P(SnapuserdTest, Snapshot_Passthrough) {
+    if (!harness_->HasUserDevice()) {
+        GTEST_SKIP() << "Skipping snapshot read; not supported";
+    }
+    ASSERT_NO_FATAL_FAILURE(SetupDeviceForPassthrough());
+    // I/O before merge
+    ASSERT_NO_FATAL_FAILURE(ValidateDeviceWithNoBlockChanges());
+    ASSERT_TRUE(Merge());
+    ValidateMerge();
+    // I/O after merge - daemon should read directly
+    // from base device
+    ASSERT_NO_FATAL_FAILURE(ValidateDeviceWithNoBlockChanges());
+}
+
 TEST_P(SnapuserdTest, Snapshot_IO_TEST) {
     if (!harness_->HasUserDevice()) {
         GTEST_SKIP() << "Skipping snapshot read; not supported";
@@ -853,7 +942,7 @@
         GTEST_SKIP() << "Skipping snapshot read; not supported";
     }
     ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_2());
-    ASSERT_NO_FATAL_FAILURE(MergeInterruptAndValidate(2));
+    ASSERT_NO_FATAL_FAILURE(MergeInterruptFixed(300));
     ValidateMerge();
 }
 
@@ -881,11 +970,244 @@
     ValidateMerge();
 }
 
+class SnapuserdVariableBlockSizeTest : public SnapuserdTest {
+  public:
+    void SetupCowV3ForVariableBlockSize();
+    void ReadSnapshotWithVariableBlockSize();
+
+  protected:
+    void SetUp() override;
+    void TearDown() override;
+
+    void CreateV3CowDeviceForVariableBlockSize();
+};
+
+void SnapuserdVariableBlockSizeTest::SetupCowV3ForVariableBlockSize() {
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(CreateV3CowDeviceForVariableBlockSize());
+    ASSERT_NO_FATAL_FAILURE(SetupDaemon());
+}
+
+void SnapuserdVariableBlockSizeTest::CreateV3CowDeviceForVariableBlockSize() {
+    auto writer = CreateV3Cow();
+
+    ASSERT_NE(writer, nullptr);
+    size_t total_data_to_write = size_;
+
+    size_t total_blocks_to_write = total_data_to_write / BLOCK_SZ;
+    size_t num_blocks_per_op = total_blocks_to_write / 4;
+    size_t source_block = 0;
+
+    size_t seq_len = num_blocks_per_op;
+    uint32_t sequence[seq_len];
+    size_t xor_block_start = seq_len * 3;
+    for (size_t i = 0; i < seq_len; i++) {
+        sequence[i] = xor_block_start + i;
+    }
+    ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
+
+    size_t total_replace_blocks = num_blocks_per_op;
+    // Write some data which can be compressed
+    std::string data;
+    data.resize(total_replace_blocks * BLOCK_SZ, '\0');
+    for (size_t i = 0; i < data.size(); i++) {
+        data[i] = static_cast<char>('A' + i / BLOCK_SZ);
+    }
+    // REPLACE ops
+    ASSERT_TRUE(writer->AddRawBlocks(source_block, data.data(), data.size()));
+
+    total_blocks_to_write -= total_replace_blocks;
+    source_block = source_block + total_replace_blocks;
+
+    // ZERO ops
+    size_t total_zero_blocks = total_blocks_to_write / 3;
+    ASSERT_TRUE(writer->AddZeroBlocks(source_block, total_zero_blocks));
+
+    total_blocks_to_write -= total_zero_blocks;
+    source_block = source_block + total_zero_blocks;
+
+    // Generate some random data wherein few blocks cannot be compressed.
+    // This is to test the I/O path for those blocks which aren't compressed.
+    size_t total_random_data_blocks = total_blocks_to_write / 2;
+    unique_fd rnd_fd(open("/dev/random", O_RDONLY));
+
+    ASSERT_GE(rnd_fd, 0);
+    std::string random_buffer;
+    random_buffer.resize(total_random_data_blocks * BLOCK_SZ, '\0');
+    ASSERT_EQ(
+            android::base::ReadFullyAtOffset(rnd_fd, random_buffer.data(), random_buffer.size(), 0),
+            true);
+    // REPLACE ops
+    ASSERT_TRUE(writer->AddRawBlocks(source_block, random_buffer.data(), random_buffer.size()));
+
+    total_blocks_to_write -= total_random_data_blocks;
+    source_block = source_block + total_random_data_blocks;
+
+    // XOR ops will always be 4k blocks
+    std::string xor_buffer;
+    xor_buffer.resize(total_blocks_to_write * BLOCK_SZ, '\0');
+    for (size_t i = 0; i < xor_buffer.size(); i++) {
+        xor_buffer[i] = static_cast<char>('C' + i / BLOCK_SZ);
+    }
+    size_t xor_offset = 21;
+    std::string source_buffer;
+    source_buffer.resize(total_blocks_to_write * BLOCK_SZ, '\0');
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, source_buffer.data(), source_buffer.size(),
+                                               size_ + xor_offset),
+              true);
+    for (size_t i = 0; i < xor_buffer.size(); i++) {
+        xor_buffer[i] ^= source_buffer[i];
+    }
+
+    ASSERT_EQ(xor_block_start, source_block);
+
+    ASSERT_TRUE(writer->AddXorBlocks(source_block, xor_buffer.data(), xor_buffer.size(),
+                                     (size_ / BLOCK_SZ), xor_offset));
+    // Flush operations
+    ASSERT_TRUE(writer->Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // REPLACE ops which are compressed
+    std::memcpy(orig_buffer_.get(), data.data(), data.size());
+    size_t offset = data.size();
+
+    // ZERO ops
+    std::string zero_buffer(total_zero_blocks * BLOCK_SZ, 0);
+    std::memcpy((char*)orig_buffer_.get() + offset, (void*)zero_buffer.c_str(), zero_buffer.size());
+    offset += zero_buffer.size();
+
+    // REPLACE ops - Random buffers which aren't compressed
+    std::memcpy((char*)orig_buffer_.get() + offset, random_buffer.c_str(), random_buffer.size());
+    offset += random_buffer.size();
+
+    // XOR Ops which default to 4k block size compression irrespective of
+    // compression factor
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, (char*)orig_buffer_.get() + offset,
+                                               xor_buffer.size(), size_ + xor_offset),
+              true);
+    for (size_t i = 0; i < xor_buffer.size(); i++) {
+        orig_buffer_.get()[offset + i] = (uint8_t)(orig_buffer_.get()[offset + i] ^ xor_buffer[i]);
+    }
+}
+
+void SnapuserdVariableBlockSizeTest::ReadSnapshotWithVariableBlockSize() {
+    unique_fd fd(open(dmuser_dev_->GetPath().c_str(), O_RDONLY | O_DIRECT));
+    ASSERT_GE(fd, 0);
+
+    void* addr;
+    ssize_t page_size = getpagesize();
+    ASSERT_EQ(posix_memalign(&addr, page_size, size_), 0);
+    std::unique_ptr<void, decltype(&::free)> snapshot_buffer(addr, ::free);
+
+    const TestParam params = GetParam();
+
+    // Issue I/O request with various block sizes
+    size_t num_blocks = size_ / params.block_size;
+    off_t offset = 0;
+    for (size_t i = 0; i < num_blocks; i++) {
+        ASSERT_EQ(ReadFullyAtOffset(fd, (char*)snapshot_buffer.get() + offset, params.block_size,
+                                    offset),
+                  true);
+        offset += params.block_size;
+    }
+    // Validate buffer
+    ASSERT_EQ(memcmp(snapshot_buffer.get(), orig_buffer_.get(), size_), 0);
+
+    // Reset the buffer
+    std::memset(snapshot_buffer.get(), 0, size_);
+
+    // Read one full chunk in a single shot and re-validate.
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), size_, 0), true);
+    ASSERT_EQ(memcmp(snapshot_buffer.get(), orig_buffer_.get(), size_), 0);
+
+    // Reset the buffer
+    std::memset(snapshot_buffer.get(), 0, size_);
+
+    // Buffered I/O test
+    fd.reset(open(dmuser_dev_->GetPath().c_str(), O_RDONLY));
+    ASSERT_GE(fd, 0);
+
+    // Try not to cache
+    posix_fadvise(fd.get(), 0, size_, POSIX_FADV_DONTNEED);
+
+    size_t num_blocks_per_op = (size_ / BLOCK_SZ) / 4;
+    offset = num_blocks_per_op * BLOCK_SZ;
+    size_t read_size = 1019;  // bytes
+    offset -= 111;
+
+    // Issue a un-aligned read which crosses the boundary between a REPLACE block and a ZERO
+    // block.
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), read_size, offset), true);
+
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapshot_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0);
+
+    offset = (num_blocks_per_op * 3) * BLOCK_SZ;
+    offset -= (BLOCK_SZ - 119);
+    read_size = 8111;
+
+    // Issue an un-aligned read which crosses the boundary between a REPLACE block of random
+    // un-compressed data and a XOR block
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), read_size, offset), true);
+
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapshot_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0);
+
+    // Reset the buffer
+    std::memset(snapshot_buffer.get(), 0, size_);
+
+    // Read just one byte at an odd offset which is a REPLACE op
+    offset = 19;
+    read_size = 1;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), read_size, offset), true);
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapshot_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0);
+
+    // Reset the buffer
+    std::memset(snapshot_buffer.get(), 0, size_);
+
+    // Read a block which has no mapping to a COW operation. This read should be
+    // a pass-through to the underlying base device.
+    offset = size_ + 9342;
+    read_size = 30;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), read_size, offset), true);
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapshot_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0);
+}
+
+void SnapuserdVariableBlockSizeTest::SetUp() {
+    ASSERT_NO_FATAL_FAILURE(SnapuserdTest::SetUp());
+}
+
+void SnapuserdVariableBlockSizeTest::TearDown() {
+    SnapuserdTest::TearDown();
+}
+
+TEST_P(SnapuserdVariableBlockSizeTest, Snapshot_Test_Variable_Block_Size) {
+    if (!harness_->HasUserDevice()) {
+        GTEST_SKIP() << "Skipping snapshot read; not supported";
+    }
+    ASSERT_NO_FATAL_FAILURE(SetupCowV3ForVariableBlockSize());
+    ASSERT_NO_FATAL_FAILURE(ReadSnapshotWithVariableBlockSize());
+    ASSERT_TRUE(StartMerge());
+    CheckMergeCompletion();
+    ValidateMerge();
+    ASSERT_NO_FATAL_FAILURE(ReadSnapshotWithVariableBlockSize());
+}
+
 class HandlerTest : public SnapuserdTestBase {
   protected:
     void SetUp() override;
     void TearDown() override;
 
+    void SetUpV2Cow();
+    void InitializeDevice();
     AssertionResult ReadSectors(sector_t sector, uint64_t size, void* buffer);
 
     TestBlockServerFactory factory_;
@@ -896,10 +1218,11 @@
     std::future<bool> handler_thread_;
 };
 
-void HandlerTest::SetUp() {
-    ASSERT_NO_FATAL_FAILURE(SnapuserdTestBase::SetUp());
-    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+void HandlerTest::SetUpV2Cow() {
     ASSERT_NO_FATAL_FAILURE(CreateCowDevice());
+}
+
+void HandlerTest::InitializeDevice() {
     ASSERT_NO_FATAL_FAILURE(SetDeviceControlName());
 
     opener_ = factory_.CreateTestOpener(system_device_ctrl_name_);
@@ -921,6 +1244,13 @@
     handler_thread_ = std::async(std::launch::async, &SnapshotHandler::Start, handler_.get());
 }
 
+void HandlerTest::SetUp() {
+    ASSERT_NO_FATAL_FAILURE(SnapuserdTestBase::SetUp());
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(SetUpV2Cow());
+    ASSERT_NO_FATAL_FAILURE(InitializeDevice());
+}
+
 void HandlerTest::TearDown() {
     ASSERT_TRUE(factory_.DeleteQueue(system_device_ctrl_name_));
     ASSERT_TRUE(handler_thread_.get());
@@ -986,6 +1316,148 @@
     ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), SECTOR_SIZE), 0);
 }
 
+class HandlerTestV3 : public HandlerTest {
+  public:
+    void ReadSnapshotWithVariableBlockSize();
+
+  protected:
+    void SetUp() override;
+    void TearDown() override;
+    void SetUpV3Cow();
+};
+
+void HandlerTestV3::SetUp() {
+    ASSERT_NO_FATAL_FAILURE(SnapuserdTestBase::SetUp());
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(SetUpV3Cow());
+    ASSERT_NO_FATAL_FAILURE(InitializeDevice());
+}
+
+void HandlerTestV3::TearDown() {
+    ASSERT_NO_FATAL_FAILURE(HandlerTest::TearDown());
+}
+
+void HandlerTestV3::SetUpV3Cow() {
+    auto writer = CreateV3Cow();
+
+    ASSERT_NE(writer, nullptr);
+    size_t total_data_to_write = size_;
+
+    size_t total_blocks_to_write = total_data_to_write / BLOCK_SZ;
+    size_t num_blocks_per_op = total_blocks_to_write / 4;
+    size_t source_block = 0;
+
+    size_t total_replace_blocks = num_blocks_per_op;
+    // Write some data which can be compressed
+    std::string data;
+    data.resize(total_replace_blocks * BLOCK_SZ, '\0');
+    for (size_t i = 0; i < data.size(); i++) {
+        data[i] = static_cast<char>('A' + i / BLOCK_SZ);
+    }
+    // REPLACE ops
+    ASSERT_TRUE(writer->AddRawBlocks(source_block, data.data(), data.size()));
+
+    total_blocks_to_write -= total_replace_blocks;
+    source_block = source_block + total_replace_blocks;
+
+    // ZERO ops
+    size_t total_zero_blocks = total_blocks_to_write / 3;
+    ASSERT_TRUE(writer->AddZeroBlocks(source_block, total_zero_blocks));
+
+    total_blocks_to_write -= total_zero_blocks;
+    source_block = source_block + total_zero_blocks;
+
+    // Generate some random data wherein few blocks cannot be compressed.
+    // This is to test the I/O path for those blocks which aren't compressed.
+    size_t total_random_data_blocks = total_blocks_to_write;
+    unique_fd rnd_fd(open("/dev/random", O_RDONLY));
+
+    ASSERT_GE(rnd_fd, 0);
+    std::string random_buffer;
+    random_buffer.resize(total_random_data_blocks * BLOCK_SZ, '\0');
+    ASSERT_EQ(
+            android::base::ReadFullyAtOffset(rnd_fd, random_buffer.data(), random_buffer.size(), 0),
+            true);
+    // REPLACE ops
+    ASSERT_TRUE(writer->AddRawBlocks(source_block, random_buffer.data(), random_buffer.size()));
+    // Flush operations
+    ASSERT_TRUE(writer->Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // REPLACE ops which are compressed
+    std::memcpy(orig_buffer_.get(), data.data(), data.size());
+    size_t offset = data.size();
+
+    // ZERO ops
+    std::string zero_buffer(total_zero_blocks * BLOCK_SZ, 0);
+    std::memcpy((char*)orig_buffer_.get() + offset, (void*)zero_buffer.c_str(), zero_buffer.size());
+    offset += zero_buffer.size();
+
+    // REPLACE ops - Random buffers which aren't compressed
+    std::memcpy((char*)orig_buffer_.get() + offset, random_buffer.c_str(), random_buffer.size());
+}
+
+TEST_P(HandlerTestV3, Read) {
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
+
+    size_t read_size = SECTOR_SIZE;
+    off_t offset = 0;
+    // Read the first sector
+    ASSERT_TRUE(ReadSectors(1, read_size, snapuserd_buffer.get()));
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), orig_buffer_.get(), read_size), 0);
+
+    // Read the second block at offset 7680 (Sector 15). This will map to the
+    // first COW operation for variable block size
+    offset += (((BLOCK_SZ * 2) - SECTOR_SIZE));
+    read_size = BLOCK_SZ;  // Span across two REPLACE ops
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, read_size, snapuserd_buffer.get()));
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + offset, read_size),
+              0);
+
+    // Fill some other data since we are going to read zero blocks
+    std::memset(snapuserd_buffer.get(), 'Z', size_);
+
+    size_t num_blocks_per_op = (size_ / BLOCK_SZ) / 4;
+    offset = num_blocks_per_op * BLOCK_SZ;
+    // Issue read spanning between a REPLACE op and ZERO ops. The starting point
+    // is the last REPLACE op at sector 5118
+    offset -= (SECTOR_SIZE * 2);
+    // This will make sure it falls back to aligned reads after reading the
+    // first unaligned block
+    read_size = BLOCK_SZ * 6;
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, read_size, snapuserd_buffer.get()));
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + offset, read_size),
+              0);
+
+    // Issue I/O request at the last block. The first chunk of (SECTOR_SIZE * 2)
+    // will be from REPLACE op which has random buffers
+    offset = (size_ - (SECTOR_SIZE * 2));
+    // Request will span beyond the COW mapping, thereby fetching data from base
+    // device.
+    read_size = BLOCK_SZ * 8;
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, read_size, snapuserd_buffer.get()));
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + offset, read_size),
+              0);
+
+    // Issue I/O request which are not mapped to any COW operations
+    offset = (size_ + (SECTOR_SIZE * 3));
+    read_size = BLOCK_SZ * 3;
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, read_size, snapuserd_buffer.get()));
+    // Validate the data
+    ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + offset, read_size),
+              0);
+}
+
 std::vector<bool> GetIoUringConfigs() {
 #if __ANDROID__
     if (!android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false)) {
@@ -1018,6 +1490,37 @@
     return testParams;
 }
 
+std::vector<TestParam> GetVariableBlockTestConfigs() {
+    std::vector<TestParam> testParams;
+
+    std::vector<int> block_sizes = {4096, 8192, 16384, 32768, 65536, 131072};
+    std::vector<std::string> compression_algo = {"none", "lz4", "zstd", "gz"};
+    std::vector<int> threads = {1, 2};
+    std::vector<bool> uring_configs = GetIoUringConfigs();
+
+    // This should test 96 combination and validates the I/O path
+    for (auto block : block_sizes) {
+        for (auto compression : compression_algo) {
+            for (auto thread : threads) {
+                for (auto io_uring : uring_configs) {
+                    TestParam param;
+                    param.block_size = block;
+                    param.compression = compression;
+                    param.num_threads = thread;
+                    param.io_uring = io_uring;
+                    param.o_direct = false;
+                    testParams.push_back(std::move(param));
+                }
+            }
+        }
+    }
+
+    return testParams;
+}
+
+INSTANTIATE_TEST_SUITE_P(Io, SnapuserdVariableBlockSizeTest,
+                         ::testing::ValuesIn(GetVariableBlockTestConfigs()));
+INSTANTIATE_TEST_SUITE_P(Io, HandlerTestV3, ::testing::ValuesIn(GetVariableBlockTestConfigs()));
 INSTANTIATE_TEST_SUITE_P(Io, SnapuserdTest, ::testing::ValuesIn(GetTestConfigs()));
 INSTANTIATE_TEST_SUITE_P(Io, HandlerTest, ::testing::ValuesIn(GetTestConfigs()));
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp
index 6817340..957c6a8 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp
@@ -20,6 +20,7 @@
 #include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 
+#include "android-base/properties.h"
 #include "snapuserd_core.h"
 
 namespace android {
@@ -104,7 +105,9 @@
     }
 
     loff_t file_offset = offset;
-    const uint64_t read_sz = kBlockSizeVerify;
+    auto verify_block_size = android::base::GetUintProperty<uint>("ro.virtual_ab.verify_block_size",
+                                                                  kBlockSizeVerify);
+    const uint64_t read_sz = verify_block_size;
 
     void* addr;
     ssize_t page_size = getpagesize();
@@ -130,7 +133,7 @@
         }
 
         bytes_read += to_read;
-        file_offset += (skip_blocks * kBlockSizeVerify);
+        file_offset += (skip_blocks * verify_block_size);
         if (file_offset >= dev_sz) {
             break;
         }
@@ -184,7 +187,9 @@
      * latency.
      */
     int num_threads = kMinThreadsToVerify;
-    if (dev_sz > kThresholdSize) {
+    auto verify_threshold_size = android::base::GetUintProperty<uint>(
+            "ro.virtual_ab.verify_threshold_size", kThresholdSize);
+    if (dev_sz > verify_threshold_size) {
         num_threads = kMaxThreadsToVerify;
     }
 
@@ -192,11 +197,13 @@
     off_t start_offset = 0;
     const int skip_blocks = num_threads;
 
+    auto verify_block_size =
+            android::base::GetUintProperty("ro.virtual_ab.verify_block_size", kBlockSizeVerify);
     while (num_threads) {
         threads.emplace_back(std::async(std::launch::async, &UpdateVerify::VerifyBlocks, this,
                                         partition_name, dm_block_device, start_offset, skip_blocks,
                                         dev_sz));
-        start_offset += kBlockSizeVerify;
+        start_offset += verify_block_size;
         num_threads -= 1;
         if (start_offset >= dev_sz) {
             break;
diff --git a/fs_mgr/libsnapshot/tools/cow_benchmark.cpp b/fs_mgr/libsnapshot/tools/cow_benchmark.cpp
index 4d5e346..fb463c8 100644
--- a/fs_mgr/libsnapshot/tools/cow_benchmark.cpp
+++ b/fs_mgr/libsnapshot/tools/cow_benchmark.cpp
@@ -78,7 +78,7 @@
 
     for (size_t i = 0; i < compressors.size(); i++) {
         const auto start = std::chrono::steady_clock::now();
-        std::basic_string<uint8_t> compressed_data =
+        std::vector<uint8_t> compressed_data =
                 compressors[i]->Compress(buffer.data(), buffer.size());
         const auto end = std::chrono::steady_clock::now();
         const auto latency =
@@ -141,13 +141,13 @@
     std::vector<std::pair<double, std::string>> ratios;
 
     for (size_t i = 0; i < compressors.size(); i++) {
-        std::vector<std::basic_string<uint8_t>> compressed_data_vec;
+        std::vector<std::vector<uint8_t>> compressed_data_vec;
         int num_blocks = buffer.size() / BLOCK_SZ;
         const uint8_t* iter = reinterpret_cast<const uint8_t*>(buffer.data());
 
         const auto start = std::chrono::steady_clock::now();
         while (num_blocks > 0) {
-            std::basic_string<uint8_t> compressed_data = compressors[i]->Compress(iter, BLOCK_SZ);
+            std::vector<uint8_t> compressed_data = compressors[i]->Compress(iter, BLOCK_SZ);
             compressed_data_vec.emplace_back(compressed_data);
             num_blocks--;
             iter += BLOCK_SZ;
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 1ffa89c..7eaaca9 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -199,7 +199,7 @@
 }
 
 std::ostream& operator<<(std::ostream& os, const Now&) {
-    struct tm now {};
+    struct tm now{};
     time_t t = time(nullptr);
     localtime_r(&t, &now);
     return os << std::put_time(&now, "%Y%m%d-%H%M%S");
@@ -230,11 +230,7 @@
     return fetcher->GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
 }
 
-bool CanUseUserspaceSnapshots() {
-    if (!GetUserspaceSnapshotsEnabledProperty()) {
-        return false;
-    }
-
+bool IsVendorFromAndroid12() {
     auto fetcher = IPropertyFetcher::GetInstance();
 
     const std::string UNKNOWN = "unknown";
@@ -243,8 +239,15 @@
 
     // No user-space snapshots if vendor partition is on Android 12
     if (vendor_release.find("12") != std::string::npos) {
-        LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
-                  << vendor_release;
+        return true;
+    }
+
+    return false;
+}
+
+bool CanUseUserspaceSnapshots() {
+    if (!GetUserspaceSnapshotsEnabledProperty()) {
+        LOG(INFO) << "Virtual A/B - Userspace snapshots disabled";
         return false;
     }
 
@@ -269,6 +272,11 @@
     return fetcher->GetBoolProperty("ro.virtual_ab.compression.xor.enabled", false);
 }
 
+bool GetODirectEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.o_direct.enabled", false);
+}
+
 std::string GetOtherPartitionName(const std::string& name) {
     auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
     CHECK(suffix == "_a" || suffix == "_b");
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index 370f3c4..7dae942 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -133,9 +133,11 @@
 bool GetUserspaceSnapshotsEnabledProperty();
 bool GetIouringEnabledProperty();
 bool GetXorCompressionEnabledProperty();
+bool GetODirectEnabledProperty();
 
 bool CanUseUserspaceSnapshots();
 bool IsDmSnapshotTestingEnabled();
+bool IsVendorFromAndroid12();
 
 // Swap the suffix of a partition name.
 std::string GetOtherPartitionName(const std::string& name);
diff --git a/fs_mgr/libsnapshot/vts_ota_config_test.cpp b/fs_mgr/libsnapshot/vts_ota_config_test.cpp
index d387eb3..b5618c4 100755
--- a/fs_mgr/libsnapshot/vts_ota_config_test.cpp
+++ b/fs_mgr/libsnapshot/vts_ota_config_test.cpp
@@ -21,6 +21,7 @@
     return android::base::GetIntProperty("ro.vendor.api_level", -1);
 }
 
+// @VsrTest = 3.7.6
 TEST(VAB, Enabled) {
     if (!android::base::GetBoolProperty("ro.build.ab_update", false) && (GetVsrLevel() < __ANDROID_API_T__)) {
         GTEST_SKIP();
diff --git a/fs_mgr/tests/Android.bp b/fs_mgr/tests/Android.bp
index 2aeba0a..041762f 100644
--- a/fs_mgr/tests/Android.bp
+++ b/fs_mgr/tests/Android.bp
@@ -14,6 +14,7 @@
 
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
+    default_team: "trendy_team_android_kernel",
 }
 
 cc_test {
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 7ac7a16..526c761 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -1081,7 +1081,9 @@
 LOG RUN "Testing adb disable-verity -R"
 
 T=$(adb_date)
-adb_su disable-verity -R >&2 ||
+adb_su disable-verity -R >&2
+err=${?}
+[[ ${err} -eq 0 || ${err} -eq 255 ]] ||
   die -t "${T}" "disable-verity -R failed"
 sleep 2
 adb_wait "${ADB_WAIT}" ||
@@ -1192,7 +1194,9 @@
 LOG RUN "Testing adb remount -R"
 
 T=$(adb_date)
-adb_su remount -R </dev/null >&2 ||
+adb_su remount -R </dev/null >&2
+err=${?}
+[[ ${err} -eq 0 || ${err} -eq 255 ]] ||
   die -t "${T}" "adb remount -R failed"
 sleep 2
 adb_wait "${ADB_WAIT}" ||
diff --git a/fs_mgr/tests/vts_fs_test.cpp b/fs_mgr/tests/vts_fs_test.cpp
index 9503072..f55cadb 100644
--- a/fs_mgr/tests/vts_fs_test.cpp
+++ b/fs_mgr/tests/vts_fs_test.cpp
@@ -74,6 +74,7 @@
     ASSERT_EQ(access("/sys/fs/erofs", F_OK), 0);
 }
 
+// @VsrTest = 3.7.10
 TEST(fs, PartitionTypes) {
     // Requirements only apply to Android 13+, 5.10+ devices.
     int vsr_level = GetVsrLevel();
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 7273087..00f8038 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -50,8 +50,10 @@
     std::cerr << "  create <dm-name> [-ro] <targets...>" << std::endl;
     std::cerr << "  delete <dm-name>" << std::endl;
     std::cerr << "  list <devices | targets> [-v]" << std::endl;
+    std::cerr << "  message <dm-name> <sector> <message>" << std::endl;
     std::cerr << "  getpath <dm-name>" << std::endl;
     std::cerr << "  getuuid <dm-name>" << std::endl;
+    std::cerr << "  ima <dm-name>" << std::endl;
     std::cerr << "  info <dm-name>" << std::endl;
     std::cerr << "  replace <dm-name> <targets...>" << std::endl;
     std::cerr << "  status <dm-name>" << std::endl;
@@ -115,6 +117,21 @@
             std::string block_device = NextArg();
             return std::make_unique<DmTargetAndroidVerity>(start_sector, num_sectors, keyid,
                                                            block_device);
+        } else if (target_type == "striped") {
+            if (!HasArgs(3)) {
+                std::cerr << "Expected \"striped\" <block_device0> <block_device1> <chunksize>"
+                          << std::endl;
+                return nullptr;
+            }
+            std::string block_device0 = NextArg();
+            std::string block_device1 = NextArg();
+            uint64_t chunk_size;
+            if (!android::base::ParseUint(NextArg(), &chunk_size)) {
+                std::cerr << "Expected start sector, got: " << PreviousArg() << std::endl;
+                return nullptr;
+            }
+            return std::make_unique<DmTargetStripe>(start_sector, num_sectors, chunk_size,
+                                                    block_device0, block_device1);
         } else if (target_type == "bow") {
             if (!HasArgs(1)) {
                 std::cerr << "Expected \"bow\" <block_device>" << std::endl;
@@ -187,6 +204,46 @@
             return std::make_unique<DmTargetUser>(start_sector, num_sectors, control_device);
         } else if (target_type == "error") {
             return std::make_unique<DmTargetError>(start_sector, num_sectors);
+        } else if (target_type == "thin-pool") {
+            if (!HasArgs(4)) {
+                std::cerr << "Expected \"thin-pool\" <metadata dev> <data dev> <data block size> "
+                             "<low water mark> <feature args>"
+                          << std::endl;
+                return nullptr;
+            }
+
+            std::string metadata_dev = NextArg();
+            std::string data_dev = NextArg();
+            std::string data_block_size_str = NextArg();
+            std::string low_water_mark_str = NextArg();
+
+            uint64_t data_block_size;
+            if (!android::base::ParseUint(data_block_size_str, &data_block_size)) {
+                std::cerr << "Data block size must be an unsigned integer.\n";
+                return nullptr;
+            }
+            uint64_t low_water_mark;
+            if (!android::base::ParseUint(low_water_mark_str, &low_water_mark)) {
+                std::cerr << "Low water mark must be an unsigned integer.\n";
+                return nullptr;
+            }
+            return std::make_unique<DmTargetThinPool>(start_sector, num_sectors, metadata_dev,
+                                                      data_dev, data_block_size, low_water_mark);
+        } else if (target_type == "thin") {
+            if (!HasArgs(2)) {
+                std::cerr << "Expected \"thin\" <pool dev> <dev id>" << std::endl;
+                return nullptr;
+            }
+
+            std::string pool_dev = NextArg();
+            std::string dev_id_str = NextArg();
+
+            uint64_t dev_id;
+            if (!android::base::ParseUint(dev_id_str, &dev_id)) {
+                std::cerr << "Dev id must be an unsigned integer.\n";
+                return nullptr;
+            }
+            return std::make_unique<DmTargetThin>(start_sector, num_sectors, pool_dev, dev_id);
         } else {
             std::cerr << "Unrecognized target type: " << target_type << std::endl;
             return nullptr;
@@ -401,6 +458,24 @@
     return -EINVAL;
 }
 
+static int DmMessageCmdHandler(int argc, char** argv) {
+    if (argc != 3) {
+        std::cerr << "Usage: dmctl message <name> <sector> <message>" << std::endl;
+        return -EINVAL;
+    }
+    uint64_t sector;
+    if (!android::base::ParseUint(argv[1], &sector)) {
+        std::cerr << "Invalid argument for sector: " << argv[1] << std::endl;
+        return -EINVAL;
+    }
+    DeviceMapper& dm = DeviceMapper::Instance();
+    if (!dm.SendMessage(argv[0], sector, argv[2])) {
+        std::cerr << "Could not send message to " << argv[0] << std::endl;
+        return -EINVAL;
+    }
+    return 0;
+}
+
 static int HelpCmdHandler(int /* argc */, char** /* argv */) {
     Usage();
     return 0;
@@ -493,7 +568,14 @@
                       << std::endl;
             return -EINVAL;
         }
+    } else if (mode == "ima") {
+        if (!dm.GetTableStatusIma(argv[0], &table)) {
+            std::cerr << "Could not query table status of device \"" << argv[0] << "\"."
+                      << std::endl;
+            return -EINVAL;
+        }
     }
+
     std::cout << "Targets in the device-mapper table for " << argv[0] << ":" << std::endl;
     for (const auto& target : table) {
         std::cout << target.spec.sector_start << "-"
@@ -515,6 +597,10 @@
     return DumpTable("status", argc, argv);
 }
 
+static int ImaCmdHandler(int argc, char** argv) {
+    return DumpTable("ima", argc, argv);
+}
+
 static int ResumeCmdHandler(int argc, char** argv) {
     if (argc != 1) {
         std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
@@ -549,12 +635,14 @@
         {"delete", DmDeleteCmdHandler},
         {"replace", DmReplaceCmdHandler},
         {"list", DmListCmdHandler},
+        {"message", DmMessageCmdHandler},
         {"help", HelpCmdHandler},
         {"getpath", GetPathCmdHandler},
         {"getuuid", GetUuidCmdHandler},
         {"info", InfoCmdHandler},
         {"table", TableCmdHandler},
         {"status", StatusCmdHandler},
+        {"ima", ImaCmdHandler},
         {"resume", ResumeCmdHandler},
         {"suspend", SuspendCmdHandler},
         // clang-format on
diff --git a/init/Android.bp b/init/Android.bp
index a7278d6..7b7a856 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -88,7 +88,6 @@
 init_host_sources = [
     "check_builtins.cpp",
     "host_import_parser.cpp",
-    "host_init_verifier.cpp",
 ]
 
 soong_config_module_type {
@@ -97,6 +96,7 @@
     config_namespace: "ANDROID",
     bool_variables: [
         "PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT",
+        "release_write_appcompat_override_system_properties",
     ],
     properties: [
         "cflags",
@@ -160,11 +160,14 @@
                 "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=1",
             ],
         },
+        release_write_appcompat_override_system_properties: {
+            cflags: ["-DWRITE_APPCOMPAT_OVERRIDE_SYSTEM_PROPERTIES"],
+        },
     },
     static_libs: [
         "libavb",
+        "libavf_cc_flags",
         "libbootloader_message",
-        "libc++fs",
         "libcgrouprc_format",
         "liblmkd_utils",
         "liblz4",
@@ -255,7 +258,11 @@
 
 cc_library_static {
     name: "libinit.microdroid",
-    defaults: ["libinit_defaults"],
+    defaults: [
+        "avf_build_flags_cc",
+        "libinit_defaults",
+    ],
+    recovery_available: false,
     cflags: ["-DMICRODROID=1"],
 }
 
@@ -273,6 +280,13 @@
     defaults: ["init_defaults"],
     srcs: ["main.cpp"],
     symlinks: ["ueventd"],
+}
+
+cc_binary {
+    name: "init_second_stage",
+    defaults: ["init_second_stage_defaults"],
+    static_libs: ["libinit"],
+    visibility: ["//visibility:any_system_partition"],
     target: {
         platform: {
             required: [
@@ -307,21 +321,18 @@
 }
 
 cc_binary {
-    name: "init_second_stage",
-    defaults: ["init_second_stage_defaults"],
-    static_libs: ["libinit"],
-}
-
-cc_binary {
     name: "init_second_stage.microdroid",
-    defaults: ["init_second_stage_defaults"],
+    defaults: [
+        "avf_build_flags_cc",
+        "init_second_stage_defaults",
+    ],
+    recovery_available: false,
     static_libs: ["libinit.microdroid"],
     cflags: ["-DMICRODROID=1"],
-    installable: false,
+    no_full_install: true,
     visibility: ["//packages/modules/Virtualization/microdroid"],
 }
 
-
 soong_config_module_type {
     name: "init_first_stage_cc_defaults",
     module_type: "cc_defaults",
@@ -359,8 +370,8 @@
     ],
 
     static_libs: [
-        "libc++fs",
         "libfs_avb",
+        "libavf_cc_flags",
         "libfs_mgr",
         "libfec",
         "libfec_rs",
@@ -443,6 +454,7 @@
 
         // First stage init is weird: it may start without stdout/stderr, and no /proc.
         hwaddress: false,
+        memtag_stack: false,
     },
 
     // Install adb_debug.prop into debug ramdisk.
@@ -461,9 +473,12 @@
 
 cc_binary {
     name: "init_first_stage.microdroid",
-    defaults: ["init_first_stage_defaults"],
+    defaults: [
+        "avf_build_flags_cc",
+        "init_first_stage_defaults",
+    ],
     cflags: ["-DMICRODROID=1"],
-    installable: false,
+    no_full_install: true,
 }
 
 phony {
@@ -475,6 +490,7 @@
 // ------------------------------------------------------------------------------
 
 cc_test {
+    // Note: This is NOT a CTS test. See b/320800872
     name: "CtsInitTestCases",
     defaults: ["init_defaults"],
     require_root: true,
@@ -507,7 +523,6 @@
     ],
 
     test_suites: [
-        "cts",
         "device-tests",
     ],
 }
@@ -554,6 +569,11 @@
     ],
     export_include_dirs: ["test_utils/include"], // for tests
     header_libs: ["bionic_libc_platform_headers"],
+    product_variables: {
+        shipping_api_level: {
+            cflags: ["-DBUILD_SHIPPING_API_LEVEL=%s"],
+        },
+    },
 }
 
 // Host Verifier
@@ -599,7 +619,6 @@
     },
     generated_headers: [
         "generated_stub_builtin_function_map",
-        "generated_android_ids",
     ],
     target: {
         android: {
@@ -609,18 +628,26 @@
             enabled: false,
         },
     },
+    product_variables: {
+        shipping_api_level: {
+            cflags: ["-DBUILD_SHIPPING_API_LEVEL=%s"],
+        },
+    },
 }
 
 cc_binary {
     name: "host_init_verifier",
     defaults: ["init_host_defaults"],
-    srcs: init_common_sources + init_host_sources,
+    srcs: ["host_init_verifier.cpp"] + init_common_sources + init_host_sources,
+    generated_headers: [
+        "generated_android_ids",
+    ],
 }
 
 cc_library_host_static {
     name: "libinit_host",
     defaults: ["init_host_defaults"],
-    srcs: init_common_sources,
+    srcs: init_common_sources + init_host_sources,
     export_include_dirs: ["."],
     proto: {
         export_proto_headers: true,
@@ -636,3 +663,11 @@
     src: "extra_free_kbytes.sh",
     filename_from_src: true,
 }
+
+phony {
+    name: "init_vendor",
+    required: select(soong_config_variable("ANDROID", "BOARD_USES_RECOVERY_AS_BOOT"), {
+        true: [],
+        default: ["init_first_stage"],
+    }),
+}
diff --git a/init/Android.mk b/init/Android.mk
deleted file mode 100644
index 4b85c15..0000000
--- a/init/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2005 The Android Open Source Project
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := init_vendor
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
-ifneq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
-LOCAL_REQUIRED_MODULES := \
-   init_first_stage \
-
-endif  # BOARD_USES_RECOVERY_AS_BOOT
-include $(BUILD_PHONY_PACKAGE)
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
index 3c7107a..7d00195 100644
--- a/init/README.ueventd.md
+++ b/init/README.ueventd.md
@@ -59,9 +59,10 @@
 
 `subsystem_name` is used to match uevent `SUBSYSTEM` value
 
-`devname` takes one of two options
+`devname` takes one of three options
   1. `uevent_devname` specifies that the name of the node will be the uevent `DEVNAME`
-  2. `uevent_devpath` specified that the name of the node will be basename uevent `DEVPATH`
+  2. `uevent_devpath` specifies that the name of the node will be basename uevent `DEVPATH`
+  3. `sys_name` specifies that the name of the node will be the contents of `/sys/DEVPATH/name`
 
 `dirname` is an optional parameter that specifies a directory within `/dev` where the node will be
 created.
diff --git a/init/apex_init_util.cpp b/init/apex_init_util.cpp
index 6d17f36..e5a7fbc 100644
--- a/init/apex_init_util.cpp
+++ b/init/apex_init_util.cpp
@@ -107,8 +107,9 @@
     }
     // APEXes can have versioned RC files. These should be filtered based on
     // SDK version.
-    auto filtered = FilterVersionedConfigs(
-            files, android::base::GetIntProperty("ro.build.version.sdk", INT_MAX));
+    int sdk = android::base::GetIntProperty("ro.build.version.sdk", INT_MAX);
+    if (sdk < 35) sdk = 35;  // aosp/main merges only into sdk=35+ (ie. __ANDROID_API_V__+)
+    auto filtered = FilterVersionedConfigs(files, sdk);
     if (filtered.empty()) {
         return {};
     }
diff --git a/init/block_dev_initializer.cpp b/init/block_dev_initializer.cpp
index 05e00ed..a686d05 100644
--- a/init/block_dev_initializer.cpp
+++ b/init/block_dev_initializer.cpp
@@ -132,11 +132,19 @@
 bool BlockDevInitializer::InitDmDevice(const std::string& device) {
     const std::string device_name(basename(device.c_str()));
     const std::string syspath = "/sys/block/" + device_name;
+    return InitDevice(syspath, device_name);
+}
+
+bool BlockDevInitializer::InitPlatformDevice(const std::string& dev_name) {
+    return InitDevice("/sys/devices/platform", dev_name);
+}
+
+bool BlockDevInitializer::InitDevice(const std::string& syspath, const std::string& device_name) {
     bool found = false;
 
-    auto uevent_callback = [&device_name, &device, this, &found](const Uevent& uevent) {
+    auto uevent_callback = [&device_name, this, &found](const Uevent& uevent) {
         if (uevent.device_name == device_name) {
-            LOG(VERBOSE) << "Creating device-mapper device : " << device;
+            LOG(VERBOSE) << "Creating device : " << device_name;
             device_handler_->HandleUevent(uevent);
             found = true;
             return ListenerAction::kStop;
@@ -146,13 +154,13 @@
 
     uevent_listener_.RegenerateUeventsForPath(syspath, uevent_callback);
     if (!found) {
-        LOG(INFO) << "dm device '" << device << "' not found in /sys, waiting for its uevent";
+        LOG(INFO) << "device '" << device_name << "' not found in /sys, waiting for its uevent";
         Timer t;
         uevent_listener_.Poll(uevent_callback, 10s);
-        LOG(INFO) << "wait for dm device '" << device << "' returned after " << t;
+        LOG(INFO) << "wait for device '" << device_name << "' returned after " << t;
     }
     if (!found) {
-        LOG(ERROR) << "dm device '" << device << "' not found after polling timeout";
+        LOG(ERROR) << "device '" << device_name << "' not found after polling timeout";
         return false;
     }
     return true;
diff --git a/init/block_dev_initializer.h b/init/block_dev_initializer.h
index ec39ce0..d5b1f60 100644
--- a/init/block_dev_initializer.h
+++ b/init/block_dev_initializer.h
@@ -24,6 +24,7 @@
 namespace android {
 namespace init {
 
+// TODO: should this be renamed to FirstStageDevInitialize?
 class BlockDevInitializer final {
   public:
     BlockDevInitializer();
@@ -32,11 +33,13 @@
     bool InitDmUser(const std::string& name);
     bool InitDevices(std::set<std::string> devices);
     bool InitDmDevice(const std::string& device);
+    bool InitPlatformDevice(const std::string& device);
 
   private:
     ListenerAction HandleUevent(const Uevent& uevent, std::set<std::string>* devices);
 
     bool InitMiscDevice(const std::string& name);
+    bool InitDevice(const std::string& syspath, const std::string& device);
 
     std::unique_ptr<DeviceHandler> device_handler_;
     UeventListener uevent_listener_;
diff --git a/init/check_builtins.cpp b/init/check_builtins.cpp
index 461ed22..9725458 100644
--- a/init/check_builtins.cpp
+++ b/init/check_builtins.cpp
@@ -28,9 +28,9 @@
 #include <android-base/parsedouble.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
+#include <property_info_parser/property_info_parser.h>
 
 #include "builtin_arguments.h"
-#include "host_init_verifier.h"
 #include "interface_utils.h"
 #include "property_type.h"
 #include "rlimit_parser.h"
@@ -39,6 +39,9 @@
 
 using android::base::ParseInt;
 using android::base::StartsWith;
+using android::properties::BuildTrie;
+using android::properties::PropertyInfoArea;
+using android::properties::PropertyInfoEntry;
 
 #define ReturnIfAnyArgsEmpty()     \
     for (const auto& arg : args) { \
@@ -50,6 +53,26 @@
 namespace android {
 namespace init {
 
+const PropertyInfoArea* property_info_area;
+
+Result<void> InitializeHostPropertyInfoArea(const std::vector<PropertyInfoEntry>& property_infos) {
+    static std::string serialized_contexts;
+    std::string trie_error;
+    if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
+                   &trie_error)) {
+        return Error() << "Unable to serialize property contexts: " << trie_error;
+    }
+
+    property_info_area = reinterpret_cast<const PropertyInfoArea*>(serialized_contexts.c_str());
+    return {};
+}
+
+static Result<void> check_stub(const BuiltinArguments& args) {
+    return {};
+}
+
+#include "generated_stub_builtin_function_map.h"
+
 Result<void> check_chown(const BuiltinArguments& args) {
     if (!args[1].empty()) {
         auto uid = DecodeUid(args[1]);
diff --git a/init/check_builtins.h b/init/check_builtins.h
index dc1b752..9b00a7c 100644
--- a/init/check_builtins.h
+++ b/init/check_builtins.h
@@ -19,6 +19,10 @@
 #include "builtin_arguments.h"
 #include "result.h"
 
+#include <vector>
+
+#include <property_info_serializer/property_info_serializer.h>
+
 namespace android {
 namespace init {
 
@@ -43,5 +47,8 @@
 Result<void> check_wait(const BuiltinArguments& args);
 Result<void> check_wait_for_prop(const BuiltinArguments& args);
 
+Result<void> InitializeHostPropertyInfoArea(
+        const std::vector<properties::PropertyInfoEntry>& property_infos);
+
 }  // namespace init
 }  // namespace android
diff --git a/init/devices.cpp b/init/devices.cpp
index e76786a..5560c20 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -25,6 +25,7 @@
 #include <filesystem>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <thread>
 
 #include <android-base/chrono_utils.h>
@@ -377,8 +378,8 @@
 
     if (FindPlatformDevice(uevent.path, &device)) {
         // Skip /devices/platform or /devices/ if present
-        static const std::string devices_platform_prefix = "/devices/platform/";
-        static const std::string devices_prefix = "/devices/";
+        static constexpr std::string_view devices_platform_prefix = "/devices/platform/";
+        static constexpr std::string_view devices_prefix = "/devices/";
 
         if (StartsWith(device, devices_platform_prefix)) {
             device = device.substr(devices_platform_prefix.length());
@@ -434,6 +435,7 @@
     if (ReadFileToString("/sys/class/block/" + uevent.device_name + "/queue/zoned", &model) &&
         !StartsWith(model, "none")) {
         links.emplace_back("/dev/block/by-name/zoned_device");
+        links.emplace_back("/dev/sys/block/by-name/zoned_device");
     }
 
     auto last_slash = uevent.path.rfind('/');
@@ -482,11 +484,21 @@
     // event.
     if (action == "add" || (action == "change" && StartsWith(devpath, "/dev/block/dm-"))) {
         for (const auto& link : links) {
+            std::string target;
+            if (StartsWith(link, "/dev/block/")) {
+                target = devpath;
+            } else if (StartsWith(link, "/dev/sys/block/")) {
+                target = "/sys/class/block/" + Basename(devpath);
+            } else {
+                LOG(ERROR) << "Unrecognized link type: " << link;
+                continue;
+            }
+
             if (!mkdir_recursive(Dirname(link), 0755)) {
                 PLOG(ERROR) << "Failed to create directory " << Dirname(link);
             }
 
-            if (symlink(devpath.c_str(), link.c_str())) {
+            if (symlink(target.c_str(), link.c_str())) {
                 if (errno != EEXIST) {
                     PLOG(ERROR) << "Failed to symlink " << devpath << " to " << link;
                 } else if (std::string link_path;
diff --git a/init/devices.h b/init/devices.h
index f9f4d79..6da1232 100644
--- a/init/devices.h
+++ b/init/devices.h
@@ -82,6 +82,7 @@
     enum DevnameSource {
         DEVNAME_UEVENT_DEVNAME,
         DEVNAME_UEVENT_DEVPATH,
+        DEVNAME_SYS_NAME,
     };
 
     Subsystem() {}
@@ -92,10 +93,18 @@
     // Returns the full path for a uevent of a device that is a member of this subsystem,
     // according to the rules parsed from ueventd.rc
     std::string ParseDevPath(const Uevent& uevent) const {
-        std::string devname = devname_source_ == DEVNAME_UEVENT_DEVNAME
-                                      ? uevent.device_name
-                                      : android::base::Basename(uevent.path);
-
+        std::string devname;
+        if (devname_source_ == DEVNAME_UEVENT_DEVNAME) {
+            devname = uevent.device_name;
+        } else if (devname_source_ == DEVNAME_UEVENT_DEVPATH) {
+            devname = android::base::Basename(uevent.path);
+        } else if (devname_source_ == DEVNAME_SYS_NAME) {
+            if (android::base::ReadFileToString("/sys/" + uevent.path + "/name", &devname)) {
+                devname.pop_back();  // Remove terminating newline
+            } else {
+                devname = uevent.device_name;
+            }
+        }
         return dir_name_ + "/" + devname;
     }
 
@@ -118,9 +127,6 @@
     virtual ~DeviceHandler() = default;
 
     void HandleUevent(const Uevent& uevent) override;
-    void ColdbootDone() override;
-
-    std::vector<std::string> GetBlockDeviceSymlinks(const Uevent& uevent) const;
 
     // `androidboot.partition_map` allows associating a partition name for a raw block device
     // through a comma separated and semicolon deliminated list. For example,
@@ -129,11 +135,13 @@
     static std::string GetPartitionNameForDevice(const std::string& device);
 
   private:
+    void ColdbootDone() override;
     bool FindPlatformDevice(std::string path, std::string* platform_device_path) const;
     std::tuple<mode_t, uid_t, gid_t> GetDevicePermissions(
         const std::string& path, const std::vector<std::string>& links) const;
     void MakeDevice(const std::string& path, bool block, int major, int minor,
                     const std::vector<std::string>& links) const;
+    std::vector<std::string> GetBlockDeviceSymlinks(const Uevent& uevent) const;
     void HandleDevice(const std::string& action, const std::string& devpath, bool block, int major,
                       int minor, const std::vector<std::string>& links) const;
     void FixupSysPermissions(const std::string& upath, const std::string& subsystem) const;
diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp
index 3c012fe..01957ef 100644
--- a/init/firmware_handler.cpp
+++ b/init/firmware_handler.cpp
@@ -264,8 +264,9 @@
     return uevent.firmware;
 }
 
-void FirmwareHandler::ProcessFirmwareEvent(const std::string& root,
+void FirmwareHandler::ProcessFirmwareEvent(const std::string& path,
                                            const std::string& firmware) const {
+    std::string root = "/sys" + path;
     std::string loading = root + "/loading";
     std::string data = root + "/data";
 
@@ -296,6 +297,7 @@
                                                     ", fstat failed: " + strerror(errno));
             return false;
         }
+        LOG(INFO) << "found " << file << " for " << path;
         LoadFirmware(firmware, root, fw_fd.get(), sb.st_size, loading_fd.get(), data_fd.get());
         return true;
     };
@@ -362,7 +364,7 @@
     if (pid == 0) {
         Timer t;
         auto firmware = GetFirmwarePath(uevent);
-        ProcessFirmwareEvent("/sys" + uevent.path, firmware);
+        ProcessFirmwareEvent(uevent.path, firmware);
         LOG(INFO) << "loading " << uevent.path << " took " << t;
         _exit(EXIT_SUCCESS);
     }
diff --git a/init/firmware_handler.h b/init/firmware_handler.h
index d2f7347..fceb392 100644
--- a/init/firmware_handler.h
+++ b/init/firmware_handler.h
@@ -57,7 +57,7 @@
     Result<std::string> RunExternalHandler(const std::string& handler, uid_t uid, gid_t gid,
                                            const Uevent& uevent) const;
     std::string GetFirmwarePath(const Uevent& uevent) const;
-    void ProcessFirmwareEvent(const std::string& root, const std::string& firmware) const;
+    void ProcessFirmwareEvent(const std::string& path, const std::string& firmware) const;
     bool ForEachFirmwareDirectory(std::function<bool(const std::string&)> handler) const;
 
     std::vector<std::string> firmware_directories_;
diff --git a/init/first_stage_console.cpp b/init/first_stage_console.cpp
index 67cac19..f6f9329 100644
--- a/init/first_stage_console.cpp
+++ b/init/first_stage_console.cpp
@@ -16,6 +16,7 @@
 
 #include "first_stage_console.h"
 
+#include <spawn.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
@@ -65,19 +66,20 @@
     return true;
 }
 
-static void RunScript() {
-    LOG(INFO) << "Attempting to run /first_stage.sh...";
-    pid_t pid = fork();
-    if (pid != 0) {
-        int status;
-        waitpid(pid, &status, 0);
-        LOG(INFO) << "/first_stage.sh exited with status " << status;
-        return;
-    }
-    const char* path = "/system/bin/sh";
-    const char* args[] = {path, "/first_stage.sh", nullptr};
-    int rv = execv(path, const_cast<char**>(args));
-    LOG(ERROR) << "unable to execv /first_stage.sh, returned " << rv << " errno " << errno;
+static pid_t SpawnImage(const char* file) {
+    const char* argv[] = {file, NULL};
+    const char* envp[] = {NULL};
+
+    char* const* argvp = const_cast<char* const*>(argv);
+    char* const* envpp = const_cast<char* const*>(envp);
+
+    pid_t pid;
+    errno = posix_spawn(&pid, argv[0], NULL, NULL, argvp, envpp);
+    if (!errno) return pid;
+
+    PLOG(ERROR) << "Failed to spawn '" << file << "'";
+
+    return (pid_t)0;
 }
 
 namespace android {
@@ -86,24 +88,28 @@
 void StartConsole(const std::string& cmdline) {
     bool console = KernelConsolePresent(cmdline);
     // Use a simple sigchld handler -- first_stage_console doesn't need to track or log zombies
-    const struct sigaction chld_act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDWAIT };
+    const struct sigaction chld_act {
+        .sa_flags = SA_NOCLDWAIT, .sa_handler = SIG_DFL
+    };
 
     sigaction(SIGCHLD, &chld_act, nullptr);
     pid_t pid = fork();
     if (pid != 0) {
-        int status;
-        waitpid(pid, &status, 0);
-        LOG(ERROR) << "console shell exited with status " << status;
+        wait(NULL);
+        LOG(ERROR) << "console shell exited";
         return;
     }
 
     if (console) console = SetupConsole();
-    RunScript();
+
+    LOG(INFO) << "Attempting to run /first_stage.sh...";
+    if (SpawnImage("/first_stage.sh")) {
+        wait(NULL);
+        LOG(INFO) << "/first_stage.sh exited";
+    }
+
     if (console) {
-        const char* path = "/system/bin/sh";
-        const char* args[] = {path, nullptr};
-        int rv = execv(path, const_cast<char**>(args));
-        LOG(ERROR) << "unable to execv, returned " << rv << " errno " << errno;
+        if (SpawnImage("/system/bin/sh")) wait(NULL);
     }
     _exit(127);
 }
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index c4d0f75..bfe636b 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -37,6 +37,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <android/avf_cc_flags.h>
 #include <modprobe/modprobe.h>
 #include <private/android_filesystem_config.h>
 
@@ -385,7 +386,12 @@
     // /second_stage_resources is used to preserve files from first to second
     // stage init
     CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
-                    "mode=0755,uid=0,gid=0"))
+                    "mode=0755,uid=0,gid=0"));
+
+    if (IsMicrodroid() && android::virtualization::IsOpenDiceChangesFlagEnabled()) {
+        CHECKCALL(mount("tmpfs", "/microdroid_resources", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
+                        "mode=0750,uid=0,gid=0"));
+    }
 #undef CHECKCALL
 
     SetStdioToDevNull(argv);
@@ -402,18 +408,6 @@
 
     LOG(INFO) << "init first stage started!";
 
-    // We only allow /vendor partition in debuggable Microdrod until it is verified during boot.
-    // TODO(b/285855436): remove this check.
-    if (IsMicrodroid()) {
-        bool mount_vendor =
-                cmdline.find("androidboot.microdroid.mount_vendor=1") != std::string::npos;
-        bool debuggable =
-                bootconfig.find("androidboot.microdroid.debuggable = \"1\"") != std::string::npos;
-        if (mount_vendor && !debuggable) {
-            LOG(FATAL) << "Attempted to mount /vendor partition for non-debuggable Microdroid VM";
-        }
-    }
-
     auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir};
     if (!old_root_dir) {
         PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk";
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index c0b9281..5d3a273 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -16,6 +16,7 @@
 
 #include "first_stage_mount.h"
 
+#include <signal.h>
 #include <stdlib.h>
 #include <sys/mount.h>
 #include <unistd.h>
@@ -33,6 +34,7 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android/avf_cc_flags.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr.h>
 #include <fs_mgr_dm_linear.h>
@@ -272,6 +274,11 @@
     return true;
 }
 
+// TODO: should this be in a library in packages/modules/Virtualization first_stage_init links?
+static bool IsMicrodroidStrictBoot() {
+    return access("/proc/device-tree/chosen/avf,strict-boot", F_OK) == 0;
+}
+
 bool FirstStageMountVBootV2::InitDevices() {
     std::set<std::string> devices;
     GetSuperDeviceName(&devices);
@@ -283,6 +290,14 @@
         return false;
     }
 
+    if (IsMicrodroid() && android::virtualization::IsOpenDiceChangesFlagEnabled()) {
+        if (IsMicrodroidStrictBoot()) {
+            if (!block_dev_init_.InitPlatformDevice("open-dice0")) {
+                return false;
+            }
+        }
+    }
+
     if (IsDmLinearEnabled()) {
         auto super_symlink = "/dev/block/by-name/"s + super_partition_name_;
         if (!android::base::Realpath(super_symlink, &super_path_)) {
@@ -381,9 +396,10 @@
     use_snapuserd_ = sm->IsSnapuserdRequired();
     if (use_snapuserd_) {
         if (sm->UpdateUsesUserSnapshots()) {
-            LaunchFirstStageSnapuserd(SnapshotDriver::DM_USER);
+            LaunchFirstStageSnapuserd();
         } else {
-            LaunchFirstStageSnapuserd(SnapshotDriver::DM_SNAPSHOT);
+            LOG(FATAL) << "legacy virtual-ab is no longer supported";
+            return false;
         }
     }
 
@@ -527,9 +543,48 @@
     return true;
 }
 
+static bool MaybeDeriveMicrodroidVendorDiceNode(Fstab* fstab) {
+    std::optional<std::string> microdroid_vendor_block_dev;
+    for (auto entry = fstab->begin(); entry != fstab->end(); entry++) {
+        if (entry->mount_point == "/vendor") {
+            microdroid_vendor_block_dev.emplace(entry->blk_device);
+            break;
+        }
+    }
+    if (!microdroid_vendor_block_dev.has_value()) {
+        LOG(VERBOSE) << "No microdroid vendor partition to mount";
+        return true;
+    }
+    // clang-format off
+    const std::array<const char*, 8> args = {
+        "/system/bin/derive_microdroid_vendor_dice_node",
+                "--dice-driver", "/dev/open-dice0",
+                "--microdroid-vendor-disk-image", microdroid_vendor_block_dev->data(),
+                "--output", "/microdroid_resources/dice_chain.raw", nullptr,
+    };
+    // clang-format-on
+    // ForkExecveAndWaitForCompletion calls waitpid to wait for the fork-ed process to finish.
+    // The first_stage_console adds SA_NOCLDWAIT flag to the SIGCHLD handler, which means that
+    // waitpid will always return -ECHLD. Here we re-register a default handler, so that waitpid
+    // works.
+    LOG(INFO) << "Deriving dice node for microdroid vendor partition";
+    signal(SIGCHLD, SIG_DFL);
+    if (!ForkExecveAndWaitForCompletion(args[0], (char**)args.data())) {
+        LOG(ERROR) << "Failed to derive microdroid vendor dice node";
+        return false;
+    }
+    return true;
+}
+
 bool FirstStageMountVBootV2::MountPartitions() {
     if (!TrySwitchSystemAsRoot()) return false;
 
+    if (IsMicrodroid() && android::virtualization::IsOpenDiceChangesFlagEnabled()) {
+        if (!MaybeDeriveMicrodroidVendorDiceNode(&fstab_)) {
+            return false;
+        }
+    }
+
     if (!SkipMountingPartitions(&fstab_, true /* verbose */)) return false;
 
     for (auto current = fstab_.begin(); current != fstab_.end();) {
diff --git a/init/fuzzer/Android.bp b/init/fuzzer/Android.bp
index 9916246..5823932 100644
--- a/init/fuzzer/Android.bp
+++ b/init/fuzzer/Android.bp
@@ -20,7 +20,6 @@
 cc_defaults {
     name: "libinit_fuzzer_defaults",
     static_libs: [
-        "libc++fs",
         "liblmkd_utils",
         "libmodprobe",
         "libprotobuf-cpp-lite",
diff --git a/init/host_init_stubs.h b/init/host_init_stubs.h
index 753ed6b..2fef9d3 100644
--- a/init/host_init_stubs.h
+++ b/init/host_init_stubs.h
@@ -32,6 +32,7 @@
 #define __ANDROID_API_S__ 31
 #define __ANDROID_API_T__ 33
 #define __ANDROID_API_U__ 34
+#define __ANDROID_API_V__ 35
 
 // sys/system_properties.h
 #define PROP_VALUE_MAX 92
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index 662185c..f746ab9 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -14,8 +14,6 @@
 // limitations under the License.
 //
 
-#include "host_init_verifier.h"
-
 #include <errno.h>
 #include <getopt.h>
 #include <pwd.h>
@@ -36,6 +34,7 @@
 #include <android-base/strings.h>
 #include <generated_android_ids.h>
 #include <hidl/metadata.h>
+#include <property_info_parser/property_info_parser.h>
 #include <property_info_serializer/property_info_serializer.h>
 
 #include "action.h"
@@ -57,9 +56,7 @@
 using android::base::ParseInt;
 using android::base::ReadFileToString;
 using android::base::Split;
-using android::properties::BuildTrie;
 using android::properties::ParsePropertyInfoFile;
-using android::properties::PropertyInfoArea;
 using android::properties::PropertyInfoEntry;
 
 static std::vector<std::string> passwd_files;
@@ -148,12 +145,6 @@
 namespace android {
 namespace init {
 
-static Result<void> check_stub(const BuiltinArguments& args) {
-    return {};
-}
-
-#include "generated_stub_builtin_function_map.h"
-
 void PrintUsage() {
     fprintf(stdout, R"(usage: host_init_verifier [options]
 
@@ -196,8 +187,6 @@
     return result;
 }
 
-const PropertyInfoArea* property_info_area;
-
 void HandlePropertyContexts(const std::string& filename,
                             std::vector<PropertyInfoEntry>* property_infos) {
     auto file_contents = std::string();
@@ -288,16 +277,11 @@
     }
     SetKnownInterfaces(*interface_inheritance_hierarchy_map);
 
-    std::string serialized_contexts;
-    std::string trie_error;
-    if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
-                   &trie_error)) {
-        LOG(ERROR) << "Unable to serialize property contexts: " << trie_error;
+    if (auto result = InitializeHostPropertyInfoArea(property_infos); !result.ok()) {
+        LOG(ERROR) << result.error();
         return EXIT_FAILURE;
     }
 
-    property_info_area = reinterpret_cast<const PropertyInfoArea*>(serialized_contexts.c_str());
-
     if (!partition_map.empty()) {
         std::vector<std::string> vendor_prefixes;
         for (const auto& partition : {"vendor", "odm"}) {
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 7e8513b..5088273 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -630,7 +630,7 @@
 
     ASSERT_TRUE(parser.ParseConfig(tf.path));
 
-    if (GetIntProperty("ro.vendor.api_level", 0) > __ANDROID_API_U__) {
+    if (GetIntProperty("ro.vendor.api_level", 0) > 202404) {
         ASSERT_EQ(1u, parser.parse_error_count());
     } else {
         ASSERT_EQ(0u, parser.parse_error_count());
diff --git a/init/persistent_properties.cpp b/init/persistent_properties.cpp
index 6f8a4de..1d17e3c 100644
--- a/init/persistent_properties.cpp
+++ b/init/persistent_properties.cpp
@@ -23,6 +23,7 @@
 #include <sys/types.h>
 
 #include <memory>
+#include <unordered_map>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -235,6 +236,9 @@
                            persistent_properties->mutable_properties()->end(),
                            [&name](const auto& record) { return record.name() == name; });
     if (it != persistent_properties->mutable_properties()->end()) {
+        if (it->value() == value) {
+            return;
+        }
         it->set_name(name);
         it->set_value(value);
     } else {
diff --git a/init/persistent_properties_test.cpp b/init/persistent_properties_test.cpp
index 5763050..97865d7 100644
--- a/init/persistent_properties_test.cpp
+++ b/init/persistent_properties_test.cpp
@@ -17,6 +17,9 @@
 #include "persistent_properties.h"
 
 #include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include <vector>
 
@@ -155,6 +158,31 @@
     EXPECT_FALSE(it == read_back_properties.properties().end());
 }
 
+TEST(persistent_properties, NopUpdateDoesntWriteFile) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    persistent_property_filename = tf.path;
+
+    auto last_modified = [&tf]() -> time_t {
+        struct stat buf;
+        EXPECT_EQ(fstat(tf.fd, &buf), 0);
+        return buf.st_mtime;
+    };
+
+    std::vector<std::pair<std::string, std::string>> persistent_properties = {
+            {"persist.sys.locale", "en-US"},
+            {"persist.sys.timezone", "America/Los_Angeles"},
+    };
+    ASSERT_RESULT_OK(
+            WritePersistentPropertyFile(VectorToPersistentProperties(persistent_properties)));
+
+    time_t t = last_modified();
+    sleep(2);
+    WritePersistentProperty("persist.sys.locale", "en-US");
+    // Ensure that the file was not modified
+    ASSERT_EQ(last_modified(), t);
+}
+
 TEST(persistent_properties, RejectNonPersistProperty) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 30ad800..0d6eb15 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -58,6 +58,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <fs_mgr.h>
+#include <private/android_filesystem_config.h>
 #include <property_info_parser/property_info_parser.h>
 #include <property_info_serializer/property_info_serializer.h>
 #include <selinux/android.h>
@@ -117,12 +118,13 @@
 
 static bool persistent_properties_loaded = false;
 
-static int property_set_fd = -1;
 static int from_init_socket = -1;
 static int init_socket = -1;
 static bool accept_messages = false;
 static std::mutex accept_messages_lock;
+static std::mutex selinux_check_access_lock;
 static std::thread property_service_thread;
+static std::thread property_service_for_system_thread;
 
 static std::unique_ptr<PersistWriteThread> persist_write_thread;
 
@@ -167,6 +169,7 @@
     ucred cr = {.pid = 0, .uid = 0, .gid = 0};
     audit_data.cr = &cr;
 
+    auto lock = std::lock_guard{selinux_check_access_lock};
     return selinux_check_access(source_context.c_str(), target_context, "file", "read",
                                 &audit_data) == 0;
 }
@@ -182,10 +185,9 @@
     audit_data.name = name.c_str();
     audit_data.cr = &cr;
 
-    bool has_access = (selinux_check_access(source_context, target_context, "property_service",
-                                            "set", &audit_data) == 0);
-
-    return has_access;
+    auto lock = std::lock_guard{selinux_check_access_lock};
+    return selinux_check_access(source_context, target_context, "property_service", "set",
+                                &audit_data) == 0;
 }
 
 void NotifyPropertyChange(const std::string& name, const std::string& value) {
@@ -400,30 +402,38 @@
         return {PROP_ERROR_INVALID_VALUE};
     }
 
-    prop_info* pi = (prop_info*)__system_property_find(name.c_str());
-    if (pi != nullptr) {
-        // ro.* properties are actually "write-once".
-        if (StartsWith(name, "ro.")) {
-            *error = "Read-only property was already set";
-            return {PROP_ERROR_READ_ONLY_PROPERTY};
-        }
-
-        __system_property_update(pi, value.c_str(), valuelen);
+    if (name == "sys.powerctl") {
+        // No action here - NotifyPropertyChange will trigger the appropriate action, and since this
+        // can come to the second thread, we mustn't call out to the __system_property_* functions
+        // which support multiple readers but only one mutator.
     } else {
-        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
-        if (rc < 0) {
-            *error = "__system_property_add failed";
-            return {PROP_ERROR_SET_FAILED};
-        }
-    }
+        prop_info* pi = (prop_info*)__system_property_find(name.c_str());
+        if (pi != nullptr) {
+            // ro.* properties are actually "write-once".
+            if (StartsWith(name, "ro.")) {
+                *error = "Read-only property was already set";
+                return {PROP_ERROR_READ_ONLY_PROPERTY};
+            }
 
-    bool need_persist = StartsWith(name, "persist.") || StartsWith(name, "next_boot.");
-    if (socket && persistent_properties_loaded && need_persist) {
-        if (persist_write_thread) {
-            persist_write_thread->Write(name, value, std::move(*socket));
-            return {};
+            __system_property_update(pi, value.c_str(), valuelen);
+        } else {
+            int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
+            if (rc < 0) {
+                *error = "__system_property_add failed";
+                return {PROP_ERROR_SET_FAILED};
+            }
         }
-        WritePersistentProperty(name, value);
+
+        // Don't write properties to disk until after we have read all default
+        // properties to prevent them from being overwritten by default values.
+        bool need_persist = StartsWith(name, "persist.") || StartsWith(name, "next_boot.");
+        if (socket && persistent_properties_loaded && need_persist) {
+            if (persist_write_thread) {
+                persist_write_thread->Write(name, value, std::move(*socket));
+                return {};
+            }
+            WritePersistentProperty(name, value);
+        }
     }
 
     NotifyPropertyChange(name, value);
@@ -443,6 +453,10 @@
                                    SocketConnection* socket, std::string* error) {
     auto lock = std::lock_guard{accept_messages_lock};
     if (!accept_messages) {
+        // If we're already shutting down and you're asking us to stop something,
+        // just say we did (https://issuetracker.google.com/336223505).
+        if (msg == "stop") return PROP_SUCCESS;
+
         *error = "Received control message after shutdown, ignoring";
         return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
     }
@@ -584,10 +598,10 @@
     return *ret;
 }
 
-static void handle_property_set_fd() {
+static void handle_property_set_fd(int fd) {
     static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
 
-    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
+    int s = accept4(fd, nullptr, nullptr, SOCK_CLOEXEC);
     if (s == -1) {
         return;
     }
@@ -960,6 +974,17 @@
     std::string build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
     build_fingerprint += '/';
     build_fingerprint += GetProperty("ro.product.name", UNKNOWN);
+
+    // should be set in /product/etc/build.prop
+    // when we have a dev option device, and we've switched the kernel to 16kb mode
+    // we use the same system image, but we've switched out the kernel, so make it
+    // visible at a high level
+    bool has16KbDevOption =
+            android::base::GetBoolProperty("ro.product.build.16k_page.enabled", false);
+    if (has16KbDevOption && getpagesize() == 16384) {
+        build_fingerprint += "_16kb";
+    }
+
     build_fingerprint += '/';
     build_fingerprint += GetProperty("ro.product.device", UNKNOWN);
     build_fingerprint += ':';
@@ -1090,6 +1115,12 @@
     // required to support.
     constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
 
+    if (__system_property_find(VENDOR_API_LEVEL_PROP) != nullptr) {
+        // The device already have ro.vendor.api_level in its vendor/build.prop.
+        // Skip initializing the ro.vendor.api_level property.
+        return;
+    }
+
     auto vendor_api_level = GetIntProperty("ro.board.first_api_level", __ANDROID_VENDOR_API_MAX__);
     if (vendor_api_level != __ANDROID_VENDOR_API_MAX__) {
         // Update the vendor_api_level with "ro.board.api_level" only if both "ro.board.api_level"
@@ -1104,7 +1135,8 @@
         product_first_api_level = GetIntProperty("ro.build.version.sdk", __ANDROID_API_FUTURE__);
     }
 
-    vendor_api_level = std::min(vendor_api_level_of(product_first_api_level), vendor_api_level);
+    vendor_api_level =
+            std::min(AVendorSupport_getVendorApiLevelOf(product_first_api_level), vendor_api_level);
 
     if (vendor_api_level < 0) {
         LOG(ERROR) << "Unexpected vendor api level for " << VENDOR_API_LEVEL_PROP << ". Check "
@@ -1291,12 +1323,14 @@
     }
     selinux_android_restorecon(PROP_TREE_FILE, 0);
 
+#ifdef WRITE_APPCOMPAT_OVERRIDE_SYSTEM_PROPERTIES
     mkdir(APPCOMPAT_OVERRIDE_PROP_FOLDERNAME, S_IRWXU | S_IXGRP | S_IXOTH);
     if (!WriteStringToFile(serialized_contexts, APPCOMPAT_OVERRIDE_PROP_TREE_FILE, 0444, 0, 0,
                            false)) {
-        PLOG(ERROR) << "Unable to write vendor overrides to file";
+        PLOG(ERROR) << "Unable to write appcompat override property infos to file";
     }
     selinux_android_restorecon(APPCOMPAT_OVERRIDE_PROP_TREE_FILE, 0);
+#endif
 }
 
 static void ExportKernelBootProps() {
@@ -1431,19 +1465,21 @@
     }
 }
 
-static void PropertyServiceThread() {
+static void PropertyServiceThread(int fd, bool listen_init) {
     Epoll epoll;
     if (auto result = epoll.Open(); !result.ok()) {
         LOG(FATAL) << result.error();
     }
 
-    if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd);
+    if (auto result = epoll.RegisterHandler(fd, std::bind(handle_property_set_fd, fd));
         !result.ok()) {
         LOG(FATAL) << result.error();
     }
 
-    if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result.ok()) {
-        LOG(FATAL) << result.error();
+    if (listen_init) {
+        if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result.ok()) {
+            LOG(FATAL) << result.error();
+        }
     }
 
     while (true) {
@@ -1492,6 +1528,23 @@
     cv_.notify_all();
 }
 
+void StartThread(const char* name, int mode, int gid, std::thread& t, bool listen_init) {
+    int fd = -1;
+    if (auto result = CreateSocket(name, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+                                   /*passcred=*/false, /*should_listen=*/false, mode, /*uid=*/0,
+                                   /*gid=*/gid, /*socketcon=*/{});
+        result.ok()) {
+        fd = *result;
+    } else {
+        LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
+    }
+
+    listen(fd, 8);
+
+    auto new_thread = std::thread(PropertyServiceThread, fd, listen_init);
+    t.swap(new_thread);
+}
+
 void StartPropertyService(int* epoll_socket) {
     InitPropertySet("ro.property_service.version", "2");
 
@@ -1503,19 +1556,9 @@
     init_socket = sockets[1];
     StartSendingMessages();
 
-    if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
-                                   /*passcred=*/false, /*should_listen=*/false, 0666, /*uid=*/0,
-                                   /*gid=*/0, /*socketcon=*/{});
-        result.ok()) {
-        property_set_fd = *result;
-    } else {
-        LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
-    }
-
-    listen(property_set_fd, 8);
-
-    auto new_thread = std::thread{PropertyServiceThread};
-    property_service_thread.swap(new_thread);
+    StartThread(PROP_SERVICE_FOR_SYSTEM_NAME, 0660, AID_SYSTEM, property_service_for_system_thread,
+                true);
+    StartThread(PROP_SERVICE_NAME, 0666, 0, property_service_thread, false);
 
     auto async_persist_writes =
             android::base::GetBoolProperty("ro.property_service.async_persist_writes", false);
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 1a26c4d..150f8f4 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -1083,7 +1083,8 @@
                         return;
                     }
                 }
-            } else if (reboot_target == "quiescent") {
+            } else if (std::find(cmd_params.begin(), cmd_params.end(), "quiescent")
+                    != cmd_params.end()) { // Quiescent can be either subreason or details.
                 bootloader_message boot = {};
                 if (std::string err; !read_bootloader_message(&boot, &err)) {
                     LOG(ERROR) << "Failed to read bootloader message: " << err;
diff --git a/init/security.cpp b/init/security.cpp
index 3e15447..a0d63bc 100644
--- a/init/security.cpp
+++ b/init/security.cpp
@@ -114,10 +114,9 @@
         return {};
     }
 #elif defined(__riscv)
-    // TODO: sv48 and sv57 have both been added to the kernel, but the kernel
-    // still doesn't support more than 24 bits.
-    // https://github.com/google/android-riscv64/issues/1
-    if (SetMmapRndBitsMin(24, 24, false)) {
+    // riscv64 supports 24 rnd bits with Sv39, and starting with the 6.9 kernel,
+    // 33 bits with Sv48 and Sv57.
+    if (SetMmapRndBitsMin(33, 24, false)) {
         return {};
     }
 #elif defined(__x86_64__)
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 1f211dd..c2d9b8d 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -66,6 +66,7 @@
 #include <android-base/result.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <android/avf_cc_flags.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr.h>
 #include <libgsi/libgsi.h>
@@ -111,97 +112,6 @@
     return true;
 }
 
-// Forks, executes the provided program in the child, and waits for the completion in the parent.
-// Child's stderr is captured and logged using LOG(ERROR).
-bool ForkExecveAndWaitForCompletion(const char* filename, char* const argv[]) {
-    // Create a pipe used for redirecting child process's output.
-    // * pipe_fds[0] is the FD the parent will use for reading.
-    // * pipe_fds[1] is the FD the child will use for writing.
-    int pipe_fds[2];
-    if (pipe(pipe_fds) == -1) {
-        PLOG(ERROR) << "Failed to create pipe";
-        return false;
-    }
-
-    pid_t child_pid = fork();
-    if (child_pid == -1) {
-        PLOG(ERROR) << "Failed to fork for " << filename;
-        return false;
-    }
-
-    if (child_pid == 0) {
-        // fork succeeded -- this is executing in the child process
-
-        // Close the pipe FD not used by this process
-        close(pipe_fds[0]);
-
-        // Redirect stderr to the pipe FD provided by the parent
-        if (TEMP_FAILURE_RETRY(dup2(pipe_fds[1], STDERR_FILENO)) == -1) {
-            PLOG(ERROR) << "Failed to redirect stderr of " << filename;
-            _exit(127);
-            return false;
-        }
-        close(pipe_fds[1]);
-
-        if (execv(filename, argv) == -1) {
-            PLOG(ERROR) << "Failed to execve " << filename;
-            return false;
-        }
-        // Unreachable because execve will have succeeded and replaced this code
-        // with child process's code.
-        _exit(127);
-        return false;
-    } else {
-        // fork succeeded -- this is executing in the original/parent process
-
-        // Close the pipe FD not used by this process
-        close(pipe_fds[1]);
-
-        // Log the redirected output of the child process.
-        // It's unfortunate that there's no standard way to obtain an istream for a file descriptor.
-        // As a result, we're buffering all output and logging it in one go at the end of the
-        // invocation, instead of logging it as it comes in.
-        const int child_out_fd = pipe_fds[0];
-        std::string child_output;
-        if (!android::base::ReadFdToString(child_out_fd, &child_output)) {
-            PLOG(ERROR) << "Failed to capture full output of " << filename;
-        }
-        close(child_out_fd);
-        if (!child_output.empty()) {
-            // Log captured output, line by line, because LOG expects to be invoked for each line
-            std::istringstream in(child_output);
-            std::string line;
-            while (std::getline(in, line)) {
-                LOG(ERROR) << filename << ": " << line;
-            }
-        }
-
-        // Wait for child to terminate
-        int status;
-        if (TEMP_FAILURE_RETRY(waitpid(child_pid, &status, 0)) != child_pid) {
-            PLOG(ERROR) << "Failed to wait for " << filename;
-            return false;
-        }
-
-        if (WIFEXITED(status)) {
-            int status_code = WEXITSTATUS(status);
-            if (status_code == 0) {
-                return true;
-            } else {
-                LOG(ERROR) << filename << " exited with status " << status_code;
-            }
-        } else if (WIFSIGNALED(status)) {
-            LOG(ERROR) << filename << " killed by signal " << WTERMSIG(status);
-        } else if (WIFSTOPPED(status)) {
-            LOG(ERROR) << filename << " stopped by signal " << WSTOPSIG(status);
-        } else {
-            LOG(ERROR) << "waitpid for " << filename << " returned unexpected status: " << status;
-        }
-
-        return false;
-    }
-}
-
 bool ReadFirstLine(const char* file, std::string* line) {
     line->clear();
 
@@ -793,6 +703,15 @@
 
     SelinuxSetEnforcement();
 
+    if (IsMicrodroid() && android::virtualization::IsOpenDiceChangesFlagEnabled()) {
+        // We run restorecon of /microdroid_resources while we are still in kernel context to avoid
+        // granting init `tmpfs:file relabelfrom` capability.
+        const int flags = SELINUX_ANDROID_RESTORECON_RECURSE;
+        if (selinux_android_restorecon("/microdroid_resources", flags) == -1) {
+            PLOG(FATAL) << "restorecon of /microdroid_resources failed";
+        }
+    }
+
     // We're in the kernel domain and want to transition to the init domain.  File systems that
     // store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here,
     // but other file systems do.  In particular, this is needed for ramdisks such as the
diff --git a/init/service.cpp b/init/service.cpp
index eb24dd5..31308a0 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -355,20 +355,35 @@
     // If we crash > 4 times in 'fatal_crash_window_' minutes or before boot_completed,
     // reboot into bootloader or set crashing property
     boot_clock::time_point now = boot_clock::now();
+    constexpr const char native_watchdog_reboot_time[] = "persist.init.svc.last_fatal_reboot_epoch";
+    uint64_t throttle_window =
+            std::chrono::duration_cast<std::chrono::seconds>(std::chrono::hours(24)).count();
     if (((flags_ & SVC_CRITICAL) || is_process_updatable) && !(flags_ & SVC_RESTART) &&
         !was_last_exit_ok_) {
         bool boot_completed = GetBoolProperty("sys.boot_completed", false);
         if (now < time_crashed_ + fatal_crash_window_ || !boot_completed) {
             if (++crash_count_ > 4) {
-                auto exit_reason = boot_completed ?
-                    "in " + std::to_string(fatal_crash_window_.count()) + " minutes" :
-                    "before boot completed";
+                auto exit_reason =
+                        boot_completed
+                                ? "in " + std::to_string(fatal_crash_window_.count()) + " minutes"
+                                : "before boot completed";
                 if (flags_ & SVC_CRITICAL) {
                     if (!GetBoolProperty("init.svc_debug.no_fatal." + name_, false)) {
-                        // Aborts into `fatal_reboot_target_'.
-                        SetFatalRebootTarget(fatal_reboot_target_);
-                        LOG(FATAL) << "critical process '" << name_ << "' exited 4 times "
-                                   << exit_reason;
+                        uint64_t epoch_time =
+                                std::chrono::duration_cast<std::chrono::seconds>(
+                                        std::chrono::system_clock::now().time_since_epoch())
+                                        .count();
+                        // Do not reboot again If it was already initiated in the last 24hrs
+                        if (epoch_time - GetIntProperty(native_watchdog_reboot_time, 0) >
+                            throttle_window) {
+                            SetProperty(native_watchdog_reboot_time, std::to_string(epoch_time));
+                            // Aborts into `fatal_reboot_target_'.
+                            SetFatalRebootTarget(fatal_reboot_target_);
+                            LOG(FATAL) << "critical process '" << name_ << "' exited 4 times "
+                                       << exit_reason;
+                        } else {
+                            LOG(INFO) << "Reboot already performed in last 24hrs because of crash.";
+                        }
                     }
                 } else {
                     LOG(ERROR) << "process with updatable components '" << name_
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 92e350b..6781c70 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -52,6 +52,18 @@
 namespace android {
 namespace init {
 
+#ifdef INIT_FULL_SOURCES
+// on full sources, we have better information on device to
+// make this decision
+constexpr bool kAlwaysErrorUserRoot = false;
+#else
+constexpr uint64_t kBuildShippingApiLevel = BUILD_SHIPPING_API_LEVEL + 0 /* +0 if empty */;
+// on partial sources, the host build, we don't have the specific
+// vendor API level, but we can enforce things based on the
+// shipping API level.
+constexpr bool kAlwaysErrorUserRoot = kBuildShippingApiLevel > __ANDROID_API_V__;
+#endif
+
 Result<void> ServiceParser::ParseCapabilities(std::vector<std::string>&& args) {
     service_->capabilities_ = 0;
 
@@ -680,12 +692,13 @@
     }
 
     if (service_->proc_attr_.parsed_uid == std::nullopt) {
-        if (android::base::GetIntProperty("ro.vendor.api_level", 0) > __ANDROID_API_U__) {
+        if (kAlwaysErrorUserRoot ||
+            android::base::GetIntProperty("ro.vendor.api_level", 0) > 202404) {
             return Error() << "No user specified for service '" << service_->name()
-                           << "'. Defaults to root.";
+                           << "', so it would have been root.";
         } else {
             LOG(WARNING) << "No user specified for service '" << service_->name()
-                         << "'. Defaults to root.";
+                         << "', so it is root.";
         }
     }
 
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
index 3a78343..9e3ff41 100644
--- a/init/snapuserd_transition.cpp
+++ b/init/snapuserd_transition.cpp
@@ -62,7 +62,7 @@
 static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0";
 static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0";
 
-void LaunchFirstStageSnapuserd(SnapshotDriver driver) {
+void LaunchFirstStageSnapuserd() {
     SocketDescriptor socket_desc;
     socket_desc.name = android::snapshot::kSnapuserdSocket;
     socket_desc.type = SOCK_STREAM;
@@ -85,22 +85,13 @@
     if (pid == 0) {
         socket->Publish();
 
-        if (driver == SnapshotDriver::DM_USER) {
-            char arg0[] = "/system/bin/snapuserd";
-            char arg1[] = "-user_snapshot";
-            char* const argv[] = {arg0, arg1, nullptr};
-            if (execv(arg0, argv) < 0) {
-                PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
-            }
-            _exit(127);
-        } else {
-            char arg0[] = "/system/bin/snapuserd";
-            char* const argv[] = {arg0, nullptr};
-            if (execv(arg0, argv) < 0) {
-                PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
-            }
-            _exit(127);
+        char arg0[] = "/system/bin/snapuserd";
+        char arg1[] = "-user_snapshot";
+        char* const argv[] = {arg0, arg1, nullptr};
+        if (execv(arg0, argv) < 0) {
+            PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
         }
+        _exit(127);
     }
 
     auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 10s);
@@ -195,22 +186,20 @@
             return;
         }
         auto start = reinterpret_cast<const void*>(map.start);
-        auto len = map.end - map.start;
+        uint64_t len = android::procinfo::MappedFileSize(map);
         if (!len) {
             return;
         }
+
         if (mlock(start, len) < 0) {
-            LOG(ERROR) << "mlock failed, " << start << " for " << len << " bytes.";
+            PLOG(ERROR) << "\"" << map.name << "\": mlock(" << start << ", " << len
+                        << ") failed: pgoff = " << map.pgoff;
             ok = false;
         }
     };
 
     if (!android::procinfo::ReadProcessMaps(getpid(), callback) || !ok) {
-        LOG(FATAL) << "Could not process /proc/" << getpid() << "/maps file for init, "
-                   << "falling back to mlockall().";
-        if (mlockall(MCL_CURRENT) < 0) {
-            LOG(FATAL) << "mlockall failed";
-        }
+        LOG(FATAL) << "Could not process /proc/" << getpid() << "/maps file for init";
     }
 }
 
diff --git a/init/snapuserd_transition.h b/init/snapuserd_transition.h
index 557d105..e86e8da 100644
--- a/init/snapuserd_transition.h
+++ b/init/snapuserd_transition.h
@@ -29,13 +29,8 @@
 namespace android {
 namespace init {
 
-enum class SnapshotDriver {
-    DM_SNAPSHOT,
-    DM_USER,
-};
-
 // Fork and exec a new copy of snapuserd.
-void LaunchFirstStageSnapuserd(SnapshotDriver driver);
+void LaunchFirstStageSnapuserd();
 
 class SnapuserdSelinuxHelper final {
     using SnapshotManager = android::snapshot::SnapshotManager;
diff --git a/init/test_kill_services/Android.bp b/init/test_kill_services/Android.bp
index 37361a8..ada87d8 100644
--- a/init/test_kill_services/Android.bp
+++ b/init/test_kill_services/Android.bp
@@ -10,7 +10,10 @@
 cc_test {
     name: "init_kill_services_test",
     srcs: ["init_kill_services_test.cpp"],
-    shared_libs: ["libbase"],
+    shared_libs: [
+        "libbase",
+        "libhidlbase",
+    ],
     test_suites: ["general-tests"],
 
     // TODO(b/153565474): switch back to auto-generation
diff --git a/init/test_kill_services/init_kill_services_test.cpp b/init/test_kill_services/init_kill_services_test.cpp
index 510ad8a..3af92bb 100644
--- a/init/test_kill_services/init_kill_services_test.cpp
+++ b/init/test_kill_services/init_kill_services_test.cpp
@@ -18,15 +18,20 @@
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <hidl/ServiceManagement.h>
 
 #include <iostream>
 
 using ::android::base::GetProperty;
 using ::android::base::SetProperty;
 using ::android::base::WaitForProperty;
+using ::android::hardware::isHidlSupported;
 using std::literals::chrono_literals::operator""s;
 
 void ExpectKillingServiceRecovers(const std::string& service_name) {
+    if (!isHidlSupported() && service_name == "hwservicemanager") {
+        GTEST_SKIP() << "No HIDL support on device so hwservicemanager will not be running";
+    }
     LOG(INFO) << "before we say hi to " << service_name << ", I can't have apexd around!";
 
     // b/280514080 - servicemanager will restart apexd, and apexd will restart the
diff --git a/init/test_upgrade_mte/AndroidTest.xml b/init/test_upgrade_mte/AndroidTest.xml
index b89cde8..e08afc0 100644
--- a/init/test_upgrade_mte/AndroidTest.xml
+++ b/init/test_upgrade_mte/AndroidTest.xml
@@ -20,11 +20,13 @@
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
       <option name="cleanup" value="true" />
       <option name="remount-system" value="true" />
-      <option name="push" value="mte_upgrade_test.rc->/system/etc/init/mte_upgrade_test.rc" />
-      <option name="push" value="mte_upgrade_test_helper->/system/bin/mte_upgrade_test_helper" />
-      <option name="push" value="mte_upgrade_test_helper->/data/local/tmp/app_process64" />
+
+      <option name="push-file" key="mte_upgrade_test.rc" value="/system/etc/init/mte_upgrade_test.rc" />
+      <option name="push-file" key="mte_upgrade_test_helper" value="/system/bin/mte_upgrade_test_helper" />
+      <option name="push-file" key="mte_upgrade_test_helper" value="/data/local/tmp/app_process64" />
+      <option name="post-push" value="chmod 644 /system/etc/init/mte_upgrade_test.rc" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="jar" value="mte_upgrade_test.jar" />
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/init/test_upgrade_mte/OWNERS b/init/test_upgrade_mte/OWNERS
new file mode 100644
index 0000000..79625df
--- /dev/null
+++ b/init/test_upgrade_mte/OWNERS
@@ -0,0 +1,5 @@
+fmayer@google.com
+
+eugenis@google.com
+mitchp@google.com
+pcc@google.com
diff --git a/init/test_upgrade_mte/mte_upgrade_test_helper.cpp b/init/test_upgrade_mte/mte_upgrade_test_helper.cpp
index 6728cc6..c4b175a 100644
--- a/init/test_upgrade_mte/mte_upgrade_test_helper.cpp
+++ b/init/test_upgrade_mte/mte_upgrade_test_helper.cpp
@@ -22,6 +22,7 @@
 #include <sys/prctl.h>
 #include <time.h>
 #include <unistd.h>
+
 #include <memory>
 
 int MaybeDowngrade() {
@@ -65,7 +66,5 @@
     // This binary gets run by src/com/android/tests/init/MteUpgradeTest.java, which
     // asserts that it crashes as expected.
     f[17] = 'x';
-    char buf[1];
-    read(1, buf, 1);
     return 0;
 }
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index d34672e..4395d88 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -218,6 +218,10 @@
         subsystem_.devname_source_ = Subsystem::DEVNAME_UEVENT_DEVPATH;
         return {};
     }
+    if (args[1] == "sys_name") {
+        subsystem_.devname_source_ = Subsystem::DEVNAME_SYS_NAME;
+        return {};
+    }
 
     return Error() << "invalid devname '" << args[1] << "'";
 }
diff --git a/init/util.cpp b/init/util.cpp
index e760a59..375e905 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -27,6 +27,7 @@
 #include <string.h>
 #include <sys/socket.h>
 #include <sys/un.h>
+#include <sys/wait.h>
 #include <time.h>
 #include <unistd.h>
 
@@ -569,6 +570,8 @@
             {"--recursive", SELINUX_ANDROID_RESTORECON_RECURSE},
             {"--skip-ce", SELINUX_ANDROID_RESTORECON_SKIPCE},
             {"--cross-filesystems", SELINUX_ANDROID_RESTORECON_CROSS_FILESYSTEMS},
+            {"--force", SELINUX_ANDROID_RESTORECON_FORCE},
+            {"--data-data", SELINUX_ANDROID_RESTORECON_DATADATA},
             {0, 0}};
 
     int flag = 0;
@@ -747,5 +750,96 @@
     return filtered_configs;
 }
 
+// Forks, executes the provided program in the child, and waits for the completion in the parent.
+// Child's stderr is captured and logged using LOG(ERROR).
+bool ForkExecveAndWaitForCompletion(const char* filename, char* const argv[]) {
+    // Create a pipe used for redirecting child process's output.
+    // * pipe_fds[0] is the FD the parent will use for reading.
+    // * pipe_fds[1] is the FD the child will use for writing.
+    int pipe_fds[2];
+    if (pipe(pipe_fds) == -1) {
+        PLOG(ERROR) << "Failed to create pipe";
+        return false;
+    }
+
+    pid_t child_pid = fork();
+    if (child_pid == -1) {
+        PLOG(ERROR) << "Failed to fork for " << filename;
+        return false;
+    }
+
+    if (child_pid == 0) {
+        // fork succeeded -- this is executing in the child process
+
+        // Close the pipe FD not used by this process
+        close(pipe_fds[0]);
+
+        // Redirect stderr to the pipe FD provided by the parent
+        if (TEMP_FAILURE_RETRY(dup2(pipe_fds[1], STDERR_FILENO)) == -1) {
+            PLOG(ERROR) << "Failed to redirect stderr of " << filename;
+            _exit(127);
+            return false;
+        }
+        close(pipe_fds[1]);
+
+        if (execv(filename, argv) == -1) {
+            PLOG(ERROR) << "Failed to execve " << filename;
+            return false;
+        }
+        // Unreachable because execve will have succeeded and replaced this code
+        // with child process's code.
+        _exit(127);
+        return false;
+    } else {
+        // fork succeeded -- this is executing in the original/parent process
+
+        // Close the pipe FD not used by this process
+        close(pipe_fds[1]);
+
+        // Log the redirected output of the child process.
+        // It's unfortunate that there's no standard way to obtain an istream for a file descriptor.
+        // As a result, we're buffering all output and logging it in one go at the end of the
+        // invocation, instead of logging it as it comes in.
+        const int child_out_fd = pipe_fds[0];
+        std::string child_output;
+        if (!android::base::ReadFdToString(child_out_fd, &child_output)) {
+            PLOG(ERROR) << "Failed to capture full output of " << filename;
+        }
+        close(child_out_fd);
+        if (!child_output.empty()) {
+            // Log captured output, line by line, because LOG expects to be invoked for each line
+            std::istringstream in(child_output);
+            std::string line;
+            while (std::getline(in, line)) {
+                LOG(ERROR) << filename << ": " << line;
+            }
+        }
+
+        // Wait for child to terminate
+        int status;
+        if (TEMP_FAILURE_RETRY(waitpid(child_pid, &status, 0)) != child_pid) {
+            PLOG(ERROR) << "Failed to wait for " << filename;
+            return false;
+        }
+
+        if (WIFEXITED(status)) {
+            int status_code = WEXITSTATUS(status);
+            if (status_code == 0) {
+                return true;
+            } else {
+                LOG(ERROR) << filename << " exited with status " << status_code;
+            }
+        } else if (WIFSIGNALED(status)) {
+            LOG(ERROR) << filename << " killed by signal " << WTERMSIG(status);
+        } else if (WIFSTOPPED(status)) {
+            LOG(ERROR) << filename << " stopped by signal " << WSTOPSIG(status);
+        } else {
+            LOG(ERROR) << "waitpid for " << filename << " returned unexpected status: " << status;
+        }
+
+        return false;
+    }
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/util.h b/init/util.h
index 2d02182..aa24123 100644
--- a/init/util.h
+++ b/init/util.h
@@ -117,5 +117,10 @@
 // (.rc == .0rc for ranking purposes)
 std::vector<std::string> FilterVersionedConfigs(const std::vector<std::string>& configs,
                                                   int active_sdk);
+
+// Forks, executes the provided program in the child, and waits for the completion in the parent.
+// Child's stderr is captured and logged using LOG(ERROR).
+bool ForkExecveAndWaitForCompletion(const char* filename, char* const argv[]);
+
 }  // namespace init
 }  // namespace android
diff --git a/janitors/OWNERS b/janitors/OWNERS
index a28737e..c25d9e4 100644
--- a/janitors/OWNERS
+++ b/janitors/OWNERS
@@ -3,4 +3,5 @@
 cferris@google.com
 dwillemsen@google.com
 enh@google.com
+maco@google.com
 sadafebrahimi@google.com
diff --git a/libcrypto_utils/Android.bp b/libcrypto_utils/Android.bp
index 2559137..b2af06c 100644
--- a/libcrypto_utils/Android.bp
+++ b/libcrypto_utils/Android.bp
@@ -24,9 +24,6 @@
     ramdisk_available: true,
     vendor_ramdisk_available: true,
     recovery_available: true,
-    vndk: {
-        enabled: true,
-    },
     host_supported: true,
     srcs: [
         "android_pubkey.cpp",
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 8ae7d9e..39cae1b 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -47,6 +47,8 @@
     defaults: ["libcutils_defaults"],
 
     export_include_dirs: ["include"],
+    header_libs: ["libprocessgroup_headers"],
+    export_header_lib_headers: ["libprocessgroup_headers"],
     target: {
         vendor: {
             override_export_include_dirs: ["include_outside_system"],
@@ -116,27 +118,6 @@
     },
 }
 
-cc_test {
-    name: "libcutils_sockets_test",
-    test_suites: ["device-tests"],
-    static_libs: ["libbase", "libcutils_sockets"],
-    cflags: [
-        "-Wall",
-        "-Wextra",
-        "-Werror",
-    ],
-
-    srcs: ["sockets_test.cpp"],
-    target: {
-        android: {
-            srcs: [
-                "android_get_control_file_test.cpp",
-                "android_get_control_socket_test.cpp",
-            ],
-        },
-    },
-}
-
 // some files must not be compiled when building against Mingw
 // they correspond to features not used by our host development tools
 // which are also hard or even impossible to port to native Win32
@@ -150,10 +131,7 @@
 cc_library {
     name: "libcutils",
     defaults: ["libcutils_defaults"],
-    vndk: {
-        enabled: true,
-        support_system_process: true,
-    },
+    double_loadable: true,
     srcs: [
         "config_utils.cpp",
         "iosched_policy.cpp",
@@ -347,7 +325,10 @@
 
 cc_test {
     name: "KernelLibcutilsTest",
-    test_suites: ["general-tests", "vts"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
     defaults: ["libcutils_test_static_defaults"],
     test_config: "KernelLibcutilsTest.xml",
 }
diff --git a/libcutils/abi-dumps/arm64/source-based/libcutils.so.lsdump b/libcutils/abi-dumps/arm64/source-based/libcutils.so.lsdump
index 333e61c..67c7514 100644
--- a/libcutils/abi-dumps/arm64/source-based/libcutils.so.lsdump
+++ b/libcutils/abi-dumps/arm64/source-based/libcutils.so.lsdump
@@ -290,6 +290,9 @@
    "name" : "fs_write_atomic_int"
   },
   {
+   "name" : "get_fs_config"
+  },
+  {
    "name" : "hashmapCreate"
   },
   {
@@ -1274,6 +1277,27 @@
    "source_file" : "system/core/libcutils/include/cutils/fs.h"
   },
   {
+   "function_name" : "get_fs_config",
+   "linker_set_key" : "get_fs_config",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIb"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIP9fs_config"
+    }
+   ],
+   "return_type" : "_ZTIb",
+   "source_file" : "system/core/libcutils/include/private/fs_config.h"
+  },
+  {
    "function_name" : "hashmapCreate",
    "linker_set_key" : "hashmapCreate",
    "parameters" :
@@ -2392,6 +2416,15 @@
   },
   {
    "alignment" : 8,
+   "linker_set_key" : "_ZTIP9fs_config",
+   "name" : "fs_config *",
+   "referenced_type" : "_ZTI9fs_config",
+   "self_type" : "_ZTIP9fs_config",
+   "size" : 8,
+   "source_file" : "system/core/libcutils/include/private/fs_config.h"
+  },
+  {
+   "alignment" : 8,
    "linker_set_key" : "_ZTIP9str_parms",
    "name" : "str_parms *",
    "referenced_type" : "_ZTI9str_parms",
@@ -2684,6 +2717,37 @@
    "self_type" : "_ZTI5cnode",
    "size" : 40,
    "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "alignment" : 8,
+   "fields" :
+   [
+    {
+     "field_name" : "uid",
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "field_name" : "gid",
+     "field_offset" : 32,
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "field_name" : "mode",
+     "field_offset" : 64,
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "field_name" : "capabilities",
+     "field_offset" : 128,
+     "referenced_type" : "_ZTIm"
+    }
+   ],
+   "linker_set_key" : "_ZTI9fs_config",
+   "name" : "fs_config",
+   "referenced_type" : "_ZTI9fs_config",
+   "self_type" : "_ZTI9fs_config",
+   "size" : 24,
+   "source_file" : "system/core/libcutils/include/private/fs_config.h"
   }
  ],
  "rvalue_reference_types" : []
diff --git a/libcutils/abi-dumps/arm_arm64/source-based/libcutils.so.lsdump b/libcutils/abi-dumps/arm_arm64/source-based/libcutils.so.lsdump
index f612fb9..f75240c 100644
--- a/libcutils/abi-dumps/arm_arm64/source-based/libcutils.so.lsdump
+++ b/libcutils/abi-dumps/arm_arm64/source-based/libcutils.so.lsdump
@@ -300,6 +300,9 @@
    "name" : "fs_write_atomic_int"
   },
   {
+   "name" : "get_fs_config"
+  },
+  {
    "name" : "hashmapCreate"
   },
   {
@@ -1284,6 +1287,27 @@
    "source_file" : "system/core/libcutils/include/cutils/fs.h"
   },
   {
+   "function_name" : "get_fs_config",
+   "linker_set_key" : "get_fs_config",
+   "parameters" :
+   [
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIb"
+    },
+    {
+     "referenced_type" : "_ZTIPKc"
+    },
+    {
+     "referenced_type" : "_ZTIP9fs_config"
+    }
+   ],
+   "return_type" : "_ZTIb",
+   "source_file" : "system/core/libcutils/include/private/fs_config.h"
+  },
+  {
    "function_name" : "hashmapCreate",
    "linker_set_key" : "hashmapCreate",
    "parameters" :
@@ -2402,6 +2426,15 @@
   },
   {
    "alignment" : 4,
+   "linker_set_key" : "_ZTIP9fs_config",
+   "name" : "fs_config *",
+   "referenced_type" : "_ZTI9fs_config",
+   "self_type" : "_ZTIP9fs_config",
+   "size" : 4,
+   "source_file" : "system/core/libcutils/include/private/fs_config.h"
+  },
+  {
+   "alignment" : 4,
    "linker_set_key" : "_ZTIP9str_parms",
    "name" : "str_parms *",
    "referenced_type" : "_ZTI9str_parms",
@@ -2694,6 +2727,37 @@
    "self_type" : "_ZTI5cnode",
    "size" : 20,
    "source_file" : "system/core/libcutils/include/cutils/config_utils.h"
+  },
+  {
+   "alignment" : 8,
+   "fields" :
+   [
+    {
+     "field_name" : "uid",
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "field_name" : "gid",
+     "field_offset" : 32,
+     "referenced_type" : "_ZTIj"
+    },
+    {
+     "field_name" : "mode",
+     "field_offset" : 64,
+     "referenced_type" : "_ZTIt"
+    },
+    {
+     "field_name" : "capabilities",
+     "field_offset" : 128,
+     "referenced_type" : "_ZTIy"
+    }
+   ],
+   "linker_set_key" : "_ZTI9fs_config",
+   "name" : "fs_config",
+   "referenced_type" : "_ZTI9fs_config",
+   "self_type" : "_ZTI9fs_config",
+   "size" : 24,
+   "source_file" : "system/core/libcutils/include/private/fs_config.h"
   }
  ],
  "rvalue_reference_types" : []
diff --git a/libcutils/ashmem-dev.cpp b/libcutils/ashmem-dev.cpp
index 410dbfd..46b8ef2 100644
--- a/libcutils/ashmem-dev.cpp
+++ b/libcutils/ashmem-dev.cpp
@@ -301,6 +301,12 @@
         return -1;
     }
 
+    // forbid size changes to match ashmem behaviour
+    if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) == -1) {
+        ALOGE("memfd_create(%s, %zd) F_ADD_SEALS failed: %m", name, size);
+        return -1;
+    }
+
     if (debug_log) {
         ALOGE("memfd_create(%s, %zd) success. fd=%d\n", name, size, fd.get());
     }
@@ -352,14 +358,29 @@
 }
 
 static int memfd_set_prot_region(int fd, int prot) {
-    /* Only proceed if an fd needs to be write-protected */
+    int seals = fcntl(fd, F_GET_SEALS);
+    if (seals == -1) {
+        ALOGE("memfd_set_prot_region(%d, %d): F_GET_SEALS failed: %s\n", fd, prot, strerror(errno));
+        return -1;
+    }
+
     if (prot & PROT_WRITE) {
+        /* Now we want the buffer to be read-write, let's check if the buffer
+         * has been previously marked as read-only before, if so return error
+         */
+        if (seals & F_SEAL_FUTURE_WRITE) {
+            ALOGE("memfd_set_prot_region(%d, %d): region is write protected\n", fd, prot);
+            errno = EINVAL;  // inline with ashmem error code, if already in
+                             // read-only mode
+            return -1;
+        }
         return 0;
     }
 
-    if (fcntl(fd, F_ADD_SEALS, F_SEAL_FUTURE_WRITE) == -1) {
-        ALOGE("memfd_set_prot_region(%d, %d): F_SEAL_FUTURE_WRITE seal failed: %s\n", fd, prot,
-              strerror(errno));
+    /* We would only allow read-only for any future file operations */
+    if (fcntl(fd, F_ADD_SEALS, F_SEAL_FUTURE_WRITE | F_SEAL_SEAL) == -1) {
+        ALOGE("memfd_set_prot_region(%d, %d): F_SEAL_FUTURE_WRITE | F_SEAL_SEAL seal failed: %s\n",
+              fd, prot, strerror(errno));
         return -1;
     }
 
diff --git a/libcutils/ashmem_test.cpp b/libcutils/ashmem_test.cpp
index d158427..571b410 100644
--- a/libcutils/ashmem_test.cpp
+++ b/libcutils/ashmem_test.cpp
@@ -22,6 +22,8 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <vector>
+
 #include <android-base/macros.h>
 #include <android-base/unique_fd.h>
 #include <cutils/ashmem.h>
@@ -61,16 +63,16 @@
     EXPECT_EQ(prot, ioctl(fd, ASHMEM_GET_PROT_MASK));
 }
 
-void FillData(uint8_t* data, size_t dataLen) {
-    for (size_t i = 0; i < dataLen; i++) {
+void FillData(std::vector<uint8_t>& data) {
+    for (size_t i = 0; i < data.size(); i++) {
         data[i] = i & 0xFF;
     }
 }
 
 TEST(AshmemTest, BasicTest) {
-    constexpr size_t size = PAGE_SIZE;
-    uint8_t data[size];
-    FillData(data, size);
+    const size_t size = getpagesize();
+    std::vector<uint8_t> data(size);
+    FillData(data);
 
     unique_fd fd;
     ASSERT_NO_FATAL_FAILURE(TestCreateRegion(size, fd, PROT_READ | PROT_WRITE));
@@ -78,21 +80,21 @@
     void* region1 = nullptr;
     ASSERT_NO_FATAL_FAILURE(TestMmap(fd, size, PROT_READ | PROT_WRITE, &region1));
 
-    memcpy(region1, &data, size);
-    ASSERT_EQ(0, memcmp(region1, &data, size));
+    memcpy(region1, data.data(), size);
+    ASSERT_EQ(0, memcmp(region1, data.data(), size));
 
     EXPECT_EQ(0, munmap(region1, size));
 
     void *region2;
     ASSERT_NO_FATAL_FAILURE(TestMmap(fd, size, PROT_READ, &region2));
-    ASSERT_EQ(0, memcmp(region2, &data, size));
+    ASSERT_EQ(0, memcmp(region2, data.data(), size));
     EXPECT_EQ(0, munmap(region2, size));
 }
 
 TEST(AshmemTest, ForkTest) {
-    constexpr size_t size = PAGE_SIZE;
-    uint8_t data[size];
-    FillData(data, size);
+    const size_t size = getpagesize();
+    std::vector<uint8_t> data(size);
+    FillData(data);
 
     unique_fd fd;
     ASSERT_NO_FATAL_FAILURE(TestCreateRegion(size, fd, PROT_READ | PROT_WRITE));
@@ -100,8 +102,8 @@
     void* region1 = nullptr;
     ASSERT_NO_FATAL_FAILURE(TestMmap(fd, size, PROT_READ | PROT_WRITE, &region1));
 
-    memcpy(region1, &data, size);
-    ASSERT_EQ(0, memcmp(region1, &data, size));
+    memcpy(region1, data.data(), size);
+    ASSERT_EQ(0, memcmp(region1, data.data(), size));
     EXPECT_EQ(0, munmap(region1, size));
 
     ASSERT_EXIT(
@@ -113,7 +115,7 @@
             if (region2 == MAP_FAILED) {
                 _exit(1);
             }
-            if (memcmp(region2, &data, size) != 0) {
+            if (memcmp(region2, data.data(), size) != 0) {
                 _exit(2);
             }
             memset(region2, 0, size);
@@ -122,10 +124,10 @@
         },
         ::testing::ExitedWithCode(0), "");
 
-    memset(&data, 0, size);
+    memset(data.data(), 0, size);
     void *region2;
     ASSERT_NO_FATAL_FAILURE(TestMmap(fd, size, PROT_READ | PROT_WRITE, &region2));
-    ASSERT_EQ(0, memcmp(region2, &data, size));
+    ASSERT_EQ(0, memcmp(region2, data.data(), size));
     EXPECT_EQ(0, munmap(region2, size));
 }
 
@@ -134,18 +136,19 @@
     void* region = nullptr;
 
     // Allocate a 4-page buffer, but leave page-sized holes on either side
-    constexpr size_t size = PAGE_SIZE * 4;
-    constexpr size_t dataSize = PAGE_SIZE * 2;
-    constexpr size_t holeSize = PAGE_SIZE;
+    const size_t pageSize = getpagesize();
+    const size_t size = pageSize * 4;
+    const size_t dataSize = pageSize * 2;
+    const size_t holeSize = pageSize;
     ASSERT_NO_FATAL_FAILURE(TestCreateRegion(size, fd, PROT_READ | PROT_WRITE));
     ASSERT_NO_FATAL_FAILURE(TestMmap(fd, dataSize, PROT_READ | PROT_WRITE, &region, holeSize));
 
-    uint8_t data[dataSize];
-    FillData(data, dataSize);
-    memcpy(region, data, dataSize);
+    std::vector<uint8_t> data(dataSize);
+    FillData(data);
+    memcpy(region, data.data(), dataSize);
 
-    constexpr off_t dataStart = holeSize;
-    constexpr off_t dataEnd = dataStart + dataSize;
+    const off_t dataStart = holeSize;
+    const off_t dataEnd = dataStart + dataSize;
 
     // The sequence of seeks below looks something like this:
     //
@@ -163,9 +166,12 @@
         // Expected lseek() return value
         off_t ret;
     } seeks[] = {
-        {99, SEEK_SET, 99},         {dataStart, SEEK_CUR, dataStart + 99},
-        {0, SEEK_DATA, dataStart},  {dataStart, SEEK_HOLE, dataEnd},
-        {-99, SEEK_END, size - 99}, {-dataStart, SEEK_CUR, dataEnd - 99},
+            {99, SEEK_SET, 99},
+            {dataStart, SEEK_CUR, dataStart + 99},
+            {0, SEEK_DATA, dataStart},
+            {dataStart, SEEK_HOLE, dataEnd},
+            {-99, SEEK_END, static_cast<off_t>(size) - 99},
+            {-dataStart, SEEK_CUR, dataEnd - 99},
     };
     for (const auto& cfg : seeks) {
         errno = 0;
@@ -180,7 +186,7 @@
             uint8_t buf[readSize];
 
             ASSERT_EQ(readSize, TEMP_FAILURE_RETRY(read(fd, buf, readSize)));
-            EXPECT_EQ(0, memcmp(buf, data + dataOff, readSize));
+            EXPECT_EQ(0, memcmp(buf, &data[dataOff], readSize));
         }
     }
 
@@ -189,7 +195,7 @@
 
 TEST(AshmemTest, ProtTest) {
     unique_fd fd;
-    constexpr size_t size = PAGE_SIZE;
+    const size_t size = getpagesize();
     void *region;
 
     ASSERT_NO_FATAL_FAILURE(TestCreateRegion(size, fd, PROT_READ));
@@ -217,7 +223,7 @@
 
 TEST(AshmemTest, ForkProtTest) {
     unique_fd fd;
-    constexpr size_t size = PAGE_SIZE;
+    const size_t size = getpagesize();
 
     int protFlags[] = { PROT_READ, PROT_WRITE };
     for (size_t i = 0; i < arraysize(protFlags); i++) {
@@ -238,9 +244,9 @@
 }
 
 TEST(AshmemTest, ForkMultiRegionTest) {
-    constexpr size_t size = PAGE_SIZE;
-    uint8_t data[size];
-    FillData(data, size);
+    const size_t size = getpagesize();
+    std::vector<uint8_t> data(size);
+    FillData(data);
 
     constexpr int nRegions = 16;
     unique_fd fd[nRegions];
@@ -248,8 +254,8 @@
         ASSERT_NO_FATAL_FAILURE(TestCreateRegion(size, fd[i], PROT_READ | PROT_WRITE));
         void* region = nullptr;
         ASSERT_NO_FATAL_FAILURE(TestMmap(fd[i], size, PROT_READ | PROT_WRITE, &region));
-        memcpy(region, &data, size);
-        ASSERT_EQ(0, memcmp(region, &data, size));
+        memcpy(region, data.data(), size);
+        ASSERT_EQ(0, memcmp(region, data.data(), size));
         EXPECT_EQ(0, munmap(region, size));
     }
 
@@ -262,7 +268,7 @@
             if (region == MAP_FAILED) {
                 _exit(1);
             }
-            if (memcmp(region, &data, size) != 0) {
+            if (memcmp(region, data.data(), size) != 0) {
                 munmap(region, size);
                 _exit(2);
             }
@@ -272,11 +278,11 @@
         _exit(0);
     }, ::testing::ExitedWithCode(0), "");
 
-    memset(&data, 0, size);
+    memset(data.data(), 0, size);
     for (int i = 0; i < nRegions; i++) {
         void *region;
         ASSERT_NO_FATAL_FAILURE(TestMmap(fd[i], size, PROT_READ | PROT_WRITE, &region));
-        ASSERT_EQ(0, memcmp(region, &data, size));
+        ASSERT_EQ(0, memcmp(region, data.data(), size));
         EXPECT_EQ(0, munmap(region, size));
     }
 }
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 919be2f..2e4b9b4 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -68,10 +68,6 @@
     { 01771, AID_SYSTEM,       AID_MISC,         0, "data/misc" },
     { 00775, AID_MEDIA_RW,     AID_MEDIA_RW,     0, "data/media/Music" },
     { 00775, AID_MEDIA_RW,     AID_MEDIA_RW,     0, "data/media" },
-    { 00750, AID_ROOT,         AID_SHELL,        0, "data/nativetest" },
-    { 00750, AID_ROOT,         AID_SHELL,        0, "data/nativetest64" },
-    { 00750, AID_ROOT,         AID_SHELL,        0, "data/benchmarktest" },
-    { 00750, AID_ROOT,         AID_SHELL,        0, "data/benchmarktest64" },
     { 00775, AID_ROOT,         AID_ROOT,         0, "data/preloads" },
     { 00771, AID_SYSTEM,       AID_SYSTEM,       0, "data" },
     { 00755, AID_ROOT,         AID_SYSTEM,       0, "mnt" },
@@ -91,7 +87,7 @@
     { 00751, AID_ROOT,         AID_SHELL,        0, "vendor/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "vendor/apex/*/bin" },
     { 00755, AID_ROOT,         AID_SHELL,        0, "vendor" },
-    { 00755, AID_ROOT,         AID_ROOT,         0, 0 },
+    {},
         // clang-format on
 };
 #ifndef __ANDROID_VNDK__
@@ -143,12 +139,6 @@
     { 00644, AID_SYSTEM,    AID_SYSTEM,    0, "data/app-private/*" },
     { 00644, AID_APP,       AID_APP,       0, "data/data/*" },
     { 00644, AID_MEDIA_RW,  AID_MEDIA_RW,  0, "data/media/*" },
-    { 00640, AID_ROOT,      AID_SHELL,     0, "data/nativetest/tests.txt" },
-    { 00640, AID_ROOT,      AID_SHELL,     0, "data/nativetest64/tests.txt" },
-    { 00750, AID_ROOT,      AID_SHELL,     0, "data/nativetest/*" },
-    { 00750, AID_ROOT,      AID_SHELL,     0, "data/nativetest64/*" },
-    { 00750, AID_ROOT,      AID_SHELL,     0, "data/benchmarktest/*" },
-    { 00750, AID_ROOT,      AID_SHELL,     0, "data/benchmarktest64/*" },
     { 00600, AID_ROOT,      AID_ROOT,      0, "default.prop" }, // legacy
     { 00600, AID_ROOT,      AID_ROOT,      0, "system/etc/prop.default" },
     { 00600, AID_ROOT,      AID_ROOT,      0, "odm/build.prop" }, // legacy; only for P release
@@ -217,18 +207,34 @@
     { 00755, AID_ROOT,      AID_ROOT,      0, "bin/*" },
     { 00640, AID_ROOT,      AID_SHELL,     0, "fstab.*" },
     { 00750, AID_ROOT,      AID_SHELL,     0, "init*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "*.rc" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "odm/bin/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "odm/framework/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "odm/app/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "odm/priv-app/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "product/bin/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "product/apex/*bin/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "product/framework/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "product/app/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "product/priv-app/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "system/bin/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "system/xbin/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "system/apex/*/bin/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "system/framework/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "system/app/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "system/priv-app/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "system_ext/bin/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "system_ext/apex/*/bin/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "system_ext/framework/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "system_ext/app/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "system_ext/priv-app/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "vendor/bin/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "vendor/apex/*bin/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "vendor/xbin/*" },
-    { 00644, AID_ROOT,      AID_ROOT,      0, 0 },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "vendor/framework/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "vendor/app/*" },
+    { 00644, AID_ROOT,      AID_ROOT,      0, "vendor/priv-app/*" },
+    {},
         // clang-format on
 };
 #ifndef __ANDROID_VNDK__
@@ -318,8 +324,8 @@
 auto __for_testing_only__fs_config_cmp = fs_config_cmp;
 #endif
 
-void fs_config(const char* path, int dir, const char* target_out_path, unsigned* uid, unsigned* gid,
-               unsigned* mode, uint64_t* capabilities) {
+bool get_fs_config(const char* path, bool dir, const char* target_out_path,
+                   struct fs_config* fs_conf) {
     const struct fs_path_config* pc;
     size_t which, plen;
 
@@ -362,11 +368,11 @@
             if (fs_config_cmp(dir, prefix, len, path, plen)) {
                 free(prefix);
                 close(fd);
-                *uid = header.uid;
-                *gid = header.gid;
-                *mode = (*mode & (~07777)) | header.mode;
-                *capabilities = header.capabilities;
-                return;
+                fs_conf->uid = header.uid;
+                fs_conf->gid = header.gid;
+                fs_conf->mode = header.mode;
+                fs_conf->capabilities = header.capabilities;
+                return true;
             }
             free(prefix);
         }
@@ -375,11 +381,28 @@
 
     for (pc = dir ? android_dirs : android_files; pc->prefix; pc++) {
         if (fs_config_cmp(dir, pc->prefix, strlen(pc->prefix), path, plen)) {
-            break;
+            fs_conf->uid = pc->uid;
+            fs_conf->gid = pc->gid;
+            fs_conf->mode = pc->mode;
+            fs_conf->capabilities = pc->capabilities;
+            return true;
         }
     }
-    *uid = pc->uid;
-    *gid = pc->gid;
-    *mode = (*mode & (~07777)) | pc->mode;
-    *capabilities = pc->capabilities;
+    return false;
+}
+
+void fs_config(const char* path, int dir, const char* target_out_path, unsigned* uid, unsigned* gid,
+               unsigned* mode, uint64_t* capabilities) {
+    struct fs_config conf;
+    if (get_fs_config(path, dir, target_out_path, &conf)) {
+        *uid = conf.uid;
+        *gid = conf.gid;
+        *mode = (*mode & S_IFMT) | conf.mode;
+        *capabilities = conf.capabilities;
+    } else {
+        *uid = AID_ROOT;
+        *gid = AID_ROOT;
+        *mode = (*mode & S_IFMT) | (dir ? 0755 : 0644);
+        *capabilities = 0;
+    }
 }
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 8c6e548..ea61cc2 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -170,6 +170,7 @@
 #define AID_WAKELOCK 3010     /* Allow system wakelock read/write access */
 #define AID_UHID 3011         /* Allow read/write to /dev/uhid node */
 #define AID_READTRACEFS 3012  /* Allow tracefs read */
+#define AID_VIRTUALMACHINE 3013 /* Allows VMs to tune for performance*/
 
 /* The range 5000-5999 is also reserved for vendor partition. */
 #define AID_OEM_RESERVED_2_START 5000
diff --git a/libcutils/include/private/fs_config.h b/libcutils/include/private/fs_config.h
index 45f46e5..9a727bc 100644
--- a/libcutils/include/private/fs_config.h
+++ b/libcutils/include/private/fs_config.h
@@ -21,8 +21,11 @@
 
 #pragma once
 
+#include <fcntl.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <sys/cdefs.h>
+#include <unistd.h>
 
 #include <linux/capability.h>
 
@@ -30,17 +33,27 @@
 
 __BEGIN_DECLS
 
-/*
- * Used in:
- *  build/tools/fs_config/fs_config.c
- *  build/tools/fs_get_stats/fs_get_stats.c
- *  system/extras/ext4_utils/make_ext4fs_main.c
- *  external/squashfs-tools/squashfs-tools/android.c
- *  system/core/cpio/mkbootfs.c
- *  system/core/adb/file_sync_service.cpp
- *  system/extras/ext4_utils/canned_fs_config.c
- */
+/* This API is deprecated. New users should call get_fs_config. */
 void fs_config(const char* path, int dir, const char* target_out_path, unsigned* uid, unsigned* gid,
                unsigned* mode, uint64_t* capabilities);
 
+struct fs_config {
+  uid_t uid;
+  gid_t gid;
+  mode_t mode;
+  uint64_t capabilities;
+};
+
+/*
+ * If a file system configuration was found for the specified path, store it to *conf.
+ * Returns whether a file system configuration was found.
+ *
+ * dir: Whether path refers to a directory.
+ * target_out_path: Path to the base directory to read the file system configuration from, or a null
+ * pointer to use the root directory as the base. Host code should pass $ANDROID_PRODUCT_OUT or
+ * equivalent, and device code should pass a null pointer.
+ */
+bool get_fs_config(const char* path, bool dir, const char* target_out_path,
+                   struct fs_config* conf);
+
 __END_DECLS
diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp
index 1d94a96..12906cc 100644
--- a/libmodprobe/Android.bp
+++ b/libmodprobe/Android.bp
@@ -10,6 +10,7 @@
     vendor_available: true,
     ramdisk_available: true,
     recovery_available: true,
+    vendor_ramdisk_available: true,
     host_supported: true,
     srcs: [
         "libmodprobe.cpp",
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index 5d79d6a..d7a90c4 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -41,6 +41,7 @@
                             std::vector<std::string>* post_dependencies);
     void ResetModuleCount() { module_count_ = 0; }
     int GetModuleCount() { return module_count_; }
+    bool IsBlocklisted(const std::string& module_name);
 
   private:
     std::string MakeCanonical(const std::string& module_path);
@@ -52,7 +53,6 @@
     void AddOption(const std::string& module_name, const std::string& option_name,
                    const std::string& value);
     std::string GetKernelCmdline();
-    bool IsBlocklisted(const std::string& module_name);
 
     bool ParseDepCallback(const std::string& base_path, const std::vector<std::string>& args);
     bool ParseAliasCallback(const std::vector<std::string>& args);
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 5023c79..1a40da1 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -484,7 +484,11 @@
                 return false;
             }
 
-            if (module_options_[cnd_last].find("load_sequential=1") != std::string::npos) {
+            std::string str = "load_sequential=1";
+            auto it = module_options_[cnd_last].find(str);
+            if (it != std::string::npos) {
+                module_options_[cnd_last].erase(it, it + str.size());
+
                 if (!LoadWithAliases(cnd_last, true)) {
                     return false;
                 }
diff --git a/libnetutils/Android.bp b/libnetutils/Android.bp
index 02bd2e3..0bca662 100644
--- a/libnetutils/Android.bp
+++ b/libnetutils/Android.bp
@@ -18,9 +18,6 @@
 cc_library_shared {
     name: "libnetutils",
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
 
     srcs: [
         "dhcpclient.c",
diff --git a/libpackagelistparser/include/packagelistparser/packagelistparser.h b/libpackagelistparser/include/packagelistparser/packagelistparser.h
index e89cb54..9bd212a 100644
--- a/libpackagelistparser/include/packagelistparser/packagelistparser.h
+++ b/libpackagelistparser/include/packagelistparser/packagelistparser.h
@@ -33,7 +33,10 @@
   /** Package name like "com.android.blah". */
   char* name;
 
-  /** Package uid like 10014. */
+  /**
+   * Package uid like 10014.
+   * Note that apexes and SDK libraries may have a bogus 0xffffffff value.
+   */
   uid_t uid;
 
   /** Package's AndroidManifest.xml debuggable flag. */
diff --git a/libpackagelistparser/packagelistparser.cpp b/libpackagelistparser/packagelistparser.cpp
index 59c3a74..638cc43 100644
--- a/libpackagelistparser/packagelistparser.cpp
+++ b/libpackagelistparser/packagelistparser.cpp
@@ -63,14 +63,13 @@
 }
 
 static bool parse_line(const char* path, size_t line_number, const char* line, pkg_info* info) {
-  unsigned long uid;
   int debuggable;
   char* gid_list;
   int profileable_from_shell = 0;
-
   int fields =
-      sscanf(line, "%ms %lu %d %ms %ms %ms %d %ld", &info->name, &uid, &debuggable, &info->data_dir,
-             &info->seinfo, &gid_list, &profileable_from_shell, &info->version_code);
+      sscanf(line, "%ms %u %d %ms %ms %ms %d %ld", &info->name, &info->uid,
+             &debuggable, &info->data_dir, &info->seinfo, &gid_list,
+             &profileable_from_shell, &info->version_code);
 
   // Handle the more complicated gids field and free the temporary string.
   bool gids_okay = parse_gids(path, line_number, gid_list, info);
@@ -84,14 +83,7 @@
     return false;
   }
 
-  // Extra validation.
-  if (uid > UID_MAX) {
-    ALOGE("%s:%zu: uid %lu > UID_MAX", path, line_number, uid);
-    return false;
-  }
-  info->uid = uid;
-
-  // Integer to bool conversions.
+  // Convert integers to bools.
   info->debuggable = debuggable;
   info->profileable_from_shell = profileable_from_shell;
 
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index c6a0737..33e00bc 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -2,15 +2,34 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_defaults {
-    name: "libprocessgroup_defaults",
-    cpp_std: "gnu++20",
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wexit-time-destructors",
-        "-Wno-unused-parameter",
+soong_config_module_type {
+    name: "libprocessgroup_flag_aware_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "memcg_v2_force_enabled",
+        "cgroup_v2_sys_app_isolation",
     ],
+    properties: [
+        "cflags",
+    ],
+}
+
+libprocessgroup_flag_aware_cc_defaults {
+    name: "libprocessgroup_build_flags_cc",
+    cpp_std: "gnu++20",
+    soong_config_variables: {
+        memcg_v2_force_enabled: {
+            cflags: [
+                "-DMEMCG_V2_FORCE_ENABLED=true",
+            ],
+        },
+        cgroup_v2_sys_app_isolation: {
+            cflags: [
+                "-DCGROUP_V2_SYS_APP_ISOLATION=true",
+            ],
+        },
+    },
 }
 
 cc_library_headers {
@@ -53,10 +72,7 @@
     recovery_available: true,
     vendor_available: true,
     product_available: true,
-    vndk: {
-        enabled: true,
-        support_system_process: true,
-    },
+    double_loadable: true,
     shared_libs: [
         "libbase",
         "libcgrouprc",
@@ -68,12 +84,13 @@
     header_libs: [
         "libcutils_headers",
         "libprocessgroup_headers",
+        "libprocessgroup_util",
     ],
     export_include_dirs: ["include"],
     export_header_lib_headers: [
         "libprocessgroup_headers",
     ],
-    defaults: ["libprocessgroup_defaults"],
+    defaults: ["libprocessgroup_build_flags_cc"],
     apex_available: [
         "//apex_available:platform",
         "//apex_available:anyapex",
@@ -84,7 +101,7 @@
 cc_test {
     name: "task_profiles_test",
     host_supported: true,
-    defaults: ["libprocessgroup_defaults"],
+    defaults: ["libprocessgroup_build_flags_cc"],
     srcs: [
         "task_profiles_test.cpp",
     ],
diff --git a/libprocessgroup/build_flags.h b/libprocessgroup/build_flags.h
new file mode 100644
index 0000000..bc3e7df
--- /dev/null
+++ b/libprocessgroup/build_flags.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifndef MEMCG_V2_FORCE_ENABLED
+#define MEMCG_V2_FORCE_ENABLED false
+#endif
+
+#ifndef CGROUP_V2_SYS_APP_ISOLATION
+#define CGROUP_V2_SYS_APP_ISOLATION false
+#endif
+
+namespace android::libprocessgroup_flags {
+
+inline consteval bool force_memcg_v2() {
+    return MEMCG_V2_FORCE_ENABLED;
+}
+
+inline consteval bool cgroup_v2_sys_app_isolation() {
+    return CGROUP_V2_SYS_APP_ISOLATION;
+}
+
+}  // namespace android::libprocessgroup_flags
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index ce7f10b..52b5afe 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -18,33 +18,20 @@
 #define LOG_TAG "libprocessgroup"
 
 #include <errno.h>
-#include <fcntl.h>
-#include <grp.h>
-#include <pwd.h>
-#include <sys/mman.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
 #include <unistd.h>
 
 #include <regex>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
-#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
 #include <cgroup_map.h>
-#include <json/reader.h>
-#include <json/value.h>
 #include <processgroup/processgroup.h>
+#include <processgroup/util.h>
 
-using android::base::GetBoolProperty;
 using android::base::StartsWith;
 using android::base::StringPrintf;
-using android::base::unique_fd;
 using android::base::WriteStringToFile;
 
 static constexpr const char* CGROUP_PROCS_FILE = "/cgroup.procs";
@@ -104,7 +91,7 @@
     return proc_path.append(CGROUP_PROCS_FILE);
 }
 
-bool CgroupController::GetTaskGroup(int tid, std::string* group) const {
+bool CgroupController::GetTaskGroup(pid_t tid, std::string* group) const {
     std::string file_name = StringPrintf("/proc/%d/cgroup", tid);
     std::string content;
     if (!android::base::ReadFileToString(file_name, &content)) {
@@ -230,7 +217,13 @@
         for (uint32_t i = 0; i < controller_count; ++i) {
             const ACgroupController* controller = ACgroupFile_getController(i);
             const uint32_t flags = ACgroupController_getFlags(controller);
-            if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+            uint32_t max_activation_depth = UINT32_MAX;
+            if (__builtin_available(android 36, *)) {
+                max_activation_depth = ACgroupController_getMaxActivationDepth(controller);
+            }
+            const int depth = util::GetCgroupDepth(ACgroupController_getPath(controller), path);
+
+            if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && depth < max_activation_depth) {
                 std::string str("+");
                 str.append(ACgroupController_getName(controller));
                 if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) {
diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h
index 5cdf8b2..31925d5 100644
--- a/libprocessgroup/cgroup_map.h
+++ b/libprocessgroup/cgroup_map.h
@@ -16,14 +16,9 @@
 
 #pragma once
 
-#include <sys/cdefs.h>
 #include <sys/types.h>
 
-#include <map>
-#include <memory>
-#include <mutex>
 #include <string>
-#include <vector>
 
 #include <android/cgrouprc.h>
 
@@ -32,7 +27,7 @@
   public:
     // Does not own controller
     explicit CgroupController(const ACgroupController* controller)
-        : controller_(controller), state_(UNKNOWN) {}
+        : controller_(controller) {}
 
     uint32_t version() const;
     const char* name() const;
@@ -43,7 +38,8 @@
 
     std::string GetTasksFilePath(const std::string& path) const;
     std::string GetProcsFilePath(const std::string& path, uid_t uid, pid_t pid) const;
-    bool GetTaskGroup(int tid, std::string* group) const;
+    bool GetTaskGroup(pid_t tid, std::string* group) const;
+
   private:
     enum ControllerState {
         UNKNOWN = 0,
@@ -52,7 +48,7 @@
     };
 
     const ACgroupController* controller_ = nullptr;
-    ControllerState state_;
+    ControllerState state_ = ControllerState::UNKNOWN;
 };
 
 class CgroupMap {
diff --git a/libprocessgroup/cgrouprc/cgroup_controller.cpp b/libprocessgroup/cgrouprc/cgroup_controller.cpp
index 5a326e5..889b3be 100644
--- a/libprocessgroup/cgrouprc/cgroup_controller.cpp
+++ b/libprocessgroup/cgrouprc/cgroup_controller.cpp
@@ -32,6 +32,11 @@
     return controller->flags();
 }
 
+uint32_t ACgroupController_getMaxActivationDepth(const ACgroupController* controller) {
+    CHECK(controller != nullptr);
+    return controller->max_activation_depth();
+}
+
 const char* ACgroupController_getName(const ACgroupController* controller) {
     CHECK(controller != nullptr);
     return controller->name();
diff --git a/libprocessgroup/cgrouprc/include/android/cgrouprc.h b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
index e704a36..3a57df5 100644
--- a/libprocessgroup/cgrouprc/include/android/cgrouprc.h
+++ b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
@@ -79,6 +79,14 @@
         const ACgroupController*) __INTRODUCED_IN(30);
 
 /**
+ * Returns the maximum activation depth of the given controller.
+ * Only applicable to cgroup v2 controllers.
+ * Returns UINT32_MAX if no maximum activation depth is set.
+ */
+__attribute__((warn_unused_result, weak)) uint32_t ACgroupController_getMaxActivationDepth(
+        const ACgroupController* controller) __INTRODUCED_IN(36);
+
+/**
  * Returns the name of the given controller.
  * If the given controller is null, return nullptr.
  */
diff --git a/libprocessgroup/cgrouprc/libcgrouprc.map.txt b/libprocessgroup/cgrouprc/libcgrouprc.map.txt
index b62b10f..30bd25f 100644
--- a/libprocessgroup/cgrouprc/libcgrouprc.map.txt
+++ b/libprocessgroup/cgrouprc/libcgrouprc.map.txt
@@ -16,3 +16,10 @@
   local:
     *;
 };
+
+LIBCGROUPRC_36 { # introduced=36
+  global:
+    ACgroupController_getMaxActivationDepth; # llndk=202504 systemapi
+  local:
+    *;
+};
diff --git a/libprocessgroup/cgrouprc_format/cgroup_controller.cpp b/libprocessgroup/cgrouprc_format/cgroup_controller.cpp
index 202b23e..0dd909a 100644
--- a/libprocessgroup/cgrouprc_format/cgroup_controller.cpp
+++ b/libprocessgroup/cgrouprc_format/cgroup_controller.cpp
@@ -20,19 +20,12 @@
 namespace cgrouprc {
 namespace format {
 
-CgroupController::CgroupController() : version_(0), flags_(0) {
-    memset(name_, 0, sizeof(name_));
-    memset(path_, 0, sizeof(path_));
-}
-
 CgroupController::CgroupController(uint32_t version, uint32_t flags, const std::string& name,
-                                   const std::string& path)
-    : CgroupController() {
+                                   const std::string& path, uint32_t max_activation_depth)
+    : version_(version), flags_(flags), max_activation_depth_(max_activation_depth) {
     // strlcpy isn't available on host. Although there is an implementation
     // in licutils, libcutils itself depends on libcgrouprc_format, causing
     // a circular dependency.
-    version_ = version;
-    flags_ = flags;
     strncpy(name_, name.c_str(), sizeof(name_) - 1);
     name_[sizeof(name_) - 1] = '\0';
     strncpy(path_, path.c_str(), sizeof(path_) - 1);
@@ -47,6 +40,10 @@
     return flags_;
 }
 
+uint32_t CgroupController::max_activation_depth() const {
+    return max_activation_depth_;
+}
+
 const char* CgroupController::name() const {
     return name_;
 }
diff --git a/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_controller.h b/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_controller.h
index 40d8548..c0c1f60 100644
--- a/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_controller.h
+++ b/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_controller.h
@@ -16,7 +16,8 @@
 
 #pragma once
 
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
 #include <string>
 
 namespace android {
@@ -26,12 +27,13 @@
 // Minimal controller description to be mmapped into process address space
 struct CgroupController {
   public:
-    CgroupController();
+    CgroupController() = default;
     CgroupController(uint32_t version, uint32_t flags, const std::string& name,
-                     const std::string& path);
+                     const std::string& path, uint32_t max_activation_depth);
 
     uint32_t version() const;
     uint32_t flags() const;
+    uint32_t max_activation_depth() const;
     const char* name() const;
     const char* path() const;
 
@@ -41,10 +43,11 @@
     static constexpr size_t CGROUP_NAME_BUF_SZ = 16;
     static constexpr size_t CGROUP_PATH_BUF_SZ = 32;
 
-    uint32_t version_;
-    uint32_t flags_;
-    char name_[CGROUP_NAME_BUF_SZ];
-    char path_[CGROUP_PATH_BUF_SZ];
+    uint32_t version_ = 0;
+    uint32_t flags_ = 0;
+    uint32_t max_activation_depth_ = UINT32_MAX;
+    char name_[CGROUP_NAME_BUF_SZ] = {};
+    char path_[CGROUP_PATH_BUF_SZ] = {};
 };
 
 }  // namespace format
diff --git a/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_file.h b/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_file.h
index f1678a1..2d9786f 100644
--- a/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_file.h
+++ b/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_file.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <cstdint>
+
 #include <processgroup/format/cgroup_controller.h>
 
 namespace android {
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index ca6868c..ffffeb4 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -33,19 +33,20 @@
 bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
 bool CgroupGetControllerFromPath(const std::string& path, std::string* cgroup_name);
 bool CgroupGetAttributePath(const std::string& attr_name, std::string* path);
-bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
+bool CgroupGetAttributePathForTask(const std::string& attr_name, pid_t tid, std::string* path);
 
-bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache = false);
+bool SetTaskProfiles(pid_t tid, const std::vector<std::string>& profiles,
+                     bool use_fd_cache = false);
 bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
 bool SetUserProfiles(uid_t uid, const std::vector<std::string>& profiles);
 
 __END_DECLS
 
-bool SetTaskProfiles(int tid, std::initializer_list<std::string_view> profiles,
+bool SetTaskProfiles(pid_t tid, std::initializer_list<std::string_view> profiles,
                      bool use_fd_cache = false);
 bool SetProcessProfiles(uid_t uid, pid_t pid, std::initializer_list<std::string_view> profiles);
 #if _LIBCPP_STD_VER > 17
-bool SetTaskProfiles(int tid, std::span<const std::string_view> profiles,
+bool SetTaskProfiles(pid_t tid, std::span<const std::string_view> profiles,
                      bool use_fd_cache = false);
 bool SetProcessProfiles(uid_t uid, pid_t pid, std::span<const std::string_view> profiles);
 #endif
@@ -67,35 +68,35 @@
 
 // Return 0 if all processes were killed and the cgroup was successfully removed.
 // Returns -1 in the case of an error occurring or if there are processes still running.
-int killProcessGroup(uid_t uid, int initialPid, int signal);
+int killProcessGroup(uid_t uid, pid_t initialPid, int signal);
 
 // Returns the same as killProcessGroup(), however it does not retry, which means
 // that it only returns 0 in the case that the cgroup exists and it contains no processes.
-int killProcessGroupOnce(uid_t uid, int initialPid, int signal);
+int killProcessGroupOnce(uid_t uid, pid_t initialPid, int signal);
 
 // Sends the provided signal to all members of a process group, but does not wait for processes to
 // exit, or for the cgroup to be removed. Callers should also ensure that killProcessGroup is called
 // later to ensure the cgroup is fully removed, otherwise system resources will leak.
 // Returns true if no errors are encountered sending signals, otherwise false.
-bool sendSignalToProcessGroup(uid_t uid, int initialPid, int signal);
+bool sendSignalToProcessGroup(uid_t uid, pid_t initialPid, int signal);
 
-int createProcessGroup(uid_t uid, int initialPid, bool memControl = false);
+int createProcessGroup(uid_t uid, pid_t initialPid, bool memControl = false);
 
 // Set various properties of a process group. For these functions to work, the process group must
 // have been created by passing memControl=true to createProcessGroup.
-bool setProcessGroupSwappiness(uid_t uid, int initialPid, int swappiness);
-bool setProcessGroupSoftLimit(uid_t uid, int initialPid, int64_t softLimitInBytes);
-bool setProcessGroupLimit(uid_t uid, int initialPid, int64_t limitInBytes);
+bool setProcessGroupSwappiness(uid_t uid, pid_t initialPid, int swappiness);
+bool setProcessGroupSoftLimit(uid_t uid, pid_t initialPid, int64_t softLimitInBytes);
+bool setProcessGroupLimit(uid_t uid, pid_t initialPid, int64_t limitInBytes);
 
 void removeAllEmptyProcessGroups(void);
 
 // Provides the path for an attribute in a specific process group
 // Returns false in case of error, true in case of success
-bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
+bool getAttributePathForTask(const std::string& attr_name, pid_t 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);
+bool isProfileValidForProcess(const std::string& profile_name, uid_t uid, pid_t pid);
 
 #endif // __ANDROID_VNDK__
 
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 3209adf..387c104 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -78,14 +78,6 @@
     return true;
 }
 
-static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
-    return StringPrintf("%s/uid_%u", cgroup, uid);
-}
-
-static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) {
-    return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid);
-}
-
 static bool CgroupKillAvailable() {
     static std::once_flag f;
     static bool cgroup_kill_available = false;
@@ -147,7 +139,7 @@
     return true;
 }
 
-bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
+bool CgroupGetAttributePathForTask(const std::string& attr_name, pid_t tid, std::string* path) {
     const TaskProfiles& tp = TaskProfiles::GetInstance();
     const IProfileAttribute* attr = tp.GetAttribute(attr_name);
 
@@ -198,17 +190,18 @@
             uid, pid, std::span<const std::string>(profiles), true);
 }
 
-bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
+bool SetTaskProfiles(pid_t tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
     return TaskProfiles::GetInstance().SetTaskProfiles(tid, std::span<const std::string>(profiles),
                                                        use_fd_cache);
 }
 
-bool SetTaskProfiles(int tid, std::initializer_list<std::string_view> profiles, bool use_fd_cache) {
+bool SetTaskProfiles(pid_t tid, std::initializer_list<std::string_view> profiles,
+                     bool use_fd_cache) {
     return TaskProfiles::GetInstance().SetTaskProfiles(
             tid, std::span<const std::string_view>(profiles), use_fd_cache);
 }
 
-bool SetTaskProfiles(int tid, std::span<const std::string_view> profiles, bool use_fd_cache) {
+bool SetTaskProfiles(pid_t tid, std::span<const std::string_view> profiles, bool use_fd_cache) {
     return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache);
 }
 
@@ -232,7 +225,7 @@
                                                        false);
 }
 
-static int RemoveCgroup(const char* cgroup, uid_t uid, int pid) {
+static int RemoveCgroup(const char* cgroup, uid_t uid, pid_t pid) {
     auto path = ConvertUidPidToPath(cgroup, uid, pid);
     int ret = TEMP_FAILURE_RETRY(rmdir(path.c_str()));
 
@@ -370,7 +363,7 @@
     return false;
 }
 
-bool sendSignalToProcessGroup(uid_t uid, int initialPid, int signal) {
+bool sendSignalToProcessGroup(uid_t uid, pid_t initialPid, int signal) {
     std::set<pid_t> pgids, pids;
 
     if (CgroupsAvailable()) {
@@ -525,10 +518,16 @@
 // implementation of this function. The default retry value was 40 for killing and 400 for cgroup
 // removal with 5ms sleeps between each retry.
 static int KillProcessGroup(
-        uid_t uid, int initialPid, int signal, bool once = false,
+        uid_t uid, pid_t initialPid, int signal, bool once = false,
         std::chrono::steady_clock::time_point until = std::chrono::steady_clock::now() + 2200ms) {
-    CHECK_GE(uid, 0);
-    CHECK_GT(initialPid, 0);
+    if (uid < 0) {
+        LOG(ERROR) << __func__ << ": invalid UID " << uid;
+        return -1;
+    }
+    if (initialPid <= 0) {
+        LOG(ERROR) << __func__ << ": invalid PID " << initialPid;
+        return -1;
+    }
 
     // Always attempt to send a kill signal to at least the initialPid, at least once, regardless of
     // whether its cgroup exists or not. This should only be necessary if a bug results in the
@@ -632,15 +631,15 @@
     return ret;
 }
 
-int killProcessGroup(uid_t uid, int initialPid, int signal) {
+int killProcessGroup(uid_t uid, pid_t initialPid, int signal) {
     return KillProcessGroup(uid, initialPid, signal);
 }
 
-int killProcessGroupOnce(uid_t uid, int initialPid, int signal) {
+int killProcessGroupOnce(uid_t uid, pid_t initialPid, int signal) {
     return KillProcessGroup(uid, initialPid, signal, true);
 }
 
-static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup,
+static int createProcessGroupInternal(uid_t uid, pid_t initialPid, std::string cgroup,
                                       bool activate_controllers) {
     auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
 
@@ -687,9 +686,15 @@
     return ret;
 }
 
-int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
-    CHECK_GE(uid, 0);
-    CHECK_GT(initialPid, 0);
+int createProcessGroup(uid_t uid, pid_t initialPid, bool memControl) {
+    if (uid < 0) {
+        LOG(ERROR) << __func__ << ": invalid UID " << uid;
+        return -1;
+    }
+    if (initialPid <= 0) {
+        LOG(ERROR) << __func__ << ": invalid PID " << initialPid;
+        return -1;
+    }
 
     if (memControl && !UsePerAppMemcg()) {
         LOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
@@ -712,7 +717,7 @@
     return createProcessGroupInternal(uid, initialPid, cgroup, true);
 }
 
-static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
+static bool SetProcessGroupValue(pid_t tid, const std::string& attr_name, int64_t value) {
     if (!isMemoryCgroupSupported()) {
         LOG(ERROR) << "Memcg is not mounted.";
         return false;
@@ -731,23 +736,23 @@
     return true;
 }
 
-bool setProcessGroupSwappiness(uid_t, int pid, int swappiness) {
+bool setProcessGroupSwappiness(uid_t, pid_t pid, int swappiness) {
     return SetProcessGroupValue(pid, "MemSwappiness", swappiness);
 }
 
-bool setProcessGroupSoftLimit(uid_t, int pid, int64_t soft_limit_in_bytes) {
+bool setProcessGroupSoftLimit(uid_t, pid_t pid, int64_t soft_limit_in_bytes) {
     return SetProcessGroupValue(pid, "MemSoftLimit", soft_limit_in_bytes);
 }
 
-bool setProcessGroupLimit(uid_t, int pid, int64_t limit_in_bytes) {
+bool setProcessGroupLimit(uid_t, pid_t pid, int64_t limit_in_bytes) {
     return SetProcessGroupValue(pid, "MemLimit", limit_in_bytes);
 }
 
-bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
+bool getAttributePathForTask(const std::string& attr_name, pid_t tid, std::string* path) {
     return CgroupGetAttributePathForTask(attr_name, tid, path);
 }
 
-bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid) {
+bool isProfileValidForProcess(const std::string& profile_name, uid_t uid, pid_t pid) {
     const TaskProfile* tp = TaskProfiles::GetInstance().GetProfile(profile_name);
 
     if (tp == nullptr) {
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index d013ec8..3e4393d 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -1,6 +1,13 @@
 {
   "Cgroups": [
     {
+      "Controller": "blkio",
+      "Path": "/dev/blkio",
+      "Mode": "0775",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
       "Controller": "cpu",
       "Path": "/dev/cpuctl",
       "Mode": "0755",
@@ -32,12 +39,6 @@
       {
         "Controller": "freezer",
         "Path": "."
-      },
-      {
-        "Controller": "io",
-        "Path": ".",
-        "NeedsActivation": true,
-        "Optional": true
       }
     ]
   }
diff --git a/libprocessgroup/profiles/cgroups.proto b/libprocessgroup/profiles/cgroups.proto
index f2de345..d2fd472 100644
--- a/libprocessgroup/profiles/cgroups.proto
+++ b/libprocessgroup/profiles/cgroups.proto
@@ -24,7 +24,7 @@
     Cgroups2 cgroups2 = 2 [json_name = "Cgroups2"];
 }
 
-// Next: 8
+// Next: 9
 message Cgroup {
     string controller = 1 [json_name = "Controller"];
     string path = 2 [json_name = "Path"];
@@ -36,6 +36,7 @@
 // https://developers.google.com/protocol-buffers/docs/proto3#default
     bool needs_activation = 6 [json_name = "NeedsActivation"];
     bool is_optional = 7 [json_name = "Optional"];
+    uint32 max_activation_depth = 8 [json_name = "MaxActivationDepth"];
 }
 
 // Next: 6
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index f2ef316..1fc66ba 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -76,26 +76,6 @@
       "Name": "FreezerState",
       "Controller": "freezer",
       "File": "cgroup.freeze"
-    },
-    {
-      "Name": "BfqWeight",
-      "Controller": "io",
-      "File": "io.bfq.weight"
-    },
-    {
-      "Name": "CfqGroupIdle",
-      "Controller": "io",
-      "File": "io.group_idle"
-    },
-    {
-      "Name": "CfqWeight",
-      "Controller": "io",
-      "File": "io.weight"
-    },
-    {
-      "Name": "IoPrioClass",
-      "Controller": "io",
-      "File": "io.prio.class"
     }
   ],
 
@@ -459,39 +439,11 @@
       "Name": "LowIoPriority",
       "Actions": [
         {
-          "Name": "SetAttribute",
+          "Name": "JoinCgroup",
           "Params":
           {
-            "Name": "BfqWeight",
-            "Value": "10",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqGroupIdle",
-            "Value": "0",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqWeight",
-            "Value": "200",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "IoPrioClass",
-            "Value": "restrict-to-be",
-            "Optional": "true"
+            "Controller": "blkio",
+            "Path": "background"
           }
         }
       ]
@@ -500,39 +452,11 @@
       "Name": "NormalIoPriority",
       "Actions": [
         {
-          "Name": "SetAttribute",
+          "Name": "JoinCgroup",
           "Params":
           {
-            "Name": "BfqWeight",
-            "Value": "100",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqGroupIdle",
-            "Value": "0",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqWeight",
-            "Value": "1000",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "IoPrioClass",
-            "Value": "restrict-to-be",
-            "Optional": "true"
+            "Controller": "blkio",
+            "Path": ""
           }
         }
       ]
@@ -541,39 +465,11 @@
       "Name": "HighIoPriority",
       "Actions": [
         {
-          "Name": "SetAttribute",
+          "Name": "JoinCgroup",
           "Params":
           {
-            "Name": "BfqWeight",
-            "Value": "100",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqGroupIdle",
-            "Value": "0",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqWeight",
-            "Value": "1000",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "IoPrioClass",
-            "Value": "promote-to-rt",
-            "Optional": "true"
+            "Controller": "blkio",
+            "Path": ""
           }
         }
       ]
@@ -582,39 +478,11 @@
       "Name": "MaxIoPriority",
       "Actions": [
         {
-          "Name": "SetAttribute",
+          "Name": "JoinCgroup",
           "Params":
           {
-            "Name": "BfqWeight",
-            "Value": "100",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqGroupIdle",
-            "Value": "0",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqWeight",
-            "Value": "1000",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "IoPrioClass",
-            "Value": "promote-to-rt",
-            "Optional": "true"
+            "Controller": "blkio",
+            "Path": ""
           }
         }
       ]
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index 169b1d3..0f2640a 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -19,6 +19,7 @@
 #define LOG_TAG "SchedPolicy"
 
 #include <errno.h>
+#include <fcntl.h>
 #include <unistd.h>
 
 #include <android-base/logging.h>
@@ -38,7 +39,7 @@
 
 #if defined(__ANDROID__)
 
-int set_cpuset_policy(int tid, SchedPolicy policy) {
+int set_cpuset_policy(pid_t tid, SchedPolicy policy) {
     if (tid == 0) {
         tid = GetThreadId();
     }
@@ -64,7 +65,7 @@
     return 0;
 }
 
-int set_sched_policy(int tid, SchedPolicy policy) {
+int set_sched_policy(pid_t tid, SchedPolicy policy) {
     if (tid == 0) {
         tid = GetThreadId();
     }
@@ -154,7 +155,7 @@
     return enabled;
 }
 
-static int getCGroupSubsys(int tid, const char* subsys, std::string& subgroup) {
+static int getCGroupSubsys(pid_t tid, const char* subsys, std::string& subgroup) {
     auto controller = CgroupMap::GetInstance().FindController(subsys);
 
     if (!controller.IsUsable()) return -1;
@@ -185,7 +186,7 @@
     return 0;
 }
 
-int get_sched_policy(int tid, SchedPolicy* policy) {
+int get_sched_policy(pid_t tid, SchedPolicy* policy) {
     if (tid == 0) {
         tid = GetThreadId();
     }
diff --git a/libprocessgroup/setup/Android.bp b/libprocessgroup/setup/Android.bp
index ea6c247..76f0a11 100644
--- a/libprocessgroup/setup/Android.bp
+++ b/libprocessgroup/setup/Android.bp
@@ -37,12 +37,10 @@
     ],
     header_libs: [
         "libprocessgroup_headers",
+        "libprocessgroup_util",
     ],
     export_header_lib_headers: [
         "libprocessgroup_headers",
     ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
+    defaults: ["libprocessgroup_build_flags_cc"],
 }
diff --git a/libprocessgroup/setup/cgroup_descriptor.h b/libprocessgroup/setup/cgroup_descriptor.h
index 699c03c..06ce186 100644
--- a/libprocessgroup/setup/cgroup_descriptor.h
+++ b/libprocessgroup/setup/cgroup_descriptor.h
@@ -16,6 +16,11 @@
 
 #pragma once
 
+#include <cstdint>
+#include <string>
+
+#include <sys/stat.h>
+
 #include <processgroup/format/cgroup_controller.h>
 
 namespace android {
@@ -25,7 +30,8 @@
 class CgroupDescriptor {
   public:
     CgroupDescriptor(uint32_t version, const std::string& name, const std::string& path,
-                     mode_t mode, const std::string& uid, const std::string& gid, uint32_t flags);
+                     mode_t mode, const std::string& uid, const std::string& gid, uint32_t flags,
+                     uint32_t max_activation_depth);
 
     const format::CgroupController* controller() const { return &controller_; }
     mode_t mode() const { return mode_; }
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 4e44c91..bd41874 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -29,7 +29,7 @@
 #include <time.h>
 #include <unistd.h>
 
-#include <regex>
+#include <optional>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -42,7 +42,9 @@
 #include <processgroup/format/cgroup_file.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/setup.h>
+#include <processgroup/util.h>
 
+#include "../build_flags.h"
 #include "cgroup_descriptor.h"
 
 using android::base::GetUintProperty;
@@ -57,6 +59,8 @@
 
 static constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";
 
+static const std::string CGROUP_V2_ROOT_DEFAULT = "/sys/fs/cgroup";
+
 static bool ChangeDirModeAndOwner(const std::string& path, mode_t mode, const std::string& uid,
                                   const std::string& gid, bool permissive_mode = false) {
     uid_t pw_uid = -1;
@@ -170,9 +174,15 @@
         controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
     }
 
+    uint32_t max_activation_depth = UINT32_MAX;
+    if (cgroup.isMember("MaxActivationDepth")) {
+        max_activation_depth = cgroup["MaxActivationDepth"].asUInt();
+    }
+
     CgroupDescriptor descriptor(
             cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8),
-            cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags);
+            cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags,
+            max_activation_depth);
 
     auto iter = descriptors->find(name);
     if (iter == descriptors->end()) {
@@ -182,6 +192,8 @@
     }
 }
 
+static const bool force_memcg_v2 = android::libprocessgroup_flags::force_memcg_v2();
+
 static bool ReadDescriptorsFromFile(const std::string& file_name,
                                     std::map<std::string, CgroupDescriptor>* descriptors) {
     std::vector<CgroupDescriptor> result;
@@ -205,22 +217,41 @@
         const Json::Value& cgroups = root["Cgroups"];
         for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
             std::string name = cgroups[i]["Controller"].asString();
+
+            if (force_memcg_v2 && name == "memory") continue;
+
             MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1);
         }
     }
 
+    bool memcgv2_present = false;
+    std::string root_path;
     if (root.isMember("Cgroups2")) {
         const Json::Value& cgroups2 = root["Cgroups2"];
-        std::string root_path = cgroups2["Path"].asString();
+        root_path = cgroups2["Path"].asString();
         MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME, "", 2);
 
         const Json::Value& childGroups = cgroups2["Controllers"];
         for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
             std::string name = childGroups[i]["Controller"].asString();
+
+            if (force_memcg_v2 && name == "memory") memcgv2_present = true;
+
             MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2);
         }
     }
 
+    if (force_memcg_v2 && !memcgv2_present) {
+        LOG(INFO) << "Forcing memcg to v2 hierarchy";
+        Json::Value memcgv2;
+        memcgv2["Controller"] = "memory";
+        memcgv2["NeedsActivation"] = true;
+        memcgv2["Path"] = ".";
+        memcgv2["Optional"] = true;  // In case of cgroup_disabled=memory, so we can still boot
+        MergeCgroupToDescriptors(descriptors, memcgv2, "memory",
+                                 root_path.empty() ? CGROUP_V2_ROOT_DEFAULT : root_path, 2);
+    }
+
     return true;
 }
 
@@ -300,7 +331,8 @@
         return false;
     }
 
-    if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+    if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION &&
+        controller->max_activation_depth() > 0) {
         std::string str = "+";
         str += controller->name();
         std::string path = controller->path();
@@ -308,7 +340,8 @@
 
         if (!base::WriteStringToFile(str, path)) {
             if (IsOptionalController(controller)) {
-                PLOG(INFO) << "Failed to activate optional controller " << controller->name();
+                PLOG(INFO) << "Failed to activate optional controller " << controller->name()
+                           << " at " << path;
                 return true;
             }
             PLOG(ERROR) << "Failed to activate controller " << controller->name();
@@ -408,8 +441,12 @@
 
 CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name,
                                    const std::string& path, mode_t mode, const std::string& uid,
-                                   const std::string& gid, uint32_t flags = 0)
-    : controller_(version, flags, name, path), mode_(mode), uid_(uid), gid_(gid) {}
+                                   const std::string& gid, uint32_t flags,
+                                   uint32_t max_activation_depth)
+    : controller_(version, flags, name, path, max_activation_depth),
+      mode_(mode),
+      uid_(uid),
+      gid_(gid) {}
 
 void CgroupDescriptor::set_mounted(bool mounted) {
     uint32_t flags = controller_.flags();
@@ -424,6 +461,79 @@
 }  // namespace cgrouprc
 }  // namespace android
 
+static std::optional<bool> MGLRUDisabled() {
+    const std::string file_name = "/sys/kernel/mm/lru_gen/enabled";
+    std::string content;
+    if (!android::base::ReadFileToString(file_name, &content)) {
+        PLOG(ERROR) << "Failed to read MGLRU state from " << file_name;
+        return {};
+    }
+
+    return content == "0x0000";
+}
+
+static std::optional<bool> MEMCGDisabled(
+        const std::map<std::string, android::cgrouprc::CgroupDescriptor>& descriptors) {
+    std::string cgroup_v2_root = android::cgrouprc::CGROUP_V2_ROOT_DEFAULT;
+    const auto it = descriptors.find(CGROUPV2_HIERARCHY_NAME);
+    if (it == descriptors.end()) {
+        LOG(WARNING) << "No Cgroups2 path found in cgroups.json. Vendor has modified Android, and "
+                     << "kernel memory use will be higher than intended.";
+    } else if (it->second.controller()->path() != cgroup_v2_root) {
+        cgroup_v2_root = it->second.controller()->path();
+    }
+
+    const std::string file_name = cgroup_v2_root + "/cgroup.controllers";
+    std::string content;
+    if (!android::base::ReadFileToString(file_name, &content)) {
+        PLOG(ERROR) << "Failed to read cgroup controllers from " << file_name;
+        return {};
+    }
+
+    // If we've forced memcg to v2 and it's not available, then it could only have been disabled
+    // on the kernel command line (GKI sets CONFIG_MEMCG).
+    return content.find("memory") == std::string::npos;
+}
+
+static bool CreateV2SubHierarchy(
+        const std::string& path,
+        const std::map<std::string, android::cgrouprc::CgroupDescriptor>& descriptors) {
+    using namespace android::cgrouprc;
+
+    const auto cgv2_iter = descriptors.find(CGROUPV2_HIERARCHY_NAME);
+    if (cgv2_iter == descriptors.end()) return false;
+    const android::cgrouprc::CgroupDescriptor cgv2_descriptor = cgv2_iter->second;
+
+    if (!Mkdir(path, cgv2_descriptor.mode(), cgv2_descriptor.uid(), cgv2_descriptor.gid())) {
+        PLOG(ERROR) << "Failed to create directory for " << path;
+        return false;
+    }
+
+    // Activate all v2 controllers in path so they can be activated in
+    // children as they are created.
+    for (const auto& [name, descriptor] : descriptors) {
+        const format::CgroupController* controller = descriptor.controller();
+        std::uint32_t flags = controller->flags();
+        std::uint32_t max_activation_depth = controller->max_activation_depth();
+        const int depth = util::GetCgroupDepth(controller->path(), path);
+
+        if (controller->version() == 2 && name != CGROUPV2_HIERARCHY_NAME &&
+            flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && depth < max_activation_depth) {
+            std::string str("+");
+            str += controller->name();
+            if (!android::base::WriteStringToFile(str, path + "/cgroup.subtree_control")) {
+                if (flags & CGROUPRC_CONTROLLER_FLAG_OPTIONAL) {
+                    PLOG(WARNING) << "Activation of cgroup controller " << str << " failed in path "
+                                  << path;
+                } else {
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
 bool CgroupSetup() {
     using namespace android::cgrouprc;
 
@@ -457,6 +567,32 @@
         }
     }
 
+    if (force_memcg_v2) {
+        if (MGLRUDisabled().value_or(false)) {
+            LOG(WARNING) << "Memcg forced to v2 hierarchy with MGLRU disabled! "
+                         << "Global reclaim performance will suffer.";
+        }
+        if (MEMCGDisabled(descriptors).value_or(false)) {
+            LOG(WARNING) << "Memcg forced to v2 hierarchy while memcg is disabled by kernel "
+                         << "command line!";
+        }
+    }
+
+    // System / app isolation.
+    // This really belongs in early-init in init.rc, but we cannot use the flag there.
+    if (android::libprocessgroup_flags::cgroup_v2_sys_app_isolation()) {
+        const auto it = descriptors.find(CGROUPV2_HIERARCHY_NAME);
+        const std::string cgroup_v2_root = (it == descriptors.end())
+                                                   ? CGROUP_V2_ROOT_DEFAULT
+                                                   : it->second.controller()->path();
+
+        LOG(INFO) << "Using system/app isolation under: " << cgroup_v2_root;
+        if (!CreateV2SubHierarchy(cgroup_v2_root + "/apps", descriptors) ||
+            !CreateV2SubHierarchy(cgroup_v2_root + "/system", descriptors)) {
+            return false;
+        }
+    }
+
     // mkdir <CGROUPS_RC_DIR> 0711 system system
     if (!Mkdir(android::base::Dirname(CGROUPS_RC_PATH), 0711, "system", "system")) {
         LOG(ERROR) << "Failed to create directory for " << CGROUPS_RC_PATH << " file";
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index d5bd47c..4870548 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -17,7 +17,9 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "libprocessgroup"
 
+#include <dirent.h>
 #include <fcntl.h>
+#include <unistd.h>
 #include <task_profiles.h>
 #include <string>
 
@@ -33,6 +35,8 @@
 #include <json/reader.h>
 #include <json/value.h>
 
+#include <build_flags.h>
+
 // To avoid issues in sdk_mac build
 #if defined(__ANDROID__)
 #include <sys/prctl.h>
@@ -126,17 +130,35 @@
     file_v2_name_ = file_v2_name;
 }
 
+static bool isSystemApp(uid_t uid) {
+    return uid < AID_APP_START;
+}
+
+std::string ConvertUidToPath(const char* root_cgroup_path, uid_t uid) {
+    if (android::libprocessgroup_flags::cgroup_v2_sys_app_isolation()) {
+        if (isSystemApp(uid))
+            return StringPrintf("%s/system/uid_%u", root_cgroup_path, uid);
+        else
+            return StringPrintf("%s/apps/uid_%u", root_cgroup_path, uid);
+    }
+    return StringPrintf("%s/uid_%u", root_cgroup_path, uid);
+}
+
+std::string ConvertUidPidToPath(const char* root_cgroup_path, uid_t uid, pid_t pid) {
+    const std::string uid_path = ConvertUidToPath(root_cgroup_path, uid);
+    return StringPrintf("%s/pid_%d", uid_path.c_str(), pid);
+}
+
 bool ProfileAttribute::GetPathForProcess(uid_t uid, pid_t pid, std::string* path) const {
     if (controller()->version() == 2) {
-        // all cgroup v2 attributes use the same process group hierarchy
-        *path = StringPrintf("%s/uid_%u/pid_%d/%s", controller()->path(), uid, pid,
-                             file_name().c_str());
+        const std::string cgroup_path = ConvertUidPidToPath(controller()->path(), uid, pid);
+        *path = cgroup_path + "/" + file_name();
         return true;
     }
     return GetPathForTask(pid, path);
 }
 
-bool ProfileAttribute::GetPathForTask(int tid, std::string* path) const {
+bool ProfileAttribute::GetPathForTask(pid_t tid, std::string* path) const {
     std::string subgroup;
     if (!controller()->GetTaskGroup(tid, &subgroup)) {
         return false;
@@ -155,12 +177,14 @@
     return true;
 }
 
+// NOTE: This function is for cgroup v2 only
 bool ProfileAttribute::GetPathForUID(uid_t uid, std::string* path) const {
     if (path == nullptr) {
         return true;
     }
 
-    *path = StringPrintf("%s/uid_%u/%s", controller()->path(), uid, file_name().c_str());
+    const std::string cgroup_path = ConvertUidToPath(controller()->path(), uid);
+    *path = cgroup_path + "/" + file_name();
     return true;
 }
 
@@ -179,13 +203,13 @@
 // To avoid issues in sdk_mac build
 #if defined(__ANDROID__)
 
-bool SetTimerSlackAction::IsTimerSlackSupported(int tid) {
+bool SetTimerSlackAction::IsTimerSlackSupported(pid_t tid) {
     auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
 
     return (access(file.c_str(), W_OK) == 0);
 }
 
-bool SetTimerSlackAction::ExecuteForTask(int tid) const {
+bool SetTimerSlackAction::ExecuteForTask(pid_t tid) const {
     static bool sys_supports_timerslack = IsTimerSlackSupported(tid);
 
     // v4.6+ kernels support the /proc/<tid>/timerslack_ns interface.
@@ -250,7 +274,7 @@
     return WriteValueToFile(path);
 }
 
-bool SetAttributeAction::ExecuteForTask(int tid) const {
+bool SetAttributeAction::ExecuteForTask(pid_t tid) const {
     std::string path;
 
     if (!attribute_->GetPathForTask(tid, &path)) {
@@ -288,7 +312,7 @@
     return IsValidForTask(pid);
 }
 
-bool SetAttributeAction::IsValidForTask(int tid) const {
+bool SetAttributeAction::IsValidForTask(pid_t tid) const {
     std::string path;
 
     if (!attribute_->GetPathForTask(tid, &path)) {
@@ -316,7 +340,7 @@
     FdCacheHelper::Init(controller_.GetProcsFilePath(path_, 0, 0), fd_[ProfileAction::RCT_PROCESS]);
 }
 
-bool SetCgroupAction::AddTidToCgroup(int tid, int fd, ResourceCacheType cache_type) const {
+bool SetCgroupAction::AddTidToCgroup(pid_t tid, int fd, ResourceCacheType cache_type) const {
     if (tid <= 0) {
         return true;
     }
@@ -401,7 +425,7 @@
     return true;
 }
 
-bool SetCgroupAction::ExecuteForTask(int tid) const {
+bool SetCgroupAction::ExecuteForTask(pid_t tid) const {
     CacheUseResult result = UseCachedFd(ProfileAction::RCT_TASK, tid);
     if (result != ProfileAction::UNUSED) {
         return result == ProfileAction::SUCCESS;
@@ -489,7 +513,7 @@
 }
 
 bool WriteFileAction::WriteValueToFile(const std::string& value_, ResourceCacheType cache_type,
-                                       int uid, int pid, bool logfailures) const {
+                                       uid_t uid, pid_t pid, bool logfailures) const {
     std::string value(value_);
 
     value = StringReplace(value, "<uid>", std::to_string(uid), true);
@@ -564,7 +588,7 @@
     DIR* d;
     struct dirent* de;
     char proc_path[255];
-    int t_pid;
+    pid_t t_pid;
 
     sprintf(proc_path, "/proc/%d/task", pid);
     if (!(d = opendir(proc_path))) {
@@ -590,7 +614,7 @@
     return true;
 }
 
-bool WriteFileAction::ExecuteForTask(int tid) const {
+bool WriteFileAction::ExecuteForTask(pid_t tid) const {
     return WriteValueToFile(value_, ProfileAction::RCT_TASK, getuid(), tid, logfailures_);
 }
 
@@ -655,7 +679,7 @@
     return true;
 }
 
-bool ApplyProfileAction::ExecuteForTask(int tid) const {
+bool ApplyProfileAction::ExecuteForTask(pid_t tid) const {
     for (const auto& profile : profiles_) {
         profile->ExecuteForTask(tid);
     }
@@ -683,7 +707,7 @@
     return true;
 }
 
-bool ApplyProfileAction::IsValidForTask(int tid) const {
+bool ApplyProfileAction::IsValidForTask(pid_t tid) const {
     for (const auto& profile : profiles_) {
         if (!profile->IsValidForTask(tid)) {
             return false;
@@ -707,7 +731,7 @@
     return true;
 }
 
-bool TaskProfile::ExecuteForTask(int tid) const {
+bool TaskProfile::ExecuteForTask(pid_t tid) const {
     if (tid == 0) {
         tid = GetThreadId();
     }
@@ -761,7 +785,7 @@
     return true;
 }
 
-bool TaskProfile::IsValidForTask(int tid) const {
+bool TaskProfile::IsValidForTask(pid_t tid) const {
     for (const auto& element : elements_) {
         if (!element->IsValidForTask(tid)) return false;
     }
@@ -1043,7 +1067,7 @@
 }
 
 template <typename T>
-bool TaskProfiles::SetTaskProfiles(int tid, std::span<const T> profiles, bool use_fd_cache) {
+bool TaskProfiles::SetTaskProfiles(pid_t tid, std::span<const T> profiles, bool use_fd_cache) {
     bool success = true;
     for (const auto& name : profiles) {
         TaskProfile* profile = GetProfile(name);
@@ -1069,9 +1093,9 @@
 template bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
                                                std::span<const std::string_view> profiles,
                                                bool use_fd_cache);
-template bool TaskProfiles::SetTaskProfiles(int tid, std::span<const std::string> profiles,
+template bool TaskProfiles::SetTaskProfiles(pid_t tid, std::span<const std::string> profiles,
                                             bool use_fd_cache);
-template bool TaskProfiles::SetTaskProfiles(int tid, std::span<const std::string_view> profiles,
+template bool TaskProfiles::SetTaskProfiles(pid_t tid, std::span<const std::string_view> profiles,
                                             bool use_fd_cache);
 template bool TaskProfiles::SetUserProfiles(uid_t uid, std::span<const std::string> profiles,
                                             bool use_fd_cache);
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 16ffe63..184e9e3 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -16,10 +16,10 @@
 
 #pragma once
 
-#include <sys/cdefs.h>
 #include <sys/types.h>
-#include <functional>
+
 #include <map>
+#include <memory>
 #include <mutex>
 #include <span>
 #include <string>
@@ -37,7 +37,7 @@
     virtual const CgroupController* controller() const = 0;
     virtual const std::string& file_name() const = 0;
     virtual bool GetPathForProcess(uid_t uid, pid_t pid, std::string* path) const = 0;
-    virtual bool GetPathForTask(int tid, std::string* path) const = 0;
+    virtual bool GetPathForTask(pid_t tid, std::string* path) const = 0;
     virtual bool GetPathForUID(uid_t uid, std::string* path) const = 0;
 };
 
@@ -57,7 +57,7 @@
                const std::string& file_v2_name) override;
 
     bool GetPathForProcess(uid_t uid, pid_t pid, std::string* path) const override;
-    bool GetPathForTask(int tid, std::string* path) const override;
+    bool GetPathForTask(pid_t tid, std::string* path) const override;
     bool GetPathForUID(uid_t uid, std::string* path) const override;
 
   private:
@@ -82,8 +82,8 @@
 
     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; }
+    virtual bool IsValidForProcess(uid_t, pid_t) const { return false; }
+    virtual bool IsValidForTask(pid_t) const { return false; }
 
   protected:
     enum CacheUseResult { SUCCESS, FAIL, UNUSED };
@@ -96,7 +96,7 @@
 
     const char* Name() const override { return "SetClamps"; }
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
-    bool ExecuteForTask(int tid) const override;
+    bool ExecuteForTask(pid_t tid) const override;
 
   protected:
     int boost_;
@@ -108,14 +108,14 @@
     SetTimerSlackAction(unsigned long slack) noexcept : slack_(slack) {}
 
     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; }
+    bool ExecuteForTask(pid_t tid) const override;
+    bool IsValidForProcess(uid_t, pid_t) const override { return true; }
+    bool IsValidForTask(pid_t) const override { return true; }
 
   private:
     unsigned long slack_;
 
-    static bool IsTimerSlackSupported(int tid);
+    static bool IsTimerSlackSupported(pid_t tid);
 };
 
 // Set attribute profile element
@@ -126,10 +126,10 @@
 
     const char* Name() const override { return "SetAttribute"; }
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
-    bool ExecuteForTask(int tid) const override;
+    bool ExecuteForTask(pid_t 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;
+    bool IsValidForTask(pid_t tid) const override;
 
   private:
     const IProfileAttribute* attribute_;
@@ -146,11 +146,11 @@
 
     const char* Name() const override { return "SetCgroup"; }
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
-    bool ExecuteForTask(int tid) const override;
+    bool ExecuteForTask(pid_t 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;
+    bool IsValidForTask(pid_t tid) const override;
 
     const CgroupController* controller() const { return &controller_; }
 
@@ -160,7 +160,7 @@
     android::base::unique_fd fd_[ProfileAction::RCT_COUNT];
     mutable std::mutex fd_mutex_;
 
-    bool AddTidToCgroup(int tid, int fd, ResourceCacheType cache_type) const;
+    bool AddTidToCgroup(pid_t tid, int fd, ResourceCacheType cache_type) const;
     CacheUseResult UseCachedFd(ResourceCacheType cache_type, int id) const;
 };
 
@@ -172,11 +172,11 @@
 
     const char* Name() const override { return "WriteFile"; }
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
-    bool ExecuteForTask(int tid) const override;
+    bool ExecuteForTask(pid_t 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;
+    bool IsValidForTask(pid_t tid) const override;
 
   private:
     std::string task_path_, proc_path_, value_;
@@ -184,8 +184,8 @@
     android::base::unique_fd fd_[ProfileAction::RCT_COUNT];
     mutable std::mutex fd_mutex_;
 
-    bool WriteValueToFile(const std::string& value, ResourceCacheType cache_type, int uid, int pid,
-                          bool logfailures) const;
+    bool WriteValueToFile(const std::string& value, ResourceCacheType cache_type, uid_t uid,
+                          pid_t pid, bool logfailures) const;
     CacheUseResult UseCachedFd(ResourceCacheType cache_type, const std::string& value) const;
 };
 
@@ -198,12 +198,12 @@
     void MoveTo(TaskProfile* profile);
 
     bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    bool ExecuteForTask(int tid) const;
+    bool ExecuteForTask(pid_t tid) const;
     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;
+    bool IsValidForTask(pid_t tid) const;
 
   private:
     const std::string name_;
@@ -219,11 +219,11 @@
 
     const char* Name() const override { return "ApplyProfileAction"; }
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
-    bool ExecuteForTask(int tid) const override;
+    bool ExecuteForTask(pid_t 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;
+    bool IsValidForTask(pid_t tid) const override;
 
   private:
     std::vector<std::shared_ptr<TaskProfile>> profiles_;
@@ -240,7 +240,7 @@
     template <typename T>
     bool SetProcessProfiles(uid_t uid, pid_t pid, std::span<const T> profiles, bool use_fd_cache);
     template <typename T>
-    bool SetTaskProfiles(int tid, std::span<const T> profiles, bool use_fd_cache);
+    bool SetTaskProfiles(pid_t tid, std::span<const T> profiles, bool use_fd_cache);
     template <typename T>
     bool SetUserProfiles(uid_t uid, std::span<const T> profiles, bool use_fd_cache);
 
@@ -252,3 +252,6 @@
     std::map<std::string, std::shared_ptr<TaskProfile>, std::less<>> profiles_;
     std::map<std::string, std::unique_ptr<IProfileAttribute>, std::less<>> attributes_;
 };
+
+std::string ConvertUidToPath(const char* root_cgroup_path, uid_t uid);
+std::string ConvertUidPidToPath(const char* root_cgroup_path, uid_t uid, pid_t pid);
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index b17e695..d19da2b 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -102,8 +102,7 @@
   public:
     ProfileAttributeMock(const std::string& file_name) : file_name_(file_name) {}
     ~ProfileAttributeMock() override = default;
-    void Reset(const CgroupController& controller, const std::string& file_name,
-               const std::string& file_v2_name) override {
+    void Reset(const CgroupController&, const std::string&, const std::string&) override {
         CHECK(false);
     }
     const CgroupController* controller() const override {
@@ -111,10 +110,10 @@
         return {};
     }
     const std::string& file_name() const override { return file_name_; }
-    bool GetPathForProcess(uid_t uid, pid_t pid, std::string* path) const override {
+    bool GetPathForProcess(uid_t, pid_t pid, std::string* path) const override {
         return GetPathForTask(pid, path);
     }
-    bool GetPathForTask(int tid, std::string* path) const override {
+    bool GetPathForTask(int, std::string* path) const override {
 #ifdef __ANDROID__
         CHECK(CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, path));
         CHECK_GT(path->length(), 0);
diff --git a/libprocessgroup/util/Android.bp b/libprocessgroup/util/Android.bp
new file mode 100644
index 0000000..4a940b7
--- /dev/null
+++ b/libprocessgroup/util/Android.bp
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_team: "trendy_team_android_kernel",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_headers {
+    name: "libprocessgroup_util",
+    vendor_available: true,
+    product_available: true,
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
+    recovery_available: true,
+    host_supported: true,
+    native_bridge_supported: true,
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    min_sdk_version: "30",
+    export_include_dirs: [
+        "include",
+    ],
+    defaults: ["libprocessgroup_build_flags_cc"],
+}
+
+cc_test {
+    name: "libprocessgroup_util_test",
+    header_libs: ["libprocessgroup_util"],
+    srcs: ["tests/util.cpp"],
+    test_suites: ["general-tests"],
+}
diff --git a/libprocessgroup/util/OWNERS b/libprocessgroup/util/OWNERS
new file mode 100644
index 0000000..54ea400
--- /dev/null
+++ b/libprocessgroup/util/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1293033
+surenb@google.com
+tjmercier@google.com
diff --git a/libprocessgroup/util/TEST_MAPPING b/libprocessgroup/util/TEST_MAPPING
new file mode 100644
index 0000000..6ae2658
--- /dev/null
+++ b/libprocessgroup/util/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "libprocessgroup_util_test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/libprocessgroup/util/include/processgroup/util.h b/libprocessgroup/util/include/processgroup/util.h
new file mode 100644
index 0000000..5240744
--- /dev/null
+++ b/libprocessgroup/util/include/processgroup/util.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+#include <string>
+
+namespace util {
+
+namespace internal {
+
+const char SEP = '/';
+
+std::string DeduplicateAndTrimSeparators(const std::string& path) {
+    bool lastWasSep = false;
+    std::string ret;
+
+    std::copy_if(path.begin(), path.end(), std::back_inserter(ret), [&lastWasSep](char c) {
+        if (lastWasSep) {
+            if (c == SEP) return false;
+            lastWasSep = false;
+        } else if (c == SEP) {
+            lastWasSep = true;
+        }
+        return true;
+    });
+
+    if (ret.length() > 1 && ret.back() == SEP) ret.pop_back();
+
+    return ret;
+}
+
+}  // namespace internal
+
+unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path) {
+    const std::string deduped_root = internal::DeduplicateAndTrimSeparators(controller_root);
+    const std::string deduped_path = internal::DeduplicateAndTrimSeparators(cgroup_path);
+
+    if (deduped_root.empty() || deduped_path.empty() || !deduped_path.starts_with(deduped_root))
+        return 0;
+
+    return std::count(deduped_path.begin() + deduped_root.size(), deduped_path.end(),
+                      internal::SEP);
+}
+
+}  // namespace util
diff --git a/libprocessgroup/util/tests/util.cpp b/libprocessgroup/util/tests/util.cpp
new file mode 100644
index 0000000..1de7d6f
--- /dev/null
+++ b/libprocessgroup/util/tests/util.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 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 <processgroup/util.h>
+
+#include "gtest/gtest.h"
+
+using util::GetCgroupDepth;
+
+TEST(EmptyInputs, bothEmpty) {
+    EXPECT_EQ(GetCgroupDepth({}, {}), 0);
+}
+
+TEST(EmptyInputs, rootEmpty) {
+    EXPECT_EQ(GetCgroupDepth({}, "foo"), 0);
+}
+
+TEST(EmptyInputs, pathEmpty) {
+    EXPECT_EQ(GetCgroupDepth("foo", {}), 0);
+}
+
+TEST(InvalidInputs, pathNotInRoot) {
+    EXPECT_EQ(GetCgroupDepth("foo", "bar"), 0);
+}
+
+TEST(InvalidInputs, rootLargerThanPath) {
+    EXPECT_EQ(GetCgroupDepth("/a/long/path", "/short"), 0);
+}
+
+TEST(InvalidInputs, pathLargerThanRoot) {
+    EXPECT_EQ(GetCgroupDepth("/short", "/a/long/path"), 0);
+}
+
+TEST(InvalidInputs, missingSeparator) {
+    EXPECT_EQ(GetCgroupDepth("/controller/root", "/controller/rootcgroup"), 0);
+}
+
+TEST(ExtraSeparators, root) {
+    EXPECT_EQ(GetCgroupDepth("///sys/fs/cgroup", "/sys/fs/cgroup/a/b/c"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys///fs/cgroup", "/sys/fs/cgroup/a/b/c"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs///cgroup", "/sys/fs/cgroup/a/b/c"), 3);
+
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "///sys/fs/cgroup/a/b/c"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys///fs/cgroup/a/b/c"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs///cgroup/a/b/c"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup///a/b/c"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a///b/c"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a/b///c"), 3);
+}
+
+TEST(SeparatorEndings, rootEndsInSeparator) {
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/a/b"), 2);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup///", "/sys/fs/cgroup/a/b"), 2);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/a/b/"), 2);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup///", "/sys/fs/cgroup/a/b/"), 2);
+}
+
+TEST(SeparatorEndings, pathEndsInSeparator) {
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a/b/"), 2);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a/b///"), 2);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/a/b/"), 2);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/a/b///"), 2);
+}
+
+TEST(ValidInputs, rootHasZeroDepth) {
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup"), 0);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup"), 0);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/"), 0);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/"), 0);
+}
+
+TEST(ValidInputs, atLeastDepth10) {
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a/b/c/d/e/f/g/h/i/j"), 10);
+}
+
+TEST(ValidInputs, androidCgroupNames) {
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/system/uid_0/pid_1000"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/uid_0/pid_1000"), 2);
+
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/apps/uid_100000/pid_1000"), 3);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/uid_100000/pid_1000"), 2);
+
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/apps"), 1);
+    EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/system"), 1);
+}
+
+TEST(ValidInputs, androidCgroupNames_nonDefaultRoot) {
+    EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/system/uid_0/pid_1000"), 3);
+    EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/uid_0/pid_1000"), 2);
+
+    EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/apps/uid_100000/pid_1000"), 3);
+    EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/uid_100000/pid_1000"), 2);
+
+    EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/apps"), 1);
+    EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/system"), 1);
+}
diff --git a/libstats/expresslog/Android.bp b/libstats/expresslog/Android.bp
index 004f8b9..96ab59b 100644
--- a/libstats/expresslog/Android.bp
+++ b/libstats/expresslog/Android.bp
@@ -47,6 +47,11 @@
         "libstatssocket",
     ],
     export_include_dirs: ["include"],
+    min_sdk_version: "33",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.btservices",
+    ],
 }
 
 genrule {
@@ -75,6 +80,11 @@
     shared_libs: [
         "libstatssocket",
     ],
+    min_sdk_version: "33",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.btservices",
+    ],
 }
 
 cc_test {
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index 4609e6b..6902026 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -60,8 +60,8 @@
     crate_name: "statspull_rust",
     srcs: ["stats_pull.rs"],
     rustlibs: [
-        "liblazy_static",
         "liblog_rust",
+        "libonce_cell",
         "libstatslog_rust_header",
         "libstatspull_bindgen",
     ],
diff --git a/libstats/pull_rust/stats_pull.rs b/libstats/pull_rust/stats_pull.rs
index d188b5f..b2bebcc 100644
--- a/libstats/pull_rust/stats_pull.rs
+++ b/libstats/pull_rust/stats_pull.rs
@@ -14,7 +14,7 @@
 
 //! A Rust interface for the StatsD pull API.
 
-use lazy_static::lazy_static;
+use once_cell::sync::Lazy;
 use statslog_rust_header::{Atoms, Stat, StatsError};
 use statspull_bindgen::*;
 use std::collections::HashMap;
@@ -107,9 +107,8 @@
     }
 }
 
-lazy_static! {
-    static ref COOKIES: Mutex<HashMap<i32, fn() -> StatsPullResult>> = Mutex::new(HashMap::new());
-}
+static COOKIES: Lazy<Mutex<HashMap<i32, fn() -> StatsPullResult>>> =
+    Lazy::new(|| Mutex::new(HashMap::new()));
 
 /// # Safety
 ///
diff --git a/libstats/socket_lazy/Android.bp b/libstats/socket_lazy/Android.bp
index b2cd7b2..241e87a 100644
--- a/libstats/socket_lazy/Android.bp
+++ b/libstats/socket_lazy/Android.bp
@@ -7,6 +7,12 @@
 
 cc_library_static {
     name: "libstatssocket_lazy",
+    local_include_dirs: [
+        "include",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
     header_libs: [
         "libstatssocket_headers",
     ],
@@ -28,7 +34,10 @@
         "-Wall",
         "-Werror",
     ],
-    test_suites: ["device-tests", "mts-statsd"],
+    test_suites: [
+        "device-tests",
+        "mts-statsd",
+    ],
     test_config: "libstatssocket_lazy_test.xml",
     // TODO(b/153588990): Remove when the build system properly separates.
     // 32bit and 64bit architectures.
diff --git a/init/host_init_verifier.h b/libstats/socket_lazy/include/statssocket_lazy.h
similarity index 63%
rename from init/host_init_verifier.h
rename to libstats/socket_lazy/include/statssocket_lazy.h
index 5d24f2a..7dda0ba 100644
--- a/init/host_init_verifier.h
+++ b/libstats/socket_lazy/include/statssocket_lazy.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -16,12 +16,10 @@
 
 #pragma once
 
-#include <property_info_parser/property_info_parser.h>
+namespace android::statssocket::lazy {
 
-namespace android {
-namespace init {
+// See if libstatssocket.so is available. Early processes relying on _lazy might not have access
+// to libstatssocket.so when they start before the StatsD APEX is available.
+bool IsAvailable();
 
-extern const android::properties::PropertyInfoArea* property_info_area;
-
-}  // namespace init
-}  // namespace android
+}  // namespace android::statssocket::lazy
diff --git a/libstats/socket_lazy/libstatssocket_lazy.cpp b/libstats/socket_lazy/libstatssocket_lazy.cpp
index dd93eeb..d907c7e 100644
--- a/libstats/socket_lazy/libstatssocket_lazy.cpp
+++ b/libstats/socket_lazy/libstatssocket_lazy.cpp
@@ -23,8 +23,10 @@
 
 #include "log/log.h"
 
-#include "stats_event.h"
-#include "stats_socket.h"
+#include <stats_event.h>
+#include <stats_socket.h>
+
+#include "statssocket_lazy.h"
 
 // This file provides a lazy interface to libstatssocket.so to address early boot dependencies.
 // Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and
@@ -45,6 +47,7 @@
     k_AStatsEvent_writeBool,
     k_AStatsEvent_writeByteArray,
     k_AStatsEvent_writeString,
+    k_AStatsEvent_writeStringArray,
     k_AStatsEvent_writeAttributionChain,
     k_AStatsEvent_addBoolAnnotation,
     k_AStatsEvent_addInt32Annotation,
@@ -76,6 +79,13 @@
     return dlopen("libstatssocket.so", dlopen_flags);
 }
 
+namespace android::statssocket::lazy {
+bool IsAvailable() {
+    static const void* handle = LoadLibstatssocket(RTLD_NOW);
+    return handle != nullptr;
+}
+}  // namespace android::statssocket::lazy
+
 //
 // Initialization and symbol binding.
 
@@ -104,6 +114,7 @@
     BIND_SYMBOL(AStatsEvent_writeBool);
     BIND_SYMBOL(AStatsEvent_writeByteArray);
     BIND_SYMBOL(AStatsEvent_writeString);
+    BIND_SYMBOL(AStatsEvent_writeStringArray);
     BIND_SYMBOL(AStatsEvent_writeAttributionChain);
     BIND_SYMBOL(AStatsEvent_addBoolAnnotation);
     BIND_SYMBOL(AStatsEvent_addInt32Annotation);
@@ -179,6 +190,11 @@
     INVOKE_METHOD(AStatsEvent_writeString, event, value);
 }
 
+void AStatsEvent_writeStringArray(AStatsEvent* event, const char* const* elements,
+                                  size_t numElements) {
+    INVOKE_METHOD(AStatsEvent_writeStringArray, event, elements, numElements);
+}
+
 void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids,
                                        const char* const* tags, uint8_t numNodes) {
     INVOKE_METHOD(AStatsEvent_writeAttributionChain, event, uids, tags, numNodes);
@@ -198,4 +214,4 @@
 
 void AStatsSocket_close() {
     INVOKE_METHOD(AStatsSocket_close);
-}
\ No newline at end of file
+}
diff --git a/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
index fe13598..733f1e4 100644
--- a/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
+++ b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
@@ -21,6 +21,8 @@
 #include "stats_event.h"
 #include "stats_socket.h"
 
+#include "statssocket_lazy.h"
+
 // The tests here are just for the case when libstatssocket.so cannot be loaded by
 // libstatssocket_lazy.
 class LibstatssocketLazyTest : public ::testing::Test {
@@ -47,6 +49,7 @@
     EXPECT_DEATH(AStatsEvent_writeBool(event, false), kLoadFailed);
     EXPECT_DEATH(AStatsEvent_writeByteArray(event, NULL, 0), kLoadFailed);
     EXPECT_DEATH(AStatsEvent_writeString(event, NULL), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeStringArray(event, NULL, 0), kLoadFailed);
     EXPECT_DEATH(AStatsEvent_writeAttributionChain(event, NULL, NULL, 0), kLoadFailed);
 
     EXPECT_DEATH(AStatsEvent_addBoolAnnotation(event, 0, false), kLoadFailed);
@@ -55,4 +58,8 @@
 
 TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsSocket) {
     EXPECT_DEATH(AStatsSocket_close(), kLoadFailed);
-}
\ No newline at end of file
+}
+
+TEST_F(LibstatssocketLazyTest, IsAvailableFalse) {
+    EXPECT_FALSE(android::statssocket::lazy::IsAvailable());
+}
diff --git a/libsysutils/Android.bp b/libsysutils/Android.bp
index 1b41a6b..842db40 100644
--- a/libsysutils/Android.bp
+++ b/libsysutils/Android.bp
@@ -5,9 +5,6 @@
 cc_library {
     name: "libsysutils",
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
 
     srcs: [
         "src/SocketListener.cpp",
diff --git a/libsysutils/include/sysutils/SocketListener.h b/libsysutils/include/sysutils/SocketListener.h
index 67a691a..a7fd09e 100644
--- a/libsysutils/include/sysutils/SocketListener.h
+++ b/libsysutils/include/sysutils/SocketListener.h
@@ -19,6 +19,7 @@
 #include <pthread.h>
 
 #include <unordered_map>
+#include <vector>
 
 #include <sysutils/SocketClient.h>
 #include "SocketClientCommand.h"
diff --git a/libusbhost/Android.bp b/libusbhost/Android.bp
index 9ae73d0..f8a73ad 100644
--- a/libusbhost/Android.bp
+++ b/libusbhost/Android.bp
@@ -21,9 +21,6 @@
 cc_library {
     name: "libusbhost",
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
     host_supported: true,
     srcs: ["usbhost.c"],
     cflags: ["-Werror"],
diff --git a/libutils/Android.bp b/libutils/Android.bp
index ad5b752..305cbf0 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -33,14 +33,12 @@
         "libbase_headers",
         "libcutils_headers",
         "liblog_headers",
-        "libprocessgroup_headers",
         "libsystem_headers",
     ],
     export_header_lib_headers: [
         "libbase_headers",
         "libcutils_headers",
         "liblog_headers",
-        "libprocessgroup_headers",
         "libsystem_headers",
     ],
     export_include_dirs: ["include"],
@@ -109,7 +107,7 @@
         },
     },
     fuzz_config: {
-       cc: ["smoreland@google.com"],
+        cc: ["smoreland@google.com"],
     },
 }
 
@@ -183,10 +181,7 @@
     name: "libutils",
     defaults: ["libutils_impl_defaults"],
 
-    vndk: {
-        enabled: true,
-        support_system_process: true,
-    },
+    double_loadable: true,
 
     target: {
         product: {
@@ -228,10 +223,7 @@
     // TODO(b/153609531): remove when no longer needed.
     native_bridge_supported: true,
     min_sdk_version: "29",
-    vndk: {
-        enabled: true,
-        support_system_process: true,
-    },
+    double_loadable: true,
 
     header_libs: [
         "libbase_headers",
@@ -273,6 +265,17 @@
         "libbase",
         "liblog",
     ],
+    fuzz_config: {
+        cc: [
+            "smoreland@google.com",
+        ],
+        componentid: 128577,
+        description: "The fuzzer targets the APIs of libutils",
+        vector: "local_no_privileges_required",
+        service_privilege: "privileged",
+        users: "multi_user",
+        fuzzed_code_usage: "shipped",
+    },
 }
 
 cc_fuzz {
@@ -366,6 +369,7 @@
                 "libunwindstack_no_dex",
                 "libutils",
                 "libutilscallstack",
+                "libz",
             ],
         },
     },
diff --git a/libutils/CallStack.cpp b/libutils/CallStack.cpp
index fe827eb..029f4a7 100644
--- a/libutils/CallStack.cpp
+++ b/libutils/CallStack.cpp
@@ -89,7 +89,8 @@
 // The following four functions may be used via weak symbol references from libutils.
 // Clients assume that if any of these symbols are available, then deleteStack() is.
 
-#ifdef WEAKS_AVAILABLE
+// Apple and Windows does not support this, so only compile on other platforms.
+#if !defined(__APPLE__) && !defined(_WIN32)
 
 CallStack::CallStackUPtr CallStack::getCurrentInternal(int ignoreDepth) {
     CallStack::CallStackUPtr stack(new CallStack());
@@ -110,6 +111,6 @@
     delete stack;
 }
 
-#endif // WEAKS_AVAILABLE
+#endif  // !defined(__APPLE__) && !defined(_WIN32)
 
 }; // namespace android
diff --git a/libutils/CallStack_test.cpp b/libutils/CallStack_test.cpp
index 2cfaf61..bfe6b87 100644
--- a/libutils/CallStack_test.cpp
+++ b/libutils/CallStack_test.cpp
@@ -18,10 +18,16 @@
 
 #include <thread>
 
+#include <android-base/test_utils.h>
 #include <android-base/threads.h>
 #include <gtest/gtest.h>
 #include <utils/CallStack.h>
 
+#if defined(__ANDROID__)
+#include <log/log.h>
+#include <log/log_read.h>
+#endif
+
 __attribute__((__noinline__)) extern "C" void CurrentCaller(android::String8& backtrace) {
     android::CallStack cs;
     cs.update();
@@ -61,3 +67,39 @@
 
     ASSERT_NE(-1, cs.toString().find("(ThreadBusyWait")) << "Full backtrace:\n" << cs.toString();
 }
+
+#if defined(__ANDROID__)
+TEST(CallStackTest, log_stack) {
+    android::CallStack::logStack("callstack_test");
+    auto logger_list = android_logger_list_open(android_name_to_log_id("main"),
+                                                ANDROID_LOG_NONBLOCK,
+                                                10000 /* tail */, getpid());
+    ASSERT_NE(nullptr, logger_list);
+    std::string log;
+    while (true) {
+        log_msg log_msg;
+        auto ret = android_logger_list_read(logger_list, &log_msg);
+        if (ret == -EAGAIN) {
+            break;
+        }
+        ASSERT_GT(ret, 0);
+        if (log_msg.msg() == nullptr) {
+            continue;
+        }
+        // First get the tag.
+        char* msg = &log_msg.msg()[1];
+        if (std::string(msg) != "callstack_test") {
+            continue;
+        }
+        // Now move past the tag.
+        msg = &msg[strlen(msg) + 1];
+        log += msg;
+        log += '\n';
+    }
+    ASSERT_NE("", log) << "No messages found in the log from the test.";
+    // Look for a backtrace line such as:
+    //   #00 pc 00000000000536e4  libutils_test (testing::Test::Run()+436)
+    ASSERT_MATCH(log, "#\\d+ pc \\d+");
+    android_logger_list_close(logger_list);
+}
+#endif
diff --git a/libutils/StopWatch.cpp b/libutils/StopWatch.cpp
index c88d60f..c91cd5c 100644
--- a/libutils/StopWatch.cpp
+++ b/libutils/StopWatch.cpp
@@ -18,10 +18,6 @@
 
 #include <utils/StopWatch.h>
 
-/* for PRId64 */
-#ifndef __STDC_FORMAT_MACROS
-#define __STDC_FORMAT_MACROS 1
-#endif
 #include <inttypes.h>
 
 #include <log/log.h>
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 90ea29b..0b96ab0 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -37,11 +37,6 @@
 #include <log/log.h>
 
 #if defined(__ANDROID__)
-#include <processgroup/processgroup.h>
-#include <processgroup/sched_policy.h>
-#endif
-
-#if defined(__ANDROID__)
 # define __android_unused
 #else
 # define __android_unused __attribute__((__unused__))
diff --git a/libutils/binder/Android.bp b/libutils/binder/Android.bp
index 60b0cb6..e358391 100644
--- a/libutils/binder/Android.bp
+++ b/libutils/binder/Android.bp
@@ -22,6 +22,12 @@
         "VectorImpl.cpp",
     ],
 
+    cflags: [
+        "-Winvalid-offsetof",
+        "-Wsequence-point",
+        "-Wzero-as-null-pointer-constant",
+    ],
+
     apex_available: [
         "//apex_available:anyapex",
         "//apex_available:platform",
@@ -41,11 +47,13 @@
 cc_library {
     name: "libutils_binder",
     defaults: ["libutils_binder_impl_defaults"],
+    cmake_snapshot_supported: false,
 }
 
 cc_library_shared {
     name: "libutils_binder_sdk",
     defaults: ["libutils_binder_impl_defaults_nodeps"],
+    cmake_snapshot_supported: true,
 
     header_libs: [
         "liblog_stub",
diff --git a/libutils/binder/RefBase_test.cpp b/libutils/binder/RefBase_test.cpp
index d675598..65d40a2 100644
--- a/libutils/binder/RefBase_test.cpp
+++ b/libutils/binder/RefBase_test.cpp
@@ -300,8 +300,8 @@
     std::atomic<int>* mDeleteCount;
 };
 
-static sp<Bar> buffer;
-static std::atomic<bool> bufferFull(false);
+[[clang::no_destroy]] static constinit sp<Bar> buffer;
+static constinit std::atomic<bool> bufferFull(false);
 
 // Wait until bufferFull has value val.
 static inline void waitFor(bool val) {
@@ -380,8 +380,8 @@
     }  // Otherwise this is slow and probably pointless on a uniprocessor.
 }
 
-static wp<Bar> wpBuffer;
-static std::atomic<bool> wpBufferFull(false);
+[[clang::no_destroy]] static constinit wp<Bar> wpBuffer;
+static constinit std::atomic<bool> wpBufferFull(false);
 
 // Wait until wpBufferFull has value val.
 static inline void wpWaitFor(bool val) {
diff --git a/libutils/binder/SharedBuffer.cpp b/libutils/binder/SharedBuffer.cpp
index 3e703db..d16bdb0 100644
--- a/libutils/binder/SharedBuffer.cpp
+++ b/libutils/binder/SharedBuffer.cpp
@@ -75,7 +75,7 @@
         LOG_ALWAYS_FATAL_IF((newSize >= (SIZE_MAX - sizeof(SharedBuffer))),
                             "Invalid buffer size %zu", newSize);
 
-        buf = (SharedBuffer*)realloc(buf, sizeof(SharedBuffer) + newSize);
+        buf = (SharedBuffer*)realloc(reinterpret_cast<void*>(buf), sizeof(SharedBuffer) + newSize);
         if (buf != nullptr) {
             buf->mSize = newSize;
             return buf;
diff --git a/libutils/binder/SharedBuffer_test.cpp b/libutils/binder/SharedBuffer_test.cpp
index 1d6317f..26702b0 100644
--- a/libutils/binder/SharedBuffer_test.cpp
+++ b/libutils/binder/SharedBuffer_test.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define __STDC_LIMIT_MACROS
-
 #include <gtest/gtest.h>
 
 #include <memory>
diff --git a/libutils/binder/String16.cpp b/libutils/binder/String16.cpp
index 07a3d23..96e1477 100644
--- a/libutils/binder/String16.cpp
+++ b/libutils/binder/String16.cpp
@@ -22,6 +22,19 @@
 
 #include "SharedBuffer.h"
 
+#define LIBUTILS_PRAGMA(arg) _Pragma(#arg)
+#if defined(__clang__)
+#define LIBUTILS_PRAGMA_FOR_COMPILER(arg) LIBUTILS_PRAGMA(clang arg)
+#elif defined(__GNUC__)
+#define LIBUTILS_PRAGMA_FOR_COMPILER(arg) LIBUTILS_PRAGMA(GCC arg)
+#else
+#define LIBUTILS_PRAGMA_FOR_COMPILER(arg)
+#endif
+#define LIBUTILS_IGNORE(warning_flag)             \
+    LIBUTILS_PRAGMA_FOR_COMPILER(diagnostic push) \
+    LIBUTILS_PRAGMA_FOR_COMPILER(diagnostic ignored warning_flag)
+#define LIBUTILS_IGNORE_END() LIBUTILS_PRAGMA_FOR_COMPILER(diagnostic pop)
+
 namespace android {
 
 static const StaticString16 emptyString(u"");
@@ -347,7 +360,9 @@
 bool String16::isStaticString() const {
     // See String16.h for notes on the memory layout of String16::StaticData and
     // SharedBuffer.
+    LIBUTILS_IGNORE("-Winvalid-offsetof")
     static_assert(sizeof(SharedBuffer) - offsetof(SharedBuffer, mClientMetadata) == 4);
+    LIBUTILS_IGNORE_END()
     const uint32_t* p = reinterpret_cast<const uint32_t*>(mString);
     return (*(p - 1) & kIsSharedBufferAllocated) == 0;
 }
@@ -355,7 +370,9 @@
 size_t String16::staticStringSize() const {
     // See String16.h for notes on the memory layout of String16::StaticData and
     // SharedBuffer.
+    LIBUTILS_IGNORE("-Winvalid-offsetof")
     static_assert(sizeof(SharedBuffer) - offsetof(SharedBuffer, mClientMetadata) == 4);
+    LIBUTILS_IGNORE_END()
     const uint32_t* p = reinterpret_cast<const uint32_t*>(mString);
     return static_cast<size_t>(*(p - 1));
 }
diff --git a/libutils/binder/String16_test.cpp b/libutils/binder/String16_test.cpp
index 6f4642e..83cc599 100644
--- a/libutils/binder/String16_test.cpp
+++ b/libutils/binder/String16_test.cpp
@@ -16,6 +16,8 @@
 
 #include <utils/String16.h>
 #include <utils/String8.h>
+#include <compare>
+#include <utility>
 
 #include <gtest/gtest.h>
 
@@ -257,3 +259,45 @@
     EXPECT_EQ(NO_MEMORY, s.insert(3, u"", SIZE_MAX));
     EXPECT_STR16EQ(u"foo!bar", s.c_str());
 }
+
+TEST(String16Test, comparisons) {
+    const char16_t* cstr1 = u"abc";
+    const char16_t* cstr2 = u"def";
+
+    // str1 and str1b will point to different blocks of memory but with equal contents.
+    String16 str1(cstr1);
+    String16 str1b(cstr1);
+    String16 str2(cstr2);
+
+    EXPECT_TRUE((str1 <=> str1b) == 0);
+    EXPECT_FALSE(str1 != str1b);
+    EXPECT_FALSE(str1 < str1b);
+    EXPECT_TRUE(str1 <= str1b);
+    EXPECT_TRUE(str1 == str1b);
+    EXPECT_TRUE(str1 >= str1b);
+    EXPECT_FALSE(str1 > str1b);
+
+    EXPECT_TRUE((str1 <=> str2) < 0);
+    EXPECT_TRUE((str2 <=> str1) > 0);
+    EXPECT_TRUE(str1 != str2);
+    EXPECT_TRUE(str1 < str2);
+    EXPECT_TRUE(str1 <= str2);
+    EXPECT_FALSE(str1 == str2);
+    EXPECT_FALSE(str1 >= str2);
+    EXPECT_FALSE(str1 > str2);
+
+    // Verify that pre-C++20 comparison operators work with a std::pair of a String8, which only
+    // provides <=> in C++20 and up. See b/339775405.
+
+    std::pair<String16, int> pair1(str1, 13);
+    std::pair<String16, int> pair1b(str1b, 13);
+    std::pair<String16, int> pair2(str2, 13);
+
+    EXPECT_TRUE(pair1 == pair1b);
+    EXPECT_FALSE(pair1 < pair1b);
+    EXPECT_FALSE(pair1 > pair1b);
+
+    EXPECT_TRUE(pair1 != pair2);
+    EXPECT_TRUE(pair1 < pair2);
+    EXPECT_FALSE(pair1 > pair2);
+}
diff --git a/libutils/binder/String8.cpp b/libutils/binder/String8.cpp
index 749bfcb..1de9e8b 100644
--- a/libutils/binder/String8.cpp
+++ b/libutils/binder/String8.cpp
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-#define __STDC_LIMIT_MACROS
-#include <stdint.h>
-
 #include <utils/String8.h>
 
 #include <log/log.h>
 #include <utils/String16.h>
 
 #include <ctype.h>
+#include <stdint.h>
 
 #include <limits>
 #include <string>
diff --git a/libutils/binder/String8_test.cpp b/libutils/binder/String8_test.cpp
index 6f7882a..fc3c329 100644
--- a/libutils/binder/String8_test.cpp
+++ b/libutils/binder/String8_test.cpp
@@ -17,8 +17,10 @@
 #define LOG_TAG "String8_test"
 
 #include <log/log.h>
-#include <utils/String8.h>
 #include <utils/String16.h>
+#include <utils/String8.h>
+#include <compare>
+#include <utility>
 
 #include <gtest/gtest.h>
 
@@ -132,3 +134,45 @@
     EXPECT_TRUE(s.removeAll("o"));
     EXPECT_STREQ("Hell, wrld!", s.c_str());
 }
+
+TEST_F(String8Test, comparisons) {
+    const char* cstr1 = "abc";
+    const char* cstr2 = "def";
+
+    // str1 and str1b will point to different blocks of memory but with equal contents.
+    String8 str1(cstr1);
+    String8 str1b(cstr1);
+    String8 str2(cstr2);
+
+    EXPECT_TRUE((str1 <=> str1b) == 0);
+    EXPECT_FALSE(str1 != str1b);
+    EXPECT_FALSE(str1 < str1b);
+    EXPECT_TRUE(str1 <= str1b);
+    EXPECT_TRUE(str1 == str1b);
+    EXPECT_TRUE(str1 >= str1b);
+    EXPECT_FALSE(str1 > str1b);
+
+    EXPECT_TRUE((str1 <=> str2) < 0);
+    EXPECT_TRUE((str2 <=> str1) > 0);
+    EXPECT_TRUE(str1 != str2);
+    EXPECT_TRUE(str1 < str2);
+    EXPECT_TRUE(str1 <= str2);
+    EXPECT_FALSE(str1 == str2);
+    EXPECT_FALSE(str1 >= str2);
+    EXPECT_FALSE(str1 > str2);
+
+    // Verify that pre-C++20 comparison operators work with a std::pair of a String8, which only
+    // provides <=> in C++20 and up. See b/339775405.
+
+    std::pair<String8, int> pair1(str1, 13);
+    std::pair<String8, int> pair1b(str1b, 13);
+    std::pair<String8, int> pair2(str2, 13);
+
+    EXPECT_TRUE(pair1 == pair1b);
+    EXPECT_FALSE(pair1 < pair1b);
+    EXPECT_FALSE(pair1 > pair1b);
+
+    EXPECT_TRUE(pair1 != pair2);
+    EXPECT_TRUE(pair1 < pair2);
+    EXPECT_FALSE(pair1 > pair2);
+}
diff --git a/libutils/binder/Vector_test.cpp b/libutils/binder/Vector_test.cpp
index 6d90eaa..312dcf6 100644
--- a/libutils/binder/Vector_test.cpp
+++ b/libutils/binder/Vector_test.cpp
@@ -16,7 +16,6 @@
 
 #define LOG_TAG "Vector_test"
 
-#define __STDC_LIMIT_MACROS
 #include <stdint.h>
 #include <unistd.h>
 
diff --git a/libutils/binder/include/utils/RefBase.h b/libutils/binder/include/utils/RefBase.h
index 5e3fa7d..b6a8707 100644
--- a/libutils/binder/include/utils/RefBase.h
+++ b/libutils/binder/include/utils/RefBase.h
@@ -404,7 +404,7 @@
 public:
     typedef typename RefBase::weakref_type weakref_type;
 
-    inline wp() : m_ptr(nullptr), m_refs(nullptr) { }
+    inline constexpr wp() : m_ptr(nullptr), m_refs(nullptr) { }
 
     // if nullptr, returns nullptr
     //
@@ -555,7 +555,7 @@
 wp<T>::wp(T* other)
     : m_ptr(other)
 {
-    m_refs = other ? m_refs = other->createWeak(this) : nullptr;
+    m_refs = other ? other->createWeak(this) : nullptr;
 }
 
 template <typename T>
@@ -662,8 +662,7 @@
 template<typename T> template<typename U>
 wp<T>& wp<T>::operator = (const sp<U>& other)
 {
-    weakref_type* newRefs =
-        other != nullptr ? other->createWeak(this) : 0;
+    weakref_type* newRefs = other != nullptr ? other->createWeak(this) : nullptr;
     U* otherPtr(other.m_ptr);
     if (m_ptr) m_refs->decWeak(this);
     m_ptr = otherPtr;
@@ -695,8 +694,8 @@
 {
     if (m_ptr) {
         m_refs->decWeak(this);
-        m_refs = 0;
-        m_ptr = 0;
+        m_refs = nullptr;
+        m_ptr = nullptr;
     }
 }
 
diff --git a/libutils/binder/include/utils/String16.h b/libutils/binder/include/utils/String16.h
index c713576..867dbac 100644
--- a/libutils/binder/include/utils/String16.h
+++ b/libutils/binder/include/utils/String16.h
@@ -29,6 +29,10 @@
 #define HAS_STRING_VIEW
 #endif
 
+#if __cplusplus >= 202002L
+#include <compare>
+#endif
+
 // ---------------------------------------------------------------------------
 
 namespace android {
@@ -105,6 +109,9 @@
     inline  bool                operator!=(const String16& other) const;
     inline  bool                operator>=(const String16& other) const;
     inline  bool                operator>(const String16& other) const;
+#if __cplusplus >= 202002L
+    inline std::strong_ordering operator<=>(const String16& other) const;
+#endif
 
     inline  bool                operator<(const char16_t* other) const;
     inline  bool                operator<=(const char16_t* other) const;
@@ -112,6 +119,9 @@
     inline  bool                operator!=(const char16_t* other) const;
     inline  bool                operator>=(const char16_t* other) const;
     inline  bool                operator>(const char16_t* other) const;
+#if __cplusplus >= 202002L
+    inline std::strong_ordering operator<=>(const char16_t* other) const;
+#endif
 
     inline                      operator const char16_t*() const;
 
@@ -334,6 +344,19 @@
     return strzcmp16(mString, size(), other.mString, other.size()) > 0;
 }
 
+#if __cplusplus >= 202002L
+inline std::strong_ordering String16::operator<=>(const String16& other) const {
+    int result = strzcmp16(mString, size(), other.mString, other.size());
+    if (result == 0) {
+        return std::strong_ordering::equal;
+    } else if (result < 0) {
+        return std::strong_ordering::less;
+    } else {
+        return std::strong_ordering::greater;
+    }
+}
+#endif
+
 inline bool String16::operator<(const char16_t* other) const
 {
     return strcmp16(mString, other) < 0;
@@ -364,6 +387,19 @@
     return strcmp16(mString, other) > 0;
 }
 
+#if __cplusplus >= 202002L
+inline std::strong_ordering String16::operator<=>(const char16_t* other) const {
+    int result = strcmp16(mString, other);
+    if (result == 0) {
+        return std::strong_ordering::equal;
+    } else if (result < 0) {
+        return std::strong_ordering::less;
+    } else {
+        return std::strong_ordering::greater;
+    }
+}
+#endif
+
 inline String16::operator const char16_t*() const
 {
     return mString;
diff --git a/libutils/binder/include/utils/String8.h b/libutils/binder/include/utils/String8.h
index 6d25072..e0d7588 100644
--- a/libutils/binder/include/utils/String8.h
+++ b/libutils/binder/include/utils/String8.h
@@ -36,6 +36,10 @@
 #define HAS_STRING_VIEW
 #endif
 
+#if __cplusplus >= 202002L
+#include <compare>
+#endif
+
 // ---------------------------------------------------------------------------
 
 namespace android {
@@ -106,6 +110,9 @@
     inline  bool                operator!=(const String8& other) const;
     inline  bool                operator>=(const String8& other) const;
     inline  bool                operator>(const String8& other) const;
+#if __cplusplus >= 202002L
+    inline std::strong_ordering operator<=>(const String8& other) const;
+#endif
 
     inline  bool                operator<(const char* other) const;
     inline  bool                operator<=(const char* other) const;
@@ -113,6 +120,9 @@
     inline  bool                operator!=(const char* other) const;
     inline  bool                operator>=(const char* other) const;
     inline  bool                operator>(const char* other) const;
+#if __cplusplus >= 202002L
+    inline std::strong_ordering operator<=>(const char* other) const;
+#endif
 
     inline                      operator const char*() const;
 
@@ -302,6 +312,19 @@
     return strcmp(mString, other.mString) > 0;
 }
 
+#if __cplusplus >= 202002L
+inline std::strong_ordering String8::operator<=>(const String8& other) const {
+    int result = strcmp(mString, other.mString);
+    if (result == 0) {
+        return std::strong_ordering::equal;
+    } else if (result < 0) {
+        return std::strong_ordering::less;
+    } else {
+        return std::strong_ordering::greater;
+    }
+}
+#endif
+
 inline bool String8::operator<(const char* other) const
 {
     return strcmp(mString, other) < 0;
@@ -332,6 +355,19 @@
     return strcmp(mString, other) > 0;
 }
 
+#if __cplusplus >= 202002L
+inline std::strong_ordering String8::operator<=>(const char* other) const {
+    int result = strcmp(mString, other);
+    if (result == 0) {
+        return std::strong_ordering::equal;
+    } else if (result < 0) {
+        return std::strong_ordering::less;
+    } else {
+        return std::strong_ordering::greater;
+    }
+}
+#endif
+
 inline String8::operator const char*() const
 {
     return mString;
diff --git a/libutils/binder/include/utils/StrongPointer.h b/libutils/binder/include/utils/StrongPointer.h
index 43c00c9..fb9b8e8 100644
--- a/libutils/binder/include/utils/StrongPointer.h
+++ b/libutils/binder/include/utils/StrongPointer.h
@@ -30,7 +30,7 @@
 template<typename T>
 class sp {
 public:
-    inline sp() : m_ptr(nullptr) { }
+    inline constexpr sp() : m_ptr(nullptr) { }
 
     // The old way of using sp<> was like this. This is bad because it relies
     // on implicit conversion to sp<>, which we would like to remove (if an
diff --git a/libutils/binder/include/utils/TypeHelpers.h b/libutils/binder/include/utils/TypeHelpers.h
index 1554f52..007036b 100644
--- a/libutils/binder/include/utils/TypeHelpers.h
+++ b/libutils/binder/include/utils/TypeHelpers.h
@@ -109,6 +109,11 @@
 ANDROID_BASIC_TYPES_TRAITS( float )
 ANDROID_BASIC_TYPES_TRAITS( double )
 
+template<typename T> struct trait_trivial_ctor<T*>   { enum { value = true }; };
+template<typename T> struct trait_trivial_dtor<T*>   { enum { value = true }; };
+template<typename T> struct trait_trivial_copy<T*>   { enum { value = true }; };
+template<typename T> struct trait_trivial_move<T*>   { enum { value = true }; };
+
 // ---------------------------------------------------------------------------
 
 
@@ -195,7 +200,7 @@
 typename std::enable_if<use_trivial_move<TYPE>::value>::type
 inline
 move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {
-    memmove(d, s, n*sizeof(TYPE));
+    memmove(reinterpret_cast<void*>(d), s, n * sizeof(TYPE));
 }
 
 template<typename TYPE>
@@ -222,7 +227,7 @@
 typename std::enable_if<use_trivial_move<TYPE>::value>::type
 inline
 move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {
-    memmove(d, s, n*sizeof(TYPE));
+    memmove(reinterpret_cast<void*>(d), s, n * sizeof(TYPE));
 }
 
 template<typename TYPE>
diff --git a/libutils/include/utils/CallStack.h b/libutils/include/utils/CallStack.h
index fe4d4f5..0239b68 100644
--- a/libutils/include/utils/CallStack.h
+++ b/libutils/include/utils/CallStack.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_CALLSTACK_H
-#define ANDROID_CALLSTACK_H
+#pragma once
 
 #include <memory>
 
@@ -27,17 +26,19 @@
 #include <sys/types.h>
 
 #if !defined(__APPLE__) && !defined(_WIN32)
-# define WEAKS_AVAILABLE 1
+# define CALLSTACK_WEAKS_AVAILABLE 1
 #endif
 #ifndef CALLSTACK_WEAK
-# ifdef WEAKS_AVAILABLE
+# ifdef CALLSTACK_WEAKS_AVAILABLE
 #   define CALLSTACK_WEAK __attribute__((weak))
-# else // !WEAKS_AVAILABLE
+# else // !CALLSTACK_WEAKS_AVAILABLE
 #   define CALLSTACK_WEAK
-# endif // !WEAKS_AVAILABLE
+# endif // !CALLSTACK_WEAKS_AVAILABLE
 #endif // CALLSTACK_WEAK predefined
 
-#define ALWAYS_INLINE __attribute__((always_inline))
+#ifndef CALLSTACK_ALWAYS_INLINE
+#define CALLSTACK_ALWAYS_INLINE __attribute__((always_inline))
+#endif  // CALLSTACK_ALWAYS_INLINE predefined
 
 namespace android {
 
@@ -89,7 +90,7 @@
     //
     // DO NOT USE THESE. They will disappear.
     struct StackDeleter {
-#ifdef WEAKS_AVAILABLE
+#ifdef CALLSTACK_WEAKS_AVAILABLE
         void operator()(CallStack* stack) {
             deleteStack(stack);
         }
@@ -101,8 +102,8 @@
     typedef std::unique_ptr<CallStack, StackDeleter> CallStackUPtr;
 
     // Return current call stack if possible, nullptr otherwise.
-#ifdef WEAKS_AVAILABLE
-    static CallStackUPtr ALWAYS_INLINE getCurrent(int32_t ignoreDepth = 1) {
+#ifdef CALLSTACK_WEAKS_AVAILABLE
+    static CallStackUPtr CALLSTACK_ALWAYS_INLINE getCurrent(int32_t ignoreDepth = 1) {
         if (reinterpret_cast<uintptr_t>(getCurrentInternal) == 0) {
             ALOGW("CallStack::getCurrentInternal not linked, returning null");
             return CallStackUPtr(nullptr);
@@ -110,15 +111,16 @@
             return getCurrentInternal(ignoreDepth);
         }
     }
-#else // !WEAKS_AVAILABLE
-    static CallStackUPtr ALWAYS_INLINE getCurrent(int32_t = 1) {
+#else // !CALLSTACK_WEAKS_AVAILABLE
+    static CallStackUPtr CALLSTACK_ALWAYS_INLINE getCurrent(int32_t = 1) {
         return CallStackUPtr(nullptr);
     }
-#endif // !WEAKS_AVAILABLE
+#endif // !CALLSTACK_WEAKS_AVAILABLE
 
-#ifdef WEAKS_AVAILABLE
-    static void ALWAYS_INLINE logStack(const char* logtag, CallStack* stack = getCurrent().get(),
-                                       android_LogPriority priority = ANDROID_LOG_DEBUG) {
+#ifdef CALLSTACK_WEAKS_AVAILABLE
+    static void CALLSTACK_ALWAYS_INLINE logStack(const char* logtag,
+                                                 CallStack* stack = getCurrent().get(),
+                                                 android_LogPriority priority = ANDROID_LOG_DEBUG) {
         if (reinterpret_cast<uintptr_t>(logStackInternal) != 0 && stack != nullptr) {
             logStackInternal(logtag, stack, priority);
         } else {
@@ -127,30 +129,31 @@
     }
 
 #else
-    static void ALWAYS_INLINE logStack(const char* logtag, CallStack* = getCurrent().get(),
-                                       android_LogPriority = ANDROID_LOG_DEBUG) {
+    static void CALLSTACK_ALWAYS_INLINE logStack(const char* logtag,
+                                                 CallStack* = getCurrent().get(),
+                                                 android_LogPriority = ANDROID_LOG_DEBUG) {
         ALOG(LOG_WARN, logtag, "CallStack::logStackInternal not linked");
     }
-#endif // !WEAKS_AVAILABLE
+#endif // !CALLSTACK_WEAKS_AVAILABLE
 
-#ifdef WEAKS_AVAILABLE
-    static String8 ALWAYS_INLINE stackToString(const char* prefix = nullptr,
-                                               const CallStack* stack = getCurrent().get()) {
+#ifdef CALLSTACK_WEAKS_AVAILABLE
+    static String8 CALLSTACK_ALWAYS_INLINE
+    stackToString(const char* prefix = nullptr, const CallStack* stack = getCurrent().get()) {
         if (reinterpret_cast<uintptr_t>(stackToStringInternal) != 0 && stack != nullptr) {
             return stackToStringInternal(prefix, stack);
         } else {
             return String8::format("%s<CallStack package not linked>", (prefix ? prefix : ""));
         }
     }
-#else // !WEAKS_AVAILABLE
-    static String8 ALWAYS_INLINE stackToString(const char* prefix = nullptr,
-                                               const CallStack* = getCurrent().get()) {
+#else // !CALLSTACK_WEAKS_AVAILABLE
+    static String8 CALLSTACK_ALWAYS_INLINE stackToString(const char* prefix = nullptr,
+                                                         const CallStack* = getCurrent().get()) {
         return String8::format("%s<CallStack package not linked>", (prefix ? prefix : ""));
     }
-#endif // !WEAKS_AVAILABLE
+#endif // !CALLSTACK_WEAKS_AVAILABLE
 
   private:
-#ifdef WEAKS_AVAILABLE
+#ifdef CALLSTACK_WEAKS_AVAILABLE
     static CallStackUPtr CALLSTACK_WEAK getCurrentInternal(int32_t ignoreDepth);
     static void CALLSTACK_WEAK logStackInternal(const char* logtag, const CallStack* stack,
                                                 android_LogPriority priority);
@@ -158,11 +161,13 @@
     // The deleter is only invoked on non-null pointers. Hence it will never be
     // invoked if CallStack is not linked.
     static void CALLSTACK_WEAK deleteStack(CallStack* stack);
-#endif // WEAKS_AVAILABLE
+#endif // CALLSTACK_WEAKS_AVAILABLE
 
     Vector<String8> mFrameLines;
 };
 
 }  // namespace android
 
-#endif // ANDROID_CALLSTACK_H
+#undef CALLSTACK_WEAKS_AVAILABLE
+#undef CALLSTACK_WEAK
+#undef CALLSTACK_ALWAYS_INLINE
diff --git a/libvendorsupport/Android.bp b/libvendorsupport/Android.bp
index b4457b1..a22737c 100644
--- a/libvendorsupport/Android.bp
+++ b/libvendorsupport/Android.bp
@@ -23,7 +23,7 @@
     llndk: {
         symbol_file: "libvendorsupport.map.txt",
     },
-    srcs: ["version_props.c"],
+    srcs: ["version_props.cpp"],
     cflags: [
         "-Wall",
         "-Werror",
@@ -32,5 +32,35 @@
     export_include_dirs: ["include"],
     shared_libs: [
         "liblog",
+        "libbase",
     ],
 }
+
+cc_library_headers {
+    name: "libvendorsupport_llndk_headers",
+    host_supported: true,
+    vendor_available: true,
+    recovery_available: true,
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
+    native_bridge_supported: true,
+
+    export_include_dirs: ["include_llndk"],
+    llndk: {
+        llndk_headers: true,
+    },
+
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    min_sdk_version: "apex_inherit",
+
+    system_shared_libs: [],
+    stl: "none",
+
+    // This header library is used for libc and must be available to any sdk
+    // versions.
+    // Setting sdk_version to the lowest version allows the dependencies.
+    sdk_version: "1",
+}
diff --git a/libvendorsupport/include/vendorsupport/api_level.h b/libvendorsupport/include/vendorsupport/api_level.h
index ba1a6b8..3427bc6 100644
--- a/libvendorsupport/include/vendorsupport/api_level.h
+++ b/libvendorsupport/include/vendorsupport/api_level.h
@@ -14,38 +14,52 @@
 
 #pragma once
 
-#include <android/api-level.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
 
 #define __ANDROID_VENDOR_API_MAX__ 1000000
 #define __INVALID_API_LEVEL -1
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
 /**
  * @brief Find corresponding vendor API level from an SDK API version.
  *
  * @details
  * SDK API versions and vendor API levels are not compatible and not
- * convertible. However, this function can be used to compare the two versions
+ * exchangeable. However, this function can be used to compare the two versions
  * to know which one is newer than the other.
  *
- * @param sdk_api_level The SDK version int. This must be less than 10000.
+ * @param sdkApiLevel The SDK version int. This must be less than 10000.
  * @return The corresponding vendor API level of the SDK version. -1 if the SDK
  * version is invalid or 10000.
  */
-int vendor_api_level_of(int sdk_api_level);
+int AVendorSupport_getVendorApiLevelOf(int sdkApiLevel);
 
 /**
  * @brief Find corresponding SDK API version from a vendor API level.
  *
- * @param vendor_api_level The vendor API level int.
+ * @param vendorApiLevel The vendor API level int.
  * @return The corresponding SDK API version of the vendor API level. -1 if the
  * vendor API level is invalid.
  */
-int sdk_api_level_of(int vendor_api_level);
+int AVendorSupport_getSdkApiLevelOf(int vendorApiLevel);
 
-#ifdef __cplusplus
-}
-#endif
+#if !defined(__ANDROID_VENDOR__)
+/**
+ * @brief Provide vendor API level to system modules.
+ *
+ * @details
+ * Before deprecating VNDK, system modules read ro.vndk.version to find the
+ * API level that vendor image had implemented. With the VNDK deprecation, this
+ * must be replaced with ro.board.api_level. However, there still are devices
+ * keeping old vendor partitions with the new system upgraded. In this case, the
+ * VNDK version can be used as before.
+ * This API is for platform only.
+ *
+ * @return ro.vndk.version if exist. Otherwise fallback to ro.board.api_level.
+ * 0 if none of these properties are found. This is unexpected, though.
+ */
+int AVendorSupport_getVendorApiLevel();
+#endif  // __ANDROID_VENDOR__
+
+__END_DECLS
diff --git a/libvendorsupport/include_llndk/android/llndk-versioning.h b/libvendorsupport/include_llndk/android/llndk-versioning.h
new file mode 100644
index 0000000..cf82fb7
--- /dev/null
+++ b/libvendorsupport/include_llndk/android/llndk-versioning.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+// LLNDK (https://source.android.com/docs/core/architecture/vndk/build-system#ll-ndk) is similar to
+// NDK, but uses its own versioning of YYYYMM format for vendor builds. The LLNDK symbols are
+// enabled when the vendor api level is equal to or newer than the ro.board.api_level. These symbols
+// must be annotated in map.txt files with the `# llndk=YYYYMM` annotation. They also must be marked
+// with `__INTRODUCED_IN_LLNDK(YYYYMM)` in the header files. It leaves a no-op annotation for ABI
+// analysis.
+#if !defined(__INTRODUCED_IN_LLNDK)
+#define __INTRODUCED_IN_LLNDK(vendor_api_level) \
+    __attribute__((annotate("introduced_in_llndk=" #vendor_api_level)))
+#endif
+
+#if defined(__ANDROID_VENDOR__)
+
+// Use this macro as an `if` statement to call an API that are available to both NDK and LLNDK.
+// This returns true for the vendor modules if the vendor_api_level is less than or equal to the
+// ro.board.api_level.
+#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
+    constexpr(__ANDROID_VENDOR_API__ >= vendor_api_level)
+
+#else  // __ANDROID_VENDOR__
+
+// For non-vendor modules, API_LEVEL_AT_LEAST is replaced with __builtin_available(sdk_api_level) to
+// guard the API for __INTRODUCED_IN.
+#if !defined(API_LEVEL_AT_LEAST)
+#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
+    (__builtin_available(android sdk_api_level, *))
+#endif
+
+#endif  // __ANDROID_VENDOR__
diff --git a/libvendorsupport/libvendorsupport.map.txt b/libvendorsupport/libvendorsupport.map.txt
index 9a23b94..d99c834 100644
--- a/libvendorsupport/libvendorsupport.map.txt
+++ b/libvendorsupport/libvendorsupport.map.txt
@@ -1,7 +1,7 @@
 LIBVENDORSUPPORT {
   global:
-    vendor_api_level_of; # llndk systemapi
-    sdk_api_level_of; # llndk systemapi
+    AVendorSupport_getVendorApiLevelOf; # llndk systemapi
+    AVendorSupport_getSdkApiLevelOf; # llndk systemapi
   local:
     *;
 };
diff --git a/libvendorsupport/tests/version_props_test.cpp b/libvendorsupport/tests/version_props_test.cpp
index 538a2e2..ad54c88 100644
--- a/libvendorsupport/tests/version_props_test.cpp
+++ b/libvendorsupport/tests/version_props_test.cpp
@@ -21,17 +21,17 @@
 
 namespace {
 
-TEST(vendorsupport, get_corresponding_vendor_api_level) {
-    ASSERT_EQ(__ANDROID_API_U__, vendor_api_level_of(__ANDROID_API_U__));
-    ASSERT_EQ(202404, vendor_api_level_of(__ANDROID_API_V__));
-    ASSERT_EQ(__INVALID_API_LEVEL, vendor_api_level_of(__ANDROID_API_FUTURE__));
+TEST(VendorSupport, GetCorrespondingVendorApiLevel) {
+    ASSERT_EQ(__ANDROID_API_U__, AVendorSupport_getVendorApiLevelOf(__ANDROID_API_U__));
+    ASSERT_EQ(202404, AVendorSupport_getVendorApiLevelOf(__ANDROID_API_V__));
+    ASSERT_EQ(__INVALID_API_LEVEL, AVendorSupport_getVendorApiLevelOf(__ANDROID_API_FUTURE__));
 }
 
-TEST(vendorsupport, get_corresponding_sdk_api_level) {
-    ASSERT_EQ(__ANDROID_API_U__, sdk_api_level_of(__ANDROID_API_U__));
-    ASSERT_EQ(__ANDROID_API_V__, sdk_api_level_of(202404));
-    ASSERT_EQ(__INVALID_API_LEVEL, sdk_api_level_of(__ANDROID_VENDOR_API_MAX__));
-    ASSERT_EQ(__INVALID_API_LEVEL, sdk_api_level_of(35));
+TEST(VendorSupport, GetCorrespondingSdkApiLevel) {
+    ASSERT_EQ(__ANDROID_API_U__, AVendorSupport_getSdkApiLevelOf(__ANDROID_API_U__));
+    ASSERT_EQ(__ANDROID_API_V__, AVendorSupport_getSdkApiLevelOf(202404));
+    ASSERT_EQ(__INVALID_API_LEVEL, AVendorSupport_getSdkApiLevelOf(__ANDROID_VENDOR_API_MAX__));
+    ASSERT_EQ(__INVALID_API_LEVEL, AVendorSupport_getSdkApiLevelOf(35));
 }
 
 }  // namespace
\ No newline at end of file
diff --git a/libvendorsupport/version_props.c b/libvendorsupport/version_props.c
deleted file mode 100644
index 4d0e45e..0000000
--- a/libvendorsupport/version_props.c
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2024 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 "api_level.h"
-
-#include <log/log.h>
-
-int vendor_api_level_of(int sdk_api_level) {
-    if (sdk_api_level < __ANDROID_API_V__) {
-        return sdk_api_level;
-    }
-    // In Android V, vendor API level started with version 202404.
-    // The calculation assumes that the SDK api level bumps once a year.
-    if (sdk_api_level < __ANDROID_API_FUTURE__) {
-        return 202404 + ((sdk_api_level - __ANDROID_API_V__) * 100);
-    }
-    ALOGE("The SDK version must be less than 10000: %d", sdk_api_level);
-    return __INVALID_API_LEVEL;
-}
-
-int sdk_api_level_of(int vendor_api_level) {
-    if (vendor_api_level < __ANDROID_API_V__) {
-        return vendor_api_level;
-    }
-    if (vendor_api_level >= 202404 && vendor_api_level < __ANDROID_VENDOR_API_MAX__) {
-        return (vendor_api_level - 202404) / 100 + __ANDROID_API_V__;
-    }
-    ALOGE("Unexpected vendor api level: %d", vendor_api_level);
-    return __INVALID_API_LEVEL;
-}
diff --git a/libvendorsupport/version_props.cpp b/libvendorsupport/version_props.cpp
new file mode 100644
index 0000000..ecba899
--- /dev/null
+++ b/libvendorsupport/version_props.cpp
@@ -0,0 +1,55 @@
+// Copyright (C) 2024 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 "api_level.h"
+
+#include <log/log.h>
+
+#if !defined(__ANDROID_VENDOR__)
+#include <android-base/properties.h>
+#endif
+
+int AVendorSupport_getVendorApiLevelOf(int sdkApiLevel) {
+    if (sdkApiLevel < __ANDROID_API_V__) {
+        return sdkApiLevel;
+    }
+    // In Android V, vendor API level started with version 202404.
+    // The calculation assumes that the SDK api level bumps once a year.
+    if (sdkApiLevel < __ANDROID_API_FUTURE__) {
+        return 202404 + ((sdkApiLevel - __ANDROID_API_V__) * 100);
+    }
+    ALOGE("The SDK version must be less than 10000: %d", sdkApiLevel);
+    return __INVALID_API_LEVEL;
+}
+
+int AVendorSupport_getSdkApiLevelOf(int vendorApiLevel) {
+    if (vendorApiLevel < __ANDROID_API_V__) {
+        return vendorApiLevel;
+    }
+    if (vendorApiLevel >= 202404 && vendorApiLevel < __ANDROID_VENDOR_API_MAX__) {
+        return (vendorApiLevel - 202404) / 100 + __ANDROID_API_V__;
+    }
+    ALOGE("Unexpected vendor api level: %d", vendorApiLevel);
+    return __INVALID_API_LEVEL;
+}
+
+#if !defined(__ANDROID_VENDOR__)
+int AVendorSupport_getVendorApiLevel() {
+    int vendorApiLevel = android::base::GetIntProperty("ro.vndk.version", 0);
+    if (vendorApiLevel) {
+        return vendorApiLevel;
+    }
+    return android::base::GetIntProperty("ro.board.api_level", 0);
+}
+#endif  // __ANDROID_VENDOR__
diff --git a/mkbootfs/mkbootfs.c b/mkbootfs/mkbootfs.c
index d3922bf..84a0a4e 100644
--- a/mkbootfs/mkbootfs.c
+++ b/mkbootfs/mkbootfs.c
@@ -402,7 +402,7 @@
 static void usage(void)
 {
     fprintf(stderr,
-            "Usage: mkbootfs [-n FILE] [-d DIR|-F FILE] DIR...\n"
+            "Usage: mkbootfs [-n FILE] [-d DIR|-f FILE] DIR...\n"
             "\n"
             "\t-d, --dirname=DIR: fs-config directory\n"
             "\t-f, --file=FILE: Canned configuration file\n"
@@ -410,11 +410,11 @@
             "\t-n, --nodes=FILE: Dev nodes description file\n"
             "\n"
             "Dev nodes description:\n"
-            "\t[dir|nod] [perms] [uid] [gid] [c|b] [minor] [major]\n"
+            "\t[dir|nod] [perms] [uid] [gid] [c|b] [major] [minor]\n"
             "\tExample:\n"
             "\t\t# My device nodes\n"
             "\t\tdir dev 0755 0 0\n"
-            "\t\tnod dev/null 0600 0 0 c 1 5\n"
+            "\t\tnod dev/null 0600 0 0 c 1 3\n"
     );
 }
 
@@ -445,11 +445,6 @@
     int num_dirs = argc - optind;
     argv += optind;
 
-    if (num_dirs <= 0) {
-        usage();
-        errx(1, "no directories to process?!");
-    }
-
     while(num_dirs-- > 0){
         char *x = strchr(*argv, '=');
         if(x != 0) {
diff --git a/rootdir/Android.bp b/rootdir/Android.bp
index c8a3cd6..e8f7627 100644
--- a/rootdir/Android.bp
+++ b/rootdir/Android.bp
@@ -62,7 +62,7 @@
     name: "public.libraries.android.txt",
     src: "etc/public.libraries.android.txt",
     filename: "public.libraries.txt",
-    installable: false,
+    no_full_install: true,
 }
 
 // adb_debug.prop in debug ramdisk
@@ -71,3 +71,53 @@
     src: "adb_debug.prop",
     debug_ramdisk: true,
 }
+
+prebuilt_etc {
+    name: "init.zygote64.rc",
+    src: "init.zygote64.rc",
+    sub_dir: "init/hw",
+}
+
+prebuilt_etc {
+    name: "init.zygote32.rc",
+    src: "init.zygote32.rc",
+    sub_dir: "init/hw",
+}
+
+prebuilt_etc {
+    name: "init.zygote64_32.rc",
+    src: "init.zygote64_32.rc",
+    sub_dir: "init/hw",
+}
+
+prebuilt_etc {
+    name: "init.usb.rc",
+    src: "init.usb.rc",
+    sub_dir: "init/hw",
+}
+
+prebuilt_etc {
+    name: "init.usb.configfs.rc",
+    src: "init.usb.configfs.rc",
+    sub_dir: "init/hw",
+}
+
+prebuilt_etc {
+    name: "etc_hosts",
+    src: "etc/hosts",
+    filename: "hosts",
+}
+
+prebuilt_etc {
+    name: "init-debug.rc",
+    src: "init-debug.rc",
+    sub_dir: "init",
+}
+
+llndk_libraries_txt {
+    name: "llndk.libraries.txt",
+}
+
+sanitizer_libraries_txt {
+    name: "sanitizer.libraries.txt",
+}
\ No newline at end of file
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 7444f96..4c1f2e4 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -3,19 +3,6 @@
 $(eval $(call declare-1p-copy-files,system/core/rootdir,))
 
 #######################################
-# init-debug.rc
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := init-debug.rc
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/init
-
-include $(BUILD_PREBUILT)
-
-#######################################
 # asan.options
 ifneq ($(filter address,$(SANITIZE_TARGET)),)
 
@@ -223,33 +210,6 @@
 	$(hide) sed -i -e 's?%EXPORT_GLOBAL_HWASAN_OPTIONS%?$(EXPORT_GLOBAL_HWASAN_OPTIONS)?g' $@
 	$(hide) sed -i -e 's?%EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE%?$(EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE)?g' $@
 
-# Append PLATFORM_VNDK_VERSION to base name.
-define append_vndk_version
-$(strip \
-  $(basename $(1)).$(PLATFORM_VNDK_VERSION)$(suffix $(1)) \
-)
-endef
-
-#######################################
-# sanitizer.libraries.txt
-include $(CLEAR_VARS)
-LOCAL_MODULE := sanitizer.libraries.txt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(LOCAL_MODULE)
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_SANITIZER_RUNTIME_LIBRARIES := \
-    $(SANITIZER_STEMS) \
-    $(2ND_SANITIZER_STEMS)
-$(LOCAL_BUILT_MODULE):
-	@echo "Generate: $@"
-	@mkdir -p $(dir $@)
-	$(hide) echo -n > $@
-	$(hide) $(foreach lib,$(PRIVATE_SANITIZER_RUNTIME_LIBRARIES), \
-		echo $(lib) >> $@;)
-
 #######################################
 # ramdisk_node_list
 include $(CLEAR_VARS)
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
index d72ac66..8b3542f 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -9,8 +9,6 @@
     "libnativehelper.so",
     "libnativeloader.so",
     "libsigchain.so",
-    // TODO(b/122876336): Remove libpac.so once it's migrated to Webview
-    "libpac.so",
     // TODO(b/120786417 or b/134659294): libicuuc.so
     // and libicui18n.so are kept for app compat.
     "libicui18n.so",
@@ -32,6 +30,8 @@
   ],
   "provideLibs": [
     "libaptX_encoder.so",
-    "libaptXHD_encoder.so"
+    "libaptXHD_encoder.so",
+    "libEGL.so",
+    "libGLESv2.so"
   ]
 }
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 768e0ff..2443b7c 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -228,6 +228,27 @@
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
+    # Create blkio group and apply initial settings.
+    # This feature needs kernel to support it, and the
+    # device's init.rc must actually set the correct values.
+    mkdir /dev/blkio/background
+    chown system system /dev/blkio
+    chown system system /dev/blkio/background
+    chown system system /dev/blkio/tasks
+    chown system system /dev/blkio/background/tasks
+    chown system system /dev/blkio/cgroup.procs
+    chown system system /dev/blkio/background/cgroup.procs
+    chmod 0664 /dev/blkio/tasks
+    chmod 0664 /dev/blkio/background/tasks
+    chmod 0664 /dev/blkio/cgroup.procs
+    chmod 0664 /dev/blkio/background/cgroup.procs
+    write /dev/blkio/blkio.weight 1000
+    write /dev/blkio/background/blkio.weight 200
+    write /dev/blkio/background/blkio.bfq.weight 10
+    write /dev/blkio/blkio.group_idle 0
+    write /dev/blkio/background/blkio.group_idle 0
+    write /dev/blkio/background/blkio.prio.class restrict-to-be
+
     restorecon_recursive /mnt
 
     mount configfs none /config nodev noexec nosuid
@@ -282,6 +303,9 @@
     mkdir /mnt/runtime/full 0755 root root
     mkdir /mnt/runtime/full/self 0755 root root
 
+    # For Pre-reboot Dexopt
+    mkdir /mnt/pre_reboot_dexopt 0755 artd artd
+
     # Symlink to keep legacy apps working in multi-user world
     symlink /storage/self/primary /mnt/sdcard
     symlink /mnt/user/0/primary /mnt/runtime/default/self/primary
@@ -400,16 +424,6 @@
     chown system system /proc/pressure/memory
     chmod 0664 /proc/pressure/memory
 
-    # qtaguid will limit access to specific data based on group memberships.
-    #   net_bw_acct grants impersonation of socket owners.
-    #   net_bw_stats grants access to other apps' detailed tagged-socket stats.
-    chown root net_bw_acct /proc/net/xt_qtaguid/ctrl
-    chown root net_bw_stats /proc/net/xt_qtaguid/stats
-
-    # Allow everybody to read the xt_qtaguid resource tracking misc dev.
-    # This is needed by any process that uses socket tagging.
-    chmod 0644 /dev/xt_qtaguid
-
     mount bpf bpf /sys/fs/bpf nodev noexec nosuid
 
     # pstore/ramoops previous console log
@@ -619,6 +633,15 @@
     restorecon_recursive /metadata/apex
 
     mkdir /metadata/staged-install 0770 root system
+
+    mkdir /metadata/aconfig 0775 root system
+    mkdir /metadata/aconfig/flags 0770 root system
+    mkdir /metadata/aconfig/maps 0775 root system
+    mkdir /metadata/aconfig/boot 0775 root system
+
+    mkdir /metadata/aconfig_test_missions 0775 root system
+    exec_start aconfigd-platform-init
+
 on late-fs
     # Ensure that tracefs has the correct permissions.
     # This does not work correctly if it is called in post-fs.
@@ -632,10 +655,6 @@
     exec -- /system/bin/fsverity_init --load-verified-keys
 
 # Only enable the bootreceiver tracing instance for kernels 5.10 and above.
-on late-fs && property:ro.kernel.version=4.9
-    setprop bootreceiver.enable 0
-on late-fs && property:ro.kernel.version=4.14
-    setprop bootreceiver.enable 0
 on late-fs && property:ro.kernel.version=4.19
     setprop bootreceiver.enable 0
 on late-fs && property:ro.kernel.version=5.4
@@ -683,7 +702,7 @@
 
     # Start tombstoned early to be able to store tombstones.
     mkdir /data/anr 0775 system system encryption=Require
-    mkdir /data/tombstones 0771 system system encryption=Require
+    mkdir /data/tombstones 0775 system system encryption=Require
     mkdir /data/vendor/tombstones 0771 root root
     mkdir /data/vendor/tombstones/wifi 0771 wifi wifi
     start tombstoned
@@ -781,6 +800,7 @@
     mkdir /data/misc/apns 0770 system radio
     mkdir /data/misc/emergencynumberdb 0770 system radio
     mkdir /data/misc/network_watchlist 0774 system system
+    mkdir /data/misc/telephonyconfig 0770 system radio
     mkdir /data/misc/textclassifier 0771 system system
     mkdir /data/misc/vpn 0770 system vpn
     mkdir /data/misc/shared_relro 0771 shared_relro shared_relro
@@ -826,6 +846,8 @@
     mkdir /data/misc/odsign 0710 root system
     # directory used for odsign metrics
     mkdir /data/misc/odsign/metrics 0770 root system
+    # directory used for connectivity blob store.
+    mkdir /data/misc/connectivityblobdb 0770 system system
 
     # Directory for VirtualizationService temporary image files.
     # Delete any stale files owned by the old virtualizationservice uid (b/230056726).
@@ -847,6 +869,9 @@
     mkdir /data/app-lib 0771 system system encryption=Require
     mkdir /data/app 0771 system system encryption=Require
 
+    # Create directory for app metadata files
+    mkdir /data/app-metadata 0700 system system encryption=Require
+
     # create directory for updated font files.
     mkdir /data/fonts/ 0771 root root encryption=Require
     mkdir /data/fonts/files 0771 system system
@@ -935,6 +960,10 @@
     mkdir /data/vendor_ce 0551 root root encryption=None
     mkdir /data/vendor_de 0551 root root encryption=None
 
+    # Similar to the top-level CE and DE directories, /data/storage_area must
+    # itself be unencrypted, since it contains encrypted directories.
+    mkdir /data/storage_area 0551 root root encryption=None
+
     # Set the casefold flag on /data/media.  For upgrades, a restorecon can be
     # needed first to relabel the directory from media_rw_data_file.
     restorecon /data/media
@@ -948,8 +977,12 @@
     mkdir /data_mirror/data_de 0700 root root
     mkdir /data_mirror/misc_ce 0700 root root
     mkdir /data_mirror/misc_de 0700 root root
+    mkdir /data_mirror/storage_area 0700 root root
 
     # Create CE and DE data directory for default volume
+    # Not needed for storage_area directory, since this is
+    # not supported for non-default volumes and the path
+    # does not include the volume ID
     mkdir /data_mirror/data_ce/null 0700 root root
     mkdir /data_mirror/data_de/null 0700 root root
     mkdir /data_mirror/misc_ce/null 0700 root root
@@ -964,6 +997,9 @@
     mount none /data/misc_ce /data_mirror/misc_ce/null bind rec
     mount none /data/misc_de /data_mirror/misc_de/null bind rec
 
+    # Also bind mount for the storage area directory (minus the volume ID)
+    mount none /data/storage_area /data_mirror/storage_area bind rec
+
     # Create mirror directory for jit profiles
     mkdir /data_mirror/cur_profiles 0700 root root
     mount none /data/misc/profiles/cur /data_mirror/cur_profiles bind rec
@@ -994,9 +1030,11 @@
     # Wait for apexd to finish activating APEXes before starting more processes.
     wait_for_prop apexd.status activated
     perform_apex_config
+    exec_start aconfigd-mainline-init
+    start aconfigd
 
     # Create directories for boot animation.
-    mkdir /data/misc/bootanim 0755 system system encryption=DeleteIfNecessary
+    mkdir /data/misc/bootanim 0755 system system
 
     exec_start derive_sdk
 
@@ -1304,6 +1342,8 @@
   umount /data_mirror/data_ce/null/0
   umount /data_mirror/data_ce/null
   umount /data_mirror/data_de/null
+  umount /data_mirror/storage_area/0
+  umount /data_mirror/storage_area
   umount /data_mirror/cur_profiles
   umount /data_mirror/ref_profiles
   umount /data_mirror
@@ -1331,3 +1371,16 @@
   write /sys/kernel/mm/lru_gen/enabled 5
 on property:persist.device_config.mglru_native.lru_gen_config=all
   write /sys/kernel/mm/lru_gen/enabled 7
+
+# Allow other processes to run `snapshotctl` through `init`. This requires
+# `set_prop` permission on `snapshotctl_prop`.
+on property:sys.snapshotctl.map=requested
+    # "root" is needed to talk to gsid and pass its check on uid.
+    # "system" is needed to write to "/dev/socket/snapuserd" to talk to
+    # snapuserd.
+    exec - root root system -- /system/bin/snapshotctl map
+    setprop sys.snapshotctl.map "finished"
+
+on property:sys.snapshotctl.unmap=requested
+    exec - root root system -- /system/bin/snapshotctl unmap
+    setprop sys.snapshotctl.unmap "finished"
diff --git a/rootdir/init.zygote32.rc b/rootdir/init.zygote32.rc
index 442bd15..862afb6 100644
--- a/rootdir/init.zygote32.rc
+++ b/rootdir/init.zygote32.rc
@@ -16,5 +16,5 @@
     onrestart restart --only-if-running media.tuner
     onrestart restart netd
     onrestart restart wificond
-    task_profiles ProcessCapacityHigh
+    task_profiles ProcessCapacityHigh MaxPerformance
     critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
diff --git a/shell_and_utilities/README.md b/shell_and_utilities/README.md
index 9a733bb..c7c622e 100644
--- a/shell_and_utilities/README.md
+++ b/shell_and_utilities/README.md
@@ -37,7 +37,46 @@
 full list for a release by running `toybox` directly.
 
 
-## Android 14 ("U")
+## Android 15 (API level 35, "Vanilla Ice Cream")
+
+BSD: fsck\_msdos newfs\_msdos
+
+bzip2: bzcat bzip2 bunzip2
+
+gavinhoward/bc: bc
+
+one-true-awk: awk
+
+toolbox: getevent getprop setprop start stop
+
+toybox ([0.8.11](https://landley.net/toybox/news.html#08-04-2024)-ish):
+[ acpi base64 basename blkdiscard blkid blockdev brctl cal cat chattr
+chcon chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut
+date dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
+expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
+fsync getconf getenforce **getfattr** getopt **gpiodetect** **gpiofind**
+**gpioget** **gpioinfo** **gpioset** grep groups gunzip gzip head help hostname
+hwclock i2cdetect i2cdump i2cget i2cset **i2ctransfer** iconv id ifconfig
+inotifyd insmod install ionice iorenice iotop kill killall ln load\_policy
+log logger logname losetup ls lsattr lsmod lsof lspci lsusb makedevs
+md5sum **memeater** microcom mkdir mkfifo mknod mkswap mktemp modinfo modprobe
+more mount mountpoint mv nbd-client nc netcat netstat nice nl nohup
+nproc nsenter od partprobe paste patch pgrep pidof ping ping6 pivot\_root
+pkill pmap printenv printf prlimit ps pwd pwdx readelf readlink realpath
+renice restorecon rev rfkill rm rmdir rmmod rtcwake runcon sed sendevent
+seq setenforce **setfattr** setsid sha1sum sha224sum sha256sum sha384sum
+sha512sum sleep sort split stat strings stty swapoff swapon sync sysctl
+tac tail tar taskset tee test time timeout top touch tr traceroute
+traceroute6 true truncate tty tunctl uclampset ulimit umount uname
+uniq unix2dos unlink unshare uptime usleep uudecode uuencode uuidgen
+vconfig vi vmstat watch wc which whoami xargs xxd yes zcat
+
+Note: technically getfattr and setfattr were available in earlier versions,
+but the symlinks were missing until this release, so they were only available
+as `toybox getfattr` and `toybox setfattr` rather than directly.
+
+
+## Android 14 (API level 34, "Upside Down Cake")
 
 BSD: fsck\_msdos newfs\_msdos
 
@@ -71,7 +110,7 @@
 vconfig vi vmstat watch wc which whoami xargs xxd yes zcat
 
 
-## Android 13 ("T")
+## Android 13 (33, "Tiramisu")
 
 BSD: fsck\_msdos newfs\_msdos
 
@@ -105,7 +144,7 @@
 vconfig vi vmstat watch wc which whoami xargs xxd yes zcat
 
 
-## Android 12 ("S")
+## Android 12 (31, "Snow Cone")
 
 BSD: fsck\_msdos newfs\_msdos
 
@@ -139,7 +178,7 @@
 vmstat watch wc which whoami xargs xxd yes zcat
 
 
-## Android 11 ("R")
+## Android 11 (API level 30, "Red Velvet Cake")
 
 BSD: fsck\_msdos newfs\_msdos
 
@@ -173,7 +212,7 @@
 whoami xargs xxd yes zcat
 
 
-## Android 10 ("Q")
+## Android 10 (API level 29, "Quince Tart")
 
 BSD: grep fsck\_msdos newfs\_msdos
 
@@ -205,7 +244,7 @@
 wc which whoami xargs xxd yes zcat
 
 
-## Android 9.0 (Pie)
+## Android 9.0 (API level 28, "Pie")
 
 BSD: dd grep
 
@@ -232,7 +271,7 @@
 which whoami xargs xxd yes zcat
 
 
-## Android 8.0 (Oreo)
+## Android 8.0 (API level 26, "Oreo")
 
 BSD: dd grep
 
@@ -257,7 +296,7 @@
 vmstat wc which whoami xargs xxd yes **zcat**
 
 
-## Android 7.0 (Nougat)
+## Android 7.0 (API level 24, "Nougat")
 
 BSD: dd grep
 
@@ -279,7 +318,7 @@
 **uptime** usleep vmstat wc which whoami xargs **xxd** yes
 
 
-## Android 6.0 (Marshmallow)
+## Android 6.0 (API level 23, "Marshmallow")
 
 BSD: dd du grep
 
@@ -300,7 +339,7 @@
 vmstat wc which whoami xargs yes
 
 
-## Android 5.0 (Lollipop)
+## Android 5.0 (API level 21, "Lollipop")
 
 BSD: cat chown cp dd du grep kill ln mv printenv rm rmdir sleep sync
 
@@ -312,7 +351,7 @@
 top touch umount uptime vmstat watchprops wipe
 
 
-## Android 4.4 (KitKat)
+## Android 4.4 (API level 19, "KitKat")
 
 BSD: cat cp dd du grep newfs\_msdos
 
@@ -324,7 +363,7 @@
 stop swapoff swapon sync top touch umount uptime vmstat watchprops wipe
 
 
-## Android 4.1-4.3 (JellyBean)
+## Android 4.1-4.3 (API level 16, "Jelly Bean")
 
 BSD: cat cp dd du grep newfs\_msdos
 
@@ -336,7 +375,7 @@
 sync top touch umount uptime vmstat watchprops wipe
 
 
-## Android 4.0 (IceCreamSandwich)
+## Android 4.0 (API level 14, "Ice Cream Sandwich")
 
 BSD: cat dd newfs\_msdos
 
@@ -347,7 +386,7 @@
 touch umount uptime vmstat watchprops wipe
 
 
-## Android 2.3 (Gingerbread)
+## Android 2.3 (API level 9, "Gingerbread")
 
 BSD: cat dd newfs\_msdos
 
diff --git a/toolbox/Android.bp b/toolbox/Android.bp
index 8594ec4..120cc6e 100644
--- a/toolbox/Android.bp
+++ b/toolbox/Android.bp
@@ -69,6 +69,7 @@
     name: "toolbox",
     defaults: ["toolbox_binary_defaults"],
     recovery_available: true,
+    vendor_ramdisk_available: true,
 }
 
 cc_binary {
diff --git a/toolbox/OWNERS b/toolbox/OWNERS
index 5e2c581..898ddce 100644
--- a/toolbox/OWNERS
+++ b/toolbox/OWNERS
@@ -1,2 +1,3 @@
 include platform/system/core:/janitors/OWNERS
 per-file modprobe.c=willmcvicker@google.com,dvander@google.com
+per-file getevent.c=file:platform/frameworks/base:/INPUT_OWNERS
diff --git a/toolbox/getevent.c b/toolbox/getevent.c
index f65bb20..7b896e9 100644
--- a/toolbox/getevent.c
+++ b/toolbox/getevent.c
@@ -441,7 +441,7 @@
     if(res < (int)sizeof(*event)) {
         if(errno == EINTR)
             return 0;
-        fprintf(stderr, "could not get event, %s\n", strerror(errno));
+        fprintf(stderr, "could not get inotify events, %s\n", strerror(errno));
         return 1;
     }
     //printf("got %d bytes of event information\n", res);
@@ -664,7 +664,7 @@
                 if(ufds[i].revents & POLLIN) {
                     res = read(ufds[i].fd, &event, sizeof(event));
                     if(res < (int)sizeof(event)) {
-                        fprintf(stderr, "could not get event\n");
+                        fprintf(stderr, "could not get evdev event, %s\n", strerror(errno));
                         return 1;
                     }
                     if(get_time) {
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
index 17d4e31..13026ac 100644
--- a/toolbox/modprobe.cpp
+++ b/toolbox/modprobe.cpp
@@ -17,6 +17,7 @@
 #include <ctype.h>
 #include <getopt.h>
 #include <stdlib.h>
+#include <unistd.h>
 
 #include <string>
 
@@ -85,6 +86,20 @@
     }
 }
 
+static bool ModDirMatchesKernelPageSize(const char* mod_dir) {
+    static const unsigned int kernel_pgsize_kb = getpagesize() / 1024;
+    const char* mod_sfx = strrchr(mod_dir, '_');
+    unsigned int mod_pgsize_kb;
+    int mod_sfx_len;
+
+    if (mod_sfx == NULL || sscanf(mod_sfx, "_%uk%n", &mod_pgsize_kb, &mod_sfx_len) != 1 ||
+        strlen(mod_sfx) != mod_sfx_len) {
+        mod_pgsize_kb = 4;
+    }
+
+    return kernel_pgsize_kb == mod_pgsize_kb;
+}
+
 // Find directories in format of "/lib/modules/x.y.z-*".
 static int KernelVersionNameFilter(const dirent* de) {
     unsigned int major, minor;
@@ -100,7 +115,7 @@
     }
 
     if (android::base::StartsWith(de->d_name, kernel_version)) {
-        return 1;
+        return ModDirMatchesKernelPageSize(de->d_name);
     }
     return 0;
 }
@@ -112,6 +127,7 @@
     android::base::SetMinimumLogSeverity(android::base::INFO);
 
     std::vector<std::string> modules;
+    std::string modules_load_file;
     std::string module_parameters;
     std::string mods;
     std::vector<std::string> mod_dirs;
@@ -119,7 +135,7 @@
     bool blocklist = false;
     int rv = EXIT_SUCCESS;
 
-    int opt;
+    int opt, fd;
     int option_index = 0;
     // NB: We have non-standard short options -l and -D to make it easier for
     // OEMs to transition from toybox.
@@ -144,16 +160,19 @@
                 // is supported here by default, ignore flag if no argument.
                 check_mode();
                 if (optarg == NULL) break;
-                if (!android::base::ReadFileToString(optarg, &mods)) {
+
+                // Since libmodprobe doesn't fail when the modules load file
+                // doesn't exist, let's check that here so that we don't
+                // silently fail.
+                fd = open(optarg, O_RDONLY | O_CLOEXEC | O_BINARY);
+                if (fd == -1) {
                     PLOG(ERROR) << "Failed to open " << optarg;
-                    rv = EXIT_FAILURE;
+                    return EXIT_FAILURE;
                 }
-                for (auto mod : android::base::Split(stripComments(mods), "\n")) {
-                    mod = android::base::Trim(mod);
-                    if (mod == "") continue;
-                    if (std::find(modules.begin(), modules.end(), mod) != modules.end()) continue;
-                    modules.emplace_back(mod);
-                }
+                close(fd);
+
+                mod_dirs.emplace_back(android::base::Dirname(optarg));
+                modules_load_file = android::base::Basename(optarg);
                 break;
             case 'b':
                 blocklist = true;
@@ -226,37 +245,48 @@
         }
         free(kernel_dirs);
 
-        // Allow modules to be directly inside /lib/modules
-        mod_dirs.emplace_back(LIB_MODULES_PREFIX);
+        if (mod_dirs.empty() || getpagesize() == 4096) {
+            // Allow modules to be directly inside /lib/modules
+            mod_dirs.emplace_back(LIB_MODULES_PREFIX);
+        }
     }
 
     LOG(DEBUG) << "mode is " << mode;
     LOG(DEBUG) << "mod_dirs is: " << android::base::Join(mod_dirs, " ");
     LOG(DEBUG) << "modules is: " << android::base::Join(modules, " ");
+    LOG(DEBUG) << "modules load file is: " << modules_load_file;
     LOG(DEBUG) << "module parameters is: " << android::base::Join(module_parameters, " ");
 
     if (modules.empty()) {
         if (mode == ListModulesMode) {
             // emulate toybox modprobe list with no pattern (list all)
             modules.emplace_back("*");
-        } else {
+        } else if (modules_load_file.empty()) {
             LOG(ERROR) << "No modules given.";
             print_usage();
             return EXIT_FAILURE;
         }
     }
-    if (parameter_count && modules.size() > 1) {
+    if (parameter_count && (modules.size() > 1 || !modules_load_file.empty())) {
         LOG(ERROR) << "Only one module may be loaded when specifying module parameters.";
         print_usage();
         return EXIT_FAILURE;
     }
 
-    Modprobe m(mod_dirs, "modules.load", blocklist);
+    Modprobe m(mod_dirs, modules_load_file.empty() ? "modules.load" : modules_load_file, blocklist);
+    if (mode == AddModulesMode && !modules_load_file.empty()) {
+        if (!m.LoadListedModules(false)) {
+            PLOG(ERROR) << "Failed to load all the modules from " << modules_load_file;
+            return EXIT_FAILURE;
+        }
+        /* Fall-through to load modules provided on the command line (if any)*/
+    }
 
     for (const auto& module : modules) {
         switch (mode) {
             case AddModulesMode:
                 if (!m.LoadWithAliases(module, true, module_parameters)) {
+                    if (m.IsBlocklisted(module)) continue;
                     PLOG(ERROR) << "Failed to load module " << module;
                     rv = EXIT_FAILURE;
                 }
diff --git a/toolbox/setprop.cpp b/toolbox/setprop.cpp
index acf8c3e..91edf45 100644
--- a/toolbox/setprop.cpp
+++ b/toolbox/setprop.cpp
@@ -58,7 +58,7 @@
         }
     }
 
-    if (value.size() >= PROP_VALUE_MAX && !StartsWith(value, "ro.")) {
+    if (value.size() >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
         std::cerr << "Value '" << value << "' is too long, " << value.size()
                   << " bytes vs a max of " << PROP_VALUE_MAX << std::endl;
         return EXIT_FAILURE;
diff --git a/trusty/OWNERS b/trusty/OWNERS
index 4016792..46f1410 100644
--- a/trusty/OWNERS
+++ b/trusty/OWNERS
@@ -1,11 +1,5 @@
-armellel@google.com
-arve@android.com
-danielangell@google.com
-gmar@google.com
+# include OWNERS from the top level trusty repo
+include trusty:main:/OWNERS
+
 mikemcternan@google.com
-mmaurer@google.com
-ncbray@google.com
-swillden@google.com
-thurston@google.com
-trong@google.com
-wenhaowang@google.com
+swillden@google.com
\ No newline at end of file
diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp
index 5d0ff79..8a93e5e 100644
--- a/trusty/fuzz/Android.bp
+++ b/trusty/fuzz/Android.bp
@@ -41,9 +41,6 @@
         "utils.cpp",
     ],
     export_include_dirs: ["include"],
-    static_libs: [
-        "libFuzzer",
-    ],
     shared_libs: [
         "libtrusty_coverage",
         "libbase",
diff --git a/trusty/fuzz/counters.cpp b/trusty/fuzz/counters.cpp
index 65a3ba6..e730ec3 100644
--- a/trusty/fuzz/counters.cpp
+++ b/trusty/fuzz/counters.cpp
@@ -16,12 +16,12 @@
 
 #define LOG_TAG "trusty-fuzz-counters"
 
-#include <FuzzerDefs.h>
-
 #include <trusty/fuzz/counters.h>
 
 #include <android-base/logging.h>
+#include <assert.h>
 #include <log/log.h>
+#include <string.h>
 #include <trusty/coverage/coverage.h>
 #include <trusty/coverage/tipc.h>
 
@@ -45,9 +45,6 @@
         return;
     }
 
-    assert(fuzzer::ExtraCountersBegin());
-    assert(fuzzer::ExtraCountersEnd());
-
     volatile uint8_t* begin = NULL;
     volatile uint8_t* end = NULL;
     record_->GetRawCounts(&begin, &end);
@@ -66,9 +63,8 @@
     if (!record_->IsOpen()) {
         return;
     }
-
     record_->ResetCounts();
-    fuzzer::ClearExtraCounters();
+    memset_explicit(const_cast<uint8_t*>(counters), 0, sizeof(counters));
 }
 
 void ExtraCounters::Flush() {
diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp
index edc2a79..f265ced 100644
--- a/trusty/fuzz/tipc_fuzzer.cpp
+++ b/trusty/fuzz/tipc_fuzzer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#include <android-base/result.h>
-#include <fuzzer/FuzzedDataProvider.h>
 #include <stdlib.h>
 #include <trusty/coverage/coverage.h>
 #include <trusty/coverage/uuid.h>
@@ -25,7 +23,6 @@
 #include <iostream>
 #include <memory>
 
-using android::base::Result;
 using android::trusty::coverage::CoverageRecord;
 using android::trusty::fuzz::ExtraCounters;
 using android::trusty::fuzz::TrustyApp;
@@ -44,14 +41,7 @@
 #error "Binary file name must be parameterized using -DTRUSTY_APP_FILENAME."
 #endif
 
-#ifdef TRUSTY_APP_MAX_CONNECTIONS
-constexpr size_t MAX_CONNECTIONS = TRUSTY_APP_MAX_CONNECTIONS;
-#else
-constexpr size_t MAX_CONNECTIONS = 1;
-#endif
-
-static_assert(MAX_CONNECTIONS >= 1);
-
+static TrustyApp kTrustyApp(TIPC_DEV, TRUSTY_APP_PORT);
 static std::unique_ptr<CoverageRecord> record;
 
 extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
@@ -63,8 +53,7 @@
     }
 
     /* Make sure lazy-loaded TAs have started and connected to coverage service. */
-    TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
-    auto ret = ta.Connect();
+    auto ret = kTrustyApp.Connect();
     if (!ret.ok()) {
         std::cerr << ret.error() << std::endl;
         exit(-1);
@@ -84,56 +73,24 @@
     return 0;
 }
 
-Result<void> testOneInput(FuzzedDataProvider& provider) {
-    std::vector<TrustyApp> trustyApps;
-
-    while (provider.remaining_bytes() > 0) {
-        if (trustyApps.size() < MAX_CONNECTIONS && provider.ConsumeBool()) {
-            auto& ta = trustyApps.emplace_back(TIPC_DEV, TRUSTY_APP_PORT);
-            const auto result = ta.Connect();
-            if (!result.ok()) {
-                return result;
-            }
-        } else {
-            const auto i = provider.ConsumeIntegralInRange<size_t>(0, trustyApps.size());
-            std::swap(trustyApps[i], trustyApps.back());
-
-            if (provider.ConsumeBool()) {
-                auto& ta = trustyApps.back();
-
-                const auto data = provider.ConsumeRandomLengthString();
-                auto result = ta.Write(data.data(), data.size());
-                if (!result.ok()) {
-                    return result;
-                }
-
-                std::array<uint8_t, TIPC_MAX_MSG_SIZE> buf;
-                result = ta.Read(buf.data(), buf.size());
-                if (!result.ok()) {
-                    return result;
-                }
-
-                // Reconnect to ensure that the service is still up.
-                ta.Disconnect();
-                result = ta.Connect();
-                if (!result.ok()) {
-                    std::cerr << result.error() << std::endl;
-                    android::trusty::fuzz::Abort();
-                    return result;
-                }
-            } else {
-                trustyApps.pop_back();
-            }
-        }
-    }
-    return {};
-}
-
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    static uint8_t buf[TIPC_MAX_MSG_SIZE];
+
     ExtraCounters counters(record.get());
     counters.Reset();
 
-    FuzzedDataProvider provider(data, size);
-    const auto result = testOneInput(provider);
-    return result.ok() ? 0 : -1;
+    auto ret = kTrustyApp.Write(data, size);
+    if (ret.ok()) {
+        ret = kTrustyApp.Read(&buf, sizeof(buf));
+    }
+
+    // Reconnect to ensure that the service is still up
+    kTrustyApp.Disconnect();
+    ret = kTrustyApp.Connect();
+    if (!ret.ok()) {
+        std::cerr << ret.error() << std::endl;
+        android::trusty::fuzz::Abort();
+    }
+
+    return ret.ok() ? 0 : -1;
 }
diff --git a/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp b/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
index 6b8f90f..dec64e1 100644
--- a/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
+++ b/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
@@ -247,6 +247,7 @@
         return EXIT_FAILURE;
     } else {
         printf("done\n");
+        printf("\nNOTE: device reboot may be required before changes take effect.\n");
         return EXIT_SUCCESS;
     }
 }
diff --git a/trusty/keymint/src/keymint_hal_main.rs b/trusty/keymint/src/keymint_hal_main.rs
index cfa859f..ef0c598 100644
--- a/trusty/keymint/src/keymint_hal_main.rs
+++ b/trusty/keymint/src/keymint_hal_main.rs
@@ -82,7 +82,7 @@
 }
 
 fn main() {
-    if let Err(e) = inner_main() {
+    if let Err(HalServiceError(e)) = inner_main() {
         panic!("HAL service failed: {:?}", e);
     }
 }
@@ -92,8 +92,8 @@
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("keymint-hal-trusty")
-            .with_min_level(log::Level::Info)
-            .with_log_id(android_logger::LogId::System),
+            .with_max_level(log::LevelFilter::Info)
+            .with_log_buffer(android_logger::LogId::System),
     );
     // Redirect panic messages to logcat.
     panic::set_hook(Box::new(|panic_info| {
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index dabe118..3cf0c05 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -37,7 +37,7 @@
 #define BENCH_RESULT_TPL                                    \
 "{"                                                         \
 "    \"schema_version\": 3,"                                \
-"    \"suite_name\": \"crypto\","                           \
+"    \"suite_name\": \"tipc\","                           \
 "    \"bench_name\": \"%s\","                               \
 "    \"results\": ["                                        \
 "        {"                                                 \
@@ -1041,7 +1041,7 @@
     }
     avg /= params->bench;
 
-    fprintf(stderr, BENCH_RESULT_TPL, params->test_name, min, max, avg, cold, min, max, avg, cold);
+    printf(BENCH_RESULT_TPL, params->test_name, min, max, avg, cold, min, max, avg, cold);
     return rc;
 }
 
diff --git a/trusty/line-coverage/coverage.cpp b/trusty/line-coverage/coverage.cpp
index 5f7b3a3..e4db59c 100644
--- a/trusty/line-coverage/coverage.cpp
+++ b/trusty/line-coverage/coverage.cpp
@@ -174,7 +174,7 @@
     }
 
     uintptr_t* begin = (uintptr_t*)((char *)shm_ + sizeof(struct control));
-    bool ret = WriteFully(output_fd, begin, record_len_);
+    bool ret = WriteFully(output_fd, begin, record_len_ - sizeof(struct control));
     if(!ret) {
         fprintf(stderr, "Coverage write to file failed\n");
     }
diff --git a/trusty/metrics/include/trusty/metrics/tipc.h b/trusty/metrics/include/trusty/metrics/tipc.h
index 66d0876..b4428d5 100644
--- a/trusty/metrics/include/trusty/metrics/tipc.h
+++ b/trusty/metrics/include/trusty/metrics/tipc.h
@@ -39,14 +39,18 @@
  * repository. They must be kept in sync.
  */
 
-#define METRICS_PORT "com.android.trusty.metrics"
+#define METRICS_PORT "com.android.trusty.metrics.consumer"
+
+#define UUID_STR_SIZE (37)
 
 /**
  * enum metrics_cmd - command identifiers for metrics interface
- * @METRICS_CMD_RESP_BIT:          message is a response
- * @METRICS_CMD_REQ_SHIFT:         number of bits used by @METRICS_CMD_RESP_BIT
- * @METRICS_CMD_REPORT_EVENT_DROP: report gaps in the event stream
- * @METRICS_CMD_REPORT_CRASH:      report an app crash event
+ * @METRICS_CMD_RESP_BIT:             message is a response
+ * @METRICS_CMD_REQ_SHIFT:            number of bits used by @METRICS_CMD_RESP_BIT
+ * @METRICS_CMD_REPORT_EVENT_DROP:    report gaps in the event stream
+ * @METRICS_CMD_REPORT_CRASH:         report an app crash event
+ * @METRICS_CMD_REPORT_EXIT:          report an app exit
+ * @METRICS_CMD_REPORT_STORAGE_ERROR: report trusty storage error
  */
 enum metrics_cmd {
     METRICS_CMD_RESP_BIT = 1,
@@ -54,6 +58,8 @@
 
     METRICS_CMD_REPORT_EVENT_DROP = (1 << METRICS_CMD_REQ_SHIFT),
     METRICS_CMD_REPORT_CRASH = (2 << METRICS_CMD_REQ_SHIFT),
+    METRICS_CMD_REPORT_EXIT = (3 << METRICS_CMD_REQ_SHIFT),
+    METRICS_CMD_REPORT_STORAGE_ERROR = (4 << METRICS_CMD_REQ_SHIFT),
 };
 
 /**
@@ -88,16 +94,86 @@
 } __attribute__((__packed__));
 
 /**
- * struct metrics_report_crash_req - arguments of %METRICS_CMD_REPORT_CRASH
+ * struct metrics_report_exit_req - arguments of %METRICS_CMD_REPORT_EXIT
  *                                   requests
- * @app_id_len: length of app ID that follows this structure
+ * @app_id: app_id in the form UUID in ascii format
+ *          "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ * @exit_code: architecture-specific exit code
  */
-struct metrics_report_crash_req {
-    uint32_t app_id_len;
+struct metrics_report_exit_req {
+    char app_id[UUID_STR_SIZE];
+    uint32_t exit_code;
 } __attribute__((__packed__));
 
-#define METRICS_MAX_APP_ID_LEN 256
+/**
+ * struct metrics_report_crash_req - arguments of %METRICS_CMD_REPORT_CRASH
+ *                                   requests
+ * @app_id: app_id in the form UUID in ascii format
+ *          "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ * @crash_reason: architecture-specific code representing the reason for the
+ *                crash
+ */
+struct metrics_report_crash_req {
+    char app_id[UUID_STR_SIZE];
+    uint32_t crash_reason;
+} __attribute__((__packed__));
 
-#define METRICS_MAX_MSG_SIZE                                                \
-    (sizeof(struct metrics_req) + sizeof(struct metrics_report_crash_req) + \
-     METRICS_MAX_APP_ID_LEN)
+enum TrustyStorageErrorType {
+  TRUSTY_STORAGE_ERROR_UNKNOWN = 0,
+  TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID = 1,
+  TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH = 2,
+  TRUSTY_STORAGE_ERROR_BLOCK_HEADER_INVALID = 3,
+  TRUSTY_STORAGE_ERROR_RPMB_COUNTER_MISMATCH = 4,
+  TRUSTY_STORAGE_ERROR_RPMB_COUNTER_MISMATCH_RECOVERED = 5,
+  TRUSTY_STORAGE_ERROR_RPMB_COUNTER_READ_FAILURE = 6,
+  TRUSTY_STORAGE_ERROR_RPMB_MAC_MISMATCH = 7,
+  TRUSTY_STORAGE_ERROR_RPMB_ADDR_MISMATCH = 8,
+  TRUSTY_STORAGE_ERROR_RPMB_FAILURE_RESPONSE = 9,
+  TRUSTY_STORAGE_ERROR_RPMB_UNKNOWN = 10,
+  TRUSTY_STORAGE_ERROR_RPMB_SCSI_ERROR = 11,
+  TRUSTY_STORAGE_ERROR_IO_ERROR = 12,
+  TRUSTY_STORAGE_ERROR_PROXY_COMMUNICATION_FAILURE = 13,
+};
+
+enum TrustyFileSystem {
+  TRUSTY_FS_UNKNOWN = 0,
+  TRUSTY_FS_TP = 1,
+  TRUSTY_FS_TD = 2,
+  TRUSTY_FS_TDP = 3,
+  TRUSTY_FS_TDEA = 4,
+  TRUSTY_FS_NSP = 5,
+};
+
+enum TrustyBlockType {
+  TRUSTY_BLOCKTYPE_UNKNOWN = 0,
+  TRUSTY_BLOCKTYPE_FILES_ROOT = 1,
+  TRUSTY_BLOCKTYPE_FREE_ROOT = 2,
+  TRUSTY_BLOCKTYPE_FILES_INTERNAL = 3,
+  TRUSTY_BLOCKTYPE_FREE_INTERNAL = 4,
+  TRUSTY_BLOCKTYPE_FILE_ENTRY = 5,
+  TRUSTY_BLOCKTYPE_FILE_BLOCK_MAP = 6,
+  TRUSTY_BLOCKTYPE_FILE_DATA = 7,
+  TRUSTY_BLOCKTYPE_CHECKPOINT_ROOT = 8,
+  TRUSTY_BLOCKTYPE_CHECKPOINT_FILES_ROOT = 9,
+  TRUSTY_BLOCKTYPE_CHECKPOINT_FREE_ROOT = 10,
+};
+
+struct metrics_report_storage_error_req {
+    enum TrustyStorageErrorType error;
+    char app_id[UUID_STR_SIZE];
+    char client_app_id[UUID_STR_SIZE];
+    uint32_t write;
+    enum TrustyFileSystem file_system;
+    uint64_t file_path_hash;
+    enum TrustyBlockType block_type;
+    uint64_t repair_counter;
+} __attribute__((__packed__));
+
+struct metrics_msg {
+    struct metrics_req req;
+    union {
+        struct metrics_report_crash_req crash_args;
+        struct metrics_report_exit_req exit_args;
+        struct metrics_report_storage_error_req storage_args;
+    };
+} __attribute__((__packed__));
\ No newline at end of file
diff --git a/trusty/metrics/metrics.cpp b/trusty/metrics/metrics.cpp
index 3ac128a..d2f0b0a 100644
--- a/trusty/metrics/metrics.cpp
+++ b/trusty/metrics/metrics.cpp
@@ -78,9 +78,8 @@
         return Error() << "connection to Metrics TA has not been initialized yet";
     }
 
-    uint8_t msg[METRICS_MAX_MSG_SIZE];
-
-    auto rc = read(metrics_fd_, msg, sizeof(msg));
+    struct metrics_msg metrics_msg;
+    int rc = read(metrics_fd_, &metrics_msg, sizeof(metrics_msg));
     if (rc < 0) {
         return ErrnoError() << "failed to read metrics message";
     }
@@ -89,23 +88,14 @@
     if (msg_len < sizeof(metrics_req)) {
         return Error() << "message too small: " << rc;
     }
-    auto req = reinterpret_cast<metrics_req*>(msg);
-    size_t offset = sizeof(metrics_req);
+    uint32_t cmd = metrics_msg.req.cmd;
     uint32_t status = METRICS_NO_ERROR;
 
-    switch (req->cmd) {
+    switch (cmd) {
         case METRICS_CMD_REPORT_CRASH: {
-            if (msg_len < offset + sizeof(metrics_report_crash_req)) {
-                return Error() << "message too small: " << rc;
-            }
-            auto crash_args = reinterpret_cast<metrics_report_crash_req*>(msg + offset);
-            offset += sizeof(metrics_report_crash_req);
-
-            if (msg_len < offset + crash_args->app_id_len) {
-                return Error() << "message too small: " << rc;
-            }
-            auto app_id_ptr = reinterpret_cast<char*>(msg + offset);
-            std::string app_id(app_id_ptr, crash_args->app_id_len);
+            struct metrics_report_crash_req crash_args = metrics_msg.crash_args;
+            auto app_id_ptr = crash_args.app_id;
+            std::string app_id(app_id_ptr, UUID_STR_SIZE);
 
             HandleCrash(app_id);
             break;
@@ -121,7 +111,7 @@
     }
 
     metrics_resp resp = {
-            .cmd = req->cmd | METRICS_CMD_RESP_BIT,
+            .cmd = cmd | METRICS_CMD_RESP_BIT,
             .status = status,
     };
 
diff --git a/trusty/secretkeeper/Android.bp b/trusty/secretkeeper/Android.bp
index f6b740a..6523eda 100644
--- a/trusty/secretkeeper/Android.bp
+++ b/trusty/secretkeeper/Android.bp
@@ -28,12 +28,13 @@
     ],
     rustlibs: [
         "libandroid_logger",
-        "libbinder_rs",
         "libauthgraph_hal",
-        "libtrusty-rs",
+        "libauthgraph_wire",
+        "libbinder_rs",
         "liblibc",
         "liblog_rust",
         "libsecretkeeper_hal",
+        "libtrusty-rs",
     ],
     defaults: [
         "secretkeeper_use_latest_hal_aidl_rust",
diff --git a/trusty/secretkeeper/src/hal_main.rs b/trusty/secretkeeper/src/hal_main.rs
index 9439c36..b31db13 100644
--- a/trusty/secretkeeper/src/hal_main.rs
+++ b/trusty/secretkeeper/src/hal_main.rs
@@ -14,7 +14,8 @@
 // limitations under the License.
 
 //! This module implements the HAL service for Secretkeeper in Trusty.
-use authgraph_hal::{channel::SerializedChannel};
+use authgraph_hal::channel::SerializedChannel;
+use authgraph_wire::fragmentation::{Fragmenter, Reassembler};
 use secretkeeper_hal::SecretkeeperService;
 use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{
     ISecretkeeper, BpSecretkeeper,
@@ -22,6 +23,7 @@
 use log::{error, info};
 use std::{
     ffi::CString,
+    fmt::Debug,
     panic,
     sync::{Arc, Mutex},
 };
@@ -29,6 +31,7 @@
 
 const SK_TIPC_SERVICE_PORT: &str = "com.android.trusty.secretkeeper";
 const AG_TIPC_SERVICE_PORT: &str = "com.android.trusty.secretkeeper.authgraph";
+const TIPC_MAX_SIZE: usize = 4000;
 
 static SERVICE_INSTANCE: &str = "default";
 
@@ -47,43 +50,48 @@
     }
 }
 
+fn binderr<E: Debug>(msg: &str, e: E) -> binder::Status {
+    binder::Status::new_exception(
+        binder::ExceptionCode::TRANSACTION_FAILED,
+        Some(&CString::new(format!("Failed to {msg} via tipc channel: {e:?}",)).unwrap()),
+    )
+}
+
 impl SerializedChannel for TipcChannel {
-    const MAX_SIZE: usize = 4000;
+    // No maximum size for messages passed to `execute()` because it performs fragmentation
+    // and reassembly internally.
+    const MAX_SIZE: usize = usize::MAX;
+
     fn execute(&self, req_data: &[u8]) -> binder::Result<Vec<u8>> {
         // Hold lock across both request and response.
         let mut channel = self.channel.lock().unwrap();
-        channel.send(req_data).map_err(|e| {
-            binder::Status::new_exception(
-                binder::ExceptionCode::TRANSACTION_FAILED,
-                Some(
-                    &CString::new(format!(
-                        "Failed to send the request via tipc channel because of {:?}",
-                        e
-                    ))
-                    .unwrap(),
-                ),
-            )
-        })?;
-        // TODO: cope with fragmentation and reassembly
-        let mut rsp_data = Vec::new();
-        channel.recv(&mut rsp_data).map_err(|e| {
-            binder::Status::new_exception(
-                binder::ExceptionCode::TRANSACTION_FAILED,
-                Some(
-                    &CString::new(format!(
-                        "Failed to receive the response via tipc channel because of {:?}",
-                        e
-                    ))
-                    .unwrap(),
-                ),
-            )
-        })?;
-        Ok(rsp_data)
+        let mut pending_rsp = Reassembler::default();
+
+        // Break request message into fragments to send.
+        for req_frag in Fragmenter::new(req_data, TIPC_MAX_SIZE) {
+            channel.send(&req_frag).map_err(|e| binderr("send request", e))?;
+
+            // Every request gets a response.
+            let mut rsp_frag = Vec::new();
+            channel.recv(&mut rsp_frag).map_err(|e| binderr("receive response", e))?;
+
+            if let Some(full_rsp) = pending_rsp.accumulate(&rsp_frag) {
+                return Ok(full_rsp.to_vec());
+            }
+        }
+        // There may be additional response fragments to receive.
+        loop {
+            let mut rsp_frag = Vec::new();
+            channel.recv(&mut rsp_frag).map_err(|e| binderr("receive response", e))?;
+            if let Some(full_rsp) = pending_rsp.accumulate(&rsp_frag) {
+                return Ok(full_rsp.to_vec());
+            }
+        }
     }
 }
 
 fn main() {
-    if let Err(e) = inner_main() {
+    if let Err(HalServiceError(e)) = inner_main() {
         panic!("HAL service failed: {:?}", e);
     }
 }
@@ -93,8 +101,8 @@
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("secretkeeper-hal-trusty")
-            .with_min_level(log::Level::Info)
-            .with_log_id(android_logger::LogId::System),
+            .with_max_level(log::LevelFilter::Info)
+            .with_log_buffer(android_logger::LogId::System),
     );
     // Redirect panic messages to logcat.
     panic::set_hook(Box::new(|panic_info| {
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index 67e935e..6cb72d5 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -41,9 +41,13 @@
 static const char* trusty_devname;
 static const char* rpmb_devname;
 static const char* ss_srv_name = STORAGE_DISK_PROXY_PORT;
+static const char* max_file_size_from;
 
 static enum dev_type dev_type = MMC_RPMB;
 
+/* List head for storage mapping, elements added at init, and never removed */
+static struct storage_mapping_node* storage_mapping_head;
+
 static enum dev_type parse_dev_type(const char* dev_type_name) {
     if (!strcmp(dev_type_name, "mmc")) {
         return MMC_RPMB;
@@ -58,17 +62,61 @@
     }
 }
 
-static const char* _sopts = "hp:d:r:t:";
+static int parse_and_append_file_mapping(const char* file_mapping) {
+    if (file_mapping == NULL) {
+        ALOGE("Provided file mapping is null\n");
+        return -1;
+    }
+    char* file_mapping_dup = strdup(file_mapping);
+    if (file_mapping_dup == NULL) {
+        ALOGE("Couldn't duplicate string: %s\n", file_mapping);
+        return -1;
+    }
+    const char* file_name = strtok(file_mapping_dup, ":");
+    if (file_name == NULL) {
+        ALOGE("No file name found\n");
+        return -1;
+    }
+    const char* backing_storage = strtok(NULL, ":");
+    if (backing_storage == NULL) {
+        ALOGE("No backing storage found\n");
+        return -1;
+    }
+
+    struct storage_mapping_node* new_node = malloc(sizeof(struct storage_mapping_node));
+    if (new_node == NULL) {
+        ALOGE("Couldn't allocate additional storage_mapping_node\n");
+        return -1;
+    }
+    *new_node = (struct storage_mapping_node){.file_name = file_name,
+                                              .backing_storage = backing_storage,
+                                              .next = storage_mapping_head,
+                                              .fd = -1};
+    storage_mapping_head = new_node;
+    return 0;
+}
+
+static const char* _sopts = "hp:d:r:t:m:f:";
 static const struct option _lopts[] = {{"help", no_argument, NULL, 'h'},
                                        {"trusty_dev", required_argument, NULL, 'd'},
                                        {"data_path", required_argument, NULL, 'p'},
                                        {"rpmb_dev", required_argument, NULL, 'r'},
                                        {"dev_type", required_argument, NULL, 't'},
+                                       {"max_file_size_from", required_argument, NULL, 'm'},
+                                       {"file_storage_mapping", required_argument, NULL, 'f'},
                                        {0, 0, 0, 0}};
 
 static void show_usage_and_exit(int code) {
-    ALOGE("usage: storageproxyd -d <trusty_dev> -p <data_path> -r <rpmb_dev> -t <dev_type>\n");
+    ALOGE("usage: storageproxyd -d <trusty_dev> -p <data_path> -r <rpmb_dev> -t <dev_type>  [-m "
+          "<file>] [-f <file>:<mapping>]\n");
     ALOGE("Available dev types: mmc, virt\n");
+    ALOGE("-f = Maps secure storage files like `0` and `persist/0`\n"
+          "to block devices.  Storageproxyd will handle creating the\n"
+          "appropriate symlinks in the root datapath.\n");
+    ALOGE("-m = Specifies the max size constraint for file backed storages.\n"
+          "The constraint is chosen by giving a file, this allows for passing a\n"
+          "block device for which a max file size can be queried.  File based\n"
+          "storages will be constrained to that size as well.\n");
     exit(code);
 }
 
@@ -187,6 +235,7 @@
 static void parse_args(int argc, char* argv[]) {
     int opt;
     int oidx = 0;
+    int rc = 0;
 
     while ((opt = getopt_long(argc, argv, _sopts, _lopts, &oidx)) != -1) {
         switch (opt) {
@@ -210,6 +259,18 @@
                 }
                 break;
 
+            case 'f':
+                rc = parse_and_append_file_mapping(optarg);
+                if (rc < 0) {
+                    ALOGE("Failed to parse file mapping: %s\n", optarg);
+                    show_usage_and_exit(EXIT_FAILURE);
+                }
+                break;
+
+            case 'm':
+                max_file_size_from = strdup(optarg);
+                break;
+
             default:
                 ALOGE("unrecognized option (%c):\n", opt);
                 show_usage_and_exit(EXIT_FAILURE);
@@ -225,6 +286,12 @@
     ALOGI("storage data root: %s\n", ss_data_root);
     ALOGI("trusty dev: %s\n", trusty_devname);
     ALOGI("rpmb dev: %s\n", rpmb_devname);
+    ALOGI("File Mappings: \n");
+    const struct storage_mapping_node* curr = storage_mapping_head;
+    for (; curr != NULL; curr = curr->next) {
+        ALOGI("\t%s -> %s\n", curr->file_name, curr->backing_storage);
+    }
+    ALOGI("max file size from: %s\n", max_file_size_from ? max_file_size_from : "(unset)");
 }
 
 int main(int argc, char* argv[]) {
@@ -252,7 +319,7 @@
     ABinderProcess_startThreadPool();
 
     /* initialize secure storage directory */
-    rc = storage_init(ss_data_root);
+    rc = storage_init(ss_data_root, storage_mapping_head, max_file_size_from);
     if (rc < 0) return EXIT_FAILURE;
 
     /* open rpmb device */
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index 8c8edb7..6d0c616 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <assert.h>
 #include <cutils/properties.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -39,16 +40,20 @@
 #define ALTERNATE_DATA_DIR "alternate/"
 
 /* Maximum file size for filesystem backed storage (i.e. not block dev backed storage) */
-#define MAX_FILE_SIZE (0x10000000000)
+static uint64_t max_file_size = 0x10000000000;
 
 enum sync_state {
     SS_UNUSED = -1,
-    SS_CLEAN =  0,
-    SS_DIRTY =  1,
+    SS_CLEAN = 0,
+    SS_DIRTY = 1,
+    SS_CLEAN_NEED_SYMLINK = 2,
 };
 
 static const char *ssdir_name;
 
+/* List head for storage mapping, elements added at init, and never removed */
+static struct storage_mapping_node* storage_mapping_head;
+
 /*
  * Property set to 1 after we have opened a file under ssdir_name. The backing
  * files for both TD and TDP are currently located under /data/vendor/ss and can
@@ -75,24 +80,103 @@
    uint8_t data[MAX_READ_SIZE];
 }  read_rsp;
 
-static uint32_t insert_fd(int open_flags, int fd)
-{
+static uint32_t insert_fd(int open_flags, int fd, struct storage_mapping_node* node) {
     uint32_t handle = fd;
 
     if (handle < FD_TBL_SIZE) {
-            fd_state[fd] = SS_CLEAN; /* fd clean */
-            if (open_flags & O_TRUNC) {
-                fd_state[fd] = SS_DIRTY;  /* set fd dirty */
-            }
+        fd_state[fd] = SS_CLEAN; /* fd clean */
+        if (open_flags & O_TRUNC) {
+            assert(node == NULL);
+            fd_state[fd] = SS_DIRTY; /* set fd dirty */
+        }
+
+        if (node != NULL) {
+            fd_state[fd] = SS_CLEAN_NEED_SYMLINK;
+        }
     } else {
             ALOGW("%s: untracked fd %u\n", __func__, fd);
             if (open_flags & (O_TRUNC | O_CREAT)) {
                 fs_state = SS_DIRTY;
             }
     }
+
+    if (node != NULL) {
+        node->fd = fd;
+    }
+
     return handle;
 }
 
+static void clear_fd_symlink_status(uint32_t handle, struct storage_mapping_node* entry) {
+    /* Always clear FD, in case fd is not in FD_TBL */
+    entry->fd = -1;
+
+    if (handle >= FD_TBL_SIZE) {
+        ALOGE("%s: untracked fd=%u\n", __func__, handle);
+        return;
+    }
+
+    if (fd_state[handle] == SS_CLEAN_NEED_SYMLINK) {
+        fd_state[handle] = SS_CLEAN;
+    }
+}
+
+static struct storage_mapping_node* get_pending_symlink_mapping(uint32_t handle) {
+    /* Fast lookup failure, is it in FD TBL */
+    if (handle < FD_TBL_SIZE && fd_state[handle] != SS_CLEAN_NEED_SYMLINK) {
+        return NULL;
+    }
+
+    /* Go find our mapping */
+    struct storage_mapping_node* curr = storage_mapping_head;
+    for (; curr != NULL; curr = curr->next) {
+        if (curr->fd == handle) {
+            return curr;
+        }
+    }
+
+    /* Safety check: state inconsistent if we get here with handle inside table range */
+    assert(handle >= FD_TBL_SIZE);
+
+    return NULL;
+};
+
+static int possibly_symlink_and_clear_mapping(uint32_t handle) {
+    struct storage_mapping_node* entry = get_pending_symlink_mapping(handle);
+    if (entry == NULL) {
+        /* No mappings pending */
+        return 0;
+    }
+
+    /* Create full path */
+    char* path = NULL;
+    int rc = asprintf(&path, "%s/%s", ssdir_name, entry->file_name);
+    if (rc < 0) {
+        ALOGE("%s: asprintf failed\n", __func__);
+        return -1;
+    }
+
+    /* Try and setup the symlinking */
+    ALOGI("Creating symlink %s->%s\n", path, entry->backing_storage);
+    rc = symlink(entry->backing_storage, path);
+    if (rc < 0) {
+        ALOGE("%s: error symlinking %s->%s (%s)\n", __func__, path, entry->backing_storage,
+              strerror(errno));
+        free(path);
+        return rc;
+    }
+    free(path);
+
+    clear_fd_symlink_status(handle, entry);
+
+    return rc;
+}
+
+static bool is_pending_symlink(uint32_t handle) {
+    struct storage_mapping_node* entry = get_pending_symlink_mapping(handle);
+    return entry != NULL;
+}
+
 static int lookup_fd(uint32_t handle, bool dirty)
 {
     if (dirty) {
@@ -107,6 +191,12 @@
 
 static int remove_fd(uint32_t handle)
 {
+    /* Cleanup fd in symlink mapping if it exists */
+    struct storage_mapping_node* entry = get_pending_symlink_mapping(handle);
+    if (entry != NULL) {
+        entry->fd = -1;
+    }
+
     if (handle < FD_TBL_SIZE) {
         fd_state[handle] = SS_UNUSED; /* set to uninstalled */
     }
@@ -247,11 +337,73 @@
     watch_progress(watcher, "done syncing parent");
 }
 
+static struct storage_mapping_node* get_storage_mapping_entry(const char* source) {
+    struct storage_mapping_node* curr = storage_mapping_head;
+    for (; curr != NULL; curr = curr->next) {
+        if (!strcmp(source, curr->file_name)) {
+            ALOGI("Found backing file %s for %s\n", curr->backing_storage, source);
+            return curr;
+        }
+    }
+    return NULL;
+}
+
+static bool is_backing_storage_mapped(const char* source) {
+    const struct storage_mapping_node* curr = storage_mapping_head;
+    for (; curr != NULL; curr = curr->next) {
+        if (!strcmp(source, curr->backing_storage)) {
+            ALOGI("Backed storage mapping exists for %s\n", curr->backing_storage);
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Attempts to open a backed file, if mapped, without creating the symlink. Symlink will be created
+ * later on the first write.  This allows us to continue reporting zero read sizes until the first
+ * write. */
+static int open_possibly_mapped_file(const char* short_path, const char* full_path, int open_flags,
+                                     struct storage_mapping_node** entry) {
+    /* See if mapping exists, report upstream if there is no mapping. */
+    struct storage_mapping_node* mapping_entry = get_storage_mapping_entry(short_path);
+    if (mapping_entry == NULL) {
+        return TEMP_FAILURE_RETRY(open(full_path, open_flags, S_IRUSR | S_IWUSR));
+    }
+
+    /* Check for existence of root path, we don't allow mappings during early boot */
+    struct stat buf = {0};
+    if (stat(ssdir_name, &buf) != 0) {
+        ALOGW("Root path not accessible yet, refuse to open mappings for now.\n");
+        return -1;
+    }
+
+    /* We don't support exclusive opening of mapped files */
+    if (open_flags & O_EXCL) {
+        ALOGE("Requesting exclusive open on backed storage isn't supported: %s\n", full_path);
+        return -1;
+    }
+
+    /* Try and open mapping file */
+    open_flags &= ~(O_CREAT | O_EXCL);
+    ALOGI("%s Attempting to open mapped file: %s\n", __func__, mapping_entry->backing_storage);
+    int fd =
+            TEMP_FAILURE_RETRY(open(mapping_entry->backing_storage, open_flags, S_IRUSR | S_IWUSR));
+    if (fd < 0) {
+        ALOGE("%s Failed to open mapping file: %s\n", __func__, mapping_entry->backing_storage);
+        return -1;
+    }
+
+    /* Let caller know which entry we used for opening */
+    *entry = mapping_entry;
+    return fd;
+}
+
 int storage_file_open(struct storage_msg* msg, const void* r, size_t req_len,
                       struct watcher* watcher) {
     char* path = NULL;
     const struct storage_file_open_req *req = r;
     struct storage_file_open_resp resp = {0};
+    struct storage_mapping_node* mapping_entry = NULL;
 
     if (req_len < sizeof(*req)) {
         ALOGE("%s: invalid request length (%zd < %zd)\n",
@@ -321,14 +473,18 @@
         if (req->flags & STORAGE_FILE_OPEN_CREATE_EXCLUSIVE) {
             /* create exclusive */
             open_flags |= O_CREAT | O_EXCL;
-            rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR));
+
+            /* Look for and attempt opening a mapping, else just do normal open. */
+            rc = open_possibly_mapped_file(req->name, path, open_flags, &mapping_entry);
         } else {
             /* try open first */
             rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR));
             if (rc == -1 && errno == ENOENT) {
                 /* then try open with O_CREATE */
                 open_flags |= O_CREAT;
-                rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR));
+
+                /* Look for and attempt opening a mapping, else just do normal open. */
+                rc = open_possibly_mapped_file(req->name, path, open_flags, &mapping_entry);
             }
 
         }
@@ -356,7 +512,7 @@
 
     /* at this point rc contains storage file fd */
     msg->result = STORAGE_NO_ERROR;
-    resp.handle = insert_fd(open_flags, rc);
+    resp.handle = insert_fd(open_flags, rc, mapping_entry);
     ALOGV("%s: \"%s\": fd = %u: handle = %d\n",
           __func__, path, rc, resp.handle);
 
@@ -433,6 +589,14 @@
         goto err_response;
     }
 
+    /* Handle any delayed symlinking for this handle if any */
+    rc = possibly_symlink_and_clear_mapping(req->handle);
+    if (rc < 0) {
+        ALOGE("Failed to symlink storage\n");
+        msg->result = STORAGE_ERR_GENERIC;
+        goto err_response;
+    }
+
     int fd = lookup_fd(req->handle, true);
     watch_progress(watcher, "writing");
     if (write_with_retry(fd, &req->data[0], req_len - sizeof(*req),
@@ -479,6 +643,14 @@
         goto err_response;
     }
 
+    /* If this handle has a delayed symlink we should report 0 size reads until first write occurs
+     */
+    if (is_pending_symlink(req->handle)) {
+        ALOGI("Pending symlink: Forcing read result 0.\n");
+        msg->result = STORAGE_NO_ERROR;
+        return ipc_respond(msg, &read_rsp, sizeof(read_rsp.hdr));
+    }
+
     int fd = lookup_fd(req->handle, false);
     watch_progress(watcher, "reading");
     ssize_t read_res = read_with_retry(fd, read_rsp.hdr.data, req->size,
@@ -592,7 +764,7 @@
             goto err_response;
         }
     } else {
-        max_size = MAX_FILE_SIZE;
+        max_size = max_file_size;
     }
 
     resp.max_size = max_size;
@@ -603,17 +775,79 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-int storage_init(const char *dirname)
-{
+int determine_max_file_size(const char* max_file_size_from) {
+    /* Use default if none passed in */
+    if (max_file_size_from == NULL) {
+        ALOGI("No max file source given, continuing to use default: 0x%" PRIx64 "\n",
+              max_file_size);
+        return 0;
+    }
+
+    /* Check that max_file_size_from is part of our mapping list. */
+    if (!is_backing_storage_mapped(max_file_size_from)) {
+        ALOGE("%s: file doesn't match mapped storages (filename=%s)\n", __func__,
+              max_file_size_from);
+        return -1;
+    }
+
+    ALOGI("Using %s to determine max file size.\n", max_file_size_from);
+
+    /* Error if max file size source not found, possible misconfig. */
+    struct stat buf = {0};
+    int rc = stat(max_file_size_from, &buf);
+    if (rc < 0) {
+        ALOGE("%s: error stat'ing file (filename=%s): %s\n", __func__, max_file_size_from,
+              strerror(errno));
+        return -1;
+    }
+
+    /* Currently only support block device as max file size source */
+    if ((buf.st_mode & S_IFMT) != S_IFBLK) {
+        ALOGE("Unsupported max file size source type: %d\n", buf.st_mode);
+        return -1;
+    }
+
+    ALOGI("%s is a block device, determining block device size\n", max_file_size_from);
+    uint64_t max_size = 0;
+    int fd = TEMP_FAILURE_RETRY(open(max_file_size_from, O_RDONLY | O_NONBLOCK));
+    if (fd < 0) {
+        ALOGE("%s: failed to open backing file %s for ioctl: %s\n", __func__, max_file_size_from,
+              strerror(errno));
+        return -1;
+    }
+    rc = ioctl(fd, BLKGETSIZE64, &max_size);
+    if (rc < 0) {
+        ALOGE("%s: error calling ioctl on file (fd=%d): %s\n", __func__, fd, strerror(errno));
+        close(fd);
+        return -1;
+    }
+    close(fd);
+    max_file_size = max_size;
+
+    ALOGI("Using 0x%" PRIx64 " as max file size\n", max_file_size);
+    return 0;
+}
+
+int storage_init(const char* dirname, struct storage_mapping_node* mappings,
+                 const char* max_file_size_from) {
     /* If there is an active DSU image, use the alternate fs mode. */
     alternate_mode = is_gsi_running();
 
     fs_state = SS_CLEAN;
     for (uint i = 0; i < FD_TBL_SIZE; i++) {
-        fd_state[i] = SS_UNUSED;  /* uninstalled */
+        fd_state[i] = SS_UNUSED; /* uninstalled */
     }
 
     ssdir_name = dirname;
+
+    storage_mapping_head = mappings;
+
+    /* Set the max file size based on incoming configuration */
+    int rc = determine_max_file_size(max_file_size_from);
+    if (rc < 0) {
+        return rc;
+    }
+
     return 0;
 }
 
@@ -623,17 +857,17 @@
     watch_progress(watcher, "sync fd table");
     /* sync fd table and reset it to clean state first */
     for (uint fd = 0; fd < FD_TBL_SIZE; fd++) {
-         if (fd_state[fd] == SS_DIRTY) {
-             if (fs_state == SS_CLEAN) {
-                 /* need to sync individual fd */
-                 rc = fsync(fd);
-                 if (rc < 0) {
-                     ALOGE("fsync for fd=%d failed: %s\n", fd, strerror(errno));
-                     return rc;
-                 }
-             }
-             fd_state[fd] = SS_CLEAN; /* set to clean */
-         }
+        if (fd_state[fd] == SS_DIRTY) {
+            if (fs_state == SS_CLEAN) {
+                /* need to sync individual fd */
+                rc = fsync(fd);
+                if (rc < 0) {
+                    ALOGE("fsync for fd=%d failed: %s\n", fd, strerror(errno));
+                    return rc;
+                }
+            }
+            fd_state[fd] = SS_CLEAN; /* set to clean */
+        }
     }
 
     /* check if we need to sync all filesystems */
diff --git a/trusty/storage/proxy/storage.h b/trusty/storage/proxy/storage.h
index f29fdf2..6dbfe37 100644
--- a/trusty/storage/proxy/storage.h
+++ b/trusty/storage/proxy/storage.h
@@ -21,6 +21,14 @@
 /* Defined in watchdog.h */
 struct watcher;
 
+/* Is used for managing alternate backing storage, generally will be a block device. */
+struct storage_mapping_node {
+    struct storage_mapping_node* next;
+    const char* file_name;
+    const char* backing_storage;
+    int fd;
+};
+
 int storage_file_delete(struct storage_msg* msg, const void* req, size_t req_len,
                         struct watcher* watcher);
 
@@ -45,6 +53,7 @@
 int storage_file_get_max_size(struct storage_msg* msg, const void* req, size_t req_len,
                               struct watcher* watcher);
 
-int storage_init(const char* dirname);
+int storage_init(const char* dirname, struct storage_mapping_node* head,
+                 const char* max_file_size_from);
 
 int storage_sync_checkpoint(struct watcher* watcher);
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index d645c3e..b21eca6 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -35,11 +35,10 @@
     LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service.trusty
 endif
 
-# TODO(b/306364873): move this to be flag-controlled?
-ifeq ($(SECRETKEEPER_ENABLED),)
-    LOCAL_SECRETKEEPER_PRODUCT_PACKAGE :=
-else
+ifeq ($(SECRETKEEPER_ENABLED),true)
     LOCAL_SECRETKEEPER_PRODUCT_PACKAGE := android.hardware.security.secretkeeper.trusty
+else
+    LOCAL_SECRETKEEPER_PRODUCT_PACKAGE :=
 endif
 
 PRODUCT_PACKAGES += \
diff --git a/trusty/utils/coverage-controller/controller.cpp b/trusty/utils/coverage-controller/controller.cpp
index 381a452..f5d70b1 100644
--- a/trusty/utils/coverage-controller/controller.cpp
+++ b/trusty/utils/coverage-controller/controller.cpp
@@ -60,6 +60,7 @@
                 filename.insert(0, output_dir);
                 android::base::Result<void> res = record_list_[index]->SaveFile(filename);
                 counters[index]++;
+                WRITE_ONCE(control->read_buffer_cnt, counters[index]);
             }
             if(complete_cnt == counters[index] &&
                 !(flags & FLAG_RUN)) {
diff --git a/trusty/utils/coverage-controller/controller.h b/trusty/utils/coverage-controller/controller.h
index f7789bf..841a1ae 100644
--- a/trusty/utils/coverage-controller/controller.h
+++ b/trusty/utils/coverage-controller/controller.h
@@ -35,9 +35,9 @@
 struct control {
     /* Written by controller, read by instrumented TA */
     uint64_t        cntrl_flags;
+    uint64_t        read_buffer_cnt;
 
     /* Written by instrumented TA, read by controller */
-    uint64_t        oper_flags;
     uint64_t        write_buffer_start_count;
     uint64_t        write_buffer_complete_count;
 };
diff --git a/trusty/utils/rpmb_dev/Android.bp b/trusty/utils/rpmb_dev/Android.bp
index a270087..5e9caaf 100644
--- a/trusty/utils/rpmb_dev/Android.bp
+++ b/trusty/utils/rpmb_dev/Android.bp
@@ -24,6 +24,7 @@
     ],
     shared_libs: [
         "libc",
+        "libcutils",
         "liblog",
         "libcrypto",
     ],
diff --git a/trusty/utils/rpmb_dev/rpmb_dev.c b/trusty/utils/rpmb_dev/rpmb_dev.c
index 0a9e6a1..cee3037 100644
--- a/trusty/utils/rpmb_dev/rpmb_dev.c
+++ b/trusty/utils/rpmb_dev/rpmb_dev.c
@@ -19,6 +19,7 @@
 #include "rpmb_protocol.h"
 
 #include <assert.h>
+#include <cutils/sockets.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
@@ -613,20 +614,24 @@
         return EXIT_FAILURE;
     }
 
-    cmdres_sock = socket(AF_UNIX, SOCK_STREAM, 0);
+    cmdres_sock = android_get_control_socket(socket_path);
     if (cmdres_sock < 0) {
-        ALOGE("rpmb_dev: Failed to create command/response socket: %s\n", strerror(errno));
-        return EXIT_FAILURE;
-    }
+        ALOGW("android_get_control_socket(%s) failed, fall back to create it\n", socket_path);
+        cmdres_sock = socket(AF_UNIX, SOCK_STREAM, 0);
+        if (cmdres_sock < 0) {
+            ALOGE("rpmb_dev: Failed to create command/response socket: %s\n", strerror(errno));
+            return EXIT_FAILURE;
+        }
 
-    cmdres_sockaddr.sun_family = AF_UNIX;
-    strncpy(cmdres_sockaddr.sun_path, socket_path, sizeof(cmdres_sockaddr.sun_path));
+        cmdres_sockaddr.sun_family = AF_UNIX;
+        strncpy(cmdres_sockaddr.sun_path, socket_path, sizeof(cmdres_sockaddr.sun_path));
 
-    ret = bind(cmdres_sock, (struct sockaddr*)&cmdres_sockaddr, sizeof(struct sockaddr_un));
-    if (ret < 0) {
-        ALOGE("rpmb_dev: Failed to bind command/response socket: %s: %s\n", socket_path,
-              strerror(errno));
-        return EXIT_FAILURE;
+        ret = bind(cmdres_sock, (struct sockaddr*)&cmdres_sockaddr, sizeof(struct sockaddr_un));
+        if (ret < 0) {
+            ALOGE("rpmb_dev: Failed to bind command/response socket: %s: %s\n", socket_path,
+                  strerror(errno));
+            return EXIT_FAILURE;
+        }
     }
 
     ret = listen(cmdres_sock, 1);
diff --git a/trusty/utils/rpmb_dev/rpmb_dev.rc b/trusty/utils/rpmb_dev/rpmb_dev.rc
index 9e203b8..33ec0f2 100644
--- a/trusty/utils/rpmb_dev/rpmb_dev.rc
+++ b/trusty/utils/rpmb_dev/rpmb_dev.rc
@@ -1,29 +1,34 @@
 # RPMB Mock
-on post-fs-data
-    mkdir /data/vendor/ss
-    chown root system /data/vendor/ss
-    chmod 0770 /data/vendor/ss
-    rm /data/vendor/ss/rpmb_sock
-    start rpmb_mock_init
+on post-fs
+    mkdir /mnt/vendor/persist/ss 0770 root system
+    exec_start rpmb_mock_init
     start rpmb_mock
 
+on post-fs-data
+    mkdir /data/vendor/ss 0770 root system
+    symlink /mnt/vendor/persist/ss /data/vendor/ss/persist
+    chown root system /data/vendor/ss/persist
+    chmod 0770 /data/vendor/ss/persist
+
     # Storage proxy
-    start storageproxyd
+    restart storageproxyd
 
-service storageproxyd /vendor/bin/storageproxyd -d /dev/trusty-ipc-dev0 \
-        -r /data/vendor/ss/rpmb_sock -p /data/vendor/ss -t sock
-    class main
-    disabled
-    user root
+service storageproxyd /vendor/bin/storageproxyd -d ${ro.hardware.trusty_ipc_dev:-/dev/trusty-ipc-dev0} \
+        -r /dev/socket/rpmb_mock -p /data/vendor/ss -t sock
+    class early_hal
+    user system
+    group system
 
-service rpmb_mock_init /vendor/bin/rpmb_dev --dev /data/vendor/ss/RPMB_DATA --init --size 2048
+service rpmb_mock_init /vendor/bin/rpmb_dev --dev /mnt/vendor/persist/ss/RPMB_DATA --init --size 2048
     disabled
     user system
     group system
     oneshot
 
-service rpmb_mock /vendor/bin/rpmb_dev --dev /data/vendor/ss/RPMB_DATA --sock /data/vendor/ss/rpmb_sock
+service rpmb_mock /vendor/bin/rpmb_dev --dev /mnt/vendor/persist/ss/RPMB_DATA \
+                          --sock rpmb_mock
     class main
     disabled
     user system
     group system
+    socket rpmb_mock stream 660 system system