Merge android13-tests-dev

Change-Id: I98a97dcfeb3bfbf711333b132b99108d8321536a
diff --git a/bootstat/boot_reason_test.sh b/bootstat/boot_reason_test.sh
index 7cff7dc..299b5d4 100755
--- a/bootstat/boot_reason_test.sh
+++ b/bootstat/boot_reason_test.sh
@@ -518,20 +518,20 @@
     reboot,bootloader | reboot,bootloader,* ) var=${var#reboot,} ; var=${var%,} ;;
     reboot | reboot,?* ) ;;
     # Aliases and Heuristics
-    *wdog* | *watchdog* )                   var="watchdog" ;;
-    *powerkey* | *power_key* | *PowerKey* ) var="cold,powerkey" ;;
-    *panic* | *kernel_panic* )              var="kernel_panic" ;;
-    *thermal* )                             var="shutdown,thermal" ;;
-    *s3_wakeup* )                           var="warm,s3_wakeup" ;;
-    *hw_reset* )                            var="hard,hw_reset" ;;
-    *usb* )                                 var="cold,charger" ;;
-    *rtc* )                                 var="cold,rtc" ;;
-    *2sec_reboot* )                         var="cold,rtc,2sec" ;;
-    *wdt_by_pass_pwk* )                     var="warm" ;;
-    wdt )                                   var="reboot" ;;
-    *tool_by_pass_pwk* )                    var="reboot,tool" ;;
-    *bootloader* )                          var="bootloader" ;;
-    * )                                     var="reboot" ;;
+    *wdog* | *watchdog* )                                    var="watchdog" ;;
+    *powerkey* | *power_on_key* | *power_key* | *PowerKey* ) var="cold,powerkey" ;;
+    *panic* | *kernel_panic* )                               var="kernel_panic" ;;
+    *thermal* )                                              var="shutdown,thermal" ;;
+    *s3_wakeup* )                                            var="warm,s3_wakeup" ;;
+    *hw_reset* )                                             var="hard,hw_reset" ;;
+    *usb* | *power_on_cable* )                               var="cold,charger" ;;
+    *rtc* )                                                  var="cold,rtc" ;;
+    *2sec_reboot* )                                          var="cold,rtc,2sec" ;;
+    *wdt_by_pass_pwk* )                                      var="warm" ;;
+    wdt )                                                    var="reboot" ;;
+    *tool_by_pass_pwk* )                                     var="reboot,tool" ;;
+    *bootloader* )                                           var="bootloader" ;;
+    * )                                                      var="reboot" ;;
   esac
   echo ${var}
 }
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 2c878f0..589b99a 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -58,7 +58,7 @@
 };
 
 // Maps BootEvent used inside bootstat into statsd atom defined in
-// frameworks/base/cmds/statsd/src/atoms.proto.
+// frameworks/proto_logging/stats/atoms.proto.
 const std::unordered_map<std::string_view, AtomInfo> kBootEventToAtomInfo = {
     // ELAPSED_TIME
     {"ro.boottime.init",
@@ -463,6 +463,18 @@
     {"watchdog,gsa,hard", 215},
     {"watchdog,gsa,soft", 216},
     {"watchdog,pmucal", 217},
+    {"reboot,early,bl", 218},
+    {"watchdog,apc,gsa,crashed", 219},
+    {"watchdog,apc,bl31,crashed", 220},
+    {"watchdog,apc,pbl,crashed", 221},
+    {"reboot,memory_protect,hyp", 222},
+    {"reboot,tsd,pmic,main", 223},
+    {"reboot,tsd,pmic,sub", 224},
+    {"reboot,ocp,pmic,main", 225},
+    {"reboot,ocp,pmic,sub", 226},
+    {"reboot,sys_ldo_ok,pmic,main", 227},
+    {"reboot,sys_ldo_ok,pmic,sub", 228},
+    {"reboot,smpl_timeout,pmic,main", 229},
 };
 
 // Converts a string value representing the reason the system booted to an
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 967b942..e3ea455 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -51,12 +51,9 @@
 #define ATRACE_TAG ATRACE_TAG_BIONIC
 #include <utils/Trace.h>
 
-#include <unwindstack/DexFiles.h>
-#include <unwindstack/JitDebug.h>
-#include <unwindstack/Maps.h>
-#include <unwindstack/Memory.h>
+#include <unwindstack/AndroidUnwinder.h>
+#include <unwindstack/Error.h>
 #include <unwindstack/Regs.h>
-#include <unwindstack/Unwinder.h>
 
 #include "libdebuggerd/backtrace.h"
 #include "libdebuggerd/tombstone.h"
@@ -623,9 +620,12 @@
   }
 
   // TODO: Use seccomp to lock ourselves down.
-  unwindstack::UnwinderFromPid unwinder(256, vm_pid, unwindstack::Regs::CurrentArch());
-  if (!unwinder.Init()) {
-    LOG(FATAL) << "Failed to init unwinder object.";
+
+  unwindstack::AndroidRemoteUnwinder unwinder(vm_pid, unwindstack::Regs::CurrentArch());
+  unwindstack::ErrorData error_data;
+  if (!unwinder.Initialize(error_data)) {
+    LOG(FATAL) << "Failed to initialize unwinder object: "
+               << unwindstack::GetErrorCodeString(error_data.code);
   }
 
   std::string amfd_data;
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index e113308..ed90ab4 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -1486,6 +1486,37 @@
   ASSERT_BACKTRACE_FRAME(result, "abort");
 }
 
+TEST_F(CrasherTest, seccomp_tombstone_multiple_threads_abort) {
+  int intercept_result;
+  unique_fd output_fd;
+
+  static const auto dump_type = kDebuggerdTombstone;
+  StartProcess(
+      []() {
+        std::thread a(foo);
+        std::thread b(bar);
+
+        std::this_thread::sleep_for(100ms);
+
+        std::thread abort_thread([] { abort(); });
+        abort_thread.join();
+      },
+      &seccomp_fork);
+
+  StartIntercept(&output_fd, dump_type);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_BACKTRACE_FRAME(result, "abort");
+  ASSERT_BACKTRACE_FRAME(result, "foo");
+  ASSERT_BACKTRACE_FRAME(result, "bar");
+  ASSERT_BACKTRACE_FRAME(result, "main");
+}
+
 TEST_F(CrasherTest, seccomp_backtrace) {
   int intercept_result;
   unique_fd output_fd;
@@ -1516,6 +1547,40 @@
   ASSERT_BACKTRACE_FRAME(result, "bar");
 }
 
+TEST_F(CrasherTest, seccomp_backtrace_from_thread) {
+  int intercept_result;
+  unique_fd output_fd;
+
+  static const auto dump_type = kDebuggerdNativeBacktrace;
+  StartProcess(
+      []() {
+        std::thread a(foo);
+        std::thread b(bar);
+
+        std::this_thread::sleep_for(100ms);
+
+        std::thread raise_thread([] {
+          raise_debugger_signal(dump_type);
+          _exit(0);
+        });
+        raise_thread.join();
+      },
+      &seccomp_fork);
+
+  StartIntercept(&output_fd, dump_type);
+  FinishCrasher();
+  AssertDeath(0);
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal");
+  ASSERT_BACKTRACE_FRAME(result, "foo");
+  ASSERT_BACKTRACE_FRAME(result, "bar");
+  ASSERT_BACKTRACE_FRAME(result, "main");
+}
+
 TEST_F(CrasherTest, seccomp_crash_logcat) {
   StartProcess([]() { abort(); }, &seccomp_fork);
   FinishCrasher();
diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp
index c8b25ae..4721391 100644
--- a/debuggerd/handler/debuggerd_fallback.cpp
+++ b/debuggerd/handler/debuggerd_fallback.cpp
@@ -31,12 +31,9 @@
 #include <android-base/unique_fd.h>
 #include <async_safe/log.h>
 #include <bionic/reserved_signals.h>
-#include <unwindstack/DexFiles.h>
-#include <unwindstack/JitDebug.h>
-#include <unwindstack/Maps.h>
+#include <unwindstack/AndroidUnwinder.h>
 #include <unwindstack/Memory.h>
 #include <unwindstack/Regs.h>
-#include <unwindstack/Unwinder.h>
 
 #include "debuggerd/handler.h"
 #include "handler/fallback.h"
@@ -75,11 +72,11 @@
 
     // Do not use the thread cache here because it will call pthread_key_create
     // which doesn't work in linker code. See b/189803009.
-    // Use a normal cached object because the process is stopped, and there
+    // Use a normal cached object because the thread is stopped, and there
     // is no chance of data changing between reads.
     auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid());
     // TODO: Create this once and store it in a global?
-    unwindstack::UnwinderFromPid unwinder(kMaxFrames, getpid(), process_memory);
+    unwindstack::AndroidLocalUnwinder unwinder(process_memory);
     dump_backtrace_thread(output_fd, &unwinder, thread);
   }
   __linker_disable_fallback_allocator();
@@ -213,7 +210,10 @@
 
   // Send a signal to all of our siblings, asking them to dump their stack.
   pid_t current_tid = gettid();
-  if (!iterate_tids(current_tid, [&output_fd](pid_t tid) {
+  if (!iterate_tids(current_tid, [&output_fd, &current_tid](pid_t tid) {
+        if (current_tid == tid) {
+          return;
+        }
         // Use a pipe, to be able to detect situations where the thread gracefully exits before
         // receiving our signal.
         unique_fd pipe_read, pipe_write;
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 35be2bf..92e7675 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -38,6 +38,8 @@
 #include <unistd.h>
 
 #include <android-base/macros.h>
+#include <android-base/parsebool.h>
+#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 #include <async_safe/log.h>
 #include <bionic/reserved_signals.h>
@@ -49,7 +51,10 @@
 
 #include "handler/fallback.h"
 
-using android::base::Pipe;
+using ::android::base::GetBoolProperty;
+using ::android::base::ParseBool;
+using ::android::base::ParseBoolResult;
+using ::android::base::Pipe;
 
 // We muck with our fds in a 'thread' that doesn't share the same fd table.
 // Close fds in that thread with a raw close syscall instead of going through libc.
@@ -82,6 +87,13 @@
   return syscall(__NR_gettid);
 }
 
+static bool is_permissive_mte() {
+  // Environment variable for testing or local use from shell.
+  char* permissive_env = getenv("MTE_PERMISSIVE");
+  return GetBoolProperty("persist.sys.mte.permissive", false) ||
+         (permissive_env && ParseBool(permissive_env) == ParseBoolResult::kTrue);
+}
+
 static inline void futex_wait(volatile void* ftx, int value) {
   syscall(__NR_futex, ftx, FUTEX_WAIT, value, nullptr, nullptr, 0);
 }
@@ -592,7 +604,28 @@
     // 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 {
+  }
+#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);
+  }
+#endif
+  else {
     // Resend the signal, so that either the debugger or the parent's waitpid sees it.
     resend_signal(info);
   }
diff --git a/debuggerd/libdebuggerd/backtrace.cpp b/debuggerd/libdebuggerd/backtrace.cpp
index fd91038..3ff9710 100644
--- a/debuggerd/libdebuggerd/backtrace.cpp
+++ b/debuggerd/libdebuggerd/backtrace.cpp
@@ -37,6 +37,7 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <log/log.h>
+#include <unwindstack/AndroidUnwinder.h>
 #include <unwindstack/Unwinder.h>
 
 #include "libdebuggerd/types.h"
@@ -57,7 +58,7 @@
   _LOG(log, logtype::BACKTRACE, "\n----- end %d -----\n", pid);
 }
 
-void dump_backtrace_thread(int output_fd, unwindstack::Unwinder* unwinder,
+void dump_backtrace_thread(int output_fd, unwindstack::AndroidUnwinder* unwinder,
                            const ThreadInfo& thread) {
   log_t log;
   log.tfd = output_fd;
@@ -65,21 +66,17 @@
 
   _LOG(&log, logtype::BACKTRACE, "\n\"%s\" sysTid=%d\n", thread.thread_name.c_str(), thread.tid);
 
-  unwinder->SetRegs(thread.registers.get());
-  unwinder->Unwind();
-  if (unwinder->NumFrames() == 0) {
-    _LOG(&log, logtype::THREAD, "Unwind failed: tid = %d\n", thread.tid);
-    if (unwinder->LastErrorCode() != unwindstack::ERROR_NONE) {
-      _LOG(&log, logtype::THREAD, "  Error code: %s\n", unwinder->LastErrorCodeString());
-      _LOG(&log, logtype::THREAD, "  Error address: 0x%" PRIx64 "\n", unwinder->LastErrorAddress());
-    }
+  unwindstack::AndroidUnwinderData data;
+  if (!unwinder->Unwind(thread.registers.get(), data)) {
+    _LOG(&log, logtype::THREAD, "Unwind failed: tid = %d: Error %s\n", thread.tid,
+         data.GetErrorString().c_str());
     return;
   }
 
-  log_backtrace(&log, unwinder, "  ");
+  log_backtrace(&log, unwinder, data, "  ");
 }
 
-void dump_backtrace(android::base::unique_fd output_fd, unwindstack::Unwinder* unwinder,
+void dump_backtrace(android::base::unique_fd output_fd, unwindstack::AndroidUnwinder* unwinder,
                     const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread) {
   log_t log;
   log.tfd = output_fd.get();
diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index 3d96627..d8f74e0 100644
--- a/debuggerd/libdebuggerd/gwp_asan.cpp
+++ b/debuggerd/libdebuggerd/gwp_asan.cpp
@@ -21,9 +21,8 @@
 #include "gwp_asan/common.h"
 #include "gwp_asan/crash_handler.h"
 
-#include <unwindstack/Maps.h>
+#include <unwindstack/AndroidUnwinder.h>
 #include <unwindstack/Memory.h>
-#include <unwindstack/Regs.h>
 #include <unwindstack/Unwinder.h>
 
 #include "tombstone.pb.h"
@@ -106,7 +105,8 @@
 
 constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
 
-void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
+void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone,
+                                      unwindstack::AndroidUnwinder* unwinder) const {
   if (!CrashIsMine()) {
     ALOGE("Internal Error: AddCauseProtos() on a non-GWP-ASan crash.");
     return;
@@ -140,7 +140,6 @@
 
   heap_object->set_address(__gwp_asan_get_allocation_address(responsible_allocation_));
   heap_object->set_size(__gwp_asan_get_allocation_size(responsible_allocation_));
-  unwinder->SetDisplayBuildID(true);
 
   std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
 
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/backtrace.h b/debuggerd/libdebuggerd/include/libdebuggerd/backtrace.h
index c20d090..531afea 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/backtrace.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/backtrace.h
@@ -30,16 +30,16 @@
 
 // Forward delcaration
 namespace unwindstack {
-class Unwinder;
+class AndroidUnwinder;
 }
 
 // Dumps a backtrace using a format similar to what Dalvik uses so that the result
 // can be intermixed in a bug report.
-void dump_backtrace(android::base::unique_fd output_fd, unwindstack::Unwinder* unwinder,
+void dump_backtrace(android::base::unique_fd output_fd, unwindstack::AndroidUnwinder* unwinder,
                     const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread);
 
 void dump_backtrace_header(int output_fd);
-void dump_backtrace_thread(int output_fd, unwindstack::Unwinder* unwinder,
+void dump_backtrace_thread(int output_fd, unwindstack::AndroidUnwinder* unwinder,
                            const ThreadInfo& thread);
 void dump_backtrace_footer(int output_fd);
 
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
index a979370..0429643 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
@@ -26,9 +26,15 @@
 #include "types.h"
 #include "utility.h"
 
+// Forward delcarations
 class Cause;
 class Tombstone;
 
+namespace unwindstack {
+class AndroidUnwinder;
+class Memory;
+}  // namespace unwindstack
+
 class GwpAsanCrashData {
  public:
   GwpAsanCrashData() = delete;
@@ -52,7 +58,7 @@
   // allocator crash state.
   uintptr_t GetFaultAddress() const;
 
-  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
+  void AddCauseProtos(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder) const;
 
  protected:
   // Is GWP-ASan responsible for this crash.
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
index 172ffe9..a506859 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
@@ -23,9 +23,15 @@
 
 #include "scudo/interface.h"
 
+// Forward delcarations
 class Cause;
 class Tombstone;
 
+namespace unwindstack {
+class AndroidUnwinder;
+class Memory;
+}  // namespace unwindstack
+
 class ScudoCrashData {
  public:
   ScudoCrashData() = delete;
@@ -34,12 +40,12 @@
 
   bool CrashIsMine() const;
 
-  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
+  void AddCauseProtos(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder) const;
 
  private:
   scudo_error_info error_info_ = {};
   uintptr_t untagged_fault_addr_;
 
   void FillInCause(Cause* cause, const scudo_error_report* report,
-                   unwindstack::Unwinder* unwinder) const;
+                   unwindstack::AndroidUnwinder* unwinder) const;
 };
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
index 7bf1688..be999e0 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
@@ -37,7 +37,7 @@
 
 namespace unwindstack {
 struct FrameData;
-class Unwinder;
+class AndroidUnwinder;
 }
 
 // The maximum number of frames to save when unwinding.
@@ -51,7 +51,7 @@
 
 /* Creates a tombstone file and writes the crash dump to it. */
 void engrave_tombstone(android::base::unique_fd output_fd, android::base::unique_fd proto_fd,
-                       unwindstack::Unwinder* unwinder,
+                       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);
@@ -59,7 +59,7 @@
 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::Unwinder* unwinder,
+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);
 
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 63e142f..25b03af 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -73,11 +73,13 @@
 void _VLOG(log_t* log, logtype ltype, const char* fmt, va_list ap);
 
 namespace unwindstack {
-class Unwinder;
+class AndroidUnwinder;
 class Memory;
+struct AndroidUnwinderData;
 }
 
-void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix);
+void log_backtrace(log_t* log, unwindstack::AndroidUnwinder* unwinder,
+                   unwindstack::AndroidUnwinderData& data, const char* prefix);
 
 ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
                     unwindstack::Memory* memory);
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index a4836d7..5d861f8 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -17,8 +17,8 @@
 #include "libdebuggerd/scudo.h"
 #include "libdebuggerd/tombstone.h"
 
+#include "unwindstack/AndroidUnwinder.h"
 #include "unwindstack/Memory.h"
-#include "unwindstack/Unwinder.h"
 
 #include <android-base/macros.h>
 #include <bionic/macros.h>
@@ -46,6 +46,9 @@
                                        __scudo_get_region_info_size());
   auto ring_buffer = AllocAndReadFully(process_memory, process_info.scudo_ring_buffer,
                                        __scudo_get_ring_buffer_size());
+  if (!stack_depot || !region_info || !ring_buffer) {
+    return;
+  }
 
   untagged_fault_addr_ = process_info.untagged_fault_address;
   uintptr_t fault_page = untagged_fault_addr_ & ~(PAGE_SIZE - 1);
@@ -80,7 +83,7 @@
 }
 
 void ScudoCrashData::FillInCause(Cause* cause, const scudo_error_report* report,
-                                 unwindstack::Unwinder* unwinder) const {
+                                 unwindstack::AndroidUnwinder* unwinder) const {
   MemoryError* memory_error = cause->mutable_memory_error();
   HeapObject* heap_object = memory_error->mutable_heap();
 
@@ -102,7 +105,6 @@
 
   heap_object->set_address(report->allocation_address);
   heap_object->set_size(report->allocation_size);
-  unwinder->SetDisplayBuildID(true);
 
   heap_object->set_allocation_tid(report->allocation_tid);
   for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i]; ++i) {
@@ -123,7 +125,8 @@
   set_human_readable_cause(cause, untagged_fault_addr_);
 }
 
-void ScudoCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
+void ScudoCrashData::AddCauseProtos(Tombstone* tombstone,
+                                    unwindstack::AndroidUnwinder* unwinder) const {
   size_t report_num = 0;
   while (report_num < sizeof(error_info_.reports) / sizeof(error_info_.reports[0]) &&
          error_info_.reports[report_num].error_type != UNKNOWN) {
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index eda7182..e5b4d74 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -36,9 +36,9 @@
 #include <async_safe/log.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
-#include <unwindstack/Memory.h>
+#include <unwindstack/AndroidUnwinder.h>
+#include <unwindstack/Error.h>
 #include <unwindstack/Regs.h>
-#include <unwindstack/Unwinder.h>
 
 #include "libdebuggerd/backtrace.h"
 #include "libdebuggerd/open_files_list.h"
@@ -55,15 +55,15 @@
                                 siginfo_t* siginfo, ucontext_t* ucontext) {
   pid_t uid = getuid();
   pid_t pid = getpid();
-  pid_t tid = gettid();
+  pid_t target_tid = gettid();
 
   log_t log;
-  log.current_tid = tid;
-  log.crashed_tid = tid;
+  log.current_tid = target_tid;
+  log.crashed_tid = target_tid;
   log.tfd = tombstone_fd;
   log.amfd_data = nullptr;
 
-  std::string thread_name = get_thread_name(tid);
+  std::string thread_name = get_thread_name(target_tid);
   std::vector<std::string> command_line = get_command_line(pid);
 
   std::unique_ptr<unwindstack::Regs> regs(
@@ -73,50 +73,57 @@
   android::base::ReadFileToString("/proc/self/attr/current", &selinux_label);
 
   std::map<pid_t, ThreadInfo> threads;
-  threads[tid] = ThreadInfo{
-    .registers = std::move(regs), .uid = uid, .tid = tid, .thread_name = std::move(thread_name),
-    .pid = pid, .command_line = std::move(command_line), .selinux_label = std::move(selinux_label),
-    .siginfo = siginfo,
+  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,
 #if defined(__aarch64__)
     // Only supported on aarch64 for now.
         .tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0),
     .pac_enabled_keys = prctl(PR_PAC_GET_ENABLED_KEYS, 0, 0, 0, 0),
 #endif
   };
-  if (pid == tid) {
-    const ThreadInfo& thread = threads[pid];
-    if (!iterate_tids(pid, [&threads, &thread](pid_t tid) {
-          threads[tid] = ThreadInfo{
-              .uid = thread.uid,
-              .tid = tid,
-              .pid = thread.pid,
-              .command_line = thread.command_line,
-              .thread_name = get_thread_name(tid),
-              .tagged_addr_ctrl = thread.tagged_addr_ctrl,
-              .pac_enabled_keys = thread.pac_enabled_keys,
-          };
-        })) {
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to open /proc/%d/task: %s", pid,
-                            strerror(errno));
-    }
+  const ThreadInfo& thread = threads[pid];
+  if (!iterate_tids(pid, [&threads, &thread, &target_tid](pid_t tid) {
+        if (target_tid == tid) {
+          return;
+        }
+        async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "Adding thread %d", tid);
+        threads[tid] = ThreadInfo{
+            .uid = thread.uid,
+            .tid = tid,
+            .pid = thread.pid,
+            .command_line = thread.command_line,
+            .thread_name = get_thread_name(tid),
+            .tagged_addr_ctrl = thread.tagged_addr_ctrl,
+            .pac_enabled_keys = thread.pac_enabled_keys,
+        };
+      })) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to open /proc/%d/task: %s", pid,
+                          strerror(errno));
   }
 
-  auto process_memory =
-      unwindstack::Memory::CreateProcessMemoryCached(getpid());
-  unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch(), nullptr,
-                                        process_memory);
-  if (!unwinder.Init()) {
-    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to init unwinder object");
+  // Do not use the thread cache here because it will call pthread_key_create
+  // which doesn't work in linker code. See b/189803009.
+  // Use a normal cached object because the thread is stopped, and there
+  // is no chance of data changing between reads.
+  auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid());
+  unwindstack::AndroidLocalUnwinder unwinder(process_memory);
+  unwindstack::ErrorData error;
+  if (!unwinder.Initialize(error)) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to init unwinder object: %s",
+                          unwindstack::GetErrorCodeString(error.code));
     return;
   }
 
   ProcessInfo process_info;
   process_info.abort_msg_address = abort_msg_address;
-  engrave_tombstone(unique_fd(dup(tombstone_fd)), unique_fd(dup(proto_fd)), &unwinder, threads, tid,
-                    process_info, nullptr, nullptr);
+  engrave_tombstone(unique_fd(dup(tombstone_fd)), unique_fd(dup(proto_fd)), &unwinder, threads,
+                    target_tid, process_info, nullptr, nullptr);
 }
 
-void engrave_tombstone(unique_fd output_fd, unique_fd proto_fd, unwindstack::Unwinder* unwinder,
+void engrave_tombstone(unique_fd output_fd, unique_fd proto_fd,
+                       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) {
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index bd05837..159ebc8 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -56,10 +56,11 @@
 #include <private/android_filesystem_config.h>
 
 #include <procinfo/process.h>
+#include <unwindstack/AndroidUnwinder.h>
+#include <unwindstack/Error.h>
+#include <unwindstack/MapInfo.h>
 #include <unwindstack/Maps.h>
-#include <unwindstack/Memory.h>
 #include <unwindstack/Regs.h>
-#include <unwindstack/Unwinder.h>
 
 #include "libdebuggerd/open_files_list.h"
 #include "libdebuggerd/utility.h"
@@ -189,7 +190,7 @@
       error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address()));
 }
 
-static void dump_probable_cause(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder,
                                 const ProcessInfo& process_info, const ThreadInfo& main_thread) {
 #if defined(USE_SCUDO)
   ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
@@ -245,9 +246,9 @@
   }
 }
 
-static void dump_abort_message(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+static void dump_abort_message(Tombstone* tombstone,
+                               std::shared_ptr<unwindstack::Memory>& process_memory,
                                const ProcessInfo& process_info) {
-  std::shared_ptr<unwindstack::Memory> process_memory = unwinder->GetProcessMemory();
   uintptr_t address = process_info.abort_msg_address;
   if (address == 0) {
     return;
@@ -348,7 +349,7 @@
   f->set_build_id(frame.map_info->GetPrintableBuildID());
 }
 
-static void dump_registers(unwindstack::Unwinder* unwinder,
+static void dump_registers(unwindstack::AndroidUnwinder* unwinder,
                            const std::unique_ptr<unwindstack::Regs>& regs, Thread& thread,
                            bool memory_dump) {
   if (regs == nullptr) {
@@ -402,27 +403,9 @@
   });
 }
 
-static void log_unwinder_error(unwindstack::Unwinder* unwinder) {
-  if (unwinder->LastErrorCode() == unwindstack::ERROR_NONE) {
-    return;
-  }
-
-  async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error code: %s",
-                        unwinder->LastErrorCodeString());
-  async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error address: 0x%" PRIx64,
-                        unwinder->LastErrorAddress());
-}
-
-static void dump_thread_backtrace(unwindstack::Unwinder* unwinder, Thread& thread) {
-  if (unwinder->NumFrames() == 0) {
-    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to unwind");
-    log_unwinder_error(unwinder);
-    return;
-  }
-
-  unwinder->SetDisplayBuildID(true);
+static void dump_thread_backtrace(std::vector<unwindstack::FrameData>& frames, Thread& thread) {
   std::set<std::string> unreadable_elf_files;
-  for (const auto& frame : unwinder->frames()) {
+  for (const auto& frame : frames) {
     BacktraceFrame* f = thread.add_current_backtrace();
     fill_in_backtrace_frame(f, frame);
     if (frame.map_info != nullptr && frame.map_info->ElfFileNotReadable()) {
@@ -446,7 +429,7 @@
   }
 }
 
-static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+static void dump_thread(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder,
                         const ThreadInfo& thread_info, bool memory_dump = false) {
   Thread thread;
 
@@ -455,36 +438,29 @@
   thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl);
   thread.set_pac_enabled_keys(thread_info.pac_enabled_keys);
 
-  if (thread_info.registers == nullptr) {
-    // Fallback path for non-main thread, doing unwind from running process.
-    unwindstack::ThreadUnwinder thread_unwinder(kMaxFrames, unwinder->GetMaps());
-    if (!thread_unwinder.Init()) {
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
-                            "Unable to initialize ThreadUnwinder object.");
-      log_unwinder_error(&thread_unwinder);
-      return;
-    }
-
-    std::unique_ptr<unwindstack::Regs> initial_regs;
-    thread_unwinder.UnwindWithSignal(BIONIC_SIGNAL_BACKTRACE, thread_info.tid, &initial_regs);
-    dump_registers(&thread_unwinder, initial_regs, thread, memory_dump);
-    dump_thread_backtrace(&thread_unwinder, thread);
+  unwindstack::AndroidUnwinderData data;
+  // Indicate we want a copy of the initial registers.
+  data.saved_initial_regs = std::make_optional<std::unique_ptr<unwindstack::Regs>>();
+  bool unwind_ret;
+  if (thread_info.registers != nullptr) {
+    unwind_ret = unwinder->Unwind(thread_info.registers.get(), data);
   } else {
-    dump_registers(unwinder, thread_info.registers, thread, memory_dump);
-    std::unique_ptr<unwindstack::Regs> regs_copy(thread_info.registers->Clone());
-    unwinder->SetRegs(regs_copy.get());
-    unwinder->Unwind();
-    dump_thread_backtrace(unwinder, thread);
+    unwind_ret = unwinder->Unwind(thread_info.tid, data);
   }
+  if (!unwind_ret) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "Unwind failed for tid %d: Error %s",
+                          thread_info.tid, data.GetErrorString().c_str());
+  } else {
+    dump_thread_backtrace(data.frames, thread);
+  }
+  dump_registers(unwinder, *data.saved_initial_regs, thread, memory_dump);
 
   auto& threads = *tombstone->mutable_threads();
   threads[thread_info.tid] = thread;
 }
 
-static void dump_mappings(Tombstone* tombstone, unwindstack::Unwinder* unwinder) {
-  unwindstack::Maps* maps = unwinder->GetMaps();
-  std::shared_ptr<unwindstack::Memory> process_memory = unwinder->GetProcessMemory();
-
+static void dump_mappings(Tombstone* tombstone, unwindstack::Maps* maps,
+                          std::shared_ptr<unwindstack::Memory>& process_memory) {
   for (const auto& map_info : *maps) {
     auto* map = tombstone->add_memory_mappings();
     map->set_begin_address(map_info->start());
@@ -593,7 +569,8 @@
 }
 
 static void dump_tags_around_fault_addr(Signal* signal, const Tombstone& tombstone,
-                                        unwindstack::Unwinder* unwinder, uintptr_t fault_addr) {
+                                        std::shared_ptr<unwindstack::Memory>& process_memory,
+                                        uintptr_t fault_addr) {
   if (tombstone.arch() != Architecture::ARM64) return;
 
   fault_addr = untag_address(fault_addr);
@@ -604,8 +581,6 @@
   // a valid address for us to dump tags from.
   if (fault_addr < kBytesToRead / 2) return;
 
-  unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
-
   constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1);
   size_t start_address = (fault_addr & kRowStartMask) - kBytesToRead / 2;
   MemoryDump tag_dump;
@@ -614,7 +589,7 @@
   // Attempt to read the first tag. If reading fails, this likely indicates the
   // lowest touched page is inaccessible or not marked with PROT_MTE.
   // Fast-forward over pages until one has tags, or we exhaust the search range.
-  while (memory->ReadTag(start_address) < 0) {
+  while (process_memory->ReadTag(start_address) < 0) {
     size_t page_size = sysconf(_SC_PAGE_SIZE);
     size_t bytes_to_next_page = page_size - (start_address % page_size);
     if (bytes_to_next_page >= granules_to_read * kTagGranuleSize) return;
@@ -626,7 +601,7 @@
   std::string* mte_tags = tag_dump.mutable_arm_mte_metadata()->mutable_memory_tags();
 
   for (size_t i = 0; i < granules_to_read; ++i) {
-    long tag = memory->ReadTag(start_address + i * kTagGranuleSize);
+    long tag = process_memory->ReadTag(start_address + i * kTagGranuleSize);
     if (tag < 0) break;
     mte_tags->push_back(static_cast<uint8_t>(tag));
   }
@@ -636,7 +611,7 @@
   }
 }
 
-void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+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) {
   Tombstone result;
@@ -691,12 +666,12 @@
     sig.set_has_fault_address(true);
     uintptr_t fault_addr = process_info.maybe_tagged_fault_address;
     sig.set_fault_address(fault_addr);
-    dump_tags_around_fault_addr(&sig, result, unwinder, fault_addr);
+    dump_tags_around_fault_addr(&sig, result, unwinder->GetProcessMemory(), fault_addr);
   }
 
   *result.mutable_signal_info() = sig;
 
-  dump_abort_message(&result, unwinder, process_info);
+  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);
@@ -709,7 +684,7 @@
 
   dump_probable_cause(&result, unwinder, process_info, main_thread);
 
-  dump_mappings(&result, unwinder);
+  dump_mappings(&result, unwinder->GetMaps(), unwinder->GetProcessMemory());
 
   // Only dump logs on debuggable devices.
   if (android::base::GetBoolProperty("ro.debuggable", false)) {
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index ecd98a4..74a1423 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -39,6 +39,7 @@
 #include <bionic/reserved_signals.h>
 #include <debuggerd/handler.h>
 #include <log/log.h>
+#include <unwindstack/AndroidUnwinder.h>
 #include <unwindstack/Memory.h>
 #include <unwindstack/Unwinder.h>
 
@@ -483,10 +484,10 @@
   return describe_end(value, desc);
 }
 
-void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix) {
+void log_backtrace(log_t* log, unwindstack::AndroidUnwinder* unwinder,
+                   unwindstack::AndroidUnwinderData& data, const char* prefix) {
   std::set<std::string> unreadable_elf_files;
-  unwinder->SetDisplayBuildID(true);
-  for (const auto& frame : unwinder->frames()) {
+  for (const auto& frame : data.frames) {
     if (frame.map_info != nullptr && frame.map_info->ElfFileNotReadable()) {
       unreadable_elf_files.emplace(frame.map_info->name());
     }
@@ -509,7 +510,7 @@
     }
   }
 
-  for (const auto& frame : unwinder->frames()) {
+  for (const auto& frame : data.frames) {
     _LOG(log, logtype::BACKTRACE, "%s%s\n", prefix, unwinder->FormatFrame(frame).c_str());
   }
 }
diff --git a/debuggerd/test_permissive_mte/Android.bp b/debuggerd/test_permissive_mte/Android.bp
new file mode 100644
index 0000000..1c09240
--- /dev/null
+++ b/debuggerd/test_permissive_mte/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+  name: "mte_crash",
+  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"],
+    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
new file mode 100644
index 0000000..bd3d018
--- /dev/null
+++ b/debuggerd/test_permissive_mte/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs the permissive MTE tests">
+    <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" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="jar" value="permissive_mte_test.jar" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/debuggerd/test_permissive_mte/mte_crash.cpp b/debuggerd/test_permissive_mte/mte_crash.cpp
new file mode 100644
index 0000000..97ad73f
--- /dev/null
+++ b/debuggerd/test_permissive_mte/mte_crash.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int, char**) {
+  volatile char* f = (char*)malloc(1);
+  printf("%c\n", f[17]);
+  return 0;
+}
diff --git a/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
new file mode 100644
index 0000000..5ff2b5b
--- /dev/null
+++ b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.init;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.server.os.TombstoneProtos.Tombstone;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PermissiveMteTest extends BaseHostJUnit4Test {
+  String mUUID;
+
+  @Before
+  public void setUp() throws Exception {
+    mUUID = java.util.UUID.randomUUID().toString();
+    CommandResult result =
+        getDevice().executeShellV2Command("/data/local/tmp/mte_crash setUp " + mUUID);
+    assumeTrue("mte_crash needs to segfault", result.getExitCode() == 139);
+  }
+
+  Tombstone parseTombstone(String tombstonePath) throws Exception {
+    File tombstoneFile = getDevice().pullFile(tombstonePath);
+    InputStream istr = new FileInputStream(tombstoneFile);
+    Tombstone tombstoneProto;
+    try {
+      tombstoneProto = Tombstone.parseFrom(istr);
+    } finally {
+      istr.close();
+    }
+    return tombstoneProto;
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    String[] tombstones = getDevice().getChildren("/data/tombstones");
+    for (String tombstone : tombstones) {
+      if (!tombstone.endsWith(".pb")) {
+        continue;
+      }
+      String tombstonePath = "/data/tombstones/" + tombstone;
+      Tombstone tombstoneProto = parseTombstone(tombstonePath);
+      if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) {
+        continue;
+      }
+      getDevice().deleteFile(tombstonePath);
+      // remove the non .pb file as well.
+      getDevice().deleteFile(tombstonePath.substring(0, tombstonePath.length() - 3));
+    }
+  }
+
+  @Test
+  public void testCrash() throws Exception {
+    CommandResult result = getDevice().executeShellV2Command(
+        "MTE_PERMISSIVE=1 /data/local/tmp/mte_crash testCrash " + mUUID);
+    assertThat(result.getExitCode()).isEqualTo(0);
+    int numberTombstones = 0;
+    String[] tombstones = getDevice().getChildren("/data/tombstones");
+    for (String tombstone : tombstones) {
+      if (!tombstone.endsWith(".pb")) {
+        continue;
+      }
+      String tombstonePath = "/data/tombstones/" + tombstone;
+      Tombstone tombstoneProto = parseTombstone(tombstonePath);
+      if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) {
+        continue;
+      }
+      if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains("testCrash"))) {
+        continue;
+      }
+      numberTombstones++;
+    }
+    assertThat(numberTombstones).isEqualTo(1);
+  }
+}
diff --git a/debuggerd/util.cpp b/debuggerd/util.cpp
index 5c6abc9..df033df 100644
--- a/debuggerd/util.cpp
+++ b/debuggerd/util.cpp
@@ -90,9 +90,7 @@
     if (tid == 0) {
       continue;
     }
-    if (pid != tid) {
-      callback(tid);
-    }
+    callback(tid);
   }
   return true;
 }
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 9ae2c37..b478e6e 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -130,12 +130,10 @@
         "-Werror",
         "-Wvla",
         "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+        "-Wthread-safety",
     ],
     rtti: true,
 
-    clang_cflags: [
-        "-Wthread-safety",
-    ],
 }
 
 cc_binary {
@@ -156,6 +154,7 @@
         "device/flashing.cpp",
         "device/main.cpp",
         "device/usb.cpp",
+        "device/usb_iouring.cpp",
         "device/usb_client.cpp",
         "device/tcp_client.cpp",
         "device/utility.cpp",
@@ -166,6 +165,8 @@
     shared_libs: [
         "android.hardware.boot@1.0",
         "android.hardware.boot@1.1",
+        "android.hardware.boot-V1-ndk",
+        "libboot_control_client",
         "android.hardware.fastboot@1.1",
         "android.hardware.health@2.0",
         "android.hardware.health-V1-ndk",
@@ -192,9 +193,12 @@
         "libhealthhalutils",
         "libhealthshim",
         "libsnapshot_cow",
+        "liblz4",
         "libsnapshot_nobinder",
         "update_metadata-protos",
+        "liburing",
     ],
+    include_dirs: ["bionic/libc/kernel"],
 
     header_libs: [
         "avb_headers",
@@ -215,7 +219,7 @@
         "-Werror",
         "-Wunreachable-code",
         "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
-        "-D_FILE_OFFSET_BITS=64"
+        "-D_FILE_OFFSET_BITS=64",
     ],
 
     target: {
@@ -256,6 +260,7 @@
         "libsparse",
         "libutils",
         "liblog",
+        "liblz4",
         "libz",
         "libdiagnose_usb",
         "libbase",
@@ -344,9 +349,7 @@
     target: {
         not_windows: {
             required: [
-                "e2fsdroid",
                 "mke2fs.conf",
-                "sload_f2fs",
             ],
         },
         windows: {
@@ -411,7 +414,7 @@
         ":fastboot_test_vendor_ramdisk_replace",
         ":fastboot_test_vendor_boot_v3",
         ":fastboot_test_vendor_boot_v4_without_frag",
-        ":fastboot_test_vendor_boot_v4_with_frag"
+        ":fastboot_test_vendor_boot_v4_with_frag",
     ],
 }
 
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index 10bed6d..cde0cb2 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -19,9 +19,7 @@
 #
 
 my_dist_files := $(HOST_OUT_EXECUTABLES)/mke2fs
-my_dist_files += $(HOST_OUT_EXECUTABLES)/e2fsdroid
 my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs
 my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold
-my_dist_files += $(HOST_OUT_EXECUTABLES)/sload_f2fs
 $(call dist-for-goals,dist_files sdk,$(my_dist_files))
 my_dist_files :=
diff --git a/fastboot/OWNERS b/fastboot/OWNERS
index 17b3466..3dec07e 100644
--- a/fastboot/OWNERS
+++ b/fastboot/OWNERS
@@ -1,3 +1,5 @@
 dvander@google.com
 elsk@google.com
 enh@google.com
+zhangkelvin@google.com
+
diff --git a/fastboot/constants.h b/fastboot/constants.h
index 4ea68da..b732c76 100644
--- a/fastboot/constants.h
+++ b/fastboot/constants.h
@@ -79,3 +79,4 @@
 #define FB_VAR_SECURITY_PATCH_LEVEL "security-patch-level"
 #define FB_VAR_TREBLE_ENABLED "treble-enabled"
 #define FB_VAR_MAX_FETCH_SIZE "max-fetch-size"
+#define FB_VAR_DMESG "dmesg"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index b9f6c97..823783e 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -40,6 +40,7 @@
 #include <storage_literals/storage_literals.h>
 #include <uuid/uuid.h>
 
+#include "BootControlClient.h"
 #include "constants.h"
 #include "fastboot_device.h"
 #include "flashing.h"
@@ -52,15 +53,12 @@
 #endif
 
 using android::fs_mgr::MetadataBuilder;
+using android::hal::CommandResult;
 using ::android::hardware::hidl_string;
-using ::android::hardware::boot::V1_0::BoolResult;
-using ::android::hardware::boot::V1_0::CommandResult;
-using ::android::hardware::boot::V1_0::Slot;
-using ::android::hardware::boot::V1_1::MergeStatus;
 using ::android::hardware::fastboot::V1_0::Result;
 using ::android::hardware::fastboot::V1_0::Status;
 using android::snapshot::SnapshotManager;
-using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl;
+using MergeStatus = android::hal::BootControlClient::MergeStatus;
 
 using namespace android::storage_literals;
 
@@ -112,52 +110,65 @@
     }
 }
 
-bool GetVarHandler(FastbootDevice* device, const std::vector<std::string>& args) {
-    const std::unordered_map<std::string, VariableHandlers> kVariableMap = {
-            {FB_VAR_VERSION, {GetVersion, nullptr}},
-            {FB_VAR_VERSION_BOOTLOADER, {GetBootloaderVersion, nullptr}},
-            {FB_VAR_VERSION_BASEBAND, {GetBasebandVersion, nullptr}},
-            {FB_VAR_VERSION_OS, {GetOsVersion, nullptr}},
-            {FB_VAR_VERSION_VNDK, {GetVndkVersion, nullptr}},
-            {FB_VAR_PRODUCT, {GetProduct, nullptr}},
-            {FB_VAR_SERIALNO, {GetSerial, nullptr}},
-            {FB_VAR_VARIANT, {GetVariant, nullptr}},
-            {FB_VAR_SECURE, {GetSecure, nullptr}},
-            {FB_VAR_UNLOCKED, {GetUnlocked, nullptr}},
-            {FB_VAR_MAX_DOWNLOAD_SIZE, {GetMaxDownloadSize, nullptr}},
-            {FB_VAR_CURRENT_SLOT, {::GetCurrentSlot, nullptr}},
-            {FB_VAR_SLOT_COUNT, {GetSlotCount, nullptr}},
-            {FB_VAR_HAS_SLOT, {GetHasSlot, GetAllPartitionArgsNoSlot}},
-            {FB_VAR_SLOT_SUCCESSFUL, {GetSlotSuccessful, nullptr}},
-            {FB_VAR_SLOT_UNBOOTABLE, {GetSlotUnbootable, nullptr}},
-            {FB_VAR_PARTITION_SIZE, {GetPartitionSize, GetAllPartitionArgsWithSlot}},
-            {FB_VAR_PARTITION_TYPE, {GetPartitionType, GetAllPartitionArgsWithSlot}},
-            {FB_VAR_IS_LOGICAL, {GetPartitionIsLogical, GetAllPartitionArgsWithSlot}},
-            {FB_VAR_IS_USERSPACE, {GetIsUserspace, nullptr}},
-            {FB_VAR_OFF_MODE_CHARGE_STATE, {GetOffModeChargeState, nullptr}},
-            {FB_VAR_BATTERY_VOLTAGE, {GetBatteryVoltage, nullptr}},
-            {FB_VAR_BATTERY_SOC_OK, {GetBatterySoCOk, nullptr}},
-            {FB_VAR_HW_REVISION, {GetHardwareRevision, nullptr}},
-            {FB_VAR_SUPER_PARTITION_NAME, {GetSuperPartitionName, nullptr}},
-            {FB_VAR_SNAPSHOT_UPDATE_STATUS, {GetSnapshotUpdateStatus, nullptr}},
-            {FB_VAR_CPU_ABI, {GetCpuAbi, nullptr}},
-            {FB_VAR_SYSTEM_FINGERPRINT, {GetSystemFingerprint, nullptr}},
-            {FB_VAR_VENDOR_FINGERPRINT, {GetVendorFingerprint, nullptr}},
-            {FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}},
-            {FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}},
-            {FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}},
-            {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}},
-            {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}},
-    };
+const std::unordered_map<std::string, VariableHandlers> kVariableMap = {
+        {FB_VAR_VERSION, {GetVersion, nullptr}},
+        {FB_VAR_VERSION_BOOTLOADER, {GetBootloaderVersion, nullptr}},
+        {FB_VAR_VERSION_BASEBAND, {GetBasebandVersion, nullptr}},
+        {FB_VAR_VERSION_OS, {GetOsVersion, nullptr}},
+        {FB_VAR_VERSION_VNDK, {GetVndkVersion, nullptr}},
+        {FB_VAR_PRODUCT, {GetProduct, nullptr}},
+        {FB_VAR_SERIALNO, {GetSerial, nullptr}},
+        {FB_VAR_VARIANT, {GetVariant, nullptr}},
+        {FB_VAR_SECURE, {GetSecure, nullptr}},
+        {FB_VAR_UNLOCKED, {GetUnlocked, nullptr}},
+        {FB_VAR_MAX_DOWNLOAD_SIZE, {GetMaxDownloadSize, nullptr}},
+        {FB_VAR_CURRENT_SLOT, {::GetCurrentSlot, nullptr}},
+        {FB_VAR_SLOT_COUNT, {GetSlotCount, nullptr}},
+        {FB_VAR_HAS_SLOT, {GetHasSlot, GetAllPartitionArgsNoSlot}},
+        {FB_VAR_SLOT_SUCCESSFUL, {GetSlotSuccessful, nullptr}},
+        {FB_VAR_SLOT_UNBOOTABLE, {GetSlotUnbootable, nullptr}},
+        {FB_VAR_PARTITION_SIZE, {GetPartitionSize, GetAllPartitionArgsWithSlot}},
+        {FB_VAR_PARTITION_TYPE, {GetPartitionType, GetAllPartitionArgsWithSlot}},
+        {FB_VAR_IS_LOGICAL, {GetPartitionIsLogical, GetAllPartitionArgsWithSlot}},
+        {FB_VAR_IS_USERSPACE, {GetIsUserspace, nullptr}},
+        {FB_VAR_OFF_MODE_CHARGE_STATE, {GetOffModeChargeState, nullptr}},
+        {FB_VAR_BATTERY_VOLTAGE, {GetBatteryVoltage, nullptr}},
+        {FB_VAR_BATTERY_SOC_OK, {GetBatterySoCOk, nullptr}},
+        {FB_VAR_HW_REVISION, {GetHardwareRevision, nullptr}},
+        {FB_VAR_SUPER_PARTITION_NAME, {GetSuperPartitionName, nullptr}},
+        {FB_VAR_SNAPSHOT_UPDATE_STATUS, {GetSnapshotUpdateStatus, nullptr}},
+        {FB_VAR_CPU_ABI, {GetCpuAbi, nullptr}},
+        {FB_VAR_SYSTEM_FINGERPRINT, {GetSystemFingerprint, nullptr}},
+        {FB_VAR_VENDOR_FINGERPRINT, {GetVendorFingerprint, nullptr}},
+        {FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}},
+        {FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}},
+        {FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}},
+        {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}},
+        {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}},
+};
 
+static bool GetVarAll(FastbootDevice* device) {
+    for (const auto& [name, handlers] : kVariableMap) {
+        GetAllVars(device, name, handlers);
+    }
+    return true;
+}
+
+const std::unordered_map<std::string, std::function<bool(FastbootDevice*)>> kSpecialVars = {
+        {"all", GetVarAll},
+        {"dmesg", GetDmesg},
+};
+
+bool GetVarHandler(FastbootDevice* device, const std::vector<std::string>& args) {
     if (args.size() < 2) {
         return device->WriteFail("Missing argument");
     }
 
-    // Special case: return all variables that we can.
-    if (args[1] == "all") {
-        for (const auto& [name, handlers] : kVariableMap) {
-            GetAllVars(device, name, handlers);
+    // "all" and "dmesg" are multiline and handled specially.
+    auto found_special = kSpecialVars.find(args[1]);
+    if (found_special != kSpecialVars.end()) {
+        if (!found_special->second(device)) {
+            return false;
         }
         return device->WriteOkay("");
     }
@@ -254,6 +265,7 @@
         return device->WriteStatus(FastbootResult::FAIL, ret.message);
     }
 
+    device->WriteInfo(ret.message);
     return device->WriteStatus(FastbootResult::OKAY, ret.message);
 }
 
@@ -303,7 +315,7 @@
                                    "set_active command is not allowed on locked devices");
     }
 
-    Slot slot;
+    int32_t slot = 0;
     if (!GetSlotNumber(args[1], &slot)) {
         // Slot suffix needs to be between 'a' and 'z'.
         return device->WriteStatus(FastbootResult::FAIL, "Bad slot suffix");
@@ -315,7 +327,7 @@
         return device->WriteStatus(FastbootResult::FAIL,
                                    "Cannot set slot: boot control HAL absent");
     }
-    if (slot >= boot_control_hal->getNumberSlots()) {
+    if (slot >= boot_control_hal->GetNumSlots()) {
         return device->WriteStatus(FastbootResult::FAIL, "Slot out of range");
     }
 
@@ -344,10 +356,8 @@
         }
     }
 
-    CommandResult ret;
-    auto cb = [&ret](CommandResult result) { ret = result; };
-    auto result = boot_control_hal->setActiveBootSlot(slot, cb);
-    if (result.isOk() && ret.success) {
+    CommandResult ret = boot_control_hal->SetActiveBootSlot(slot);
+    if (ret.success) {
         // Save as slot suffix to match the suffix format as returned from
         // the boot control HAL.
         auto current_slot = "_" + args[1];
@@ -668,9 +678,14 @@
     if (args[1] == "cancel") {
         switch (status) {
             case MergeStatus::SNAPSHOTTED:
-            case MergeStatus::MERGING:
-                hal->setSnapshotMergeStatus(MergeStatus::CANCELLED);
+            case MergeStatus::MERGING: {
+                const auto ret = hal->SetSnapshotMergeStatus(MergeStatus::CANCELLED);
+                if (!ret.success) {
+                    device->WriteFail("Failed to SetSnapshotMergeStatus(MergeStatus::CANCELLED) " +
+                                      ret.errMsg);
+                }
                 break;
+            }
             default:
                 break;
         }
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index ae225de..4932e5c 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 
+#include <BootControlClient.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
@@ -38,9 +39,8 @@
 using android::fs_mgr::EnsurePathUnmounted;
 using android::fs_mgr::Fstab;
 using ::android::hardware::hidl_string;
-using ::android::hardware::boot::V1_0::IBootControl;
-using ::android::hardware::boot::V1_0::Slot;
 using ::android::hardware::fastboot::V1_1::IFastboot;
+using BootControlClient = FastbootDevice::BootControlClient;
 
 namespace sph = std::placeholders;
 
@@ -85,7 +85,7 @@
               {FB_CMD_SNAPSHOT_UPDATE, SnapshotUpdateHandler},
               {FB_CMD_FETCH, FetchHandler},
       }),
-      boot_control_hal_(IBootControl::getService()),
+      boot_control_hal_(BootControlClient::WaitForService()),
       health_hal_(get_health_service()),
       fastboot_hal_(IFastboot::getService()),
       active_slot_("") {
@@ -95,10 +95,6 @@
         transport_ = std::make_unique<ClientUsbTransport>();
     }
 
-    if (boot_control_hal_) {
-        boot1_1_ = android::hardware::boot::V1_1::IBootControl::castFrom(boot_control_hal_);
-    }
-
     // Make sure cache is unmounted, since recovery will have mounted it for
     // logging.
     Fstab fstab;
@@ -125,12 +121,17 @@
     if (!boot_control_hal_) {
         return "";
     }
-    std::string suffix;
-    auto cb = [&suffix](hidl_string s) { suffix = s; };
-    boot_control_hal_->getSuffix(boot_control_hal_->getCurrentSlot(), cb);
+    std::string suffix = boot_control_hal_->GetSuffix(boot_control_hal_->GetCurrentSlot());
     return suffix;
 }
 
+BootControlClient* FastbootDevice::boot1_1() const {
+    if (boot_control_hal_->GetVersion() >= android::hal::BootControlVersion::BOOTCTL_V1_1) {
+        return boot_control_hal_.get();
+    }
+    return nullptr;
+}
+
 bool FastbootDevice::WriteStatus(FastbootResult result, const std::string& message) {
     constexpr size_t kResponseReasonSize = 4;
     constexpr size_t kNumResponseTypes = 4;  // "FAIL", "OKAY", "INFO", "DATA"
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index 91ffce3..9df8fa5 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -22,9 +22,8 @@
 #include <utility>
 #include <vector>
 
+#include <BootControlClient.h>
 #include <aidl/android/hardware/health/IHealth.h>
-#include <android/hardware/boot/1.0/IBootControl.h>
-#include <android/hardware/boot/1.1/IBootControl.h>
 #include <android/hardware/fastboot/1.1/IFastboot.h>
 
 #include "commands.h"
@@ -33,6 +32,7 @@
 
 class FastbootDevice {
   public:
+    using BootControlClient = android::hal::BootControlClient;
     FastbootDevice();
     ~FastbootDevice();
 
@@ -50,10 +50,8 @@
 
     std::vector<char>& download_data() { return download_data_; }
     Transport* get_transport() { return transport_.get(); }
-    android::sp<android::hardware::boot::V1_0::IBootControl> boot_control_hal() {
-        return boot_control_hal_;
-    }
-    android::sp<android::hardware::boot::V1_1::IBootControl> boot1_1() { return boot1_1_; }
+    BootControlClient* boot_control_hal() const { return boot_control_hal_.get(); }
+    BootControlClient* boot1_1() const;
     android::sp<android::hardware::fastboot::V1_1::IFastboot> fastboot_hal() {
         return fastboot_hal_;
     }
@@ -65,8 +63,7 @@
     const std::unordered_map<std::string, CommandHandler> kCommandMap;
 
     std::unique_ptr<Transport> transport_;
-    android::sp<android::hardware::boot::V1_0::IBootControl> boot_control_hal_;
-    android::sp<android::hardware::boot::V1_1::IBootControl> boot1_1_;
+    std::unique_ptr<BootControlClient> boot_control_hal_;
     std::shared_ptr<aidl::android::hardware::health::IHealth> health_hal_;
     android::sp<android::hardware::fastboot::V1_1::IFastboot> fastboot_hal_;
     std::vector<char> download_data_;
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 06ffe0f..05186a2 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -121,7 +121,12 @@
 int WriteCallback(void* priv, const void* data, size_t len) {
     PartitionHandle* handle = reinterpret_cast<PartitionHandle*>(priv);
     if (!data) {
-        return lseek64(handle->fd(), len, SEEK_CUR) >= 0 ? 0 : -errno;
+        if (lseek64(handle->fd(), len, SEEK_CUR) < 0) {
+            int rv = -errno;
+            PLOG(ERROR) << "lseek failed";
+            return rv;
+        }
+        return 0;
     }
     return FlashRawDataChunk(handle, reinterpret_cast<const char*>(data), len);
 }
@@ -131,6 +136,7 @@
                                                       downloaded_data.size(), true, false);
     if (!file) {
         // Invalid sparse format
+        LOG(ERROR) << "Unable to open sparse data for flashing";
         return -EINVAL;
     }
     return sparse_file_callback(file, false, false, WriteCallback, reinterpret_cast<void*>(handle));
@@ -175,10 +181,13 @@
 
     std::vector<char> data = std::move(device->download_data());
     if (data.size() == 0) {
+        LOG(ERROR) << "Cannot flash empty data vector";
         return -EINVAL;
     }
     uint64_t block_device_size = get_block_device_size(handle.fd());
     if (data.size() > block_device_size) {
+        LOG(ERROR) << "Cannot flash " << data.size() << " bytes to block device of size "
+                   << block_device_size;
         return -EOVERFLOW;
     } else if (data.size() < block_device_size &&
                (partition_name == "boot" || partition_name == "boot_a" ||
diff --git a/fastboot/device/main.cpp b/fastboot/device/main.cpp
index df9c900..08c9358 100644
--- a/fastboot/device/main.cpp
+++ b/fastboot/device/main.cpp
@@ -14,13 +14,30 @@
  * limitations under the License.
  */
 
+#include <stdarg.h>
+
 #include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <sparse/sparse.h>
 
 #include "fastboot_device.h"
 
+static void LogSparseVerboseMessage(const char* fmt, ...) {
+    std::string message;
+
+    va_list ap;
+    va_start(ap, fmt);
+    android::base::StringAppendV(&message, fmt, ap);
+    va_end(ap);
+
+    LOG(ERROR) << "libsparse message: " << message;
+}
+
 int main(int /*argc*/, char* argv[]) {
     android::base::InitLogging(argv, &android::base::KernelLogger);
 
+    sparse_print_verbose = LogSparseVerboseMessage;
+
     while (true) {
         FastbootDevice device;
         device.ExecuteCommands();
diff --git a/fastboot/device/usb.cpp b/fastboot/device/usb.cpp
index 4115a6d..b77d772 100644
--- a/fastboot/device/usb.cpp
+++ b/fastboot/device/usb.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "usb.h"
+#include "usb_iouring.h"
 
 #include <dirent.h>
 #include <errno.h>
@@ -28,6 +29,7 @@
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/functionfs.h>
+#include <sys/utsname.h>
 
 #include <algorithm>
 #include <atomic>
@@ -38,6 +40,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <liburing.h>
 
 using namespace std::chrono_literals;
 
@@ -65,8 +68,8 @@
     }
 }
 
-static int getMaxPacketSize(int ffs_fd) {
-    usb_endpoint_descriptor desc;
+int getMaxPacketSize(int ffs_fd) {
+    usb_endpoint_descriptor desc{};
     if (ioctl(ffs_fd, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&desc))) {
         D("[ could not get endpoint descriptor! (%d) ]", errno);
         return MAX_PACKET_SIZE_HS;
@@ -128,11 +131,9 @@
 
 static int usb_ffs_do_aio(usb_handle* h, const void* data, int len, bool read) {
     aio_block* aiob = read ? &h->read_aiob : &h->write_aiob;
-    bool zero_packet = false;
 
     int num_bufs = len / h->io_size + (len % h->io_size == 0 ? 0 : 1);
     const char* cur_data = reinterpret_cast<const char*>(data);
-    int packet_size = getMaxPacketSize(aiob->fd);
 
     if (posix_madvise(const_cast<void*>(data), len, POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED) <
         0) {
@@ -145,17 +146,6 @@
 
         len -= buf_len;
         cur_data += buf_len;
-
-        if (len == 0 && buf_len % packet_size == 0 && read) {
-            // adb does not expect the device to send a zero packet after data transfer,
-            // but the host *does* send a zero packet for the device to read.
-            zero_packet = h->reads_zero_packets;
-        }
-    }
-    if (zero_packet) {
-        io_prep(&aiob->iocb[num_bufs], aiob->fd, reinterpret_cast<const void*>(cur_data),
-                packet_size, 0, read);
-        num_bufs += 1;
     }
 
     while (true) {
@@ -204,21 +194,46 @@
     h->open_new_connection = true;
     h->lock.unlock();
     h->notify.notify_one();
+    if (h->aio_type == AIOType::IO_URING) {
+        exit_io_uring_ffs(h);
+    }
 }
 
-usb_handle* create_usb_handle(unsigned num_bufs, unsigned io_size) {
-    usb_handle* h = new usb_handle();
+bool DoesKernelSupportIouring() {
+    struct utsname uts {};
+    unsigned int major = 0, minor = 0;
+    if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
+        return false;
+    }
+    if (major > 5) {
+        return true;
+    }
+    // We will only support kernels from 5.6 onwards as IOSQE_ASYNC flag and
+    // IO_URING_OP_READ/WRITE opcodes were introduced only on 5.6 kernel
+    return minor >= 6;
+}
 
-    if (android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false)) {
+std::unique_ptr<usb_handle> create_usb_handle(unsigned num_bufs, unsigned io_size) {
+    auto h = std::make_unique<usb_handle>();
+    if (DoesKernelSupportIouring() &&
+        android::base::GetBoolProperty("sys.usb.ffs.io_uring_enabled", false)) {
+        init_io_uring_ffs(h.get(), num_bufs);
+        h->aio_type = AIOType::IO_URING;
+        LOG(INFO) << "Using io_uring for usb ffs";
+    } else if (android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false)) {
         // Devices on older kernels (< 3.18) will not have aio support for ffs
         // unless backported. Fall back on the non-aio functions instead.
         h->write = usb_ffs_write;
         h->read = usb_ffs_read;
+        h->aio_type = AIOType::SYNC_IO;
+        LOG(INFO) << "Using sync io for usb ffs";
     } else {
         h->write = usb_ffs_aio_write;
         h->read = usb_ffs_aio_read;
         aio_block_init(&h->read_aiob, num_bufs);
         aio_block_init(&h->write_aiob, num_bufs);
+        h->aio_type = AIOType::AIO;
+        LOG(INFO) << "Using aio for usb ffs";
     }
     h->io_size = io_size;
     h->close = usb_ffs_close;
diff --git a/fastboot/device/usb.h b/fastboot/device/usb.h
index 6c3f542..8996c31 100644
--- a/fastboot/device/usb.h
+++ b/fastboot/device/usb.h
@@ -18,8 +18,10 @@
 
 #include <linux/usb/functionfs.h>
 
+#include <liburing.h>
 #include <atomic>
 #include <condition_variable>
+#include <memory>
 #include <mutex>
 #include <vector>
 
@@ -35,9 +37,11 @@
     int fd;
 };
 
-struct usb_handle {
-    usb_handle() {}
+int getMaxPacketSize(int ffs_fd);
 
+enum class AIOType { SYNC_IO, AIO, IO_URING };
+
+struct usb_handle {
     std::condition_variable notify;
     std::mutex lock;
     bool open_new_connection = true;
@@ -56,8 +60,9 @@
     struct aio_block read_aiob;
     struct aio_block write_aiob;
 
-    bool reads_zero_packets;
+    io_uring ring;
     size_t io_size;
+    AIOType aio_type;
 };
 
-usb_handle* create_usb_handle(unsigned num_bufs, unsigned io_size);
+std::unique_ptr<usb_handle> create_usb_handle(unsigned num_bufs, unsigned io_size);
diff --git a/fastboot/device/usb_client.cpp b/fastboot/device/usb_client.cpp
index 3f9b0f0..d1b38d4 100644
--- a/fastboot/device/usb_client.cpp
+++ b/fastboot/device/usb_client.cpp
@@ -232,7 +232,6 @@
 
     h->read_aiob.fd = h->bulk_out.get();
     h->write_aiob.fd = h->bulk_in.get();
-    h->reads_zero_packets = false;
     return true;
 
 err:
diff --git a/fastboot/device/usb_iouring.cpp b/fastboot/device/usb_iouring.cpp
new file mode 100644
index 0000000..d987712
--- /dev/null
+++ b/fastboot/device/usb_iouring.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <liburing.h>
+#include "liburing/io_uring.h"
+#include "usb.h"
+
+static int prep_async_read(struct io_uring* ring, int fd, void* data, size_t len, int64_t offset) {
+    if (io_uring_sq_space_left(ring) <= 0) {
+        LOG(ERROR) << "Submission queue run out of space.";
+        return -1;
+    }
+    auto sqe = io_uring_get_sqe(ring);
+    if (sqe == nullptr) {
+        return -1;
+    }
+    io_uring_sqe_set_flags(sqe, IOSQE_IO_LINK | IOSQE_ASYNC);
+    io_uring_prep_read(sqe, fd, data, len, offset);
+    return 0;
+}
+
+static int prep_async_write(struct io_uring* ring, int fd, const void* data, size_t len,
+                            int64_t offset) {
+    if (io_uring_sq_space_left(ring) <= 0) {
+        LOG(ERROR) << "Submission queue run out of space.";
+        return -1;
+    }
+    auto sqe = io_uring_get_sqe(ring);
+    if (sqe == nullptr) {
+        return -1;
+    }
+    io_uring_sqe_set_flags(sqe, IOSQE_IO_LINK | IOSQE_ASYNC);
+    io_uring_prep_write(sqe, fd, data, len, offset);
+    return 0;
+}
+
+template <bool read, typename T>
+int prep_async_io(struct io_uring* ring, int fd, T* data, size_t len, int64_t offset) {
+    if constexpr (read) {
+        return prep_async_read(ring, fd, data, len, offset);
+    } else {
+        return prep_async_write(ring, fd, data, len, offset);
+    }
+}
+
+template <typename T>
+static constexpr T DivRoundup(T x, T y) {
+    return (x + y - 1) / y;
+}
+
+extern int getMaxPacketSize(int ffs_fd);
+
+template <bool read, typename T>
+static int usb_ffs_do_aio(usb_handle* h, T* const data, const int len) {
+    const aio_block* aiob = read ? &h->read_aiob : &h->write_aiob;
+    const int num_requests = DivRoundup<int>(len, h->io_size);
+    auto cur_data = data;
+    const auto packet_size = getMaxPacketSize(aiob->fd);
+
+    for (int bytes_remain = len; bytes_remain > 0;) {
+        const int buf_len = std::min(bytes_remain, static_cast<int>(h->io_size));
+        const auto ret = prep_async_io<read>(&h->ring, aiob->fd, cur_data, buf_len, 0);
+        if (ret < 0) {
+            PLOG(ERROR) << "Failed to queue io_uring request";
+            return -1;
+        }
+
+        bytes_remain -= buf_len;
+        cur_data = reinterpret_cast<T*>(reinterpret_cast<size_t>(cur_data) + buf_len);
+    }
+    const int ret = io_uring_submit(&h->ring);
+    if (ret <= 0 || ret != num_requests) {
+        PLOG(ERROR) << "io_uring: failed to submit SQE entries to kernel";
+        return -1;
+    }
+    int res = 0;
+    bool success = true;
+    for (int i = 0; i < num_requests; ++i) {
+        struct io_uring_cqe* cqe{};
+        const auto ret = TEMP_FAILURE_RETRY(io_uring_wait_cqe(&h->ring, &cqe));
+        if (ret < 0 || cqe == nullptr) {
+            PLOG(ERROR) << "Failed to get CQE from kernel";
+            success = false;
+            continue;
+        }
+        res += cqe->res;
+        if (cqe->res < 0) {
+            LOG(ERROR) << "io_uring request failed:, i = " << i
+                       << ", num_requests = " << num_requests << ", res = " << cqe->res << ": "
+                       << strerror(cqe->res) << (read ? " read" : " write")
+                       << " request size: " << len << ", io_size: " << h->io_size
+                       << " max packet size: " << packet_size << ", fd: " << aiob->fd;
+            success = false;
+            errno = -cqe->res;
+        }
+        io_uring_cqe_seen(&h->ring, cqe);
+    }
+    if (!success) {
+        return -1;
+    }
+    return res;
+}
+
+static int usb_ffs_io_uring_read(usb_handle* h, void* data, int len, bool /* allow_partial */) {
+    return usb_ffs_do_aio<true>(h, data, len);
+}
+
+static int usb_ffs_io_uring_write(usb_handle* h, const void* data, int len) {
+    return usb_ffs_do_aio<false>(h, data, len);
+}
+
+void exit_io_uring_ffs(usb_handle* h) {
+    io_uring_queue_exit(&h->ring);
+}
+
+bool init_io_uring_ffs(usb_handle* h, size_t queue_depth) {
+    const auto err = io_uring_queue_init(queue_depth, &h->ring, 0);
+    if (err) {
+        LOG(ERROR) << "Failed to initialize io_uring of depth " << queue_depth << ": "
+                   << strerror(err);
+        return false;
+    }
+    h->write = usb_ffs_io_uring_write;
+    h->read = usb_ffs_io_uring_read;
+    return true;
+}
diff --git a/fastboot/device/usb_iouring.h b/fastboot/device/usb_iouring.h
new file mode 100644
index 0000000..7c14b81
--- /dev/null
+++ b/fastboot/device/usb_iouring.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "usb.h"
+
+bool init_io_uring_ffs(usb_handle* h, size_t queue_depth);
+
+void exit_io_uring_ffs(usb_handle* h);
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 3302c43..e12ee64 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -36,7 +36,6 @@
 using namespace android::fs_mgr;
 using namespace std::chrono_literals;
 using android::base::unique_fd;
-using android::hardware::boot::V1_0::Slot;
 
 namespace {
 
@@ -137,7 +136,7 @@
     return true;
 }
 
-bool GetSlotNumber(const std::string& slot, Slot* number) {
+bool GetSlotNumber(const std::string& slot, int32_t* number) {
     if (slot.size() != 1) {
         return false;
     }
@@ -204,7 +203,7 @@
     size_t num_slots = 1;
     auto boot_control_hal = device->boot_control_hal();
     if (boot_control_hal) {
-        num_slots = boot_control_hal->getNumberSlots();
+        num_slots = boot_control_hal->GetNumSlots();
     }
 
     bool ok = true;
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index 6e1453f..a62125e 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -21,7 +21,6 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
-#include <android/hardware/boot/1.0/IBootControl.h>
 #include <fstab/fstab.h>
 #include <liblp/liblp.h>
 
@@ -124,7 +123,7 @@
 bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
                    int flags = O_WRONLY);
 
-bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
+bool GetSlotNumber(const std::string& slot, int32_t* number);
 std::vector<std::string> ListPartitions(FastbootDevice* device);
 bool GetDeviceLockStatus();
 
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index 0cf4699..b6eb2cd 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -17,6 +17,7 @@
 #include "variables.h"
 
 #include <inttypes.h>
+#include <stdio.h>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -28,6 +29,7 @@
 #include <fs_mgr.h>
 #include <liblp/liblp.h>
 
+#include "BootControlClient.h"
 #include "fastboot_device.h"
 #include "flashing.h"
 #include "utility.h"
@@ -38,14 +40,12 @@
 static constexpr bool kEnableFetch = false;
 #endif
 
-using ::android::hardware::boot::V1_0::BoolResult;
-using ::android::hardware::boot::V1_0::Slot;
-using ::android::hardware::boot::V1_1::MergeStatus;
+using MergeStatus = android::hal::BootControlClient::MergeStatus;
 using ::android::hardware::fastboot::V1_0::FileSystemType;
 using ::android::hardware::fastboot::V1_0::Result;
 using ::android::hardware::fastboot::V1_0::Status;
-using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl;
 using namespace android::fs_mgr;
+using namespace std::string_literals;
 
 constexpr char kFastbootProtocolVersion[] = "0.4";
 
@@ -208,7 +208,7 @@
     if (!boot_control_hal) {
         *message = "0";
     } else {
-        *message = std::to_string(boot_control_hal->getNumberSlots());
+        *message = std::to_string(boot_control_hal->GetNumSlots());
     }
     return true;
 }
@@ -219,7 +219,7 @@
         *message = "Missing argument";
         return false;
     }
-    Slot slot;
+    int32_t slot = -1;
     if (!GetSlotNumber(args[0], &slot)) {
         *message = "Invalid slot";
         return false;
@@ -229,7 +229,7 @@
         *message = "Device has no slots";
         return false;
     }
-    if (boot_control_hal->isSlotMarkedSuccessful(slot) != BoolResult::TRUE) {
+    if (boot_control_hal->IsSlotMarkedSuccessful(slot).value_or(false)) {
         *message = "no";
     } else {
         *message = "yes";
@@ -243,7 +243,7 @@
         *message = "Missing argument";
         return false;
     }
-    Slot slot;
+    int32_t slot = -1;
     if (!GetSlotNumber(args[0], &slot)) {
         *message = "Invalid slot";
         return false;
@@ -253,7 +253,7 @@
         *message = "Device has no slots";
         return false;
     }
-    if (boot_control_hal->isSlotBootable(slot) != BoolResult::TRUE) {
+    if (!boot_control_hal->IsSlotBootable(slot).value_or(false)) {
         *message = "yes";
     } else {
         *message = "no";
@@ -518,3 +518,36 @@
     *message = android::base::StringPrintf("0x%X", kMaxFetchSizeDefault);
     return true;
 }
+
+bool GetDmesg(FastbootDevice* device) {
+    if (GetDeviceLockStatus()) {
+        return device->WriteFail("Cannot use when device flashing is locked");
+    }
+
+    std::unique_ptr<FILE, decltype(&::fclose)> fp(popen("/system/bin/dmesg", "re"), ::fclose);
+    if (!fp) {
+        PLOG(ERROR) << "popen /system/bin/dmesg";
+        return device->WriteFail("Unable to run dmesg: "s + strerror(errno));
+    }
+
+    ssize_t rv;
+    size_t n = 0;
+    char* str = nullptr;
+    while ((rv = ::getline(&str, &n, fp.get())) > 0) {
+        if (str[rv - 1] == '\n') {
+            rv--;
+        }
+        device->WriteInfo(std::string(str, rv));
+    }
+
+    int saved_errno = errno;
+    ::free(str);
+
+    if (rv < 0 && saved_errno) {
+        LOG(ERROR) << "dmesg getline: " << strerror(saved_errno);
+        device->WriteFail("Unable to read dmesg: "s + strerror(saved_errno));
+        return false;
+    }
+
+    return true;
+}
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index f40a025..aa4d9fc 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -83,6 +83,9 @@
 bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
                      std::string* message);
 
+// Complex cases.
+bool GetDmesg(FastbootDevice* device);
+
 // Helpers for getvar all.
 std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device);
 std::vector<std::vector<std::string>> GetAllPartitionArgsNoSlot(FastbootDevice* device);
diff --git a/fastboot/fastboot.bash b/fastboot/fastboot.bash
index e9bf9e9..911071a 100644
--- a/fastboot/fastboot.bash
+++ b/fastboot/fastboot.bash
@@ -109,7 +109,7 @@
 
     cur="${COMP_WORDS[COMP_CWORD]}"
     if [[ $i -eq $COMP_CWORD ]]; then
-        partitions="boot bootloader dtbo init_boot modem odm odm_dlkm oem product pvmfw radio recovery system system_dlkm vbmeta vendor vendor_dlkm"
+        partitions="boot bootloader dtbo init_boot modem odm odm_dlkm oem product pvmfw radio recovery system system_dlkm vbmeta vendor vendor_dlkm vendor_kernel_boot"
         COMPREPLY=( $(compgen -W "$partitions" -- $cur) )
     else
         _fastboot_util_complete_local_file "${cur}" '!*.img'
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 39d86f9..46190f2 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -186,6 +186,11 @@
                   "vendor_dlkm.img",  "vendor_dlkm.sig",
                                                       "vendor_dlkm",
                                                                   true,  ImageType::Normal },
+    { "vendor_kernel_boot",
+                  "vendor_kernel_boot.img",
+                                      "vendor_kernel_boot.sig",
+                                                      "vendor_kernel_boot",
+                                                                  true,  ImageType::BootCritical },
     { nullptr,    "vendor_other.img", "vendor.sig",   "vendor",   true,  ImageType::Normal },
         // clang-format on
 };
@@ -1678,10 +1683,9 @@
     return size;
 }
 
-static void fb_perform_format(
-                              const std::string& partition, int skip_if_not_supported,
+static void fb_perform_format(const std::string& partition, int skip_if_not_supported,
                               const std::string& type_override, const std::string& size_override,
-                              const std::string& initial_dir, const unsigned fs_options) {
+                              const unsigned fs_options) {
     std::string partition_type, partition_size;
 
     struct fastboot_buffer buf;
@@ -1743,8 +1747,7 @@
     eraseBlkSize = fb_get_flash_block_size("erase-block-size");
     logicalBlkSize = fb_get_flash_block_size("logical-block-size");
 
-    if (fs_generator_generate(gen, output.path, size, initial_dir,
-            eraseBlkSize, logicalBlkSize, fs_options)) {
+    if (fs_generator_generate(gen, output.path, size, eraseBlkSize, logicalBlkSize, fs_options)) {
         die("Cannot generate image for %s", partition.c_str());
     }
 
@@ -2086,7 +2089,7 @@
             std::string partition = next_arg(&args);
 
             auto format = [&](const std::string& partition) {
-                fb_perform_format(partition, 0, type_override, size_override, "", fs_options);
+                fb_perform_format(partition, 0, type_override, size_override, fs_options);
             };
             do_for_partitions(partition, slot_override, format, true);
         } else if (command == "signature") {
@@ -2277,7 +2280,7 @@
             }
             if (partition_type.empty()) continue;
             fb->Erase(partition);
-            fb_perform_format(partition, 1, partition_type, "", "", fs_options);
+            fb_perform_format(partition, 1, partition_type, "", fs_options);
         }
     }
     if (wants_set_active) {
diff --git a/fastboot/fs.cpp b/fastboot/fs.cpp
index d268a50..c8d1b59 100644
--- a/fastboot/fs.cpp
+++ b/fastboot/fs.cpp
@@ -111,8 +111,7 @@
 }
 #endif
 
-static int generate_ext4_image(const char* fileName, long long partSize,
-                               const std::string& initial_dir, unsigned eraseBlkSize,
+static int generate_ext4_image(const char* fileName, long long partSize, unsigned eraseBlkSize,
                                unsigned logicalBlkSize, const unsigned fsOptions) {
     static constexpr int block_size = 4096;
     const std::string exec_dir = android::base::GetExecutableDirectory();
@@ -163,16 +162,7 @@
     if (ret != 0) {
         return -1;
     }
-
-    if (initial_dir.empty()) {
-        return 0;
-    }
-
-    const std::string e2fsdroid_path = exec_dir + "/e2fsdroid";
-    std::vector<const char*> e2fsdroid_args = {e2fsdroid_path.c_str(), "-f", initial_dir.c_str(),
-                                               fileName, nullptr};
-
-    return exec_cmd(e2fsdroid_args[0], e2fsdroid_args.data(), nullptr);
+    return 0;
 }
 
 enum {
@@ -188,8 +178,7 @@
     // clang-format on
 };
 
-static int generate_f2fs_image(const char* fileName, long long partSize,
-                               const std::string& initial_dir, unsigned /* unused */,
+static int generate_f2fs_image(const char* fileName, long long partSize, unsigned /* unused */,
                                unsigned /* unused */, const unsigned fsOptions) {
     const std::string exec_dir = android::base::GetExecutableDirectory();
     const std::string mkf2fs_path = exec_dir + "/make_f2fs";
@@ -227,19 +216,6 @@
     if (ret != 0) {
         return -1;
     }
-
-    if (initial_dir.empty()) {
-        return 0;
-    }
-
-    const std::string sload_path = exec_dir + "/sload_f2fs";
-    std::vector<const char*> sload_args = {sload_path.c_str(), "-S",
-                                       "-f", initial_dir.c_str(), fileName, nullptr};
-
-    ret = exec_cmd(sload_args[0], sload_args.data(), nullptr);
-    if (ret != 0 && ret != FSCK_ERROR_CORRECTED) {
-        return -1;
-    }
     return 0;
 }
 
@@ -247,8 +223,8 @@
     const char* fs_type;  //must match what fastboot reports for partition type
 
     //returns 0 or error value
-    int (*generate)(const char* fileName, long long partSize, const std::string& initial_dir,
-                    unsigned eraseBlkSize, unsigned logicalBlkSize, const unsigned fsOptions);
+    int (*generate)(const char* fileName, long long partSize, unsigned eraseBlkSize,
+                    unsigned logicalBlkSize, const unsigned fsOptions);
 
 } generators[] = {
     { "ext4", generate_ext4_image},
@@ -265,7 +241,7 @@
 }
 
 int fs_generator_generate(const struct fs_generator* gen, const char* fileName, long long partSize,
-                          const std::string& initial_dir, unsigned eraseBlkSize,
-                          unsigned logicalBlkSize, const unsigned fsOptions) {
-    return gen->generate(fileName, partSize, initial_dir, eraseBlkSize, logicalBlkSize, fsOptions);
+                          unsigned eraseBlkSize, unsigned logicalBlkSize,
+                          const unsigned fsOptions) {
+    return gen->generate(fileName, partSize, eraseBlkSize, logicalBlkSize, fsOptions);
 }
diff --git a/fastboot/fs.h b/fastboot/fs.h
index f832938..5ae473b 100644
--- a/fastboot/fs.h
+++ b/fastboot/fs.h
@@ -13,5 +13,5 @@
 
 const struct fs_generator* fs_get_generator(const std::string& fs_type);
 int fs_generator_generate(const struct fs_generator* gen, const char* fileName, long long partSize,
-                          const std::string& initial_dir, unsigned eraseBlkSize = 0,
-                          unsigned logicalBlkSize = 0, unsigned fsOptions = 0);
+                          unsigned eraseBlkSize = 0, unsigned logicalBlkSize = 0,
+                          unsigned fsOptions = 0);
diff --git a/fastboot/fuzzy_fastboot/Android.bp b/fastboot/fuzzy_fastboot/Android.bp
index 159c314..2031170 100644
--- a/fastboot/fuzzy_fastboot/Android.bp
+++ b/fastboot/fuzzy_fastboot/Android.bp
@@ -9,6 +9,7 @@
 
 cc_test_host {
   name: "fuzzy_fastboot",
+  isolated: false,
   compile_multilib: "first",
 
   srcs: [
diff --git a/fastboot/vendor_boot_img_utils.cpp b/fastboot/vendor_boot_img_utils.cpp
index 9e09abb..9f05253 100644
--- a/fastboot/vendor_boot_img_utils.cpp
+++ b/fastboot/vendor_boot_img_utils.cpp
@@ -152,6 +152,9 @@
     if (memcmp(hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE) != 0) {
         return Errorf("Vendor boot image magic mismatch");
     }
+    if (hdr->page_size == 0) {
+        return Errorf("Page size cannot be zero");
+    }
     if (hdr->header_version < version) {
         return Errorf("Require vendor boot header V{} but is V{}", version, hdr->header_version);
     }
@@ -199,6 +202,7 @@
 }
 
 // round |value| up to a multiple of |page_size|.
+// aware that this can be integer overflow if value is too large
 inline uint32_t round_up(uint32_t value, uint32_t page_size) {
     return (value + page_size - 1) / page_size * page_size;
 }
@@ -311,7 +315,13 @@
     const uint32_t r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
     const uint32_t s = round_up(hdr->bootconfig_size, hdr->page_size);
 
-    if (hdr->vendor_ramdisk_table_entry_num == std::numeric_limits<uint32_t>::max()) {
+    uint64_t total_size = (uint64_t)o + p + q + r + s;
+    if (total_size > vendor_boot.size()) {
+        return Errorf("Vendor boot image size is too small, overflow");
+    }
+
+    if ((uint64_t)hdr->vendor_ramdisk_table_entry_num * sizeof(vendor_ramdisk_table_entry_v4) >
+        (uint64_t)o + p + q + r) {
         return Errorf("Too many vendor ramdisk entries in table, overflow");
     }
 
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 6863894..1f54f5b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -90,9 +90,6 @@
 #define SYSFS_EXT4_VERITY "/sys/fs/ext4/features/verity"
 #define SYSFS_EXT4_CASEFOLD "/sys/fs/ext4/features/casefold"
 
-// FIXME: this should be in system/extras
-#define EXT4_FEATURE_COMPAT_STABLE_INODES 0x0800
-
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
 
 using android::base::Basename;
@@ -124,8 +121,8 @@
     FS_STAT_RO_MOUNT_FAILED = 0x0040,
     FS_STAT_RO_UNMOUNT_FAILED = 0x0080,
     FS_STAT_FULL_MOUNT_FAILED = 0x0100,
-    FS_STAT_E2FSCK_FAILED = 0x0200,
-    FS_STAT_E2FSCK_FS_FIXED = 0x0400,
+    FS_STAT_FSCK_FAILED = 0x0200,
+    FS_STAT_FSCK_FS_FIXED = 0x0400,
     FS_STAT_INVALID_MAGIC = 0x0800,
     FS_STAT_TOGGLE_QUOTAS_FAILED = 0x10000,
     FS_STAT_SET_RESERVED_BLOCKS_FAILED = 0x20000,
@@ -136,7 +133,6 @@
 };
 
 static void log_fs_stat(const std::string& blk_device, int fs_stat) {
-    if ((fs_stat & FS_STAT_IS_EXT4) == 0) return; // only log ext4
     std::string msg =
             android::base::StringPrintf("\nfs_stat,%s,0x%x\n", blk_device.c_str(), fs_stat);
     android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(FSCK_LOG_FILE, O_WRONLY | O_CLOEXEC |
@@ -166,7 +162,7 @@
     return fs_stat &
            (FS_STAT_E2FSCK_F_ALWAYS | FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED |
             FS_STAT_RO_MOUNT_FAILED | FS_STAT_RO_UNMOUNT_FAILED | FS_STAT_FULL_MOUNT_FAILED |
-            FS_STAT_E2FSCK_FAILED | FS_STAT_TOGGLE_QUOTAS_FAILED |
+            FS_STAT_FSCK_FAILED | FS_STAT_TOGGLE_QUOTAS_FAILED |
             FS_STAT_SET_RESERVED_BLOCKS_FAILED | FS_STAT_ENABLE_ENCRYPTION_FAILED);
 }
 
@@ -255,10 +251,10 @@
             if (ret < 0) {
                 /* No need to check for error in fork, we can't really handle it now */
                 LERROR << "Failed trying to run " << E2FSCK_BIN;
-                *fs_stat |= FS_STAT_E2FSCK_FAILED;
+                *fs_stat |= FS_STAT_FSCK_FAILED;
             } else if (status != 0) {
                 LINFO << "e2fsck returned status 0x" << std::hex << status;
-                *fs_stat |= FS_STAT_E2FSCK_FS_FIXED;
+                *fs_stat |= FS_STAT_FSCK_FS_FIXED;
             }
         }
     } else if (is_f2fs(fs_type)) {
@@ -267,20 +263,30 @@
         const char* f2fs_fsck_forced_argv[] = {
                 F2FS_FSCK_BIN, "-f", "-c", "10000", "--debug-cache", blk_device.c_str()};
 
-        if (should_force_check(*fs_stat)) {
-            LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache "
-                  << realpath(blk_device);
-            ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_forced_argv), f2fs_fsck_forced_argv,
-                                      &status, false, LOG_KLOG | LOG_FILE, false, nullptr);
+        if (access(F2FS_FSCK_BIN, X_OK)) {
+            LINFO << "Not running " << F2FS_FSCK_BIN << " on " << realpath(blk_device)
+                  << " (executable not in system image)";
         } else {
-            LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache "
-                  << realpath(blk_device);
-            ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv, &status, false,
-                                      LOG_KLOG | LOG_FILE, false, nullptr);
-        }
-        if (ret < 0) {
-            /* No need to check for error in fork, we can't really handle it now */
-            LERROR << "Failed trying to run " << F2FS_FSCK_BIN;
+            if (should_force_check(*fs_stat)) {
+                LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache "
+                      << realpath(blk_device);
+                ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_forced_argv), f2fs_fsck_forced_argv,
+                                          &status, false, LOG_KLOG | LOG_FILE, false,
+                                          FSCK_LOG_FILE);
+            } else {
+                LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache "
+                      << realpath(blk_device);
+                ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv, &status,
+                                          false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
+            }
+            if (ret < 0) {
+                /* No need to check for error in fork, we can't really handle it now */
+                LERROR << "Failed trying to run " << F2FS_FSCK_BIN;
+                *fs_stat |= FS_STAT_FSCK_FAILED;
+            } else if (status != 0) {
+                LINFO << F2FS_FSCK_BIN << " returned status 0x" << std::hex << status;
+                *fs_stat |= FS_STAT_FSCK_FS_FIXED;
+            }
         }
     }
     android::base::SetProperty("ro.boottime.init.fsck." + Basename(target),
@@ -840,8 +846,10 @@
     if ((ret == 0) && (mountflags & MS_RDONLY) != 0) {
         fs_mgr_set_blk_ro(source);
     }
-    android::base::SetProperty("ro.boottime.init.mount." + Basename(target),
-                               std::to_string(t.duration().count()));
+    if (ret == 0) {
+        android::base::SetProperty("ro.boottime.init.mount." + Basename(target),
+                                   std::to_string(t.duration().count()));
+    }
     errno = save_errno;
     return ret;
 }
@@ -881,7 +889,7 @@
 // attempted_idx: On return, will indicate which fstab entry
 //     succeeded. In case of failure, it will be the start_idx.
 // Sets errno to match the 1st mount failure on failure.
-static bool mount_with_alternatives(const Fstab& fstab, int start_idx, int* end_idx,
+static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx,
                                     int* attempted_idx) {
     unsigned long i;
     int mount_errno = 0;
@@ -901,6 +909,14 @@
             continue;
         }
 
+        // fstab[start_idx].blk_device is already updated to /dev/dm-<N> by
+        // AVB related functions. Copy it from start_idx to the current index i.
+        if ((i != start_idx) && fstab[i].fs_mgr_flags.logical &&
+            fstab[start_idx].fs_mgr_flags.logical &&
+            (fstab[i].logical_partition_name == fstab[start_idx].logical_partition_name)) {
+            fstab[i].blk_device = fstab[start_idx].blk_device;
+        }
+
         int fs_stat = prepare_fs_for_mount(fstab[i].blk_device, fstab[i]);
         if (fs_stat & FS_STAT_INVALID_MAGIC) {
             LERROR << __FUNCTION__
@@ -1474,7 +1490,7 @@
                 if (status == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
                     if (!call_vdc({"cryptfs", "encryptFstab", attempted_entry.blk_device,
                                    attempted_entry.mount_point, wiped ? "true" : "false",
-                                   attempted_entry.fs_type},
+                                   attempted_entry.fs_type, attempted_entry.zoned_device},
                                   nullptr)) {
                         LERROR << "Encryption failed";
                         set_type_property(encryptable);
@@ -1514,7 +1530,7 @@
 
                 if (!call_vdc({"cryptfs", "encryptFstab", current_entry.blk_device,
                                current_entry.mount_point, "true" /* shouldFormat */,
-                               current_entry.fs_type},
+                               current_entry.fs_type, current_entry.zoned_device},
                               nullptr)) {
                     LERROR << "Encryption failed";
                 } else {
@@ -1539,7 +1555,7 @@
         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.mount_point, attempted_entry.zoned_device},
                           nullptr)) {
                 ++error_count;
             } else if (current_entry.mount_point == "/data") {
@@ -1911,6 +1927,7 @@
         while (retry_count-- > 0) {
             if (!__mount(n_blk_device, mount_point, fstab_entry)) {
                 fs_stat &= ~FS_STAT_FULL_MOUNT_FAILED;
+                log_fs_stat(fstab_entry.blk_device, fs_stat);
                 return FS_MGR_DOMNT_SUCCESS;
             } else {
                 if (retry_count <= 0) break;  // run check_fs only once
diff --git a/fs_mgr/fs_mgr_format.cpp b/fs_mgr/fs_mgr_format.cpp
index 6f59ed3..7385f79 100644
--- a/fs_mgr/fs_mgr_format.cpp
+++ b/fs_mgr/fs_mgr_format.cpp
@@ -117,7 +117,7 @@
 }
 
 static int format_f2fs(const std::string& fs_blkdev, uint64_t dev_sz, bool needs_projid,
-                       bool needs_casefold, bool fs_compress) {
+                       bool needs_casefold, bool fs_compress, const std::string& zoned_device) {
     if (!dev_sz) {
         int rc = get_dev_sz(fs_blkdev, &dev_sz);
         if (rc) {
@@ -146,8 +146,15 @@
         args.push_back("-O");
         args.push_back("extra_attr");
     }
-    args.push_back(fs_blkdev.c_str());
-    args.push_back(size_str.c_str());
+    if (!zoned_device.empty()) {
+        args.push_back("-c");
+        args.push_back(zoned_device.c_str());
+        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());
+    }
 
     return logwrap_fork_execvp(args.size(), args.data(), nullptr, false, LOG_KLOG, false, nullptr);
 }
@@ -164,7 +171,7 @@
 
     if (entry.fs_type == "f2fs") {
         return format_f2fs(entry.blk_device, entry.length, needs_projid, needs_casefold,
-                           entry.fs_mgr_flags.fs_compress);
+                           entry.fs_mgr_flags.fs_compress, entry.zoned_device);
     } 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_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 8c719c8..43961da 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -304,6 +304,14 @@
             if (!ParseByteCount(arg, &entry->zram_backingdev_size)) {
                 LWARNING << "Warning: zram_backingdev_size= flag malformed: " << arg;
             }
+        } else if (StartsWith(flag, "zoned_device=")) {
+            std::string zoned;
+            if (ReadFileToString("/sys/class/block/" + arg + "/queue/zoned", &zoned) &&
+                android::base::StartsWith(zoned, "host-managed")) {
+                entry->zoned_device = "/dev/block/" + arg;
+            } else {
+                LWARNING << "Warning: cannot find the zoned device: " << arg;
+            }
         } else {
             LWARNING << "Warning: unknown flag: " << flag;
         }
@@ -431,34 +439,6 @@
     return fstab_result;
 }
 
-// Return the path to the fstab file.  There may be multiple fstab files; the
-// one that is returned will be the first that exists of fstab.<fstab_suffix>,
-// fstab.<hardware>, and fstab.<hardware.platform>.  The fstab is searched for
-// in /odm/etc/ and /vendor/etc/, as well as in the locations where it may be in
-// the first stage ramdisk during early boot.  Previously, the first stage
-// ramdisk's copy of the fstab had to be located in the root directory, but now
-// the system/etc directory is supported too and is the preferred location.
-std::string GetFstabPath() {
-    for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
-        std::string suffix;
-
-        if (!fs_mgr_get_boot_config(prop, &suffix)) continue;
-
-        for (const char* prefix : {// late-boot/post-boot locations
-                                   "/odm/etc/fstab.", "/vendor/etc/fstab.",
-                                   // early boot locations
-                                   "/system/etc/fstab.", "/first_stage_ramdisk/system/etc/fstab.",
-                                   "/fstab.", "/first_stage_ramdisk/fstab."}) {
-            std::string fstab_path = prefix + suffix;
-            if (access(fstab_path.c_str(), F_OK) == 0) {
-                return fstab_path;
-            }
-        }
-    }
-
-    return "";
-}
-
 /* Extracts <device>s from the by-name symlinks specified in a fstab:
  *   /dev/block/<type>/<device>/by-name/<partition>
  *
@@ -502,37 +482,50 @@
     return boot_devices;
 }
 
-FstabEntry BuildDsuUserdataFstabEntry() {
-    constexpr uint32_t kFlags = MS_NOATIME | MS_NOSUID | MS_NODEV;
-
-    FstabEntry userdata = {
-            .blk_device = "userdata_gsi",
-            .mount_point = "/data",
-            .fs_type = "ext4",
-            .flags = kFlags,
-            .reserved_size = 128 * 1024 * 1024,
-    };
-    userdata.fs_mgr_flags.wait = true;
-    userdata.fs_mgr_flags.check = true;
-    userdata.fs_mgr_flags.logical = true;
-    userdata.fs_mgr_flags.quota = true;
-    userdata.fs_mgr_flags.late_mount = true;
-    userdata.fs_mgr_flags.formattable = true;
-    return userdata;
-}
-
-bool EraseFstabEntry(Fstab* fstab, const std::string& mount_point) {
-    auto iter = std::remove_if(fstab->begin(), fstab->end(),
-                               [&](const auto& entry) { return entry.mount_point == mount_point; });
-    if (iter != fstab->end()) {
-        fstab->erase(iter, fstab->end());
-        return true;
+template <typename Pred>
+std::vector<FstabEntry*> GetEntriesByPred(Fstab* fstab, const Pred& pred) {
+    if (fstab == nullptr) {
+        return {};
     }
-    return false;
+    std::vector<FstabEntry*> entries;
+    for (auto&& entry : *fstab) {
+        if (pred(entry)) {
+            entries.push_back(&entry);
+        }
+    }
+    return entries;
 }
 
 }  // namespace
 
+// Return the path to the fstab file.  There may be multiple fstab files; the
+// one that is returned will be the first that exists of fstab.<fstab_suffix>,
+// fstab.<hardware>, and fstab.<hardware.platform>.  The fstab is searched for
+// in /odm/etc/ and /vendor/etc/, as well as in the locations where it may be in
+// the first stage ramdisk during early boot.  Previously, the first stage
+// ramdisk's copy of the fstab had to be located in the root directory, but now
+// the system/etc directory is supported too and is the preferred location.
+std::string GetFstabPath() {
+    for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
+        std::string suffix;
+
+        if (!fs_mgr_get_boot_config(prop, &suffix)) continue;
+
+        for (const char* prefix : {// late-boot/post-boot locations
+                                   "/odm/etc/fstab.", "/vendor/etc/fstab.",
+                                   // early boot locations
+                                   "/system/etc/fstab.", "/first_stage_ramdisk/system/etc/fstab.",
+                                   "/fstab.", "/first_stage_ramdisk/fstab."}) {
+            std::string fstab_path = prefix + suffix;
+            if (access(fstab_path.c_str(), F_OK) == 0) {
+                return fstab_path;
+            }
+        }
+    }
+
+    return "";
+}
+
 bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out) {
     const int expected_fields = proc_mounts ? 4 : 5;
 
@@ -591,38 +584,28 @@
 void TransformFstabForDsu(Fstab* fstab, const std::string& dsu_slot,
                           const std::vector<std::string>& dsu_partitions) {
     static constexpr char kDsuKeysDir[] = "/avb";
-    // Convert userdata
-    // Inherit fstab properties for userdata.
-    FstabEntry userdata;
-    if (FstabEntry* entry = GetEntryForMountPoint(fstab, "/data")) {
-        userdata = *entry;
-        userdata.blk_device = android::gsi::kDsuUserdata;
-        userdata.fs_mgr_flags.logical = true;
-        userdata.fs_mgr_flags.formattable = true;
-        if (!userdata.metadata_key_dir.empty()) {
-            userdata.metadata_key_dir = android::gsi::GetDsuMetadataKeyDir(dsu_slot);
-        }
-    } else {
-        userdata = BuildDsuUserdataFstabEntry();
-    }
-
-    if (EraseFstabEntry(fstab, "/data")) {
-        fstab->emplace_back(userdata);
-    }
-
-    // Convert others
     for (auto&& partition : dsu_partitions) {
         if (!EndsWith(partition, gsi::kDsuPostfix)) {
             continue;
         }
-        // userdata has been handled
-        if (partition == android::gsi::kDsuUserdata) {
-            continue;
-        }
         // scratch is handled by fs_mgr_overlayfs
         if (partition == android::gsi::kDsuScratch) {
             continue;
         }
+        // Convert userdata partition.
+        if (partition == android::gsi::kDsuUserdata) {
+            for (auto&& entry : GetEntriesForMountPoint(fstab, "/data")) {
+                entry->blk_device = android::gsi::kDsuUserdata;
+                entry->fs_mgr_flags.logical = true;
+                entry->fs_mgr_flags.formattable = true;
+                if (!entry->metadata_key_dir.empty()) {
+                    entry->metadata_key_dir = android::gsi::GetDsuMetadataKeyDir(dsu_slot);
+                }
+            }
+            continue;
+        }
+        // Convert RO partitions.
+        //
         // dsu_partition_name = corresponding_partition_name + kDsuPostfix
         // e.g.
         //    system_gsi for system
@@ -630,45 +613,51 @@
         //    vendor_gsi for vendor
         std::string lp_name = partition.substr(0, partition.length() - strlen(gsi::kDsuPostfix));
         std::string mount_point = "/" + lp_name;
-        std::vector<FstabEntry*> entries = GetEntriesForMountPoint(fstab, mount_point);
-        if (entries.empty()) {
-            FstabEntry entry = {
-                    .blk_device = partition,
-                    // .logical_partition_name is required to look up AVB Hashtree descriptors.
-                    .logical_partition_name = "system",
-                    .mount_point = mount_point,
-                    .fs_type = "ext4",
-                    .flags = MS_RDONLY,
-                    .fs_options = "barrier=1",
-                    .avb_keys = kDsuKeysDir,
-            };
-            entry.fs_mgr_flags.wait = true;
-            entry.fs_mgr_flags.logical = true;
-            entry.fs_mgr_flags.first_stage_mount = true;
-            fstab->emplace_back(entry);
-        } else {
-            // If the corresponding partition exists, transform all its Fstab
-            // by pointing .blk_device to the DSU partition.
-            for (auto&& entry : entries) {
-                entry->blk_device = partition;
-                // AVB keys for DSU should always be under kDsuKeysDir.
-                entry->avb_keys = kDsuKeysDir;
-                entry->fs_mgr_flags.logical = true;
+
+        // List of fs_type entries we're lacking, need to synthesis these later.
+        std::vector<std::string> lack_fs_list = {"ext4", "erofs"};
+
+        // Only support early mount (first_stage_mount) partitions.
+        auto pred = [&mount_point](const FstabEntry& entry) {
+            return entry.fs_mgr_flags.first_stage_mount && entry.mount_point == mount_point;
+        };
+
+        // Transform all matching entries and assume they are all adjacent for simplicity.
+        for (auto&& entry : GetEntriesByPred(fstab, pred)) {
+            // .blk_device is replaced with the DSU partition.
+            entry->blk_device = partition;
+            // .avb_keys hints first_stage_mount to load the chained-vbmeta image from partition
+            // footer. See aosp/932779 for more details.
+            entry->avb_keys = kDsuKeysDir;
+            // .logical_partition_name is required to look up AVB Hashtree descriptors.
+            entry->logical_partition_name = lp_name;
+            entry->fs_mgr_flags.logical = true;
+            entry->fs_mgr_flags.slot_select = false;
+            entry->fs_mgr_flags.slot_select_other = false;
+
+            if (auto it = std::find(lack_fs_list.begin(), lack_fs_list.end(), entry->fs_type);
+                it != lack_fs_list.end()) {
+                lack_fs_list.erase(it);
             }
-            // Make sure the ext4 is included to support GSI.
-            auto partition_ext4 =
-                    std::find_if(fstab->begin(), fstab->end(), [&](const auto& entry) {
-                        return entry.mount_point == mount_point && entry.fs_type == "ext4";
-                    });
-            if (partition_ext4 == fstab->end()) {
-                auto new_entry = *GetEntryForMountPoint(fstab, mount_point);
-                new_entry.fs_type = "ext4";
-                auto it = std::find_if(fstab->rbegin(), fstab->rend(),
-                                       [&mount_point](const auto& entry) {
-                                           return entry.mount_point == mount_point;
-                                       });
-                auto end_of_mount_point_group = fstab->begin() + std::distance(it, fstab->rend());
-                fstab->insert(end_of_mount_point_group, new_entry);
+        }
+
+        if (!lack_fs_list.empty()) {
+            // Insert at the end of the existing mountpoint group, or at the end of fstab.
+            // We assume there is at most one matching mountpoint group, which is the common case.
+            auto it = std::find_if_not(std::find_if(fstab->begin(), fstab->end(), pred),
+                                       fstab->end(), pred);
+            for (const auto& fs_type : lack_fs_list) {
+                it = std::next(fstab->insert(it, {.blk_device = partition,
+                                                  .logical_partition_name = lp_name,
+                                                  .mount_point = mount_point,
+                                                  .fs_type = fs_type,
+                                                  .flags = MS_RDONLY,
+                                                  .avb_keys = kDsuKeysDir,
+                                                  .fs_mgr_flags{
+                                                          .wait = true,
+                                                          .logical = true,
+                                                          .first_stage_mount = true,
+                                                  }}));
             }
         }
     }
@@ -713,22 +702,13 @@
     }
     if (!is_proc_mounts) {
         if (!access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
-            // This is expected to fail if host is android Q, since Q doesn't
-            // support DSU slotting. The DSU "active" indicator file would be
-            // non-existent or empty if DSU is enabled within the guest system.
-            // In that case, just use the default slot name "dsu".
             std::string dsu_slot;
-            if (!android::gsi::GetActiveDsu(&dsu_slot) && errno != ENOENT) {
+            if (!android::gsi::GetActiveDsu(&dsu_slot)) {
                 PERROR << __FUNCTION__ << "(): failed to get active DSU slot";
                 return false;
             }
-            if (dsu_slot.empty()) {
-                dsu_slot = "dsu";
-                LWARNING << __FUNCTION__ << "(): assuming default DSU slot: " << dsu_slot;
-            }
-            // This file is non-existent on Q vendor.
             std::string lp_names;
-            if (!ReadFileToString(gsi::kGsiLpNamesFile, &lp_names) && errno != ENOENT) {
+            if (!ReadFileToString(gsi::kGsiLpNamesFile, &lp_names)) {
                 PERROR << __FUNCTION__ << "(): failed to read DSU LP names";
                 return false;
             }
@@ -824,7 +804,7 @@
 
     std::string default_fstab_path;
     // Use different fstab paths for normal boot and recovery boot, respectively
-    if (access("/system/bin/recovery", F_OK) == 0) {
+    if ((access("/sbin/recovery", F_OK) == 0) || (access("/system/bin/recovery", F_OK) == 0)) {
         default_fstab_path = "/etc/recovery.fstab";
     } else {  // normal boot
         default_fstab_path = GetFstabPath();
@@ -857,18 +837,8 @@
 }
 
 std::vector<FstabEntry*> GetEntriesForMountPoint(Fstab* fstab, const std::string& path) {
-    std::vector<FstabEntry*> entries;
-    if (fstab == nullptr) {
-        return entries;
-    }
-
-    for (auto& entry : *fstab) {
-        if (entry.mount_point == path) {
-            entries.emplace_back(&entry);
-        }
-    }
-
-    return entries;
+    return GetEntriesByPred(fstab,
+                            [&path](const FstabEntry& entry) { return entry.mount_point == path; });
 }
 
 std::set<std::string> GetBootDevices() {
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 82b5275..354d02a 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -81,10 +81,17 @@
     return filesystems.find("\t" + filesystem + "\n") != std::string::npos;
 }
 
+const auto kLowerdirOption = "lowerdir="s;
+const auto kUpperdirOption = "upperdir="s;
+
 }  // namespace
 
 #if ALLOW_ADBD_DISABLE_VERITY == 0  // If we are a user build, provide stubs
 
+bool fs_mgr_wants_overlayfs(FstabEntry*) {
+    return false;
+}
+
 Fstab fs_mgr_overlayfs_candidate_list(const Fstab&) {
     return {};
 }
@@ -93,7 +100,7 @@
     return false;
 }
 
-bool fs_mgr_overlayfs_setup(const char*, const char*, bool* change, bool) {
+bool fs_mgr_overlayfs_setup(const char*, bool* change, bool) {
     if (change) *change = false;
     return false;
 }
@@ -324,9 +331,6 @@
     return "";
 }
 
-const auto kLowerdirOption = "lowerdir="s;
-const auto kUpperdirOption = "upperdir="s;
-
 static inline bool KernelSupportsUserXattrs() {
     struct utsname uts;
     uname(&uts);
@@ -366,48 +370,6 @@
     return ret;
 }
 
-bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only = true) {
-    Fstab fstab;
-    auto save_errno = errno;
-    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
-        return false;
-    }
-    errno = save_errno;
-    const auto lowerdir = kLowerdirOption + mount_point;
-    for (const auto& entry : fstab) {
-        if (overlay_only && "overlay" != entry.fs_type && "overlayfs" != entry.fs_type) continue;
-        if (mount_point != entry.mount_point) continue;
-        if (!overlay_only) return true;
-        const auto options = android::base::Split(entry.fs_options, ",");
-        for (const auto& opt : options) {
-            if (opt == lowerdir) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-bool fs_mgr_wants_overlayfs(FstabEntry* entry) {
-    // Don't check entries that are managed by vold.
-    if (entry->fs_mgr_flags.vold_managed || entry->fs_mgr_flags.recovery_only) return false;
-
-    // *_other doesn't want overlayfs.
-    if (entry->fs_mgr_flags.slot_select_other) return false;
-
-    // Only concerned with readonly partitions.
-    if (!(entry->flags & MS_RDONLY)) return false;
-
-    // If unbindable, do not allow overlayfs as this could expose us to
-    // security issues.  On Android, this could also be used to turn off
-    // the ability to overlay an otherwise acceptable filesystem since
-    // /system and /vendor are never bound(sic) to.
-    if (entry->flags & MS_UNBINDABLE) return false;
-
-    if (!fs_mgr_overlayfs_enabled(entry)) return false;
-
-    return true;
-}
 constexpr char kOverlayfsFileContext[] = "u:object_r:overlayfs_file:s0";
 
 bool fs_mgr_overlayfs_setup_dir(const std::string& dir, std::string* overlay, bool* change) {
@@ -642,6 +604,10 @@
     if (ret) {
         PERROR << "__mount(target=" << mount_point
                << ",flag=" << (shared_flag ? "MS_SHARED" : "MS_PRIVATE") << ")=" << ret;
+        // If "/system" doesn't look like a mountpoint, retry with "/".
+        if (errno == EINVAL && mount_point == "/system") {
+            return fs_mgr_overlayfs_set_shared_mount("/", shared_flag);
+        }
         return false;
     }
     return true;
@@ -1140,7 +1106,13 @@
         return 0;
     }
 
-    return std::min(super_info.size, (uint64_t(s.f_frsize) * s.f_bfree) / 2);
+    auto ideal_size = std::min(super_info.size, (uint64_t(s.f_frsize) * s.f_bfree) / 2);
+
+    // Align up to the filesystem block size.
+    if (auto remainder = ideal_size % s.f_bsize; remainder > 0) {
+        ideal_size += s.f_bsize - remainder;
+    }
+    return ideal_size;
 }
 
 static bool CreateScratchOnData(std::string* scratch_device, bool* partition_exists, bool* change) {
@@ -1274,9 +1246,45 @@
 
 }  // namespace
 
+bool fs_mgr_wants_overlayfs(FstabEntry* entry) {
+    // Don't check entries that are managed by vold.
+    if (entry->fs_mgr_flags.vold_managed || entry->fs_mgr_flags.recovery_only) return false;
+
+    // *_other doesn't want overlayfs.
+    if (entry->fs_mgr_flags.slot_select_other) return false;
+
+    // Only concerned with readonly partitions.
+    if (!(entry->flags & MS_RDONLY)) return false;
+
+    // If unbindable, do not allow overlayfs as this could expose us to
+    // security issues.  On Android, this could also be used to turn off
+    // the ability to overlay an otherwise acceptable filesystem since
+    // /system and /vendor are never bound(sic) to.
+    if (entry->flags & MS_UNBINDABLE) return false;
+
+    if (!fs_mgr_overlayfs_enabled(entry)) return false;
+
+    return true;
+}
+
 Fstab fs_mgr_overlayfs_candidate_list(const Fstab& fstab) {
+    android::fs_mgr::Fstab mounts;
+    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
+        PLOG(ERROR) << "Failed to read /proc/mounts";
+        return {};
+    }
+
     Fstab candidates;
     for (const auto& entry : fstab) {
+        // Filter out partitions whose type doesn't match what's mounted.
+        // This avoids spammy behavior on devices which can mount different
+        // filesystems for each partition.
+        auto proc_mount_point = (entry.mount_point == "/system") ? "/" : entry.mount_point;
+        auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point);
+        if (!mounted || mounted->fs_type != entry.fs_type) {
+            continue;
+        }
+
         FstabEntry new_entry = entry;
         if (!fs_mgr_overlayfs_already_mounted(entry.mount_point) &&
             !fs_mgr_wants_overlayfs(&new_entry)) {
@@ -1349,8 +1357,7 @@
 
 // Returns false if setup not permitted, errno set to last error.
 // If something is altered, set *change.
-bool fs_mgr_overlayfs_setup(const char* backing, const char* mount_point, bool* change,
-                            bool force) {
+bool fs_mgr_overlayfs_setup(const char* mount_point, bool* change, bool force) {
     if (change) *change = false;
     auto ret = false;
     if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) return ret;
@@ -1387,7 +1394,6 @@
 
     std::string dir;
     for (const auto& overlay_mount_point : OverlayMountPoints()) {
-        if (backing && backing[0] && (overlay_mount_point != backing)) continue;
         if (overlay_mount_point == kScratchMountPoint) {
             if (!fs_mgr_overlayfs_setup_scratch(fstab, change)) continue;
         } else {
@@ -1683,6 +1689,28 @@
 
 #endif  // ALLOW_ADBD_DISABLE_VERITY != 0
 
+bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only) {
+    Fstab fstab;
+    auto save_errno = errno;
+    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
+        return false;
+    }
+    errno = save_errno;
+    const auto lowerdir = kLowerdirOption + mount_point;
+    for (const auto& entry : fstab) {
+        if (overlay_only && "overlay" != entry.fs_type && "overlayfs" != entry.fs_type) continue;
+        if (mount_point != entry.mount_point) continue;
+        if (!overlay_only) return true;
+        const auto options = android::base::Split(entry.fs_options, ",");
+        for (const auto& opt : options) {
+            if (opt == lowerdir) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev) {
     struct statfs fs;
     if ((statfs((mount_point + "/lost+found").c_str(), &fs) == -1) ||
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index deaf5f7..4a927d0 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -43,6 +43,8 @@
 #include <libgsi/libgsid.h>
 
 using namespace std::literals;
+using android::fs_mgr::Fstab;
+using android::fs_mgr::FstabEntry;
 
 namespace {
 
@@ -62,22 +64,12 @@
     ::exit(exit_status);
 }
 
-bool remountable_partition(const android::fs_mgr::FstabEntry& entry) {
-    if (entry.fs_mgr_flags.vold_managed) return false;
-    if (entry.fs_mgr_flags.recovery_only) return false;
-    if (entry.fs_mgr_flags.slot_select_other) return false;
-    if (!(entry.flags & MS_RDONLY)) return false;
-    if (entry.fs_type == "vfat") return false;
-    return true;
-}
-
 const std::string system_mount_point(const android::fs_mgr::FstabEntry& entry) {
     if (entry.mount_point == "/") return "/system";
     return entry.mount_point;
 }
 
-const android::fs_mgr::FstabEntry* is_wrapped(const android::fs_mgr::Fstab& overlayfs_candidates,
-                                              const android::fs_mgr::FstabEntry& entry) {
+const FstabEntry* GetWrappedEntry(const Fstab& overlayfs_candidates, const FstabEntry& entry) {
     auto mount_point = system_mount_point(entry);
     auto it = std::find_if(overlayfs_candidates.begin(), overlayfs_candidates.end(),
                            [&mount_point](const auto& entry) {
@@ -99,12 +91,8 @@
     logd(id, severity, tag, file, line, message);
 }
 
-[[noreturn]] void reboot(bool overlayfs = false) {
-    if (overlayfs) {
-        LOG(INFO) << "Successfully setup overlayfs\nrebooting device";
-    } else {
-        LOG(INFO) << "Successfully disabled verity\nrebooting device";
-    }
+[[noreturn]] void reboot() {
+    LOG(INFO) << "Rebooting device for new settings to take effect";
     ::sync();
     android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,remount");
     ::sleep(60);
@@ -140,15 +128,368 @@
     BAD_OVERLAY,
     NO_MOUNTS,
     REMOUNT_FAILED,
-    MUST_REBOOT,
     BINDER_ERROR,
     CHECKPOINTING,
     GSID_ERROR,
-    CLEAN_SCRATCH_FILES,
 };
 
-static int do_remount(int argc, char* argv[]) {
-    RemountStatus retval = REMOUNT_SUCCESS;
+static bool ReadFstab(const char* fstab_file, android::fs_mgr::Fstab* fstab) {
+    if (fstab_file) {
+        return android::fs_mgr::ReadFstabFromFile(fstab_file, fstab);
+    }
+    if (!android::fs_mgr::ReadDefaultFstab(fstab)) {
+        return false;
+    }
+
+    // Manufacture a / entry from /proc/mounts if missing.
+    if (!GetEntryForMountPoint(fstab, "/system") && !GetEntryForMountPoint(fstab, "/")) {
+        android::fs_mgr::Fstab mounts;
+        if (android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
+            if (auto entry = GetEntryForMountPoint(&mounts, "/")) {
+                if (entry->fs_type != "rootfs") fstab->emplace_back(*entry);
+            }
+        }
+    }
+    return true;
+}
+
+static RemountStatus VerifyCheckpointing() {
+    if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false) &&
+        !android::base::GetBoolProperty("ro.virtual_ab.retrofit", false)) {
+        return REMOUNT_SUCCESS;
+    }
+
+    // Virtual A/B devices can use /data as backing storage; make sure we're
+    // not checkpointing.
+    auto vold = GetVold();
+    bool checkpointing = false;
+    if (!vold->isCheckpointing(&checkpointing).isOk()) {
+        LOG(ERROR) << "Could not determine checkpointing status.";
+        return BINDER_ERROR;
+    }
+    if (checkpointing) {
+        LOG(ERROR) << "Cannot use remount when a checkpoint is in progress.";
+        return CHECKPOINTING;
+    }
+    return REMOUNT_SUCCESS;
+}
+
+static bool IsRemountable(Fstab& candidates, const FstabEntry& entry) {
+    if (entry.fs_mgr_flags.vold_managed || entry.fs_mgr_flags.recovery_only ||
+        entry.fs_mgr_flags.slot_select_other) {
+        return false;
+    }
+    if (!(entry.flags & MS_RDONLY)) {
+        return false;
+    }
+    if (entry.fs_type == "vfat") {
+        return false;
+    }
+    if (auto candidate_entry = GetEntryForMountPoint(&candidates, entry.mount_point)) {
+        return candidate_entry->fs_type == entry.fs_type;
+    }
+    if (GetWrappedEntry(candidates, entry)) {
+        return false;
+    }
+    return true;
+}
+
+static Fstab::const_iterator FindPartition(const Fstab& fstab, const std::string& partition) {
+    Fstab mounts;
+    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
+        LOG(ERROR) << "Failed to read /proc/mounts";
+        return fstab.end();
+    }
+
+    for (auto iter = fstab.begin(); iter != fstab.end(); iter++) {
+        const auto mount_point = system_mount_point(*iter);
+        if (partition == mount_point || partition == android::base::Basename(mount_point)) {
+            // In case fstab has multiple entries, pick the one that matches the
+            // actual mounted filesystem type.
+            auto proc_mount_point = (iter->mount_point == "/system") ? "/" : iter->mount_point;
+            auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point);
+            if (mounted && mounted->fs_type == iter->fs_type) {
+                return iter;
+            }
+        }
+    }
+    return fstab.end();
+}
+
+static Fstab GetAllRemountablePartitions(Fstab& fstab) {
+    auto candidates = fs_mgr_overlayfs_candidate_list(fstab);
+
+    Fstab partitions;
+    for (const auto& entry : fstab) {
+        if (IsRemountable(candidates, entry)) {
+            partitions.emplace_back(entry);
+        }
+    }
+    return partitions;
+}
+
+static RemountStatus GetRemountList(const Fstab& fstab, const std::vector<std::string>& argv,
+                                    Fstab* partitions) {
+    auto candidates = fs_mgr_overlayfs_candidate_list(fstab);
+
+    for (const auto& arg : argv) {
+        std::string partition = arg;
+        if (partition == "/") {
+            partition = "/system";
+        }
+
+        auto it = FindPartition(fstab, partition);
+        if (it == fstab.end()) {
+            LOG(ERROR) << "Unknown partition " << arg;
+            return UNKNOWN_PARTITION;
+        }
+
+        const FstabEntry* entry = &*it;
+        if (auto wrap = GetWrappedEntry(candidates, *entry); wrap != nullptr) {
+            LOG(INFO) << "partition " << arg << " covered by overlayfs for " << wrap->mount_point
+                      << ", switching";
+            entry = wrap;
+        }
+
+        // If it's already remounted, include it so it gets gracefully skipped
+        // later on.
+        if (!fs_mgr_overlayfs_already_mounted(entry->mount_point) &&
+            !IsRemountable(candidates, *entry)) {
+            LOG(ERROR) << "Invalid partition " << arg;
+            return INVALID_PARTITION;
+        }
+        if (GetEntryForMountPoint(partitions, entry->mount_point) != nullptr) {
+            continue;
+        }
+        partitions->emplace_back(*entry);
+    }
+
+    return REMOUNT_SUCCESS;
+}
+
+struct RemountCheckResult {
+    bool reboot_later = false;
+    bool setup_overlayfs = false;
+    bool disabled_verity = false;
+    bool verity_error = false;
+    bool remounted_anything = false;
+};
+
+static RemountStatus CheckVerity(const FstabEntry& entry, RemountCheckResult* result) {
+    if (!fs_mgr_is_verity_enabled(entry)) {
+        return REMOUNT_SUCCESS;
+    }
+    if (android::base::GetProperty("ro.boot.vbmeta.device_state", "") == "locked") {
+        return VERITY_PARTITION;
+    }
+
+    bool ok = false;
+
+    std::unique_ptr<AvbOps, decltype(&::avb_ops_user_free)> ops(avb_ops_user_new(),
+                                                                &::avb_ops_user_free);
+    if (ops) {
+        auto suffix = android::base::GetProperty("ro.boot.slot_suffix", "");
+        ok = avb_user_verity_set(ops.get(), suffix.c_str(), false);
+    }
+    if (!ok && fs_mgr_set_blk_ro(entry.blk_device, false)) {
+        fec::io fh(entry.blk_device.c_str(), O_RDWR);
+        ok = fh && fh.set_verity_status(false);
+    }
+    if (!ok) {
+        return VERITY_PARTITION;
+    }
+    result->disabled_verity = true;
+    result->reboot_later = true;
+    return REMOUNT_SUCCESS;
+}
+
+static RemountStatus CheckVerityAndOverlayfs(Fstab* partitions, RemountCheckResult* result) {
+    RemountStatus status = REMOUNT_SUCCESS;
+    for (auto it = partitions->begin(); it != partitions->end();) {
+        auto& entry = *it;
+        const auto& mount_point = entry.mount_point;
+
+        if (auto rv = CheckVerity(entry, result); rv != REMOUNT_SUCCESS) {
+            LOG(ERROR) << "Skipping verified partition " << mount_point << " for remount";
+            status = rv;
+            it = partitions->erase(it);
+            continue;
+        }
+
+        if (fs_mgr_wants_overlayfs(&entry)) {
+            bool change = false;
+            bool force = result->disabled_verity;
+            if (!fs_mgr_overlayfs_setup(mount_point.c_str(), &change, force)) {
+                LOG(ERROR) << "Overlayfs setup for " << mount_point << " failed, skipping";
+                status = BAD_OVERLAY;
+                it = partitions->erase(it);
+                continue;
+            }
+            if (change) {
+                LOG(INFO) << "Using overlayfs for " << mount_point;
+                result->reboot_later = true;
+                result->setup_overlayfs = true;
+            }
+        }
+        it++;
+    }
+    return status;
+}
+
+static RemountStatus EnableDsuIfNeeded() {
+    auto gsid = android::gsi::GetGsiService();
+    if (!gsid) {
+        return REMOUNT_SUCCESS;
+    }
+
+    auto dsu_running = false;
+    if (auto status = gsid->isGsiRunning(&dsu_running); !status.isOk()) {
+        LOG(ERROR) << "Failed to get DSU running state: " << status;
+        return BINDER_ERROR;
+    }
+    auto dsu_enabled = false;
+    if (auto status = gsid->isGsiEnabled(&dsu_enabled); !status.isOk()) {
+        LOG(ERROR) << "Failed to get DSU enabled state: " << status;
+        return BINDER_ERROR;
+    }
+    if (dsu_running && !dsu_enabled) {
+        std::string dsu_slot;
+        if (auto status = gsid->getActiveDsuSlot(&dsu_slot); !status.isOk()) {
+            LOG(ERROR) << "Failed to get active DSU slot: " << status;
+            return BINDER_ERROR;
+        }
+        LOG(INFO) << "DSU is running but disabled, enable DSU so that we stay within the "
+                     "DSU guest system after reboot";
+        int error = 0;
+        if (auto status = gsid->enableGsi(/* oneShot = */ true, dsu_slot, &error);
+            !status.isOk() || error != android::gsi::IGsiService::INSTALL_OK) {
+            LOG(ERROR) << "Failed to enable DSU: " << status << ", error code: " << error;
+            return !status.isOk() ? BINDER_ERROR : GSID_ERROR;
+        }
+        LOG(INFO) << "Successfully enabled DSU (one-shot mode)";
+    }
+    return REMOUNT_SUCCESS;
+}
+
+static RemountStatus RemountPartition(Fstab& fstab, Fstab& mounts, FstabEntry& entry) {
+    // unlock the r/o key for the mount point device
+    if (entry.fs_mgr_flags.logical) {
+        fs_mgr_update_logical_partition(&entry);
+    }
+    auto blk_device = entry.blk_device;
+    auto mount_point = entry.mount_point;
+
+    auto found = false;
+    for (auto it = mounts.rbegin(); it != mounts.rend(); ++it) {
+        auto& rentry = *it;
+        if (mount_point == rentry.mount_point) {
+            blk_device = rentry.blk_device;
+            found = true;
+            break;
+        }
+        // Find overlayfs mount point?
+        if ((mount_point == "/" && rentry.mount_point == "/system") ||
+            (mount_point == "/system" && rentry.mount_point == "/")) {
+            blk_device = rentry.blk_device;
+            mount_point = "/system";
+            found = true;
+            break;
+        }
+    }
+    if (!found) {
+        PLOG(INFO) << "skip unmounted partition dev:" << blk_device << " mnt:" << mount_point;
+        return REMOUNT_SUCCESS;
+    }
+    if (blk_device == "/dev/root") {
+        auto from_fstab = GetEntryForMountPoint(&fstab, mount_point);
+        if (from_fstab) blk_device = from_fstab->blk_device;
+    }
+    fs_mgr_set_blk_ro(blk_device, false);
+
+    // Find system-as-root mount point?
+    if ((mount_point == "/system") && !GetEntryForMountPoint(&mounts, mount_point) &&
+        GetEntryForMountPoint(&mounts, "/")) {
+        mount_point = "/";
+    }
+
+    // Now remount!
+    if (::mount(blk_device.c_str(), mount_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
+                nullptr) == 0) {
+        return REMOUNT_SUCCESS;
+    }
+    if ((errno == EINVAL) && (mount_point != entry.mount_point)) {
+        mount_point = entry.mount_point;
+        if (::mount(blk_device.c_str(), mount_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
+                    nullptr) == 0) {
+            return REMOUNT_SUCCESS;
+        }
+    }
+
+    PLOG(ERROR) << "failed to remount partition dev:" << blk_device << " mnt:" << mount_point;
+    return REMOUNT_FAILED;
+}
+
+static int do_remount(Fstab& fstab, const std::vector<std::string>& partition_args,
+                      RemountCheckResult* check_result) {
+    Fstab partitions;
+    if (partition_args.empty()) {
+        partitions = GetAllRemountablePartitions(fstab);
+    } else {
+        if (auto rv = GetRemountList(fstab, partition_args, &partitions); rv != REMOUNT_SUCCESS) {
+            return rv;
+        }
+    }
+
+    // Check verity and optionally setup overlayfs backing.
+    auto retval = CheckVerityAndOverlayfs(&partitions, check_result);
+
+    if (partitions.empty() || check_result->disabled_verity) {
+        if (partitions.empty()) {
+            LOG(WARNING) << "No remountable partitions were found.";
+        }
+        return retval;
+    }
+
+    // Mount overlayfs.
+    errno = 0;
+    if (!fs_mgr_overlayfs_mount_all(&partitions) && errno) {
+        PLOG(ERROR) << "Can not mount overlayfs for partitions";
+        return BAD_OVERLAY;
+    }
+
+    // Get actual mounts _after_ overlayfs has been added.
+    android::fs_mgr::Fstab mounts;
+    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts) || mounts.empty()) {
+        PLOG(ERROR) << "Failed to read /proc/mounts";
+        return NO_MOUNTS;
+    }
+
+    // Remount selected partitions.
+    for (auto& entry : partitions) {
+        if (auto rv = RemountPartition(fstab, mounts, entry); rv != REMOUNT_SUCCESS) {
+            retval = rv;
+        } else {
+            check_result->remounted_anything = true;
+        }
+    }
+    return retval;
+}
+
+int main(int argc, char* argv[]) {
+    // Do not use MyLogger() when running as clean_scratch_files, as stdout/stderr of daemon process
+    // are discarded.
+    if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) {
+        android::fs_mgr::CleanupOldScratchFiles();
+        return 0;
+    }
+
+    android::base::InitLogging(argv, MyLogger);
+
+    // Make sure we are root.
+    if (::getuid() != 0) {
+        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
+        return NOT_ROOT;
+    }
 
     // If somehow this executable is delivered on a "user" build, it can
     // not function, so providing a clear message to the caller rather than
@@ -159,14 +500,14 @@
     }
 
     const char* fstab_file = nullptr;
-    auto can_reboot = false;
+    auto auto_reboot = false;
+    std::vector<std::string> partition_args;
 
     struct option longopts[] = {
             {"fstab", required_argument, nullptr, 'T'},
             {"help", no_argument, nullptr, 'h'},
             {"reboot", no_argument, nullptr, 'R'},
             {"verbose", no_argument, nullptr, 'v'},
-            {"clean_scratch_files", no_argument, nullptr, 'C'},
             {0, 0, nullptr, 0},
     };
     for (int opt; (opt = ::getopt_long(argc, argv, "hRT:v", longopts, nullptr)) != -1;) {
@@ -175,7 +516,7 @@
                 usage(SUCCESS);
                 break;
             case 'R':
-                can_reboot = true;
+                auto_reboot = true;
                 break;
             case 'T':
                 if (fstab_file) {
@@ -187,8 +528,6 @@
             case 'v':
                 verbose = true;
                 break;
-            case 'C':
-                return CLEAN_SCRATCH_FILES;
             default:
                 LOG(ERROR) << "Bad Argument -" << char(opt);
                 usage(BADARG);
@@ -196,310 +535,51 @@
         }
     }
 
-    // Make sure we are root.
-    if (::getuid() != 0) {
-        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
-        return NOT_ROOT;
+    for (; argc > optind; ++optind) {
+        partition_args.emplace_back(argv[optind]);
+    }
+
+    // Make sure checkpointing is disabled if necessary.
+    if (auto rv = VerifyCheckpointing(); rv != REMOUNT_SUCCESS) {
+        return rv;
     }
 
     // Read the selected fstab.
-    android::fs_mgr::Fstab fstab;
-    auto fstab_read = false;
-    if (fstab_file) {
-        fstab_read = android::fs_mgr::ReadFstabFromFile(fstab_file, &fstab);
-    } else {
-        fstab_read = android::fs_mgr::ReadDefaultFstab(&fstab);
-        // Manufacture a / entry from /proc/mounts if missing.
-        if (!GetEntryForMountPoint(&fstab, "/system") && !GetEntryForMountPoint(&fstab, "/")) {
-            android::fs_mgr::Fstab mounts;
-            if (android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
-                if (auto entry = GetEntryForMountPoint(&mounts, "/")) {
-                    if (entry->fs_type != "rootfs") fstab.emplace_back(*entry);
-                }
-            }
-        }
-    }
-    if (!fstab_read || fstab.empty()) {
+    Fstab fstab;
+    if (!ReadFstab(fstab_file, &fstab) || fstab.empty()) {
         PLOG(ERROR) << "Failed to read fstab";
         return NO_FSTAB;
     }
 
-    if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false) &&
-        !android::base::GetBoolProperty("ro.virtual_ab.retrofit", false)) {
-        // Virtual A/B devices can use /data as backing storage; make sure we're
-        // not checkpointing.
-        auto vold = GetVold();
-        bool checkpointing = false;
-        if (!vold->isCheckpointing(&checkpointing).isOk()) {
-            LOG(ERROR) << "Could not determine checkpointing status.";
-            return BINDER_ERROR;
-        }
-        if (checkpointing) {
-            LOG(ERROR) << "Cannot use remount when a checkpoint is in progress.";
-            return CHECKPOINTING;
-        }
+    RemountCheckResult check_result;
+    int result = do_remount(fstab, partition_args, &check_result);
+
+    if (check_result.disabled_verity && check_result.setup_overlayfs) {
+        LOG(INFO) << "Verity disabled; overlayfs enabled.";
+    } else if (check_result.disabled_verity) {
+        LOG(INFO) << "Verity disabled.";
+    } else if (check_result.setup_overlayfs) {
+        LOG(INFO) << "Overlayfs enabled.";
     }
 
-    // Generate the list of supported overlayfs mount points.
-    auto overlayfs_candidates = fs_mgr_overlayfs_candidate_list(fstab);
-
-    // Generate the all remountable partitions sub-list
-    android::fs_mgr::Fstab all;
-    for (auto const& entry : fstab) {
-        if (!remountable_partition(entry)) continue;
-        if (overlayfs_candidates.empty() ||
-            GetEntryForMountPoint(&overlayfs_candidates, entry.mount_point) ||
-            (is_wrapped(overlayfs_candidates, entry) == nullptr)) {
-            all.emplace_back(entry);
-        }
-    }
-
-    // Parse the unique list of valid partition arguments.
-    android::fs_mgr::Fstab partitions;
-    for (; argc > optind; ++optind) {
-        auto partition = std::string(argv[optind]);
-        if (partition.empty()) continue;
-        if (partition == "/") partition = "/system";
-        auto find_part = [&partition](const auto& entry) {
-            const auto mount_point = system_mount_point(entry);
-            if (partition == mount_point) return true;
-            if (partition == android::base::Basename(mount_point)) return true;
-            return false;
-        };
-        // Do we know about the partition?
-        auto it = std::find_if(fstab.begin(), fstab.end(), find_part);
-        if (it == fstab.end()) {
-            LOG(ERROR) << "Unknown partition " << argv[optind] << ", skipping";
-            retval = UNKNOWN_PARTITION;
-            continue;
-        }
-        // Is that one covered by an existing overlayfs?
-        auto wrap = is_wrapped(overlayfs_candidates, *it);
-        if (wrap) {
-            LOG(INFO) << "partition " << argv[optind] << " covered by overlayfs for "
-                      << wrap->mount_point << ", switching";
-            partition = system_mount_point(*wrap);
-        }
-        // Is it a remountable partition?
-        it = std::find_if(all.begin(), all.end(), find_part);
-        if (it == all.end()) {
-            LOG(ERROR) << "Invalid partition " << argv[optind] << ", skipping";
-            retval = INVALID_PARTITION;
-            continue;
-        }
-        if (GetEntryForMountPoint(&partitions, it->mount_point) == nullptr) {
-            partitions.emplace_back(*it);
-        }
-    }
-
-    if (partitions.empty() && !retval) {
-        partitions = all;
-    }
-
-    // Check verity and optionally setup overlayfs backing.
-    auto reboot_later = false;
-    auto user_please_reboot_later = false;
-    auto setup_overlayfs = false;
-    auto just_disabled_verity = false;
-    for (auto it = partitions.begin(); it != partitions.end();) {
-        auto& entry = *it;
-        auto& mount_point = entry.mount_point;
-        if (fs_mgr_is_verity_enabled(entry)) {
-            retval = VERITY_PARTITION;
-            auto ret = false;
-            if (android::base::GetProperty("ro.boot.vbmeta.device_state", "") != "locked") {
-                if (AvbOps* ops = avb_ops_user_new()) {
-                    ret = avb_user_verity_set(
-                            ops, android::base::GetProperty("ro.boot.slot_suffix", "").c_str(),
-                            false);
-                    avb_ops_user_free(ops);
-                }
-                if (!ret && fs_mgr_set_blk_ro(entry.blk_device, false)) {
-                    fec::io fh(entry.blk_device.c_str(), O_RDWR);
-                    ret = fh && fh.set_verity_status(false);
-                }
-                if (ret) {
-                    LOG(WARNING) << "Disabling verity for " << mount_point;
-                    just_disabled_verity = true;
-                    reboot_later = can_reboot;
-                    user_please_reboot_later = true;
-                }
+    if (check_result.reboot_later) {
+        if (auto_reboot) {
+            // If (1) remount requires a reboot to take effect, (2) system is currently
+            // running a DSU guest and (3) DSU is disabled, then enable DSU so that the
+            // next reboot would not take us back to the host system but stay within
+            // the guest system.
+            if (auto rv = EnableDsuIfNeeded(); rv != REMOUNT_SUCCESS) {
+                LOG(ERROR) << "Unable to automatically enable DSU";
+                return rv;
             }
-            if (!ret) {
-                LOG(ERROR) << "Skipping " << mount_point << " for remount";
-                it = partitions.erase(it);
-                continue;
-            }
+            reboot();
+        } else {
+            LOG(INFO) << "Now reboot your device for settings to take effect";
         }
-
-        auto change = false;
-        errno = 0;
-        if (fs_mgr_overlayfs_setup(nullptr, mount_point.c_str(), &change, just_disabled_verity)) {
-            if (change) {
-                LOG(INFO) << "Using overlayfs for " << mount_point;
-                reboot_later = can_reboot;
-                user_please_reboot_later = true;
-                setup_overlayfs = true;
-            }
-        } else if (errno) {
-            PLOG(ERROR) << "Overlayfs setup for " << mount_point << " failed, skipping";
-            retval = BAD_OVERLAY;
-            it = partitions.erase(it);
-            continue;
-        }
-        ++it;
+        return REMOUNT_SUCCESS;
     }
-
-    // If (1) remount requires a reboot to take effect, (2) system is currently
-    // running a DSU guest and (3) DSU is disabled, then enable DSU so that the
-    // next reboot would not take us back to the host system but stay within
-    // the guest system.
-    if (reboot_later) {
-        if (auto gsid = android::gsi::GetGsiService()) {
-            auto dsu_running = false;
-            if (auto status = gsid->isGsiRunning(&dsu_running); !status.isOk()) {
-                LOG(ERROR) << "Failed to get DSU running state: " << status;
-                return BINDER_ERROR;
-            }
-            auto dsu_enabled = false;
-            if (auto status = gsid->isGsiEnabled(&dsu_enabled); !status.isOk()) {
-                LOG(ERROR) << "Failed to get DSU enabled state: " << status;
-                return BINDER_ERROR;
-            }
-            if (dsu_running && !dsu_enabled) {
-                std::string dsu_slot;
-                if (auto status = gsid->getActiveDsuSlot(&dsu_slot); !status.isOk()) {
-                    LOG(ERROR) << "Failed to get active DSU slot: " << status;
-                    return BINDER_ERROR;
-                }
-                LOG(INFO) << "DSU is running but disabled, enable DSU so that we stay within the "
-                             "DSU guest system after reboot";
-                int error = 0;
-                if (auto status = gsid->enableGsi(/* oneShot = */ true, dsu_slot, &error);
-                    !status.isOk() || error != android::gsi::IGsiService::INSTALL_OK) {
-                    LOG(ERROR) << "Failed to enable DSU: " << status << ", error code: " << error;
-                    return !status.isOk() ? BINDER_ERROR : GSID_ERROR;
-                }
-                LOG(INFO) << "Successfully enabled DSU (one-shot mode)";
-            }
-        }
-    }
-
-    if (partitions.empty() || just_disabled_verity) {
-        if (reboot_later) reboot(setup_overlayfs);
-        if (user_please_reboot_later) {
-            return MUST_REBOOT;
-        }
-        LOG(WARNING) << "No partitions to remount";
-        return retval;
-    }
-
-    // Mount overlayfs.
-    errno = 0;
-    if (!fs_mgr_overlayfs_mount_all(&partitions) && errno) {
-        retval = BAD_OVERLAY;
-        PLOG(ERROR) << "Can not mount overlayfs for partitions";
-    }
-
-    // Get actual mounts _after_ overlayfs has been added.
-    android::fs_mgr::Fstab mounts;
-    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts) || mounts.empty()) {
-        PLOG(ERROR) << "Failed to read /proc/mounts";
-        retval = NO_MOUNTS;
-    }
-
-    // Remount selected partitions.
-    for (auto& entry : partitions) {
-        // unlock the r/o key for the mount point device
-        if (entry.fs_mgr_flags.logical) {
-            fs_mgr_update_logical_partition(&entry);
-        }
-        auto blk_device = entry.blk_device;
-        auto mount_point = entry.mount_point;
-
-        auto found = false;
-        for (auto it = mounts.rbegin(); it != mounts.rend(); ++it) {
-            auto& rentry = *it;
-            if (mount_point == rentry.mount_point) {
-                blk_device = rentry.blk_device;
-                found = true;
-                break;
-            }
-            // Find overlayfs mount point?
-            if ((mount_point == "/" && rentry.mount_point == "/system")  ||
-                (mount_point == "/system" && rentry.mount_point == "/")) {
-                blk_device = rentry.blk_device;
-                mount_point = "/system";
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            PLOG(INFO) << "skip unmounted partition dev:" << blk_device << " mnt:" << mount_point;
-            continue;
-        }
-        if (blk_device == "/dev/root") {
-            auto from_fstab = GetEntryForMountPoint(&fstab, mount_point);
-            if (from_fstab) blk_device = from_fstab->blk_device;
-        }
-        fs_mgr_set_blk_ro(blk_device, false);
-
-        // Find system-as-root mount point?
-        if ((mount_point == "/system") && !GetEntryForMountPoint(&mounts, mount_point) &&
-            GetEntryForMountPoint(&mounts, "/")) {
-            mount_point = "/";
-        }
-
-        // Now remount!
-        if (::mount(blk_device.c_str(), mount_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
-                    nullptr) == 0) {
-            continue;
-        }
-        if ((errno == EINVAL) && (mount_point != entry.mount_point)) {
-            mount_point = entry.mount_point;
-            if (::mount(blk_device.c_str(), mount_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
-                        nullptr) == 0) {
-                continue;
-            }
-        }
-        PLOG(ERROR) << "failed to remount partition dev:" << blk_device << " mnt:" << mount_point;
-        // If errno is EROFS at this point, we are dealing with r/o
-        // filesystem types like squashfs, erofs or ext4 dedupe. We will
-        // consider such a device that does not have CONFIG_OVERLAY_FS
-        // in the kernel as a misconfigured.
-        if (errno == EROFS) {
-            LOG(ERROR) << "Consider providing all the dependencies to enable overlayfs";
-        }
-        retval = REMOUNT_FAILED;
-    }
-
-    if (reboot_later) reboot(setup_overlayfs);
-    if (user_please_reboot_later) {
-        LOG(INFO) << "Now reboot your device for settings to take effect";
-        return 0;
-    }
-
-    return retval;
-}
-
-static int do_clean_scratch_files() {
-    android::fs_mgr::CleanupOldScratchFiles();
-    return 0;
-}
-
-int main(int argc, char* argv[]) {
-    android::base::InitLogging(argv, MyLogger);
-    if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) {
-        return do_clean_scratch_files();
-    }
-    int result = do_remount(argc, argv);
-    if (result == MUST_REBOOT) {
-        LOG(INFO) << "Now reboot your device for settings to take effect";
-        result = 0;
-    } else if (result == REMOUNT_SUCCESS) {
+    if (result == REMOUNT_SUCCESS) {
         printf("remount succeeded\n");
-    } else if (result == CLEAN_SCRATCH_FILES) {
-        return do_clean_scratch_files();
     } else {
         printf("remount failed\n");
     }
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index 6caab1f..ec1d78f 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -26,12 +26,14 @@
 
 android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab);
 
+bool fs_mgr_wants_overlayfs(android::fs_mgr::FstabEntry* entry);
 bool fs_mgr_overlayfs_mount_all(android::fs_mgr::Fstab* fstab);
-bool fs_mgr_overlayfs_setup(const char* backing = nullptr, const char* mount_point = nullptr,
-                            bool* change = nullptr, bool force = true);
+bool fs_mgr_overlayfs_setup(const char* mount_point = nullptr, bool* change = nullptr,
+                            bool force = true);
 bool fs_mgr_overlayfs_teardown(const char* mount_point = nullptr, bool* change = nullptr);
 bool fs_mgr_overlayfs_is_setup();
 bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev);
+bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only = true);
 std::string fs_mgr_get_context(const std::string& mount_point);
 
 enum class OverlayfsValidResult {
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index f26fb24..689d18b 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -31,6 +31,7 @@
 
 struct FstabEntry {
     std::string blk_device;
+    std::string zoned_device;
     std::string logical_partition_name;
     std::string mount_point;
     std::string fs_type;
@@ -94,6 +95,8 @@
 
 // Exported for testability. Regular users should use ReadFstabFromFile().
 bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out);
+// Exported for testability. Regular users should use ReadDefaultFstab().
+std::string GetFstabPath();
 
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
 bool ReadFstabFromDt(Fstab* fstab, bool verbose = true);
diff --git a/fs_mgr/libfiemap/image_test.cpp b/fs_mgr/libfiemap/image_test.cpp
index 7472949..fb104b7 100644
--- a/fs_mgr/libfiemap/image_test.cpp
+++ b/fs_mgr/libfiemap/image_test.cpp
@@ -14,11 +14,13 @@
 // limitations under the License.
 //
 
+#include <inttypes.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/vfs.h>
 
 #include <chrono>
 #include <iostream>
@@ -26,12 +28,14 @@
 
 #include <android-base/file.h>
 #include <android-base/properties.h>
+#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <ext4_utils/ext4_utils.h>
 #include <fs_mgr/file_wait.h>
 #include <gtest/gtest.h>
 #include <libdm/dm.h>
+#include <libdm/loop_control.h>
 #include <libfiemap/image_manager.h>
 
 #include "utility.h"
@@ -46,7 +50,7 @@
 using android::fs_mgr::WaitForFile;
 
 static std::string gDataPath;
-static std::string gDataMountPath;
+static std::string gTestDir;
 static constexpr char kMetadataPath[] = "/metadata/gsi/test";
 
 static constexpr uint64_t kTestImageSize = 1024 * 1024;
@@ -178,6 +182,119 @@
 
 INSTANTIATE_TEST_SUITE_P(IsSubdirTest, IsSubdirTest, ::testing::ValuesIn(IsSubdirTestValues()));
 
+// This allows test cases for filesystems with larger than 4KiB alignment.
+// It creates a loop device, formats it with a FAT filesystem, and then
+// creates an ImageManager so backing images can be created on that filesystem.
+class VfatTest : public ::testing::Test {
+  protected:
+    // 64MB Filesystem and 32k block size by default
+    static constexpr uint64_t kBlockSize = 32768;
+    static constexpr uint64_t kFilesystemSize = 64 * 1024 * 1024;
+
+    void SetUp() override {
+        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        base_name_ = tinfo->name();
+
+        fs_path_ = gTestDir + "/vfat.img";
+        uint64_t count = kFilesystemSize / kBlockSize;
+        std::string dd_cmd =
+                ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
+                                              " count=%" PRIu64 " > /dev/null 2>&1",
+                                              fs_path_.c_str(), kBlockSize, count);
+        // create mount point
+        mntpoint_ = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
+        if (mkdir(mntpoint_.c_str(), S_IRWXU) < 0) {
+            ASSERT_EQ(errno, EEXIST) << strerror(errno);
+        }
+
+        // create file for the file system
+        int ret = system(dd_cmd.c_str());
+        ASSERT_EQ(ret, 0);
+
+        // Get and attach a loop device to the filesystem we created
+        loop_device_.emplace(fs_path_, 10s);
+        ASSERT_TRUE(loop_device_->valid());
+
+        // create file system
+        uint64_t sectors = kFilesystemSize / 512;
+        std::string mkfs_cmd =
+                ::android::base::StringPrintf("/system/bin/newfs_msdos -A -O Android -s %" PRIu64
+                                              " -b %" PRIu64 " %s > /dev/null 2>&1",
+                                              sectors, kBlockSize, loop_device_->device().c_str());
+        ret = system(mkfs_cmd.c_str());
+        ASSERT_EQ(ret, 0);
+
+        // Create a wrapping DM device to prevent gsid taking the loopback path.
+        auto& dm = DeviceMapper::Instance();
+        DmTable table;
+        table.Emplace<DmTargetLinear>(0, kFilesystemSize / 512, loop_device_->device(), 0);
+
+        dm_name_ = android::base::Basename(loop_device_->device()) + "-wrapper";
+        ASSERT_TRUE(dm.CreateDevice(dm_name_, table, &dm_path_, 10s));
+
+        // mount the file system
+        ASSERT_EQ(mount(dm_path_.c_str(), mntpoint_.c_str(), "vfat", 0, nullptr), 0)
+                << strerror(errno);
+    }
+
+    void TearDown() override {
+        // Clear up anything backed on the temporary FS.
+        if (manager_) {
+            manager_->UnmapImageIfExists(base_name_);
+            manager_->DeleteBackingImage(base_name_);
+        }
+
+        // Unmount temporary FS.
+        if (umount(mntpoint_.c_str()) < 0) {
+            ASSERT_EQ(errno, EINVAL) << strerror(errno);
+        }
+
+        // Destroy the dm wrapper.
+        auto& dm = DeviceMapper::Instance();
+        ASSERT_TRUE(dm.DeleteDeviceIfExists(dm_name_));
+
+        // Destroy the loop device.
+        loop_device_ = {};
+
+        // Destroy the temporary FS.
+        if (rmdir(mntpoint_.c_str()) < 0) {
+            ASSERT_EQ(errno, ENOENT) << strerror(errno);
+        }
+        if (unlink(fs_path_.c_str()) < 0) {
+            ASSERT_EQ(errno, ENOENT) << strerror(errno);
+        }
+    }
+
+    std::string base_name_;
+    std::string mntpoint_;
+    std::string fs_path_;
+    std::optional<LoopDevice> loop_device_;
+    std::string dm_name_;
+    std::string dm_path_;
+    std::unique_ptr<ImageManager> manager_;
+};
+
+// The actual size of the block device should be the requested size. For
+// example, a 16KB image should be mapped as a 16KB device, even if the
+// underlying filesystem requires 32KB to be fallocated.
+TEST_F(VfatTest, DeviceIsRequestedSize) {
+    manager_ = ImageManager::Open(kMetadataPath, mntpoint_);
+    ASSERT_NE(manager_, nullptr);
+
+    manager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
+
+    // Create something not aligned to the backing fs block size.
+    constexpr uint64_t kTestSize = (kBlockSize * 64) - (kBlockSize / 2);
+    ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestSize, false, nullptr));
+
+    std::string path;
+    ASSERT_TRUE(manager_->MapImageDevice(base_name_, 10s, &path));
+
+    unique_fd fd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_GE(fd, 0);
+    ASSERT_EQ(get_block_device_size(fd.get()), kTestSize);
+}
+
 }  // namespace
 
 bool Mkdir(const std::string& path) {
@@ -194,13 +311,27 @@
     if (argc >= 2) {
         gDataPath = argv[1];
     } else {
-        gDataPath = "/data/gsi/test";
+        gDataPath = "/data/local/tmp";
     }
-    gDataMountPath = gDataPath + "/mnt"s;
 
-    if (!Mkdir(gDataPath) || !Mkdir(kMetadataPath) || !Mkdir(gDataMountPath) ||
-        !Mkdir(kMetadataPath + "/mnt"s)) {
+    if (!Mkdir(gDataPath) || !Mkdir(kMetadataPath) || !Mkdir(kMetadataPath + "/mnt"s)) {
         return 1;
     }
-    return RUN_ALL_TESTS();
+
+    std::string tempdir = gDataPath + "/XXXXXX";
+    if (!mkdtemp(tempdir.data())) {
+        std::cerr << "unable to create tempdir on " << tempdir << "\n";
+        exit(EXIT_FAILURE);
+    }
+    if (!android::base::Realpath(tempdir, &gTestDir)) {
+        std::cerr << "unable to find realpath for " << tempdir;
+        exit(EXIT_FAILURE);
+    }
+
+    auto rv = RUN_ALL_TESTS();
+
+    std::string cmd = "rm -rf " + gTestDir;
+    system(cmd.c_str());
+
+    return rv;
 }
diff --git a/fs_mgr/liblp/TEST_MAPPING b/fs_mgr/liblp/TEST_MAPPING
index 875ccb0..cf0d22b 100644
--- a/fs_mgr/liblp/TEST_MAPPING
+++ b/fs_mgr/liblp/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "liblp_test"
     }
   ],
-  "hwasan-postsubmit": [
+  "hwasan-presubmit": [
     {
       "name": "liblp_test"
     }
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 6db8f13..d5e85e6 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -44,7 +44,6 @@
         "libext2_uuid",
         "libext4_utils",
         "libfstab",
-        "libsnapshot_cow",
         "libsnapshot_snapuserd",
         "libz",
     ],
@@ -75,6 +74,8 @@
     shared_libs: [
         "android.hardware.boot@1.0",
         "android.hardware.boot@1.1",
+        "android.hardware.boot-V1-ndk",
+        "libboot_control_client",
     ],
 }
 
@@ -154,22 +155,6 @@
         "-Wall",
         "-Werror",
     ],
-    export_include_dirs: ["include"],
-    srcs: [
-        "cow_decompress.cpp",
-        "cow_reader.cpp",
-        "cow_writer.cpp",
-        "cow_format.cpp",
-    ],
-}
-
-cc_library_static {
-    name: "libsnapshot_cow",
-    defaults: [
-        "libsnapshot_cow_defaults",
-    ],
-    host_supported: true,
-    recovery_available: true,
     shared_libs: [
         "libbase",
         "liblog",
@@ -177,7 +162,24 @@
     static_libs: [
         "libbrotli",
         "libz",
+        "liblz4",
     ],
+    export_include_dirs: ["include"],
+}
+
+cc_library_static {
+    name: "libsnapshot_cow",
+    defaults: [
+        "libsnapshot_cow_defaults",
+    ],
+    srcs: [
+        "cow_decompress.cpp",
+        "cow_reader.cpp",
+        "cow_writer.cpp",
+        "cow_format.cpp",
+    ],
+    host_supported: true,
+    recovery_available: true,
     ramdisk_available: true,
     vendor_ramdisk_available: true,
 }
@@ -214,7 +216,7 @@
 
 cc_defaults {
     name: "libsnapshot_test_defaults",
-    defaults: ["libsnapshot_defaults"],
+    defaults: ["libsnapshot_defaults", "libsnapshot_cow_defaults"],
     srcs: [
         "partition_cow_creator_test.cpp",
         "snapshot_metadata_updater_test.cpp",
@@ -233,6 +235,7 @@
     static_libs: [
         "android.hardware.boot@1.0",
         "android.hardware.boot@1.1",
+        "android.hardware.boot-V1-ndk",
         "libbrotli",
         "libc++fs",
         "libfs_mgr_binder",
@@ -261,7 +264,7 @@
 
 cc_test {
     name: "vts_libsnapshot_test",
-    defaults: ["libsnapshot_test_defaults"],
+    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
 }
 
 sh_test {
@@ -295,6 +298,7 @@
 
 cc_binary {
     name: "snapshotctl",
+    defaults: ["libsnapshot_cow_defaults", "libsnapshot_hal_deps"],
     srcs: [
         "snapshotctl.cpp",
     ],
@@ -308,8 +312,6 @@
         "update_metadata-protos",
     ],
     shared_libs: [
-        "android.hardware.boot@1.0",
-        "android.hardware.boot@1.1",
         "libbase",
         "libext2_uuid",
         "libext4_utils",
@@ -343,6 +345,9 @@
 
 cc_defaults {
     name: "libsnapshot_fuzzer_defaults",
+    defaults: [
+        "libsnapshot_cow_defaults",
+    ],
     native_coverage : true,
     srcs: [
         // Compile the protobuf definition again with type full.
@@ -416,6 +421,7 @@
     name: "cow_api_test",
     defaults: [
         "fs_mgr_defaults",
+        "libsnapshot_cow_defaults",
     ],
     srcs: [
         "cow_api_test.cpp",
@@ -471,6 +477,7 @@
         "libsparse",
         "libxz",
         "libz",
+        "liblz4",
         "libziparchive",
         "update_metadata-protos",
     ],
@@ -486,6 +493,9 @@
 
 cc_binary {
     name: "estimate_cow_from_nonab_ota",
+    defaults: [
+        "libsnapshot_cow_defaults",
+    ],
     host_supported: true,
     device_supported: false,
     cflags: [
@@ -519,6 +529,7 @@
     name: "inspect_cow",
     host_supported: true,
     device_supported: true,
+    defaults: ["libsnapshot_cow_defaults"],
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
         "-Wall",
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 5daa84d..b3763ae 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -97,8 +97,8 @@
     // This is non-zero when |state| == MERGING or MERGE_COMPLETED.
     uint64 metadata_sectors = 8;
 
-    // True if compression is enabled, false otherwise.
-    bool compression_enabled = 9;
+    // True if using snapuserd, false otherwise.
+    bool using_snapuserd = 9;
 
     // The old partition size (if none existed, this will be zero).
     uint64 old_partition_size = 10;
@@ -184,7 +184,7 @@
     uint64 metadata_sectors = 4;
 
     // Whether compression/dm-user was used for any snapshots.
-    bool compression_enabled = 5;
+    bool using_snapuserd = 5;
 
     // Merge phase (if state == MERGING).
     MergePhase merge_phase = 6;
@@ -235,4 +235,13 @@
 
     // The source fingerprint at the time the OTA was downloaded.
     string source_build_fingerprint = 10;
+
+    // Whether this update attempt uses userspace snapshots.
+    bool userspace_snapshots_used = 11;
+
+    // Whether this update attempt uses XOR compression.
+    bool xor_compression_used = 12;
+
+    // Whether this update attempt used io_uring.
+    bool iouring_used = 13;
 }
diff --git a/fs_mgr/libsnapshot/cow_decompress.cpp b/fs_mgr/libsnapshot/cow_decompress.cpp
index faceafe..a4d2277 100644
--- a/fs_mgr/libsnapshot/cow_decompress.cpp
+++ b/fs_mgr/libsnapshot/cow_decompress.cpp
@@ -20,6 +20,7 @@
 
 #include <android-base/logging.h>
 #include <brotli/decode.h>
+#include <lz4.h>
 #include <zlib.h>
 
 namespace android {
@@ -260,5 +261,43 @@
     return std::unique_ptr<IDecompressor>(new BrotliDecompressor());
 }
 
+class Lz4Decompressor final : public IDecompressor {
+  public:
+    ~Lz4Decompressor() override = default;
+
+    bool Decompress(const size_t output_size) override {
+        size_t actual_buffer_size = 0;
+        auto&& output_buffer = sink_->GetBuffer(output_size, &actual_buffer_size);
+        if (actual_buffer_size != output_size) {
+            LOG(ERROR) << "Failed to allocate buffer of size " << output_size << " only got "
+                       << actual_buffer_size << " bytes";
+            return false;
+        }
+        std::string input_buffer;
+        input_buffer.resize(stream_->Size());
+        size_t bytes_read = 0;
+        stream_->Read(input_buffer.data(), input_buffer.size(), &bytes_read);
+        if (bytes_read != input_buffer.size()) {
+            LOG(ERROR) << "Failed to read all input at once. Expected: " << input_buffer.size()
+                       << " actual: " << bytes_read;
+            return false;
+        }
+        const int bytes_decompressed =
+                LZ4_decompress_safe(input_buffer.data(), static_cast<char*>(output_buffer),
+                                    input_buffer.size(), output_size);
+        if (bytes_decompressed != output_size) {
+            LOG(ERROR) << "Failed to decompress LZ4 block, expected output size: " << output_size
+                       << ", actual: " << bytes_decompressed;
+            return false;
+        }
+        sink_->ReturnData(output_buffer, output_size);
+        return true;
+    }
+};
+
+std::unique_ptr<IDecompressor> IDecompressor::Lz4() {
+    return std::make_unique<Lz4Decompressor>();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/cow_decompress.h b/fs_mgr/libsnapshot/cow_decompress.h
index f485256..7f74eda 100644
--- a/fs_mgr/libsnapshot/cow_decompress.h
+++ b/fs_mgr/libsnapshot/cow_decompress.h
@@ -41,6 +41,7 @@
     static std::unique_ptr<IDecompressor> Uncompressed();
     static std::unique_ptr<IDecompressor> Gz();
     static std::unique_ptr<IDecompressor> Brotli();
+    static std::unique_ptr<IDecompressor> Lz4();
 
     // |output_bytes| is the expected total number of bytes to sink.
     virtual bool Decompress(size_t output_bytes) = 0;
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 746feeb..45be191 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -34,12 +34,13 @@
 namespace android {
 namespace snapshot {
 
-CowReader::CowReader(ReaderFlags reader_flag)
+CowReader::CowReader(ReaderFlags reader_flag, bool is_merge)
     : fd_(-1),
       header_(),
       fd_size_(0),
-      merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()),
-      reader_flag_(reader_flag) {}
+      block_pos_index_(std::make_shared<std::vector<int>>()),
+      reader_flag_(reader_flag),
+      is_merge_(is_merge) {}
 
 static void SHA256(const void*, size_t, uint8_t[]) {
 #if 0
@@ -58,13 +59,13 @@
     cow->fd_size_ = fd_size_;
     cow->last_label_ = last_label_;
     cow->ops_ = ops_;
-    cow->merge_op_blocks_ = merge_op_blocks_;
     cow->merge_op_start_ = merge_op_start_;
-    cow->block_map_ = block_map_;
     cow->num_total_data_ops_ = num_total_data_ops_;
     cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
     cow->has_seq_ops_ = has_seq_ops_;
     cow->data_loc_ = data_loc_;
+    cow->block_pos_index_ = block_pos_index_;
+    cow->is_merge_ = is_merge_;
     return cow;
 }
 
@@ -415,10 +416,10 @@
 //                        Replace-op-4, Zero-op-9, Replace-op-5 }
 //==============================================================
 bool CowReader::PrepMergeOps() {
-    auto merge_op_blocks = std::make_shared<std::vector<uint32_t>>();
+    auto merge_op_blocks = std::make_unique<std::vector<uint32_t>>();
     std::vector<int> other_ops;
     auto seq_ops_set = std::unordered_set<uint32_t>();
-    auto block_map = std::make_shared<std::unordered_map<uint32_t, int>>();
+    auto block_map = std::make_unique<std::unordered_map<uint32_t, int>>();
     size_t num_seqs = 0;
     size_t read;
 
@@ -482,8 +483,26 @@
         merge_op_start_ = header_.num_merge_ops;
     }
 
-    block_map_ = block_map;
-    merge_op_blocks_ = merge_op_blocks;
+    if (is_merge_) {
+        // Metadata ops are not required for merge. Thus, just re-arrange
+        // the ops vector as required for merge operations.
+        auto merge_ops_buffer = std::make_shared<std::vector<CowOperation>>();
+        merge_ops_buffer->reserve(num_total_data_ops_);
+        for (auto block : *merge_op_blocks) {
+            merge_ops_buffer->emplace_back(ops_->data()[block_map->at(block)]);
+        }
+        ops_->clear();
+        ops_ = merge_ops_buffer;
+        ops_->shrink_to_fit();
+    } else {
+        for (auto block : *merge_op_blocks) {
+            block_pos_index_->push_back(block_map->at(block));
+        }
+    }
+
+    block_map->clear();
+    merge_op_blocks->clear();
+
     return true;
 }
 
@@ -544,7 +563,7 @@
 
 class CowOpIter final : public ICowOpIter {
   public:
-    CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops);
+    CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops, uint64_t start);
 
     bool Done() override;
     const CowOperation& Get() override;
@@ -558,9 +577,9 @@
     std::vector<CowOperation>::iterator op_iter_;
 };
 
-CowOpIter::CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops) {
+CowOpIter::CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops, uint64_t start) {
     ops_ = ops;
-    op_iter_ = ops_->begin();
+    op_iter_ = ops_->begin() + start;
 }
 
 bool CowOpIter::RDone() {
@@ -589,9 +608,7 @@
 class CowRevMergeOpIter final : public ICowOpIter {
   public:
     explicit CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                               std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                               std::shared_ptr<std::unordered_map<uint32_t, int>> map,
-                               uint64_t start);
+                               std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
     bool Done() override;
     const CowOperation& Get() override;
@@ -602,17 +619,15 @@
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
-    std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
-    std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
-    std::vector<uint32_t>::reverse_iterator block_riter_;
+    std::vector<int>::reverse_iterator block_riter_;
+    std::shared_ptr<std::vector<int>> cow_op_index_vec_;
     uint64_t start_;
 };
 
 class CowMergeOpIter final : public ICowOpIter {
   public:
     explicit CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                            std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                            std::shared_ptr<std::unordered_map<uint32_t, int>> map, uint64_t start);
+                            std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
     bool Done() override;
     const CowOperation& Get() override;
@@ -623,26 +638,21 @@
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
-    std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
-    std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
-    std::vector<uint32_t>::iterator block_iter_;
+    std::vector<int>::iterator block_iter_;
+    std::shared_ptr<std::vector<int>> cow_op_index_vec_;
     uint64_t start_;
 };
 
 CowMergeOpIter::CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                               std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                               std::shared_ptr<std::unordered_map<uint32_t, int>> map,
-                               uint64_t start) {
+                               std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start) {
     ops_ = ops;
-    merge_op_blocks_ = merge_op_blocks;
-    map_ = map;
     start_ = start;
-
-    block_iter_ = merge_op_blocks->begin() + start;
+    cow_op_index_vec_ = block_pos_index;
+    block_iter_ = cow_op_index_vec_->begin() + start;
 }
 
 bool CowMergeOpIter::RDone() {
-    return block_iter_ == merge_op_blocks_->begin();
+    return block_iter_ == cow_op_index_vec_->begin();
 }
 
 void CowMergeOpIter::Prev() {
@@ -651,7 +661,7 @@
 }
 
 bool CowMergeOpIter::Done() {
-    return block_iter_ == merge_op_blocks_->end();
+    return block_iter_ == cow_op_index_vec_->end();
 }
 
 void CowMergeOpIter::Next() {
@@ -661,23 +671,20 @@
 
 const CowOperation& CowMergeOpIter::Get() {
     CHECK(!Done());
-    return ops_->data()[map_->at(*block_iter_)];
+    return ops_->data()[*block_iter_];
 }
 
 CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                                     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                                     std::shared_ptr<std::unordered_map<uint32_t, int>> map,
+                                     std::shared_ptr<std::vector<int>> block_pos_index,
                                      uint64_t start) {
     ops_ = ops;
-    merge_op_blocks_ = merge_op_blocks;
-    map_ = map;
     start_ = start;
-
-    block_riter_ = merge_op_blocks->rbegin();
+    cow_op_index_vec_ = block_pos_index;
+    block_riter_ = cow_op_index_vec_->rbegin();
 }
 
 bool CowRevMergeOpIter::RDone() {
-    return block_riter_ == merge_op_blocks_->rbegin();
+    return block_riter_ == cow_op_index_vec_->rbegin();
 }
 
 void CowRevMergeOpIter::Prev() {
@@ -686,7 +693,7 @@
 }
 
 bool CowRevMergeOpIter::Done() {
-    return block_riter_ == merge_op_blocks_->rend() - start_;
+    return block_riter_ == cow_op_index_vec_->rend() - start_;
 }
 
 void CowRevMergeOpIter::Next() {
@@ -696,20 +703,20 @@
 
 const CowOperation& CowRevMergeOpIter::Get() {
     CHECK(!Done());
-    return ops_->data()[map_->at(*block_riter_)];
+    return ops_->data()[*block_riter_];
 }
 
-std::unique_ptr<ICowOpIter> CowReader::GetOpIter() {
-    return std::make_unique<CowOpIter>(ops_);
+std::unique_ptr<ICowOpIter> CowReader::GetOpIter(bool merge_progress) {
+    return std::make_unique<CowOpIter>(ops_, merge_progress ? merge_op_start_ : 0);
 }
 
 std::unique_ptr<ICowOpIter> CowReader::GetRevMergeOpIter(bool ignore_progress) {
-    return std::make_unique<CowRevMergeOpIter>(ops_, merge_op_blocks_, block_map_,
+    return std::make_unique<CowRevMergeOpIter>(ops_, block_pos_index_,
                                                ignore_progress ? 0 : merge_op_start_);
 }
 
 std::unique_ptr<ICowOpIter> CowReader::GetMergeOpIter(bool ignore_progress) {
-    return std::make_unique<CowMergeOpIter>(ops_, merge_op_blocks_, block_map_,
+    return std::make_unique<CowMergeOpIter>(ops_, block_pos_index_,
                                             ignore_progress ? 0 : merge_op_start_);
 }
 
@@ -775,6 +782,9 @@
         case kCowCompressBrotli:
             decompressor = IDecompressor::Brotli();
             break;
+        case kCowCompressLz4:
+            decompressor = IDecompressor::Lz4();
+            break;
         default:
             LOG(ERROR) << "Unknown compression type: " << op.compression;
             return false;
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 5ce1d3b..7281fc2 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -24,8 +24,10 @@
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <brotli/encode.h>
+#include <libsnapshot/cow_format.h>
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
+#include <lz4.h>
 #include <zlib.h>
 
 namespace android {
@@ -129,6 +131,8 @@
         compression_ = kCowCompressGz;
     } else if (options_.compression == "brotli") {
         compression_ = kCowCompressBrotli;
+    } else if (options_.compression == "lz4") {
+        compression_ = kCowCompressLz4;
     } else if (options_.compression == "none") {
         compression_ = kCowCompressNone;
     } else if (!options_.compression.empty()) {
@@ -403,35 +407,56 @@
 std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
     switch (compression_) {
         case kCowCompressGz: {
-            auto bound = compressBound(length);
-            auto buffer = std::make_unique<uint8_t[]>(bound);
+            const auto bound = compressBound(length);
+            std::basic_string<uint8_t> buffer(bound, '\0');
 
             uLongf dest_len = bound;
-            auto rv = compress2(buffer.get(), &dest_len, reinterpret_cast<const Bytef*>(data),
+            auto rv = compress2(buffer.data(), &dest_len, reinterpret_cast<const Bytef*>(data),
                                 length, Z_BEST_COMPRESSION);
             if (rv != Z_OK) {
                 LOG(ERROR) << "compress2 returned: " << rv;
                 return {};
             }
-            return std::basic_string<uint8_t>(buffer.get(), dest_len);
+            buffer.resize(dest_len);
+            return buffer;
         }
         case kCowCompressBrotli: {
-            auto bound = BrotliEncoderMaxCompressedSize(length);
+            const auto bound = BrotliEncoderMaxCompressedSize(length);
             if (!bound) {
                 LOG(ERROR) << "BrotliEncoderMaxCompressedSize returned 0";
                 return {};
             }
-            auto buffer = std::make_unique<uint8_t[]>(bound);
+            std::basic_string<uint8_t> buffer(bound, '\0');
 
             size_t encoded_size = bound;
             auto rv = BrotliEncoderCompress(
                     BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, length,
-                    reinterpret_cast<const uint8_t*>(data), &encoded_size, buffer.get());
+                    reinterpret_cast<const uint8_t*>(data), &encoded_size, buffer.data());
             if (!rv) {
                 LOG(ERROR) << "BrotliEncoderCompress failed";
                 return {};
             }
-            return std::basic_string<uint8_t>(buffer.get(), encoded_size);
+            buffer.resize(encoded_size);
+            return buffer;
+        }
+        case kCowCompressLz4: {
+            const auto bound = LZ4_compressBound(length);
+            if (!bound) {
+                LOG(ERROR) << "LZ4_compressBound returned 0";
+                return {};
+            }
+            std::basic_string<uint8_t> buffer(bound, '\0');
+
+            const auto compressed_size = LZ4_compress_default(
+                    static_cast<const char*>(data), reinterpret_cast<char*>(buffer.data()), length,
+                    buffer.size());
+            if (compressed_size <= 0) {
+                LOG(ERROR) << "LZ4_compress_default failed, input size: " << length
+                           << ", compression bound: " << bound << ", ret: " << compressed_size;
+                return {};
+            }
+            buffer.resize(compressed_size);
+            return buffer;
         }
         default:
             LOG(ERROR) << "unhandled compression type: " << compression_;
diff --git a/fs_mgr/libsnapshot/device_info.cpp b/fs_mgr/libsnapshot/device_info.cpp
index a6d96ed..0ab6103 100644
--- a/fs_mgr/libsnapshot/device_info.cpp
+++ b/fs_mgr/libsnapshot/device_info.cpp
@@ -23,8 +23,9 @@
 namespace snapshot {
 
 #ifdef LIBSNAPSHOT_USE_HAL
-using android::hardware::boot::V1_0::BoolResult;
-using android::hardware::boot::V1_0::CommandResult;
+using android::hal::BootControlClient;
+using android::hal::BootControlVersion;
+using android::hal::CommandResult;
 #endif
 
 using namespace std::chrono_literals;
@@ -63,16 +64,16 @@
 #ifdef LIBSNAPSHOT_USE_HAL
 bool DeviceInfo::EnsureBootHal() {
     if (!boot_control_) {
-        auto hal = android::hardware::boot::V1_0::IBootControl::getService();
+        auto hal = BootControlClient::WaitForService();
         if (!hal) {
             LOG(ERROR) << "Could not find IBootControl HAL";
             return false;
         }
-        boot_control_ = android::hardware::boot::V1_1::IBootControl::castFrom(hal);
-        if (!boot_control_) {
+        if (hal->GetVersion() < BootControlVersion::BOOTCTL_V1_1) {
             LOG(ERROR) << "Could not find IBootControl 1.1 HAL";
             return false;
         }
+        boot_control_ = std::move(hal);
     }
     return true;
 }
@@ -83,8 +84,9 @@
     if (!EnsureBootHal()) {
         return false;
     }
-    if (!boot_control_->setSnapshotMergeStatus(status)) {
-        LOG(ERROR) << "Unable to set the snapshot merge status";
+    const auto ret = boot_control_->SetSnapshotMergeStatus(status);
+    if (!ret.IsOk()) {
+        LOG(ERROR) << "Unable to set the snapshot merge status " << ret.errMsg;
         return false;
     }
     return true;
@@ -108,9 +110,7 @@
         return false;
     }
 
-    CommandResult result = {};
-    auto cb = [&](CommandResult r) -> void { result = r; };
-    boot_control_->setSlotAsUnbootable(slot, cb);
+    CommandResult result = boot_control_->MarkSlotUnbootable(slot);
     if (!result.success) {
         LOG(ERROR) << "Error setting slot " << slot << " unbootable: " << result.errMsg;
         return false;
diff --git a/fs_mgr/libsnapshot/device_info.h b/fs_mgr/libsnapshot/device_info.h
index 8aefb85..d06f1be 100644
--- a/fs_mgr/libsnapshot/device_info.h
+++ b/fs_mgr/libsnapshot/device_info.h
@@ -17,7 +17,7 @@
 #include <string>
 
 #ifdef LIBSNAPSHOT_USE_HAL
-#include <android/hardware/boot/1.1/IBootControl.h>
+#include <BootControlClient.h>
 #endif
 #include <liblp/partition_opener.h>
 #include <libsnapshot/snapshot.h>
@@ -26,7 +26,7 @@
 namespace snapshot {
 
 class DeviceInfo final : public SnapshotManager::IDeviceInfo {
-    using MergeStatus = android::hardware::boot::V1_1::MergeStatus;
+    using MergeStatus = ::aidl::android::hardware::boot::MergeStatus;
 
   public:
     std::string GetMetadataDir() const override;
@@ -50,7 +50,7 @@
     android::fs_mgr::PartitionOpener opener_;
     bool first_stage_init_ = false;
 #ifdef LIBSNAPSHOT_USE_HAL
-    android::sp<android::hardware::boot::V1_1::IBootControl> boot_control_;
+    std::unique_ptr<::android::hal::BootControlClient> boot_control_;
 #endif
 };
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 9f4ddbb..ba75a8d 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -154,9 +154,12 @@
 static constexpr uint8_t kCowSequenceOp = 7;
 static constexpr uint8_t kCowFooterOp = -1;
 
-static constexpr uint8_t kCowCompressNone = 0;
-static constexpr uint8_t kCowCompressGz = 1;
-static constexpr uint8_t kCowCompressBrotli = 2;
+enum CowCompressionAlgorithm : uint8_t {
+    kCowCompressNone = 0,
+    kCowCompressGz = 1,
+    kCowCompressBrotli = 2,
+    kCowCompressLz4 = 3
+};
 
 static constexpr uint8_t kCowReadAheadNotStarted = 0;
 static constexpr uint8_t kCowReadAheadInProgress = 1;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index f4d5c72..e8e4d72 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -74,7 +74,7 @@
     virtual bool GetLastLabel(uint64_t* label) = 0;
 
     // Return an iterator for retrieving CowOperation entries.
-    virtual std::unique_ptr<ICowOpIter> GetOpIter() = 0;
+    virtual std::unique_ptr<ICowOpIter> GetOpIter(bool merge_progress) = 0;
 
     // Return an iterator for retrieving CowOperation entries in reverse merge order
     virtual std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress) = 0;
@@ -115,7 +115,7 @@
         USERSPACE_MERGE = 1,
     };
 
-    CowReader(ReaderFlags reader_flag = ReaderFlags::DEFAULT);
+    CowReader(ReaderFlags reader_flag = ReaderFlags::DEFAULT, bool is_merge = false);
     ~CowReader() { owned_fd_ = {}; }
 
     // Parse the COW, optionally, up to the given label. If no label is
@@ -135,7 +135,7 @@
     // CowOperation objects. Get() returns a unique CowOperation object
     // whose lifetime depends on the CowOpIter object; the return
     // value of these will never be null.
-    std::unique_ptr<ICowOpIter> GetOpIter() override;
+    std::unique_ptr<ICowOpIter> GetOpIter(bool merge_progress = false) override;
     std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress = false) override;
     std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress = false) override;
 
@@ -170,14 +170,14 @@
     uint64_t fd_size_;
     std::optional<uint64_t> last_label_;
     std::shared_ptr<std::vector<CowOperation>> ops_;
-    std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
     uint64_t merge_op_start_{};
-    std::shared_ptr<std::unordered_map<uint32_t, int>> block_map_;
+    std::shared_ptr<std::vector<int>> block_pos_index_;
     uint64_t num_total_data_ops_{};
     uint64_t num_ordered_ops_to_merge_{};
     bool has_seq_ops_{};
     std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
     ReaderFlags reader_flag_;
+    bool is_merge_{};
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index e17b5c6..e7a2f02 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -155,7 +155,7 @@
     android::base::borrowed_fd fd_;
     CowHeader header_{};
     CowFooter footer_{};
-    int compression_ = 0;
+    CowCompressionAlgorithm compression_ = kCowCompressNone;
     uint64_t next_op_pos_ = 0;
     uint64_t next_data_pos_ = 0;
     uint32_t cluster_size_ = 0;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index ba62330..d458b87 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -62,6 +62,7 @@
     MOCK_METHOD(std::unique_ptr<AutoDevice>, EnsureMetadataMounted, (), (override));
     MOCK_METHOD(ISnapshotMergeStats*, GetSnapshotMergeStatsInstance, (), (override));
     MOCK_METHOD(std::string, ReadSourceBuildFingerprint, (), (override));
+    MOCK_METHOD(void, SetMergeStatsFeatures, (ISnapshotMergeStats*), (override));
 };
 
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
index 3d384cc..8280f2e 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
@@ -28,10 +28,7 @@
     virtual ~MockSnapshotMergeStats() = default;
     // Called when merge starts or resumes.
     MOCK_METHOD(bool, Start, (), (override));
-    MOCK_METHOD(void, set_state, (android::snapshot::UpdateState, bool), (override));
-    MOCK_METHOD(void, set_cow_file_size, (uint64_t), ());
-    MOCK_METHOD(void, set_total_cow_size_bytes, (uint64_t), (override));
-    MOCK_METHOD(void, set_estimated_cow_size_bytes, (uint64_t), (override));
+    MOCK_METHOD(void, set_state, (android::snapshot::UpdateState), (override));
     MOCK_METHOD(void, set_boot_complete_time_ms, (uint32_t), (override));
     MOCK_METHOD(void, set_boot_complete_to_merge_start_time_ms, (uint32_t), (override));
     MOCK_METHOD(void, set_merge_failure_code, (MergeFailureCode), (override));
@@ -45,6 +42,7 @@
     MOCK_METHOD(MergeFailureCode, merge_failure_code, (), (override));
     MOCK_METHOD(std::unique_ptr<Result>, Finish, (), (override));
     MOCK_METHOD(bool, WriteState, (), (override));
+    MOCK_METHOD(SnapshotMergeReport*, report, (), (override));
 
     using ISnapshotMergeStats::Result;
     // Return nullptr if any failure.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 11da568..34c7baf 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -46,6 +46,10 @@
 #define DEFINED_FRIEND_TEST
 #endif
 
+namespace aidl::android::hardware::boot {
+enum class MergeStatus;
+}
+
 namespace android {
 
 namespace fiemap {
@@ -59,13 +63,6 @@
 
 // Forward declare IBootControl types since we cannot include only the headers
 // with Soong. Note: keep the enum width in sync.
-namespace hardware {
-namespace boot {
-namespace V1_1 {
-enum class MergeStatus : int32_t;
-}  // namespace V1_1
-}  // namespace boot
-}  // namespace hardware
 
 namespace snapshot {
 
@@ -95,6 +92,7 @@
     class IDeviceInfo {
       public:
         using IImageManager = android::fiemap::IImageManager;
+        using MergeStatus = aidl::android::hardware::boot::MergeStatus;
 
         virtual ~IDeviceInfo() {}
         virtual std::string GetMetadataDir() const = 0;
@@ -103,8 +101,7 @@
         virtual std::string GetSuperDevice(uint32_t slot) const = 0;
         virtual const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const = 0;
         virtual bool IsOverlayfsSetup() const = 0;
-        virtual bool SetBootControlMergeStatus(
-                android::hardware::boot::V1_1::MergeStatus status) = 0;
+        virtual bool SetBootControlMergeStatus(MergeStatus status) = 0;
         virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
         virtual bool IsRecovery() const = 0;
         virtual bool IsTestDevice() const { return false; }
@@ -134,6 +131,9 @@
     // may need to be merged before wiping.
     virtual bool FinishedSnapshotWrites(bool wipe) = 0;
 
+    // Set feature flags on an ISnapshotMergeStats object.
+    virtual void SetMergeStatsFeatures(ISnapshotMergeStats* stats) = 0;
+
     // Update an ISnapshotMergeStats object with statistics about COW usage.
     // This should be called before the merge begins as otherwise snapshots
     // may be deleted.
@@ -308,7 +308,7 @@
     using LpMetadata = android::fs_mgr::LpMetadata;
     using MetadataBuilder = android::fs_mgr::MetadataBuilder;
     using DeltaArchiveManifest = chromeos_update_engine::DeltaArchiveManifest;
-    using MergeStatus = android::hardware::boot::V1_1::MergeStatus;
+    using MergeStatus = aidl::android::hardware::boot::MergeStatus;
     using FiemapStatus = android::fiemap::FiemapStatus;
 
     friend class SnapshotMergeStats;
@@ -378,6 +378,7 @@
     bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) override;
     bool UnmapAllSnapshots() override;
     std::string ReadSourceBuildFingerprint() override;
+    void SetMergeStatsFeatures(ISnapshotMergeStats* stats) override;
 
     // We can't use WaitForFile during first-stage init, because ueventd is not
     // running and therefore will not automatically create symlinks. Instead,
@@ -640,6 +641,7 @@
     MergeFailureCode CheckMergeConsistency(LockedFile* lock, const std::string& name,
                                            const SnapshotStatus& update_status);
 
+    auto UpdateStateToStr(enum UpdateState state);
     // Get status or table information about a device-mapper node with a single target.
     enum class TableQuery {
         Table,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index 8c2fec7..8a70400 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -28,10 +28,7 @@
     virtual ~ISnapshotMergeStats() = default;
     // Called when merge starts or resumes.
     virtual bool Start() = 0;
-    virtual void set_state(android::snapshot::UpdateState state, bool using_compression) = 0;
-    virtual void set_cow_file_size(uint64_t cow_file_size) = 0;
-    virtual void set_total_cow_size_bytes(uint64_t bytes) = 0;
-    virtual void set_estimated_cow_size_bytes(uint64_t bytes) = 0;
+    virtual void set_state(android::snapshot::UpdateState state) = 0;
     virtual void set_boot_complete_time_ms(uint32_t ms) = 0;
     virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0;
     virtual void set_merge_failure_code(MergeFailureCode code) = 0;
@@ -55,6 +52,9 @@
     // Return nullptr if any failure.
     virtual std::unique_ptr<Result> Finish() = 0;
 
+    // Return the underlying implementation.
+    virtual SnapshotMergeReport* report() = 0;
+
     // Write out the current state. This should be called when data might be lost that
     // cannot be recovered (eg the COW sizes).
     virtual bool WriteState() = 0;
@@ -67,11 +67,8 @@
 
     // ISnapshotMergeStats overrides
     bool Start() override;
-    void set_state(android::snapshot::UpdateState state, bool using_compression) override;
-    void set_cow_file_size(uint64_t cow_file_size) override;
+    void set_state(android::snapshot::UpdateState state) override;
     uint64_t cow_file_size() override;
-    void set_total_cow_size_bytes(uint64_t bytes) override;
-    void set_estimated_cow_size_bytes(uint64_t bytes) override;
     uint64_t total_cow_size_bytes() override;
     uint64_t estimated_cow_size_bytes() override;
     void set_boot_complete_time_ms(uint32_t ms) override;
@@ -85,6 +82,9 @@
     std::unique_ptr<Result> Finish() override;
     bool WriteState() override;
 
+    // Access the underlying report before it is finished.
+    SnapshotMergeReport* report() override { return &report_; }
+
   private:
     bool ReadState();
     bool DeleteState();
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index 318e525..171c7c6 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -59,6 +59,7 @@
     bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) override;
     bool UnmapAllSnapshots() override;
     std::string ReadSourceBuildFingerprint() override;
+    void SetMergeStatsFeatures(ISnapshotMergeStats* stats) override;
 };
 
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index c3b40dc..f850b94 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -17,10 +17,10 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <unordered_map>
 #include <unordered_set>
 
 #include <android-base/file.h>
-#include <android/hardware/boot/1.1/IBootControl.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <libfiemap/image_manager.h>
@@ -33,10 +33,10 @@
 namespace android {
 namespace snapshot {
 
+using aidl::android::hardware::boot::MergeStatus;
 using android::fs_mgr::IPropertyFetcher;
 using android::fs_mgr::MetadataBuilder;
 using android::fs_mgr::testing::MockPropertyFetcher;
-using android::hardware::boot::V1_1::MergeStatus;
 using chromeos_update_engine::DeltaArchiveManifest;
 using chromeos_update_engine::PartitionUpdate;
 using testing::_;
@@ -166,27 +166,25 @@
     android::dm::IDeviceMapper& impl_;
 };
 
-class SnapshotTestPropertyFetcher : public android::fs_mgr::testing::MockPropertyFetcher {
+class SnapshotTestPropertyFetcher : public android::fs_mgr::IPropertyFetcher {
   public:
-    SnapshotTestPropertyFetcher(const std::string& slot_suffix) {
-        using testing::Return;
-        ON_CALL(*this, GetProperty("ro.boot.slot_suffix", _)).WillByDefault(Return(slot_suffix));
-        ON_CALL(*this, GetBoolProperty("ro.boot.dynamic_partitions", _))
-                .WillByDefault(Return(true));
-        ON_CALL(*this, GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
-                .WillByDefault(Return(false));
-        ON_CALL(*this, GetBoolProperty("ro.virtual_ab.enabled", _)).WillByDefault(Return(true));
-    }
+    explicit SnapshotTestPropertyFetcher(const std::string& slot_suffix,
+                                         std::unordered_map<std::string, std::string>&& props = {});
+
+    std::string GetProperty(const std::string& key, const std::string& defaultValue) override;
+    bool GetBoolProperty(const std::string& key, bool defaultValue) override;
 
     static void SetUp(const std::string& slot_suffix = "_a") { Reset(slot_suffix); }
-
     static void TearDown() { Reset("_a"); }
 
   private:
     static void Reset(const std::string& slot_suffix) {
         IPropertyFetcher::OverrideForTesting(
-                std::make_unique<NiceMock<SnapshotTestPropertyFetcher>>(slot_suffix));
+                std::make_unique<SnapshotTestPropertyFetcher>(slot_suffix));
     }
+
+  private:
+    std::unordered_map<std::string, std::string> properties_;
 };
 
 // Helper for error-spam-free cleanup.
@@ -197,9 +195,10 @@
 // Expect space of |path| is multiple of 4K.
 bool WriteRandomData(const std::string& path, std::optional<size_t> expect_size = std::nullopt,
                      std::string* hash = nullptr);
-bool WriteRandomData(ICowWriter* writer, std::string* hash = nullptr);
 std::string HashSnapshot(ISnapshotWriter* writer);
 
+std::string ToHexString(const uint8_t* buf, size_t len);
+
 std::optional<std::string> GetHash(const std::string& path);
 
 // Add partitions and groups described by |manifest|.
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 5569da0..7057223 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -143,7 +143,7 @@
 }
 
 std::optional<uint64_t> PartitionCowCreator::GetCowSize() {
-    if (compression_enabled) {
+    if (using_snapuserd) {
         if (update == nullptr || !update->has_estimate_cow_size()) {
             LOG(ERROR) << "Update manifest does not include a COW size";
             return std::nullopt;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
index 34b39ca..949e6c5 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.h
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -56,8 +56,8 @@
     // Extra extents that are going to be invalidated during the update
     // process.
     std::vector<ChromeOSExtent> extra_extents = {};
-    // True if compression is enabled.
-    bool compression_enabled = false;
+    // True if snapuserd COWs are enabled.
+    bool using_snapuserd = false;
     std::string compression_algorithm;
 
     struct Return {
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
index de35c13..cf26a16 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -249,7 +249,7 @@
                                 .target_partition = system_b,
                                 .current_metadata = builder_a.get(),
                                 .current_suffix = "_a",
-                                .compression_enabled = true,
+                                .using_snapuserd = true,
                                 .update = &update};
 
     auto ret = creator.Run();
@@ -275,7 +275,7 @@
                                 .target_partition = system_b,
                                 .current_metadata = builder_a.get(),
                                 .current_suffix = "_a",
-                                .compression_enabled = true,
+                                .using_snapuserd = true,
                                 .update = nullptr};
 
     auto ret = creator.Run();
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 019b64a..bc3efd9 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -53,6 +53,7 @@
 namespace android {
 namespace snapshot {
 
+using aidl::android::hardware::boot::MergeStatus;
 using android::base::unique_fd;
 using android::dm::DeviceMapper;
 using android::dm::DmDeviceState;
@@ -72,7 +73,6 @@
 using android::fs_mgr::LpMetadata;
 using android::fs_mgr::MetadataBuilder;
 using android::fs_mgr::SlotNumberForSlotSuffix;
-using android::hardware::boot::V1_1::MergeStatus;
 using chromeos_update_engine::DeltaArchiveManifest;
 using chromeos_update_engine::Extent;
 using chromeos_update_engine::FileDescriptor;
@@ -398,7 +398,7 @@
     status->set_state(SnapshotState::CREATED);
     status->set_sectors_allocated(0);
     status->set_metadata_sectors(0);
-    status->set_compression_enabled(cow_creator->compression_enabled);
+    status->set_using_snapuserd(cow_creator->using_snapuserd);
     status->set_compression_algorithm(cow_creator->compression_algorithm);
 
     if (!WriteSnapshotStatus(lock, *status)) {
@@ -788,7 +788,7 @@
         }
     }
 
-    bool compression_enabled = false;
+    bool using_snapuserd = false;
 
     std::vector<std::string> first_merge_group;
 
@@ -809,7 +809,7 @@
             return false;
         }
 
-        compression_enabled |= snapshot_status.compression_enabled();
+        using_snapuserd |= snapshot_status.using_snapuserd();
         if (DecideMergePhase(snapshot_status) == MergePhase::FIRST_PHASE) {
             first_merge_group.emplace_back(snapshot);
         }
@@ -817,7 +817,7 @@
 
     SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
     initial_status.set_state(UpdateState::Merging);
-    initial_status.set_compression_enabled(compression_enabled);
+    initial_status.set_using_snapuserd(using_snapuserd);
 
     if (!UpdateUsesUserSnapshots(lock.get())) {
         initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
@@ -988,6 +988,29 @@
     return true;
 }
 
+auto SnapshotManager::UpdateStateToStr(const enum UpdateState state) {
+    switch (state) {
+        case None:
+            return "None";
+        case Initiated:
+            return "Initiated";
+        case Unverified:
+            return "Unverified";
+        case Merging:
+            return "Merging";
+        case MergeNeedsReboot:
+            return "MergeNeedsReboot";
+        case MergeCompleted:
+            return "MergeCompleted";
+        case MergeFailed:
+            return "MergeFailed";
+        case Cancelled:
+            return "Cancelled";
+        default:
+            return "Unknown";
+    }
+}
+
 bool SnapshotManager::QuerySnapshotStatus(const std::string& dm_name, std::string* target_type,
                                           DmTargetSnapshot::Status* status) {
     DeviceMapper::TargetInfo target;
@@ -1016,7 +1039,7 @@
                                                 const std::function<bool()>& before_cancel) {
     while (true) {
         auto result = CheckMergeState(before_cancel);
-        LOG(INFO) << "ProcessUpdateState handling state: " << result.state;
+        LOG(INFO) << "ProcessUpdateState handling state: " << UpdateStateToStr(result.state);
 
         if (result.state == UpdateState::MergeFailed) {
             AcknowledgeMergeFailure(result.failure_code);
@@ -1044,7 +1067,7 @@
     }
 
     auto result = CheckMergeState(lock.get(), before_cancel);
-    LOG(INFO) << "CheckMergeState for snapshots returned: " << result.state;
+    LOG(INFO) << "CheckMergeState for snapshots returned: " << UpdateStateToStr(result.state);
 
     if (result.state == UpdateState::MergeCompleted) {
         // Do this inside the same lock. Failures get acknowledged without the
@@ -1109,7 +1132,8 @@
         }
 
         auto result = CheckTargetMergeState(lock, snapshot, update_status);
-        LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << result.state;
+        LOG(INFO) << "CheckTargetMergeState for " << snapshot
+                  << " returned: " << UpdateStateToStr(result.state);
 
         switch (result.state) {
             case UpdateState::MergeFailed:
@@ -1340,7 +1364,7 @@
 }
 
 MergeFailureCode CheckMergeConsistency(const std::string& name, const SnapshotStatus& status) {
-    if (!status.compression_enabled()) {
+    if (!status.using_snapuserd()) {
         // Do not try to verify old-style COWs yet.
         return MergeFailureCode::Ok;
     }
@@ -1601,7 +1625,7 @@
         // as unmap will fail since dm-user itself was a snapshot device prior
         // to switching of tables. Unmap will fail as the device will be mounted
         // by system partitions
-        if (status.compression_enabled()) {
+        if (status.using_snapuserd()) {
             auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
             UnmapDmUserDevice(dm_user_name);
         }
@@ -2091,8 +2115,10 @@
 }
 
 bool SnapshotManager::UpdateUsesCompression(LockedFile* lock) {
+    // This returns true even if compression is "none", since update_engine is
+    // really just trying to see if snapuserd is in use.
     SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
-    return update_status.compression_enabled();
+    return update_status.using_snapuserd();
 }
 
 bool SnapshotManager::UpdateUsesIouring(LockedFile* lock) {
@@ -2151,8 +2177,17 @@
         if (!suffix.empty() && !android::base::EndsWith(name, suffix)) {
             continue;
         }
-        snapshots->emplace_back(std::move(name));
+
+        // Insert system and product partition at the beginning so that
+        // during snapshot-merge, these partitions are merged first.
+        if (name == "system_a" || name == "system_b" || name == "product_a" ||
+            name == "product_b") {
+            snapshots->insert(snapshots->begin(), std::move(name));
+        } else {
+            snapshots->emplace_back(std::move(name));
+        }
     }
+
     return true;
 }
 
@@ -2241,8 +2276,8 @@
                 .block_device = super_device,
                 .metadata = metadata.get(),
                 .partition = &partition,
-                .partition_opener = &opener,
                 .timeout_ms = timeout_ms,
+                .partition_opener = &opener,
         };
         if (!MapPartitionWithSnapshot(lock, std::move(params), SnapshotContext::Mount, nullptr)) {
             return false;
@@ -2403,13 +2438,13 @@
     remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
-    if (context == SnapshotContext::Update && live_snapshot_status->compression_enabled()) {
+    if (context == SnapshotContext::Update && live_snapshot_status->using_snapuserd()) {
         // Stop here, we can't run dm-user yet, the COW isn't built.
         created_devices.Release();
         return true;
     }
 
-    if (live_snapshot_status->compression_enabled()) {
+    if (live_snapshot_status->using_snapuserd()) {
         // Get the source device (eg the view of the partition from before it was resized).
         std::string source_device_path;
         if (live_snapshot_status->old_partition_size() > 0) {
@@ -2719,8 +2754,8 @@
                 .block_device = super_device,
                 .metadata = metadata.get(),
                 .partition_name = snapshot,
-                .partition_opener = &opener,
                 .timeout_ms = timeout_ms,
+                .partition_opener = &opener,
         };
         if (!MapPartitionWithSnapshot(lock.get(), std::move(params), SnapshotContext::Mount,
                                       nullptr)) {
@@ -2911,7 +2946,7 @@
     // build fingerprint.
     if (!(state == UpdateState::Initiated || state == UpdateState::None)) {
         SnapshotUpdateStatus old_status = ReadSnapshotUpdateStatus(lock);
-        status.set_compression_enabled(old_status.compression_enabled());
+        status.set_using_snapuserd(old_status.using_snapuserd());
         status.set_source_build_fingerprint(old_status.source_build_fingerprint());
         status.set_merge_phase(old_status.merge_phase());
         status.set_userspace_snapshots(old_status.userspace_snapshots());
@@ -3165,18 +3200,42 @@
     LOG(INFO) << " dap_metadata.cow_version(): " << dap_metadata.cow_version()
               << " writer.GetCowVersion(): " << writer.GetCowVersion();
 
-    bool use_compression = IsCompressionEnabled() && dap_metadata.vabc_enabled() &&
-                           !device_->IsRecovery() && cow_format_support;
+    // Deduce supported features.
+    bool userspace_snapshots = CanUseUserspaceSnapshots();
+    bool legacy_compression = GetLegacyCompressionEnabledProperty();
+
+    std::string vabc_disable_reason;
+    if (!dap_metadata.vabc_enabled()) {
+        vabc_disable_reason = "not enabled metadata";
+    } else if (device_->IsRecovery()) {
+        vabc_disable_reason = "recovery";
+    } else if (!cow_format_support) {
+        vabc_disable_reason = "cow format not supported";
+    }
+
+    if (!vabc_disable_reason.empty()) {
+        if (userspace_snapshots) {
+            LOG(INFO) << "Userspace snapshots disabled: " << vabc_disable_reason;
+        }
+        if (legacy_compression) {
+            LOG(INFO) << "Compression disabled: " << vabc_disable_reason;
+        }
+        userspace_snapshots = false;
+        legacy_compression = false;
+    }
+
+    const bool using_snapuserd = userspace_snapshots || legacy_compression;
+    if (!using_snapuserd) {
+        LOG(INFO) << "Using legacy Virtual A/B (dm-snapshot)";
+    }
 
     std::string compression_algorithm;
-    if (use_compression) {
+    if (using_snapuserd) {
         compression_algorithm = dap_metadata.vabc_compression_param();
         if (compression_algorithm.empty()) {
             // Older OTAs don't set an explicit compression type, so default to gz.
             compression_algorithm = "gz";
         }
-    } else {
-        compression_algorithm = "none";
     }
 
     PartitionCowCreator cow_creator{
@@ -3187,7 +3246,7 @@
             .current_suffix = current_suffix,
             .update = nullptr,
             .extra_extents = {},
-            .compression_enabled = use_compression,
+            .using_snapuserd = using_snapuserd,
             .compression_algorithm = compression_algorithm,
     };
 
@@ -3212,11 +3271,11 @@
         return Return::Error();
     }
 
-    // If compression is enabled, we need to retain a copy of the old metadata
+    // If snapuserd is enabled, we need to retain a copy of the old metadata
     // so we can access original blocks in case they are moved around. We do
     // not want to rely on the old super metadata slot because we don't
     // guarantee its validity after the slot switch is successful.
-    if (cow_creator.compression_enabled) {
+    if (using_snapuserd) {
         auto metadata = current_metadata->Export();
         if (!metadata) {
             LOG(ERROR) << "Could not export current metadata";
@@ -3232,70 +3291,36 @@
 
     SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
     status.set_state(update_state);
-    status.set_compression_enabled(cow_creator.compression_enabled);
-    if (cow_creator.compression_enabled) {
-        if (!device()->IsTestDevice()) {
-            bool userSnapshotsEnabled = IsUserspaceSnapshotsEnabled();
-            const std::string UNKNOWN = "unknown";
-            const std::string vendor_release = android::base::GetProperty(
-                    "ro.vendor.build.version.release_or_codename", UNKNOWN);
+    status.set_using_snapuserd(using_snapuserd);
 
-            // 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;
-                userSnapshotsEnabled = false;
-            }
+    if (userspace_snapshots) {
+        status.set_userspace_snapshots(true);
+        LOG(INFO) << "Virtual A/B using userspace snapshots";
 
-            // Userspace snapshots is enabled only if compression is enabled
-            status.set_userspace_snapshots(userSnapshotsEnabled);
-            if (userSnapshotsEnabled) {
-                is_snapshot_userspace_ = true;
-                status.set_io_uring_enabled(IsIouringEnabled());
-                LOG(INFO) << "Userspace snapshots enabled";
-            } else {
-                is_snapshot_userspace_ = false;
-                LOG(INFO) << "Userspace snapshots disabled";
-            }
+        if (GetIouringEnabledProperty()) {
+            status.set_io_uring_enabled(true);
+            LOG(INFO) << "io_uring for snapshots enabled";
+        }
+    } else if (legacy_compression) {
+        LOG(INFO) << "Virtual A/B using legacy snapuserd";
+    } else {
+        LOG(INFO) << "Virtual A/B using dm-snapshot";
+    }
 
-            // Terminate stale daemon if any
-            std::unique_ptr<SnapuserdClient> snapuserd_client =
-                    SnapuserdClient::Connect(kSnapuserdSocket, 5s);
-            if (snapuserd_client) {
-                snapuserd_client->DetachSnapuserd();
-                snapuserd_client->CloseConnection();
-                snapuserd_client = nullptr;
-            }
+    is_snapshot_userspace_.emplace(userspace_snapshots);
 
-            // Clear the cached client if any
-            if (snapuserd_client_) {
-                snapuserd_client_->CloseConnection();
-                snapuserd_client_ = nullptr;
-            }
-        } else {
-            bool userSnapshotsEnabled = true;
-            const std::string UNKNOWN = "unknown";
-            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 (vendor_release.find("12") != std::string::npos) {
-                LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
-                          << vendor_release;
-                userSnapshotsEnabled = false;
-            }
-
-            userSnapshotsEnabled = (userSnapshotsEnabled && !IsDmSnapshotTestingEnabled());
-            status.set_userspace_snapshots(userSnapshotsEnabled);
-            if (!userSnapshotsEnabled) {
-                is_snapshot_userspace_ = false;
-                LOG(INFO) << "User-space snapshots disabled for testing";
-            } else {
-                is_snapshot_userspace_ = true;
-                LOG(INFO) << "User-space snapshots enabled for testing";
-            }
+    if (!device()->IsTestDevice() && using_snapuserd) {
+        // Terminate stale daemon if any
+        std::unique_ptr<SnapuserdClient> snapuserd_client = std::move(snapuserd_client_);
+        if (!snapuserd_client) {
+            snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
+        }
+        if (snapuserd_client) {
+            snapuserd_client->DetachSnapuserd();
+            snapuserd_client->CloseConnection();
         }
     }
+
     if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
         LOG(ERROR) << "Unable to write new update state";
         return Return::Error();
@@ -3488,7 +3513,7 @@
             return Return::Error();
         }
 
-        if (it->second.compression_enabled()) {
+        if (it->second.using_snapuserd()) {
             unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
             if (fd < 0) {
                 PLOG(ERROR) << "open " << cow_path << " failed for snapshot "
@@ -3534,8 +3559,8 @@
     if (!ReadSnapshotStatus(lock.get(), params.GetPartitionName(), &status)) {
         return false;
     }
-    if (status.compression_enabled()) {
-        LOG(ERROR) << "Cannot use MapUpdateSnapshot with compressed snapshots";
+    if (status.using_snapuserd()) {
+        LOG(ERROR) << "Cannot use MapUpdateSnapshot with snapuserd";
         return false;
     }
 
@@ -3592,7 +3617,7 @@
         return nullptr;
     }
 
-    if (status.compression_enabled()) {
+    if (status.using_snapuserd()) {
         return OpenCompressedSnapshotWriter(lock.get(), source_device, params.GetPartitionName(),
                                             status, paths);
     }
@@ -3722,7 +3747,10 @@
     auto update_status = ReadSnapshotUpdateStatus(file.get());
 
     ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
-    ss << "Compression: " << update_status.compression_enabled() << std::endl;
+    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 XOR compression: " << GetXorCompressionEnabledProperty() << std::endl;
     ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
     ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
     ss << "Rollback indicator: "
@@ -3943,7 +3971,7 @@
         if (!ReadSnapshotStatus(lock, snapshot, &status)) {
             return false;
         }
-        if (status.compression_enabled()) {
+        if (status.using_snapuserd()) {
             continue;
         }
 
@@ -4107,7 +4135,7 @@
     if (!lock) return false;
 
     auto status = ReadSnapshotUpdateStatus(lock.get());
-    return status.state() != UpdateState::None && status.compression_enabled();
+    return status.state() != UpdateState::None && status.using_snapuserd();
 }
 
 bool SnapshotManager::DetachSnapuserdForSelinux(std::vector<std::string>* snapuserd_argv) {
@@ -4133,7 +4161,7 @@
 }
 
 MergePhase SnapshotManager::DecideMergePhase(const SnapshotStatus& status) {
-    if (status.compression_enabled() && status.device_size() < status.old_partition_size()) {
+    if (status.using_snapuserd() && status.device_size() < status.old_partition_size()) {
         return MergePhase::FIRST_PHASE;
     }
     return MergePhase::SECOND_PHASE;
@@ -4163,9 +4191,19 @@
         estimated_cow_size += status.estimated_cow_size();
     }
 
-    stats->set_cow_file_size(cow_file_size);
-    stats->set_total_cow_size_bytes(total_cow_size);
-    stats->set_estimated_cow_size_bytes(estimated_cow_size);
+    stats->report()->set_cow_file_size(cow_file_size);
+    stats->report()->set_total_cow_size_bytes(total_cow_size);
+    stats->report()->set_estimated_cow_size_bytes(estimated_cow_size);
+}
+
+void SnapshotManager::SetMergeStatsFeatures(ISnapshotMergeStats* stats) {
+    auto lock = LockExclusive();
+    if (!lock) return;
+
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get());
+    stats->report()->set_iouring_used(update_status.io_uring_enabled());
+    stats->report()->set_userspace_snapshots_used(update_status.userspace_snapshots());
+    stats->report()->set_xor_compression_used(GetXorCompressionEnabledProperty());
 }
 
 bool SnapshotManager::DeleteDeviceIfExists(const std::string& name,
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index a648384..eb8246a 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -29,6 +29,7 @@
 // by SnapshotManager.
 
 #include "android/snapshot/snapshot_fuzz.pb.h"
+#include "libsnapshot/snapshot.h"
 
 namespace android::snapshot {
 
@@ -94,6 +95,7 @@
 
 class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
   public:
+    using MergeStatus = ISnapshotManager::IDeviceInfo::MergeStatus;
     // Client is responsible for maintaining the lifetime of |data|.
     SnapshotFuzzDeviceInfo(SnapshotFuzzEnv* env, const FuzzDeviceInfoData& data,
                            std::unique_ptr<TestPartitionOpener>&& partition_opener,
@@ -118,7 +120,7 @@
     std::string GetSlotSuffix() const override { return CurrentSlotIsA() ? "_a" : "_b"; }
     std::string GetOtherSlotSuffix() const override { return CurrentSlotIsA() ? "_b" : "_a"; }
     bool IsOverlayfsSetup() const override { return data_->is_overlayfs_setup(); }
-    bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override {
+    bool SetBootControlMergeStatus(MergeStatus) override {
         return data_->allow_set_boot_control_merge_status();
     }
     bool SetSlotAsUnbootable(unsigned int) override {
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 712eafb..9b6eb2c 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -84,27 +84,14 @@
     return WriteState();
 }
 
-void SnapshotMergeStats::set_state(android::snapshot::UpdateState state, bool using_compression) {
+void SnapshotMergeStats::set_state(android::snapshot::UpdateState state) {
     report_.set_state(state);
-    report_.set_compression_enabled(using_compression);
-}
-
-void SnapshotMergeStats::set_cow_file_size(uint64_t cow_file_size) {
-    report_.set_cow_file_size(cow_file_size);
 }
 
 uint64_t SnapshotMergeStats::cow_file_size() {
     return report_.cow_file_size();
 }
 
-void SnapshotMergeStats::set_total_cow_size_bytes(uint64_t bytes) {
-    report_.set_total_cow_size_bytes(bytes);
-}
-
-void SnapshotMergeStats::set_estimated_cow_size_bytes(uint64_t bytes) {
-    report_.set_estimated_cow_size_bytes(bytes);
-}
-
 uint64_t SnapshotMergeStats::total_cow_size_bytes() {
     return report_.total_cow_size_bytes();
 }
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 4af5367..84e2226 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -128,12 +128,9 @@
 
 class SnapshotMergeStatsStub : public ISnapshotMergeStats {
     bool Start() override { return false; }
-    void set_state(android::snapshot::UpdateState, bool) override {}
-    void set_cow_file_size(uint64_t) override {}
+    void set_state(android::snapshot::UpdateState) override {}
     uint64_t cow_file_size() override { return 0; }
     std::unique_ptr<Result> Finish() override { return nullptr; }
-    void set_total_cow_size_bytes(uint64_t) override {}
-    void set_estimated_cow_size_bytes(uint64_t) override {}
     uint64_t total_cow_size_bytes() override { return 0; }
     uint64_t estimated_cow_size_bytes() override { return 0; }
     void set_boot_complete_time_ms(uint32_t) override {}
@@ -145,6 +142,10 @@
     void set_source_build_fingerprint(const std::string&) override {}
     std::string source_build_fingerprint() override { return {}; }
     bool WriteState() override { return false; }
+    SnapshotMergeReport* report() override { return &report_; }
+
+  private:
+    SnapshotMergeReport report_;
 };
 
 ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -183,4 +184,8 @@
     return {};
 }
 
+void SnapshotManagerStub::SetMergeStatsFeatures(ISnapshotMergeStats*) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+}
+
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index c145da7..2233a38 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -26,6 +26,7 @@
 #include <future>
 #include <iostream>
 
+#include <aidl/android/hardware/boot/MergeStatus.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
@@ -39,6 +40,7 @@
 #include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
 #include <liblp/builder.h>
+#include <openssl/sha.h>
 #include <storage_literals/storage_literals.h>
 
 #include <android/snapshot/snapshot.pb.h>
@@ -51,9 +53,11 @@
 #include <libsnapshot/mock_device_info.h>
 #include <libsnapshot/mock_snapshot.h>
 
-DEFINE_string(force_config, "", "Force testing mode (dmsnap, vab, vabc) ignoring device config.");
+DEFINE_string(force_mode, "",
+              "Force testing older modes (vab-legacy, vabc-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.");
 
 namespace android {
 namespace snapshot {
@@ -90,8 +94,6 @@
 std::string fake_super;
 
 void MountMetadata();
-bool ShouldUseCompression();
-bool IsDaemonRequired();
 
 class SnapshotTest : public ::testing::Test {
   public:
@@ -107,7 +109,7 @@
     void SetUp() override {
         SKIP_IF_NON_VIRTUAL_AB();
 
-        SnapshotTestPropertyFetcher::SetUp();
+        SetupProperties();
         InitializeState();
         CleanupTestArtifacts();
         FormatFakeSuper();
@@ -115,6 +117,38 @@
         ASSERT_TRUE(sm->BeginUpdate());
     }
 
+    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") {
+            properties["ro.virtual_ab.compression.enabled"] = "false";
+            properties["ro.virtual_ab.userspace.snapshots.enabled"] = "false";
+        }
+
+        if (FLAGS_force_iouring_disable == "iouring_disabled") {
+            ASSERT_TRUE(android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1"))
+                    << "Failed to set property: snapuserd.test.io_uring.disabled";
+            properties["ro.virtual_ab.io_uring.enabled"] = "false";
+        }
+
+        auto fetcher = std::make_unique<SnapshotTestPropertyFetcher>("_a", std::move(properties));
+        IPropertyFetcher::OverrideForTesting(std::move(fetcher));
+
+        if (GetLegacyCompressionEnabledProperty() || CanUseUserspaceSnapshots()) {
+            snapuserd_required_ = true;
+        }
+    }
+
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
 
@@ -315,7 +349,7 @@
     }
 
     AssertionResult DeleteDevice(const std::string& device) {
-        if (!dm_.DeleteDeviceIfExists(device)) {
+        if (!sm->DeleteDeviceIfExists(device, 1s)) {
             return AssertionFailure() << "Can't delete " << device;
         }
         return AssertionSuccess();
@@ -357,8 +391,11 @@
         DeltaArchiveManifest manifest;
 
         auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata();
-        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+        dynamic_partition_metadata->set_vabc_enabled(snapuserd_required_);
         dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
+        if (snapuserd_required_) {
+            dynamic_partition_metadata->set_vabc_compression_param(FLAGS_compression_method);
+        }
 
         auto group = dynamic_partition_metadata->add_groups();
         group->set_name("group");
@@ -396,7 +433,7 @@
             if (!res) {
                 return res;
             }
-        } else if (!IsCompressionEnabled()) {
+        } else if (!snapuserd_required_) {
             std::string ignore;
             if (!MapUpdateSnapshot("test_partition_b", &ignore)) {
                 return AssertionFailure() << "Failed to map test_partition_b";
@@ -449,15 +486,16 @@
     std::unique_ptr<SnapshotManager::LockedFile> lock_;
     android::fiemap::IImageManager* image_manager_ = nullptr;
     std::string fake_super_;
+    bool snapuserd_required_ = false;
 };
 
 TEST_F(SnapshotTest, CreateSnapshot) {
     ASSERT_TRUE(AcquireLock());
 
     PartitionCowCreator cow_creator;
-    cow_creator.compression_enabled = ShouldUseCompression();
-    if (cow_creator.compression_enabled) {
-        cow_creator.compression_algorithm = "gz";
+    cow_creator.using_snapuserd = snapuserd_required_;
+    if (cow_creator.using_snapuserd) {
+        cow_creator.compression_algorithm = FLAGS_compression_method;
     } else {
         cow_creator.compression_algorithm = "none";
     }
@@ -483,7 +521,7 @@
         ASSERT_EQ(status.state(), SnapshotState::CREATED);
         ASSERT_EQ(status.device_size(), kDeviceSize);
         ASSERT_EQ(status.snapshot_size(), kDeviceSize);
-        ASSERT_EQ(status.compression_enabled(), cow_creator.compression_enabled);
+        ASSERT_EQ(status.using_snapuserd(), cow_creator.using_snapuserd);
         ASSERT_EQ(status.compression_algorithm(), cow_creator.compression_algorithm);
     }
 
@@ -496,7 +534,7 @@
     ASSERT_TRUE(AcquireLock());
 
     PartitionCowCreator cow_creator;
-    cow_creator.compression_enabled = ShouldUseCompression();
+    cow_creator.using_snapuserd = snapuserd_required_;
 
     static const uint64_t kDeviceSize = 1024 * 1024;
     SnapshotStatus status;
@@ -623,10 +661,10 @@
     SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
     ASSERT_EQ(status.state(), SnapshotState::CREATED);
-    if (ShouldUseCompression()) {
-        ASSERT_EQ(status.compression_algorithm(), "gz");
+    if (snapuserd_required_) {
+        ASSERT_EQ(status.compression_algorithm(), FLAGS_compression_method);
     } else {
-        ASSERT_EQ(status.compression_algorithm(), "none");
+        ASSERT_EQ(status.compression_algorithm(), "");
     }
 
     DeviceMapper::TargetInfo target;
@@ -897,8 +935,11 @@
         opener_ = std::make_unique<TestPartitionOpener>(fake_super);
 
         auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
-        dynamic_partition_metadata->set_vabc_enabled(ShouldUseCompression());
+        dynamic_partition_metadata->set_vabc_enabled(snapuserd_required_);
         dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
+        if (snapuserd_required_) {
+            dynamic_partition_metadata->set_vabc_compression_param(FLAGS_compression_method);
+        }
 
         // Create a fake update package metadata.
         // Not using full name "system", "vendor", "product" because these names collide with the
@@ -1030,7 +1071,7 @@
     }
 
     AssertionResult MapOneUpdateSnapshot(const std::string& name) {
-        if (ShouldUseCompression()) {
+        if (snapuserd_required_) {
             std::unique_ptr<ISnapshotWriter> writer;
             return MapUpdateSnapshot(name, &writer);
         } else {
@@ -1039,14 +1080,25 @@
         }
     }
 
-    AssertionResult WriteSnapshotAndHash(const std::string& name) {
-        if (ShouldUseCompression()) {
+    AssertionResult WriteSnapshots() {
+        for (const auto& partition : {sys_, vnd_, prd_}) {
+            auto res = WriteSnapshotAndHash(partition);
+            if (!res) {
+                return res;
+            }
+        }
+        return AssertionSuccess();
+    }
+
+    AssertionResult WriteSnapshotAndHash(PartitionUpdate* partition) {
+        std::string name = partition->partition_name() + "_b";
+        if (snapuserd_required_) {
             std::unique_ptr<ISnapshotWriter> writer;
             auto res = MapUpdateSnapshot(name, &writer);
             if (!res) {
                 return res;
             }
-            if (!WriteRandomData(writer.get(), &hashes_[name])) {
+            if (!WriteRandomSnapshotData(writer.get(), &hashes_[name])) {
                 return AssertionFailure() << "Unable to write random data to snapshot " << name;
             }
             if (!writer->Finalize()) {
@@ -1070,6 +1122,42 @@
                                   << ", hash: " << hashes_[name];
     }
 
+    bool WriteRandomSnapshotData(ICowWriter* writer, std::string* hash) {
+        unique_fd rand(open("/dev/urandom", O_RDONLY));
+        if (rand < 0) {
+            PLOG(ERROR) << "open /dev/urandom";
+            return false;
+        }
+
+        SHA256_CTX ctx;
+        SHA256_Init(&ctx);
+
+        if (!writer->options().max_blocks) {
+            LOG(ERROR) << "CowWriter must specify maximum number of blocks";
+            return false;
+        }
+        const auto num_blocks = writer->options().max_blocks.value();
+
+        const auto block_size = writer->options().block_size;
+        std::string block(block_size, '\0');
+        for (uint64_t i = 0; i < num_blocks; i++) {
+            if (!ReadFully(rand, block.data(), block.size())) {
+                PLOG(ERROR) << "read /dev/urandom";
+                return false;
+            }
+            if (!writer->AddRawBlocks(i, block.data(), block.size())) {
+                LOG(ERROR) << "Failed to add raw block " << i;
+                return false;
+            }
+            SHA256_Update(&ctx, block.data(), block.size());
+        }
+
+        uint8_t out[32];
+        SHA256_Final(out, &ctx);
+        *hash = ToHexString(out, sizeof(out));
+        return true;
+    }
+
     // Generate a snapshot that moves all the upper blocks down to the start.
     // It doesn't really matter the order, we just want copies that reference
     // blocks that won't exist if the partition shrinks.
@@ -1178,9 +1266,7 @@
     ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     // Assert that source partitions aren't affected.
     for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
@@ -1208,7 +1294,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsDaemonRequired());
+    ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
         // We should have started in SECOND_PHASE since nothing shrinks.
         ASSERT_TRUE(AcquireLock());
@@ -1235,8 +1321,8 @@
 }
 
 TEST_F(SnapshotUpdateTest, DuplicateOps) {
-    if (!ShouldUseCompression()) {
-        GTEST_SKIP() << "Compression-only test";
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "snapuserd-only test";
     }
 
     // Execute the update.
@@ -1244,9 +1330,7 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     std::vector<PartitionUpdate*> partitions = {sys_, vnd_, prd_};
     for (auto* partition : partitions) {
@@ -1279,9 +1363,9 @@
 // Test that shrinking and growing partitions at the same time is handled
 // correctly in VABC.
 TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
-    if (!ShouldUseCompression()) {
+    if (!snapuserd_required_) {
         // b/179111359
-        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+        GTEST_SKIP() << "Skipping snapuserd test";
     }
 
     auto old_sys_size = GetSize(sys_);
@@ -1310,8 +1394,8 @@
         ASSERT_EQ(status.old_partition_size(), 3145728);
     }
 
-    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
-    ASSERT_TRUE(WriteSnapshotAndHash("vnd_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash(sys_));
+    ASSERT_TRUE(WriteSnapshotAndHash(vnd_));
     ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size));
 
     sync();
@@ -1342,7 +1426,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsDaemonRequired());
+    ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
         // Check that the merge phase is FIRST_PHASE until at least one call
         // to ProcessUpdateState() occurs.
@@ -1396,9 +1480,9 @@
 
 // Test that a transient merge consistency check failure can resume properly.
 TEST_F(SnapshotUpdateTest, ConsistencyCheckResume) {
-    if (!ShouldUseCompression()) {
+    if (!snapuserd_required_) {
         // b/179111359
-        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+        GTEST_SKIP() << "Skipping snapuserd test";
     }
 
     auto old_sys_size = GetSize(sys_);
@@ -1414,8 +1498,8 @@
 
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
-    ASSERT_TRUE(WriteSnapshotAndHash("vnd_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash(sys_));
+    ASSERT_TRUE(WriteSnapshotAndHash(vnd_));
     ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size));
 
     sync();
@@ -1450,7 +1534,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsDaemonRequired());
+    ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
         // Check that the merge phase is FIRST_PHASE until at least one call
         // to ProcessUpdateState() occurs.
@@ -1576,9 +1660,7 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     // Assert that source partitions aren't affected.
     for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
@@ -1737,9 +1819,7 @@
     ASSERT_FALSE(image_manager_->BackingImageExists("prd_b-cow-img"));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     // Assert that source partitions aren't affected.
     for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
@@ -2011,9 +2091,7 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */));
 
@@ -2053,17 +2131,15 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     // Create a stale snapshot that should not exist.
     {
         ASSERT_TRUE(AcquireLock());
 
         PartitionCowCreator cow_creator = {
-                .compression_enabled = ShouldUseCompression(),
-                .compression_algorithm = ShouldUseCompression() ? "gz" : "none",
+                .using_snapuserd = snapuserd_required_,
+                .compression_algorithm = snapuserd_required_ ? FLAGS_compression_method : "",
         };
         SnapshotStatus status;
         status.set_name("sys_a");
@@ -2138,7 +2214,7 @@
 
     // Map and write some data to target partition.
     ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
-    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash(sys_));
 
     // Finish update.
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
@@ -2159,8 +2235,8 @@
 
 // Test for overflow bit after update
 TEST_F(SnapshotUpdateTest, Overflow) {
-    if (ShouldUseCompression()) {
-        GTEST_SKIP() << "No overflow bit set for userspace COWs";
+    if (snapuserd_required_) {
+        GTEST_SKIP() << "No overflow bit set for snapuserd COWs";
     }
 
     const auto actual_write_size = GetSize(sys_);
@@ -2174,7 +2250,7 @@
 
     // Map and write some data to target partitions.
     ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
-    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash(sys_));
 
     std::vector<android::dm::DeviceMapper::TargetInfo> table;
     ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table));
@@ -2234,8 +2310,8 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
+    for (const auto& partition : {sys_, vnd_, prd_, dlkm}) {
+        ASSERT_TRUE(WriteSnapshotAndHash(partition));
     }
 
     // Assert that source partitions aren't affected.
@@ -2294,8 +2370,8 @@
 };
 
 TEST_F(SnapshotUpdateTest, DaemonTransition) {
-    if (!ShouldUseCompression()) {
-        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "Skipping snapuserd test";
     }
 
     // Ensure a connection to the second-stage daemon, but use the first-stage
@@ -2359,9 +2435,7 @@
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
     ASSERT_TRUE(sm->MapAllSnapshots(10s));
 
@@ -2411,13 +2485,6 @@
     // fit in super, but not |prd|.
     constexpr uint64_t partition_size = 3788_KiB;
     SetSize(sys_, partition_size);
-    SetSize(vnd_, partition_size);
-    SetSize(prd_, 18_MiB);
-
-    // Make sure |prd| does not fit in super at all. On VABC, this means we
-    // fake an extra large COW for |vnd| to fill up super.
-    vnd_->set_estimate_cow_size(30_MiB);
-    prd_->set_estimate_cow_size(30_MiB);
 
     AddOperationForPartitions();
 
@@ -2429,23 +2496,7 @@
         GTEST_SKIP() << "Test does not apply to userspace snapshots";
     }
 
-    // Test that partitions prioritize using space in super.
-    auto tgt = MetadataBuilder::New(*opener_, "super", 1);
-    ASSERT_NE(tgt, nullptr);
-    ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow"));
-    ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
-    ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
-
-    // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
-
-    // Assert that source partitions aren't affected.
-    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
-        ASSERT_TRUE(IsPartitionUnchanged(name));
-    }
-
+    ASSERT_TRUE(WriteSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     ASSERT_TRUE(UnmapAll());
@@ -2609,7 +2660,6 @@
     }
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
-        return;  // BUG(149738928)
 
         EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
                     image_manager_->DeleteBackingImage(kImageName));
@@ -2618,19 +2668,6 @@
     std::unique_ptr<LowSpaceUserdata> userdata_;
 };
 
-TEST_P(ImageManagerTest, CreateImageEnoughAvailSpace) {
-    if (userdata_->available_space() == 0) {
-        GTEST_SKIP() << "/data is full (" << userdata_->available_space()
-                     << " bytes available), skipping";
-    }
-    ASSERT_TRUE(image_manager_->CreateBackingImage(kImageName, userdata_->available_space(),
-                                                   IImageManager::CREATE_IMAGE_DEFAULT))
-            << "Should be able to create image with size = " << userdata_->available_space()
-            << " bytes";
-    ASSERT_TRUE(image_manager_->DeleteBackingImage(kImageName))
-            << "Should be able to delete created image";
-}
-
 TEST_P(ImageManagerTest, CreateImageNoSpace) {
     uint64_t to_allocate = userdata_->free_space() + userdata_->bsize();
     auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
@@ -2750,41 +2787,17 @@
     }
 }
 
-bool IsDaemonRequired() {
-    if (FLAGS_force_config == "dmsnap") {
-        return false;
+void KillSnapuserd() {
+    auto status = android::base::GetProperty("init.svc.snapuserd", "stopped");
+    if (status == "stopped") {
+        return;
     }
-
-    if (!IsCompressionEnabled()) {
-        return false;
+    auto snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
+    if (!snapuserd_client) {
+        return;
     }
-
-    const std::string UNKNOWN = "unknown";
-    const std::string vendor_release =
-            android::base::GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN);
-
-    // No userspace snapshots if vendor partition is on Android 12
-    // However, for GRF devices, snapuserd daemon will be on
-    // vendor ramdisk in Android 12.
-    if (vendor_release.find("12") != std::string::npos) {
-        return true;
-    }
-
-    if (!FLAGS_force_config.empty()) {
-        return true;
-    }
-
-    return IsUserspaceSnapshotsEnabled();
-}
-
-bool ShouldUseCompression() {
-    if (FLAGS_force_config == "vab" || FLAGS_force_config == "dmsnap") {
-        return false;
-    }
-    if (FLAGS_force_config == "vabc") {
-        return true;
-    }
-    return IsCompressionEnabled();
+    snapuserd_client->DetachSnapuserd();
+    snapuserd_client->CloseConnection();
 }
 
 }  // namespace snapshot
@@ -2797,35 +2810,20 @@
 
     android::base::SetProperty("ctl.stop", "snapuserd");
 
-    std::unordered_set<std::string> configs = {"", "dmsnap", "vab", "vabc"};
-    if (configs.count(FLAGS_force_config) == 0) {
+    std::unordered_set<std::string> modes = {"", "vab-legacy", "vabc-legacy"};
+    if (modes.count(FLAGS_force_mode) == 0) {
         std::cerr << "Unexpected force_config argument\n";
         return 1;
     }
 
-    if (FLAGS_force_config == "dmsnap") {
-        if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) {
-            return testing::AssertionFailure()
-                   << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
-        }
-    }
-
-    if (FLAGS_force_iouring_disable == "iouring_disabled") {
-        if (!android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1")) {
-            return testing::AssertionFailure()
-                   << "Failed to disable property: snapuserd.test.io_uring.disabled";
-        }
-    }
+    // This is necessary if the configuration we're testing doesn't match the device.
+    android::snapshot::KillSnapuserd();
 
     int ret = RUN_ALL_TESTS();
 
-    if (FLAGS_force_config == "dmsnap") {
-        android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
-    }
+    android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
+    android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
 
-    if (FLAGS_force_iouring_disable == "iouring_disabled") {
-        android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
-    }
-
+    android::snapshot::KillSnapuserd();
     return ret;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index bc2bceb..64e0b8a 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -47,6 +47,7 @@
         "libbase",
         "liblog",
     ],
+    export_include_dirs: ["include"],
     ramdisk_available: true,
 }
 
@@ -68,11 +69,12 @@
         "user-space-merge/snapuserd_readahead.cpp",
         "user-space-merge/snapuserd_transitions.cpp",
         "user-space-merge/snapuserd_server.cpp",
+        "user-space-merge/snapuserd_verify.cpp",
     ],
 
     cflags: [
         "-Wall",
-        "-Werror"
+        "-Werror",
     ],
 
     static_libs: [
@@ -85,19 +87,18 @@
         "liblog",
         "libsnapshot_cow",
         "libz",
+        "liblz4",
         "libext4_utils",
         "liburing",
     ],
-    include_dirs: ["bionic/libc/kernel"],
-}
 
-cc_binary {
-    name: "snapuserd",
-    defaults: ["snapuserd_defaults"],
-    init_rc: [
-        "snapuserd.rc",
+    header_libs: [
+        "libstorage_literals_headers",
     ],
 
+    include_dirs: ["bionic/libc/kernel"],
+    system_shared_libs: [],
+
     // snapuserd is started during early boot by first-stage init. At that
     // point, /system is mounted using the "dm-user" device-mapper kernel
     // module. dm-user routes all I/O to userspace to be handled by
@@ -105,22 +106,47 @@
     // faults for its code pages.
     static_executable: true,
 
-    system_shared_libs: [],
-    ramdisk_available: true,
-    vendor_ramdisk_available: true,
-    recovery_available: true,
-
     // Snapuserd segfaults with ThinLTO
     // http://b/208565717
     lto: {
          never: true,
-    }
+    },
+}
+
+cc_binary {
+    name: "snapuserd",
+    defaults: ["snapuserd_defaults"],
+    init_rc: [
+        "snapuserd.rc",
+    ],
+    ramdisk_available: false,
+    vendor_ramdisk_available: true,
+    recovery_available: true,
+}
+
+// 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.
+cc_binary {
+    name: "snapuserd_ramdisk",
+    defaults: ["snapuserd_defaults"],
+    init_rc: [
+        "snapuserd.rc",
+    ],
+    // This target is specifically for generic ramdisk, therefore we set
+    // vendor_ramdisk_available to false.
+    ramdisk_available: true,
+    vendor_ramdisk_available: false,
+    ramdisk: true,
+    symlinks: ["snapuserd"],
 }
 
 cc_test {
     name: "cow_snapuserd_test",
     defaults: [
         "fs_mgr_defaults",
+        "libsnapshot_cow_defaults",
     ],
     srcs: [
         "dm-snapshot-merge/cow_snapuserd_test.cpp",
@@ -162,6 +188,7 @@
     name: "snapuserd_test",
     defaults: [
         "fs_mgr_defaults",
+        "libsnapshot_cow_defaults",
     ],
     srcs: [
         "user-space-merge/snapuserd_test.cpp",
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
index 9ddc963..577b09d 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
@@ -505,7 +505,7 @@
 
     // We don't care if the ACK is received.
     code[0] = 'a';
-    if (TEMP_FAILURE_RETRY(send(fd, code, sizeof(code), MSG_NOSIGNAL) < 0)) {
+    if (TEMP_FAILURE_RETRY(send(fd, code, sizeof(code), MSG_NOSIGNAL)) < 0) {
         PLOG(ERROR) << "Failed to send ACK to proxy";
         return false;
     }
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index cebda1c..9a69d58 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -89,6 +89,10 @@
 
     // Return the status of the snapshot
     std::string QuerySnapshotStatus(const std::string& misc_name);
+
+    // Check the update verification status - invoked by update_verifier during
+    // boot
+    bool QueryUpdateVerification();
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 7b1c7a3..e08cf9b 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -269,5 +269,15 @@
     return Receivemsg();
 }
 
+bool SnapuserdClient::QueryUpdateVerification() {
+    std::string msg = "update-verify";
+    if (!Sendmsg(msg)) {
+        LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+        return false;
+    }
+    std::string response = Receivemsg();
+    return response == "success";
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 692cb74..492c43f 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -18,6 +18,7 @@
 
 #include <sys/utsname.h>
 
+#include <android-base/chrono_utils.h>
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
 #include <android-base/strings.h>
@@ -70,6 +71,9 @@
 
     read_ahead_thread_ = std::make_unique<ReadAhead>(cow_device_, backing_store_device_, misc_name_,
                                                      GetSharedPtr());
+
+    update_verify_ = std::make_unique<UpdateVerify>(misc_name_);
+
     return true;
 }
 
@@ -143,21 +147,22 @@
     NotifyRAForMergeReady();
 }
 
-void SnapshotHandler::CheckMergeCompletionStatus() {
+bool SnapshotHandler::CheckMergeCompletionStatus() {
     if (!merge_initiated_) {
         SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: "
                        << reader_->get_num_total_data_ops();
-        return;
+        return false;
     }
 
     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();
+    return true;
 }
 
 bool SnapshotHandler::ReadMetadata() {
-    reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE);
+    reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE, true);
     CowHeader header;
     CowOptions options;
 
@@ -188,7 +193,7 @@
     UpdateMergeCompletionPercentage();
 
     // Initialize the iterator for reading metadata
-    std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+    std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetOpIter(true);
 
     int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
     int ra_index = 0;
@@ -306,206 +311,6 @@
     return ReadMetadata();
 }
 
-void SnapshotHandler::FinalizeIouring() {
-    io_uring_queue_exit(ring_.get());
-}
-
-bool SnapshotHandler::InitializeIouring(int io_depth) {
-    ring_ = std::make_unique<struct io_uring>();
-
-    int ret = io_uring_queue_init(io_depth, ring_.get(), 0);
-    if (ret) {
-        LOG(ERROR) << "io_uring_queue_init failed with ret: " << ret;
-        return false;
-    }
-
-    LOG(INFO) << "io_uring_queue_init success with io_depth: " << io_depth;
-    return true;
-}
-
-bool SnapshotHandler::ReadBlocksAsync(const std::string& dm_block_device,
-                                      const std::string& partition_name, size_t size) {
-    // 64k block size with io_depth of 64 is optimal
-    // for a single thread. We just need a single thread
-    // to read all the blocks from all dynamic partitions.
-    size_t io_depth = 64;
-    size_t bs = (64 * 1024);
-
-    if (!InitializeIouring(io_depth)) {
-        return false;
-    }
-
-    LOG(INFO) << "ReadBlockAsync start "
-              << " Block-device: " << dm_block_device << " Partition-name: " << partition_name
-              << " Size: " << size;
-
-    auto scope_guard = android::base::make_scope_guard([this]() -> void { FinalizeIouring(); });
-
-    std::vector<std::unique_ptr<struct iovec>> vecs;
-    using AlignedBuf = std::unique_ptr<void, decltype(free)*>;
-    std::vector<AlignedBuf> alignedBufVector;
-
-    /*
-     * TODO: We need aligned memory for DIRECT-IO. However, if we do
-     * a DIRECT-IO and verify the blocks then we need to inform
-     * update-verifier that block verification has been done and
-     * there is no need to repeat the same. We are not there yet
-     * as we need to see if there are any boot time improvements doing
-     * a DIRECT-IO.
-     *
-     * Also, we could you the same function post merge for block verification;
-     * again, we can do a DIRECT-IO instead of thrashing page-cache and
-     * hurting other applications.
-     *
-     * For now, we will just create aligned buffers but rely on buffered
-     * I/O until we have perf numbers to justify DIRECT-IO.
-     */
-    for (int i = 0; i < io_depth; i++) {
-        auto iovec = std::make_unique<struct iovec>();
-        vecs.push_back(std::move(iovec));
-
-        struct iovec* iovec_ptr = vecs[i].get();
-
-        if (posix_memalign(&iovec_ptr->iov_base, BLOCK_SZ, bs)) {
-            LOG(ERROR) << "posix_memalign failed";
-            return false;
-        }
-
-        iovec_ptr->iov_len = bs;
-        alignedBufVector.push_back(
-                std::unique_ptr<void, decltype(free)*>(iovec_ptr->iov_base, free));
-    }
-
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
-    if (fd.get() == -1) {
-        SNAP_PLOG(ERROR) << "File open failed - block-device " << dm_block_device
-                         << " partition-name: " << partition_name;
-        return false;
-    }
-
-    loff_t offset = 0;
-    size_t remain = size;
-    size_t read_sz = io_depth * bs;
-
-    while (remain > 0) {
-        size_t to_read = std::min(remain, read_sz);
-        size_t queue_size = to_read / bs;
-
-        for (int i = 0; i < queue_size; i++) {
-            struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get());
-            if (!sqe) {
-                SNAP_LOG(ERROR) << "io_uring_get_sqe() failed";
-                return false;
-            }
-
-            struct iovec* iovec_ptr = vecs[i].get();
-
-            io_uring_prep_read(sqe, fd.get(), iovec_ptr->iov_base, iovec_ptr->iov_len, offset);
-            sqe->flags |= IOSQE_ASYNC;
-            offset += bs;
-        }
-
-        int ret = io_uring_submit(ring_.get());
-        if (ret != queue_size) {
-            SNAP_LOG(ERROR) << "submit got: " << ret << " wanted: " << queue_size;
-            return false;
-        }
-
-        for (int i = 0; i < queue_size; i++) {
-            struct io_uring_cqe* cqe;
-
-            int ret = io_uring_wait_cqe(ring_.get(), &cqe);
-            if (ret) {
-                SNAP_PLOG(ERROR) << "wait_cqe failed" << ret;
-                return false;
-            }
-
-            if (cqe->res < 0) {
-                SNAP_LOG(ERROR) << "io failed with res: " << cqe->res;
-                return false;
-            }
-            io_uring_cqe_seen(ring_.get(), cqe);
-        }
-
-        remain -= to_read;
-    }
-
-    LOG(INFO) << "ReadBlockAsync complete: "
-              << " Block-device: " << dm_block_device << " Partition-name: " << partition_name
-              << " Size: " << size;
-    return true;
-}
-
-void SnapshotHandler::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 SnapshotHandler::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++) {
-        std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this, dm_block_device,
-                   partition_name, offset, read_sz_per_thread);
-
-        offset += read_sz_per_thread;
-    }
-}
-
 /*
  * Entry point to launch threads
  */
@@ -526,42 +331,22 @@
                 std::async(std::launch::async, &Worker::RunThread, worker_threads_[i].get()));
     }
 
-    bool second_stage_init = true;
+    bool partition_verification = true;
 
-    // We don't want to read the blocks during first stage init.
+    // We don't want to read the blocks during first stage init or
+    // during post-install phase.
     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";
+        partition_verification = false;
     }
 
     std::future<bool> merge_thread =
             std::async(std::launch::async, &Worker::RunMergeThread, merge_thread_.get());
 
+    // Now that the worker threads are up, scan the partitions.
+    if (partition_verification) {
+        update_verify_->VerifyUpdatePartition();
+    }
+
     bool ret = true;
     for (auto& t : threads) {
         ret = t.get() && ret;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index 83d40f6..42237ef 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -18,6 +18,9 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <unistd.h>
 
 #include <condition_variable>
 #include <cstring>
@@ -42,18 +45,22 @@
 #include <liburing.h>
 #include <snapuserd/snapuserd_buffer.h>
 #include <snapuserd/snapuserd_kernel.h>
+#include <storage_literals/storage_literals.h>
 
 namespace android {
 namespace snapshot {
 
 using android::base::unique_fd;
 using namespace std::chrono_literals;
+using namespace android::storage_literals;
 
 static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20);
 static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ);
 
 static constexpr int kNumWorkerThreads = 4;
 
+static constexpr int kNiceValueForMergeThreads = -5;
+
 #define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
 #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
 
@@ -165,6 +172,36 @@
     std::unique_ptr<struct io_uring> ring_;
 };
 
+class UpdateVerify {
+  public:
+    UpdateVerify(const std::string& misc_name);
+    void VerifyUpdatePartition();
+    bool CheckPartitionVerification();
+
+  private:
+    enum class UpdateVerifyState {
+        VERIFY_UNKNOWN,
+        VERIFY_FAILED,
+        VERIFY_SUCCESS,
+    };
+
+    std::string misc_name_;
+    UpdateVerifyState state_;
+    std::mutex m_lock_;
+    std::condition_variable m_cv_;
+
+    int kMinThreadsToVerify = 1;
+    int kMaxThreadsToVerify = 4;
+    uint64_t kThresholdSize = 512_MiB;
+    uint64_t kBlockSizeVerify = 1_MiB;
+
+    bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
+    void UpdatePartitionVerificationState(UpdateVerifyState state);
+    bool VerifyPartition(const std::string& partition_name, const std::string& dm_block_device);
+    bool VerifyBlocks(const std::string& partition_name, const std::string& dm_block_device,
+                      off_t offset, int skip_blocks, uint64_t dev_sz);
+};
+
 class Worker {
   public:
     Worker(const std::string& cow_device, const std::string& backing_device,
@@ -274,7 +311,7 @@
     const bool& IsAttached() const { return attached_; }
     void AttachControlDevice() { attached_ = true; }
 
-    void CheckMergeCompletionStatus();
+    bool CheckMergeCompletionStatus();
     bool CommitMerge(int num_merge_ops);
 
     void CloseFds() { cow_fd_ = {}; }
@@ -305,6 +342,8 @@
 
     // State transitions for merge
     void InitiateMerge();
+    void MonitorMerge();
+    void WakeupMonitorMergeThread();
     void WaitForMergeComplete();
     bool WaitForMergeBegin();
     void NotifyRAForMergeReady();
@@ -333,6 +372,7 @@
     void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
     void SetIouringEnabled(bool io_uring_enabled) { is_io_uring_enabled_ = io_uring_enabled; }
     bool MergeInitiated() { return merge_initiated_; }
+    bool MergeMonitored() { return merge_monitored_; }
     double GetMergePercentage() { return merge_completion_percentage_; }
 
     // Merge Block State Transitions
@@ -344,24 +384,16 @@
     MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer);
 
     bool IsIouringSupported();
+    bool CheckPartitionVerification() { return update_verify_->CheckPartitionVerification(); }
 
   private:
     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); }
+    bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
     struct BufferState* GetBufferState();
     void UpdateMergeCompletionPercentage();
 
-    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);
-
-    bool InitializeIouring(int io_depth);
-    void FinalizeIouring();
-    bool ReadBlocksAsync(const std::string& dm_block_device, const std::string& partition_name,
-                         size_t size);
-
     // COW device
     std::string cow_device_;
     // Source device
@@ -407,12 +439,14 @@
     double merge_completion_percentage_;
 
     bool merge_initiated_ = false;
+    bool merge_monitored_ = false;
     bool attached_ = false;
     bool is_socket_present_;
     bool is_io_uring_enabled_ = false;
     bool scratch_space_ = false;
 
     std::unique_ptr<struct io_uring> ring_;
+    std::unique_ptr<UpdateVerify> update_verify_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index c26a2cd..d57f434 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -71,16 +71,16 @@
 }
 
 bool Worker::MergeReplaceZeroOps() {
-    // Flush every 8192 ops. Since all ops are independent and there is no
+    // Flush after merging 2MB. Since all ops are independent and there is no
     // dependency between COW ops, we will flush the data and the number
-    // of ops merged in COW file for every 8192 ops. If there is a crash,
-    // we will end up replaying some of the COW ops which were already merged.
-    // That is ok.
+    // of ops merged in COW block device. If there is a crash, we will
+    // end up replaying some of the COW ops which were already merged. That is
+    // ok.
     //
-    // Why 8192 ops ? Increasing this may improve merge time 3-4 seconds but
-    // we need to make sure that we checkpoint; 8k ops seems optimal. In-case
-    // if there is a crash merge should always make forward progress.
-    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 32;
+    // Although increasing this greater than 2MB may help in improving merge
+    // times; however, on devices with low memory, this can be problematic
+    // when there are multiple merge threads in parallel.
+    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 2;
     int num_ops_merged = 0;
 
     SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
@@ -466,7 +466,7 @@
 }
 
 bool Worker::Merge() {
-    cowop_iter_ = reader_->GetMergeOpIter();
+    cowop_iter_ = reader_->GetOpIter(true);
 
     bool retry = false;
     bool ordered_ops_merge_status;
@@ -543,6 +543,10 @@
         return true;
     }
 
+    if (setpriority(PRIO_PROCESS, gettid(), kNiceValueForMergeThreads)) {
+        SNAP_PLOG(ERROR) << "Failed to set priority for TID: " << gettid();
+    }
+
     SNAP_LOG(INFO) << "Merge starting..";
 
     if (!Init()) {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index fa2866f..fbe57d2 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -727,6 +727,10 @@
 
     InitializeIouring();
 
+    if (setpriority(PRIO_PROCESS, gettid(), kNiceValueForMergeThreads)) {
+        SNAP_PLOG(ERROR) << "Failed to set priority for TID: " << gettid();
+    }
+
     while (!RAIterDone()) {
         if (!ReadAheadIOStart()) {
             break;
@@ -768,7 +772,7 @@
 }
 
 void ReadAhead::InitializeRAIter() {
-    cowop_iter_ = reader_->GetMergeOpIter();
+    cowop_iter_ = reader_->GetOpIter(true);
 }
 
 bool ReadAhead::RAIterDone() {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index 82b2b25..1bf33c8 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -55,10 +55,19 @@
     if (input == "initiate_merge") return DaemonOps::INITIATE;
     if (input == "merge_percent") return DaemonOps::PERCENTAGE;
     if (input == "getstatus") return DaemonOps::GETSTATUS;
+    if (input == "update-verify") return DaemonOps::UPDATE_VERIFY;
 
     return DaemonOps::INVALID;
 }
 
+UserSnapshotServer::UserSnapshotServer() {
+    monitor_merge_event_fd_.reset(eventfd(0, EFD_CLOEXEC));
+    if (monitor_merge_event_fd_ == -1) {
+        PLOG(FATAL) << "monitor_merge_event_fd_: failed to create eventfd";
+    }
+    terminating_ = false;
+}
+
 UserSnapshotServer::~UserSnapshotServer() {
     // Close any client sockets that were added via AcceptClient().
     for (size_t i = 1; i < watched_fds_.size(); i++) {
@@ -249,7 +258,7 @@
                     return Sendmsg(fd, "fail");
                 }
 
-                if (!StartMerge(*iter)) {
+                if (!StartMerge(&lock, *iter)) {
                     return Sendmsg(fd, "fail");
                 }
 
@@ -282,6 +291,14 @@
                 return Sendmsg(fd, merge_status);
             }
         }
+        case DaemonOps::UPDATE_VERIFY: {
+            std::lock_guard<std::mutex> lock(lock_);
+            if (!UpdateVerification(&lock)) {
+                return Sendmsg(fd, "fail");
+            }
+
+            return Sendmsg(fd, "success");
+        }
         default: {
             LOG(ERROR) << "Received unknown message type from client";
             Sendmsg(fd, "fail");
@@ -298,7 +315,7 @@
     }
 
     handler->snapuserd()->CloseFds();
-    handler->snapuserd()->CheckMergeCompletionStatus();
+    bool merge_completed = handler->snapuserd()->CheckMergeCompletionStatus();
     handler->snapuserd()->UnmapBufferRegion();
 
     auto misc_name = handler->misc_name();
@@ -306,7 +323,11 @@
 
     {
         std::lock_guard<std::mutex> lock(lock_);
-        num_partitions_merge_complete_ += 1;
+        if (merge_completed) {
+            num_partitions_merge_complete_ += 1;
+            active_merge_threads_ -= 1;
+            WakeupMonitorMergeThread();
+        }
         handler->SetThreadTerminated();
         auto iter = FindHandler(&lock, handler->misc_name());
         if (iter == dm_users_.end()) {
@@ -418,6 +439,9 @@
 
         if (th.joinable()) th.join();
     }
+
+    stop_monitor_merge_thread_ = true;
+    WakeupMonitorMergeThread();
 }
 
 void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
@@ -502,13 +526,24 @@
     return true;
 }
 
-bool UserSnapshotServer::StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
+bool UserSnapshotServer::StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
+                                    const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
+    CHECK(proof_of_lock);
+
     if (!handler->snapuserd()->IsAttached()) {
         LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
         return false;
     }
 
-    handler->snapuserd()->InitiateMerge();
+    handler->snapuserd()->MonitorMerge();
+
+    if (!is_merge_monitor_started_.has_value()) {
+        std::thread(&UserSnapshotServer::MonitorMerge, this).detach();
+        is_merge_monitor_started_ = true;
+    }
+
+    merge_handlers_.push(handler);
+    WakeupMonitorMergeThread();
     return true;
 }
 
@@ -590,6 +625,42 @@
     return true;
 }
 
+void UserSnapshotServer::WakeupMonitorMergeThread() {
+    uint64_t notify = 1;
+    ssize_t rc = TEMP_FAILURE_RETRY(write(monitor_merge_event_fd_.get(), &notify, sizeof(notify)));
+    if (rc < 0) {
+        PLOG(FATAL) << "failed to notify monitor merge thread";
+    }
+}
+
+void UserSnapshotServer::MonitorMerge() {
+    while (!stop_monitor_merge_thread_) {
+        uint64_t testVal;
+        ssize_t ret =
+                TEMP_FAILURE_RETRY(read(monitor_merge_event_fd_.get(), &testVal, sizeof(testVal)));
+        if (ret == -1) {
+            PLOG(FATAL) << "Failed to read from eventfd";
+        } else if (ret == 0) {
+            LOG(FATAL) << "Hit EOF on eventfd";
+        }
+
+        LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_;
+        {
+            std::lock_guard<std::mutex> lock(lock_);
+            while (active_merge_threads_ < kMaxMergeThreads && merge_handlers_.size() > 0) {
+                auto handler = merge_handlers_.front();
+                merge_handlers_.pop();
+                LOG(INFO) << "Starting merge for partition: "
+                          << handler->snapuserd()->GetMiscName();
+                handler->snapuserd()->InitiateMerge();
+                active_merge_threads_ += 1;
+            }
+        }
+    }
+
+    LOG(INFO) << "Exiting MonitorMerge: size: " << merge_handlers_.size();
+}
+
 bool UserSnapshotServer::WaitForSocket() {
     auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
 
@@ -637,7 +708,7 @@
 
     // We don't care if the ACK is received.
     code[0] = 'a';
-    if (TEMP_FAILURE_RETRY(send(fd, code, sizeof(code), MSG_NOSIGNAL) < 0)) {
+    if (TEMP_FAILURE_RETRY(send(fd, code, sizeof(code), MSG_NOSIGNAL)) < 0) {
         PLOG(ERROR) << "Failed to send ACK to proxy";
         return false;
     }
@@ -646,6 +717,7 @@
     if (!StartWithSocket(true)) {
         return false;
     }
+
     return Run();
 }
 
@@ -687,5 +759,22 @@
     return true;
 }
 
+bool UserSnapshotServer::UpdateVerification(std::lock_guard<std::mutex>* proof_of_lock) {
+    CHECK(proof_of_lock);
+
+    bool status = true;
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        auto& th = (*iter)->thread();
+        if (th.joinable() && status) {
+            status = (*iter)->snapuserd()->CheckPartitionVerification() && status;
+        } else {
+            // return immediately if there is a failure
+            return false;
+        }
+    }
+
+    return status;
+}
+
 }  // namespace snapshot
 }  // namespace android
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 34e7941..c2af61f 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -15,6 +15,7 @@
 #pragma once
 
 #include <poll.h>
+#include <sys/eventfd.h>
 
 #include <cstdio>
 #include <cstring>
@@ -22,6 +23,8 @@
 #include <future>
 #include <iostream>
 #include <mutex>
+#include <optional>
+#include <queue>
 #include <sstream>
 #include <string>
 #include <thread>
@@ -34,6 +37,7 @@
 namespace snapshot {
 
 static constexpr uint32_t kMaxPacketSize = 512;
+static constexpr uint8_t kMaxMergeThreads = 2;
 
 enum class DaemonOps {
     INIT,
@@ -46,6 +50,7 @@
     INITIATE,
     PERCENTAGE,
     GETSTATUS,
+    UPDATE_VERIFY,
     INVALID,
 };
 
@@ -84,13 +89,19 @@
     std::vector<struct pollfd> watched_fds_;
     bool is_socket_present_ = false;
     int num_partitions_merge_complete_ = 0;
+    int active_merge_threads_ = 0;
+    bool stop_monitor_merge_thread_ = false;
     bool is_server_running_ = false;
     bool io_uring_enabled_ = false;
+    std::optional<bool> is_merge_monitor_started_;
+
+    android::base::unique_fd monitor_merge_event_fd_;
 
     std::mutex lock_;
 
     using HandlerList = std::vector<std::shared_ptr<UserSnapshotDmUserHandler>>;
     HandlerList dm_users_;
+    std::queue<std::shared_ptr<UserSnapshotDmUserHandler>> merge_handlers_;
 
     void AddWatchedFd(android::base::borrowed_fd fd, int events);
     void AcceptClient();
@@ -108,6 +119,8 @@
     bool IsTerminating() { return terminating_; }
 
     void RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler);
+    void MonitorMerge();
+
     void JoinAllThreads();
     bool StartWithSocket(bool start_listening);
 
@@ -118,8 +131,10 @@
     double GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock);
     void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
 
+    bool UpdateVerification(std::lock_guard<std::mutex>* proof_of_lock);
+
   public:
-    UserSnapshotServer() { terminating_ = false; }
+    UserSnapshotServer();
     ~UserSnapshotServer();
 
     bool Start(const std::string& socketname);
@@ -133,9 +148,11 @@
                                                           const std::string& backing_device,
                                                           const std::string& base_path_merge);
     bool StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
-    bool StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
+    bool StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
+                    const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
     std::string GetMergeStatus(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
 
+    void WakeupMonitorMergeThread();
     void SetTerminating() { terminating_ = true; }
     void ReceivedSocketSignal() { received_socket_signal_ = true; }
     void SetServerRunning() { is_server_running_ = true; }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
index d4e1d7c..28c9f68 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -165,6 +165,13 @@
 using namespace android::dm;
 using android::base::unique_fd;
 
+void SnapshotHandler::MonitorMerge() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        merge_monitored_ = true;
+    }
+}
+
 // This is invoked once primarily by update-engine to initiate
 // the merge
 void SnapshotHandler::InitiateMerge() {
@@ -361,10 +368,16 @@
 
 std::string SnapshotHandler::GetMergeStatus() {
     bool merge_not_initiated = false;
+    bool merge_monitored = false;
     bool merge_failed = false;
 
     {
         std::lock_guard<std::mutex> lock(lock_);
+
+        if (MergeMonitored()) {
+            merge_monitored = true;
+        }
+
         if (!MergeInitiated()) {
             merge_not_initiated = true;
         }
@@ -387,6 +400,12 @@
             return "snapshot-merge-complete";
         }
 
+        // Merge monitor thread is tracking the merge but the merge thread
+        // is not started yet.
+        if (merge_monitored) {
+            return "snapshot-merge";
+        }
+
         // Return the state as "snapshot". If the device was rebooted during
         // merge, we will return the status as "snapshot". This is ok, as
         // libsnapshot will explicitly resume the merge. This is slightly
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp
new file mode 100644
index 0000000..18c1dfc
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/scopeguard.h>
+#include <android-base/strings.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+UpdateVerify::UpdateVerify(const std::string& misc_name)
+    : misc_name_(misc_name), state_(UpdateVerifyState::VERIFY_UNKNOWN) {}
+
+bool UpdateVerify::CheckPartitionVerification() {
+    auto now = std::chrono::system_clock::now();
+    auto deadline = now + 10s;
+    {
+        std::unique_lock<std::mutex> cv_lock(m_lock_);
+        while (state_ == UpdateVerifyState::VERIFY_UNKNOWN) {
+            auto status = m_cv_.wait_until(cv_lock, deadline);
+            if (status == std::cv_status::timeout) {
+                return false;
+            }
+        }
+    }
+
+    return (state_ == UpdateVerifyState::VERIFY_SUCCESS);
+}
+
+void UpdateVerify::UpdatePartitionVerificationState(UpdateVerifyState state) {
+    {
+        std::lock_guard<std::mutex> lock(m_lock_);
+        state_ = state;
+    }
+    m_cv_.notify_all();
+}
+
+void UpdateVerify::VerifyUpdatePartition() {
+    bool succeeded = false;
+
+    auto scope_guard = android::base::make_scope_guard([this, &succeeded]() -> void {
+        if (!succeeded) {
+            UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_FAILED);
+        }
+    });
+
+    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.";
+        return;
+    }
+
+    const auto parts = android::base::Split(misc_name_, "-");
+    std::string partition_name = parts[0];
+
+    constexpr auto&& suffix_b = "_b";
+    constexpr auto&& 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;
+        return;
+    }
+
+    if (!VerifyPartition(partition_name, dm_block_devices.at(partition_name))) {
+        SNAP_LOG(ERROR) << "Partition: " << partition_name
+                        << " Block-device: " << dm_block_devices.at(partition_name)
+                        << " verification failed";
+    }
+    succeeded = true;
+}
+
+bool UpdateVerify::VerifyBlocks(const std::string& partition_name,
+                                const std::string& dm_block_device, off_t offset, int skip_blocks,
+                                uint64_t dev_sz) {
+    unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_DIRECT)));
+    if (fd < 0) {
+        SNAP_LOG(ERROR) << "open failed: " << dm_block_device;
+        return false;
+    }
+
+    loff_t file_offset = offset;
+    const uint64_t read_sz = kBlockSizeVerify;
+
+    void* addr;
+    ssize_t page_size = getpagesize();
+    if (posix_memalign(&addr, page_size, read_sz) < 0) {
+        SNAP_PLOG(ERROR) << "posix_memalign failed "
+                         << " page_size: " << page_size << " read_sz: " << read_sz;
+        return false;
+    }
+
+    std::unique_ptr<void, decltype(&::free)> buffer(addr, ::free);
+
+    uint64_t bytes_read = 0;
+
+    while (true) {
+        size_t to_read = std::min((dev_sz - file_offset), read_sz);
+
+        if (!android::base::ReadFullyAtOffset(fd.get(), buffer.get(), to_read, file_offset)) {
+            SNAP_PLOG(ERROR) << "Failed to read block from block device: " << dm_block_device
+                             << " partition-name: " << partition_name
+                             << " at offset: " << file_offset << " read-size: " << to_read
+                             << " block-size: " << dev_sz;
+            return false;
+        }
+
+        bytes_read += to_read;
+        file_offset += (skip_blocks * kBlockSizeVerify);
+        if (file_offset >= dev_sz) {
+            break;
+        }
+    }
+
+    SNAP_LOG(DEBUG) << "Verification success with bytes-read: " << bytes_read
+                    << " dev_sz: " << dev_sz << " partition_name: " << partition_name;
+
+    return true;
+}
+
+bool UpdateVerify::VerifyPartition(const std::string& partition_name,
+                                   const std::string& dm_block_device) {
+    android::base::Timer timer;
+
+    SNAP_LOG(INFO) << "VerifyPartition: " << partition_name << " Block-device: " << dm_block_device;
+
+    bool succeeded = false;
+    auto scope_guard = android::base::make_scope_guard([this, &succeeded]() -> void {
+        if (!succeeded) {
+            UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_FAILED);
+        }
+    });
+
+    unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_DIRECT)));
+    if (fd < 0) {
+        SNAP_LOG(ERROR) << "open failed: " << dm_block_device;
+        return false;
+    }
+
+    uint64_t dev_sz = get_block_device_size(fd.get());
+    if (!dev_sz) {
+        SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
+        return false;
+    }
+
+    if (!IsBlockAligned(dev_sz)) {
+        SNAP_LOG(ERROR) << "dev_sz: " << dev_sz << " is not block aligned";
+        return false;
+    }
+
+    /*
+     * Not all partitions are of same size. Some partitions are as small as
+     * 100Mb. We can just finish them in a single thread. For bigger partitions
+     * such as product, 4 threads are sufficient enough.
+     *
+     * TODO: With io_uring SQ_POLL support, we can completely cut this
+     * down to just single thread for all partitions and potentially verify all
+     * the partitions with zero syscalls. Additionally, since block layer
+     * supports polling, IO_POLL could be used which will further cut down
+     * latency.
+     */
+    int num_threads = kMinThreadsToVerify;
+    if (dev_sz > kThresholdSize) {
+        num_threads = kMaxThreadsToVerify;
+    }
+
+    std::vector<std::future<bool>> threads;
+    off_t start_offset = 0;
+    const int skip_blocks = num_threads;
+
+    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;
+        num_threads -= 1;
+        if (start_offset >= dev_sz) {
+            break;
+        }
+    }
+
+    bool ret = true;
+    for (auto& t : threads) {
+        ret = t.get() && ret;
+    }
+
+    if (ret) {
+        succeeded = true;
+        UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_SUCCESS);
+        SNAP_LOG(INFO) << "Partition: " << partition_name << " Block-device: " << dm_block_device
+                       << " Size: " << dev_sz
+                       << " verification success. Duration : " << timer.duration().count() << " ms";
+        return true;
+    }
+
+    return false;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 71fe124..b05123a 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -18,10 +18,12 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parsebool.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
+#include <liblp/property_fetcher.h>
 #include <openssl/sha.h>
 #include <payload_consumer/file_descriptor.h>
 
@@ -128,48 +130,6 @@
     return true;
 }
 
-bool WriteRandomData(ICowWriter* writer, std::string* hash) {
-    unique_fd rand(open("/dev/urandom", O_RDONLY));
-    if (rand < 0) {
-        PLOG(ERROR) << "open /dev/urandom";
-        return false;
-    }
-
-    SHA256_CTX ctx;
-    if (hash) {
-        SHA256_Init(&ctx);
-    }
-
-    if (!writer->options().max_blocks) {
-        LOG(ERROR) << "CowWriter must specify maximum number of blocks";
-        return false;
-    }
-    uint64_t num_blocks = writer->options().max_blocks.value();
-
-    size_t block_size = writer->options().block_size;
-    std::string block(block_size, '\0');
-    for (uint64_t i = 0; i < num_blocks; i++) {
-        if (!ReadFully(rand, block.data(), block.size())) {
-            PLOG(ERROR) << "read /dev/urandom";
-            return false;
-        }
-        if (!writer->AddRawBlocks(i, block.data(), block.size())) {
-            LOG(ERROR) << "Failed to add raw block " << i;
-            return false;
-        }
-        if (hash) {
-            SHA256_Update(&ctx, block.data(), block.size());
-        }
-    }
-
-    if (hash) {
-        uint8_t out[32];
-        SHA256_Final(out, &ctx);
-        *hash = ToHexString(out, sizeof(out));
-    }
-    return true;
-}
-
 std::string HashSnapshot(ISnapshotWriter* writer) {
     auto reader = writer->OpenReader();
     if (!reader) {
@@ -320,5 +280,38 @@
     return android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
 }
 
+SnapshotTestPropertyFetcher::SnapshotTestPropertyFetcher(
+        const std::string& slot_suffix, std::unordered_map<std::string, std::string>&& props)
+    : properties_(std::move(props)) {
+    properties_["ro.boot.slot_suffix"] = slot_suffix;
+    properties_["ro.boot.dynamic_partitions"] = "true";
+    properties_["ro.boot.dynamic_partitions_retrofit"] = "false";
+    properties_["ro.virtual_ab.enabled"] = "true";
+}
+
+std::string SnapshotTestPropertyFetcher::GetProperty(const std::string& key,
+                                                     const std::string& defaultValue) {
+    auto iter = properties_.find(key);
+    if (iter == properties_.end()) {
+        return android::base::GetProperty(key, defaultValue);
+    }
+    return iter->second;
+}
+
+bool SnapshotTestPropertyFetcher::GetBoolProperty(const std::string& key, bool defaultValue) {
+    auto iter = properties_.find(key);
+    if (iter == properties_.end()) {
+        return android::base::GetBoolProperty(key, defaultValue);
+    }
+    switch (android::base::ParseBool(iter->second)) {
+        case android::base::ParseBoolResult::kTrue:
+            return true;
+        case android::base::ParseBoolResult::kFalse:
+            return false;
+        default:
+            return defaultValue;
+    }
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index f01bec9..0a1be0d 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -26,6 +26,7 @@
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <fs_mgr/roots.h>
+#include <liblp/property_fetcher.h>
 
 using android::dm::kSectorSize;
 using android::fiemap::FiemapStatus;
@@ -33,6 +34,7 @@
 using android::fs_mgr::EnsurePathUnmounted;
 using android::fs_mgr::Fstab;
 using android::fs_mgr::GetEntryForPath;
+using android::fs_mgr::IPropertyFetcher;
 using android::fs_mgr::MetadataBuilder;
 using android::fs_mgr::Partition;
 using android::fs_mgr::ReadDefaultFstab;
@@ -184,16 +186,50 @@
     new_extent->set_num_blocks(num_blocks);
 }
 
-bool IsCompressionEnabled() {
-    return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
+bool GetLegacyCompressionEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.compression.enabled", false);
 }
 
-bool IsUserspaceSnapshotsEnabled() {
-    return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+bool GetUserspaceSnapshotsEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
 }
 
-bool IsIouringEnabled() {
-    return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
+bool CanUseUserspaceSnapshots() {
+    if (!GetUserspaceSnapshotsEnabledProperty()) {
+        return false;
+    }
+
+    auto fetcher = IPropertyFetcher::GetInstance();
+
+    const std::string UNKNOWN = "unknown";
+    const std::string vendor_release =
+            fetcher->GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN);
+
+    // 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 false;
+    }
+
+    if (IsDmSnapshotTestingEnabled()) {
+        LOG(INFO) << "Userspace snapshots disabled for testing";
+        return false;
+    }
+
+    return true;
+}
+
+bool GetIouringEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
+}
+
+bool GetXorCompressionEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.compression.xor.enabled", false);
 }
 
 std::string GetOtherPartitionName(const std::string& name) {
@@ -205,7 +241,8 @@
 }
 
 bool IsDmSnapshotTestingEnabled() {
-    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("snapuserd.test.dm.snapshots", false);
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index 0ef3234..16aa81a 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -129,15 +129,16 @@
 void AppendExtent(google::protobuf::RepeatedPtrField<chromeos_update_engine::Extent>* extents,
                   uint64_t start_block, uint64_t num_blocks);
 
-bool IsCompressionEnabled();
+bool GetLegacyCompressionEnabledProperty();
+bool GetUserspaceSnapshotsEnabledProperty();
+bool GetIouringEnabledProperty();
+bool GetXorCompressionEnabledProperty();
 
-bool IsUserspaceSnapshotsEnabled();
-
+bool CanUseUserspaceSnapshots();
 bool IsDmSnapshotTestingEnabled();
 
-bool IsIouringEnabled();
-
 // Swap the suffix of a partition name.
 std::string GetOtherPartitionName(const std::string& name);
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/tests/Android.bp b/fs_mgr/tests/Android.bp
index d82d566..fdc0d8e 100644
--- a/fs_mgr/tests/Android.bp
+++ b/fs_mgr/tests/Android.bp
@@ -52,9 +52,9 @@
     ],
 }
 
-cc_prebuilt_binary {
+sh_binary_host {
     name: "adb-remount-test.sh",
-    srcs: ["adb-remount-test.sh"],
+    src: "adb-remount-test.sh",
     target: {
         darwin: {
             enabled: false,
@@ -62,11 +62,7 @@
         windows: {
             enabled: false,
         },
-        android: {
-            enabled: false,
-        },
     },
-    host_supported: true,
 }
 
 sh_test {
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 9542bc1..91024d1 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -1319,26 +1319,9 @@
 B="`adb_cat /system/priv-app/hello`" ||
   die "system priv-app hello"
 check_eq "${A}" "${B}" /system/priv-app before reboot
-SYSTEM_DEVT=`adb_sh stat --format=%D /system/hello </dev/null`
-VENDOR_DEVT=`adb_sh stat --format=%D /vendor/hello </dev/null`
 SYSTEM_INO=`adb_sh stat --format=%i /system/hello </dev/null`
 VENDOR_INO=`adb_sh stat --format=%i /vendor/hello </dev/null`
-BASE_SYSTEM_DEVT=`adb_sh stat --format=%D /system/bin/stat </dev/null`
-BASE_VENDOR_DEVT=`adb_sh stat --format=%D /vendor/bin/stat </dev/null`
-check_eq "${SYSTEM_DEVT%[0-9a-fA-F][0-9a-fA-F]}" "${VENDOR_DEVT%[0-9a-fA-F][0-9a-fA-F]}" vendor and system devt
 check_ne "${SYSTEM_INO}" "${VENDOR_INO}" vendor and system inode
-if ${overlayfs_needed}; then
-  check_ne "${SYSTEM_DEVT}" "${BASE_SYSTEM_DEVT}" system devt
-  check_ne "${VENDOR_DEVT}" "${BASE_VENDOR_DEVT}" vendor devt
-else
-  check_eq "${SYSTEM_DEVT}" "${BASE_SYSTEM_DEVT}" system devt
-  check_eq "${VENDOR_DEVT}" "${BASE_VENDOR_DEVT}" vendor devt
-fi
-check_ne "${BASE_SYSTEM_DEVT}" "${BASE_VENDOR_DEVT}" --warning system/vendor devt
-[ -n "${SYSTEM_DEVT%[0-9a-fA-F][0-9a-fA-F]}" ] ||
-  echo "${YELLOW}[  WARNING ]${NORMAL} system devt ${SYSTEM_DEVT} major 0" >&2
-[ -n "${VENDOR_DEVT%[0-9a-fA-F][0-9a-fA-F]}" ] ||
-  echo "${YELLOW}[  WARNING ]${NORMAL} vendor devt ${VENDOR_DEVT} major 0" >&2
 
 # Download libc.so, append some garbage, push back, and check if the file
 # is updated.
@@ -1411,13 +1394,8 @@
   echo "${GREEN}[       OK ]${NORMAL} ${i} content remains after reboot" >&2
 done
 
-check_eq "${SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/hello </dev/null`" system devt after reboot
-check_eq "${VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/hello </dev/null`" vendor devt after reboot
 check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello </dev/null`" system inode after reboot
 check_eq "${VENDOR_INO}" "`adb_sh stat --format=%i /vendor/hello </dev/null`" vendor inode after reboot
-check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" --warning base system devt after reboot
-check_eq "${BASE_VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/bin/stat </dev/null`" --warning base vendor devt after reboot
-check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" --warning devt for su after reboot
 
 # Feed log with selinux denials as a result of overlays
 adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null || true
@@ -1542,10 +1520,7 @@
              --warning vendor content after flash vendor
   fi
 
-  check_eq "${SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/hello </dev/null`" system devt after reboot
   check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello </dev/null`" system inode after reboot
-  check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" --warning base system devt after reboot
-  check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" --warning devt for su after reboot
 
 fi
 
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 6c881c0..e33681c 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -1109,14 +1109,17 @@
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     std::string fstab_contents = R"fs(
+data   /data        f2fs    noatime     wait,latemount
 system /system      erofs   ro  wait,logical,first_stage_mount
 system /system      ext4    ro  wait,logical,first_stage_mount
 vendor /vendor      ext4    ro  wait,logical,first_stage_mount
-data   /data        f2fs    noatime     wait
 )fs";
 
     ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
 
+    // If GSI is installed, ReadFstabFromFile() would have called TransformFstabForDsu() implicitly.
+    // In other words, TransformFstabForDsu() would be called two times if running CTS-on-GSI,
+    // which implies TransformFstabForDsu() should be idempotent.
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
@@ -1124,21 +1127,23 @@
 
     auto entry = fstab.begin();
 
-    EXPECT_EQ("/system", entry->mount_point);
-    EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("/data", entry->mount_point);
+    EXPECT_EQ("userdata_gsi", entry->blk_device);
     entry++;
 
     EXPECT_EQ("/system", entry->mount_point);
     EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("erofs", entry->fs_type);
+    entry++;
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("ext4", entry->fs_type);
     entry++;
 
     EXPECT_EQ("/vendor", entry->mount_point);
     EXPECT_EQ("vendor", entry->blk_device);
     entry++;
-
-    EXPECT_EQ("/data", entry->mount_point);
-    EXPECT_EQ("userdata_gsi", entry->blk_device);
-    entry++;
 }
 
 TEST(fs_mgr, TransformFstabForDsu_synthesisExt4Entry) {
@@ -1147,7 +1152,7 @@
     std::string fstab_contents = R"fs(
 system /system      erofs   ro  wait,logical,first_stage_mount
 vendor /vendor      ext4    ro  wait,logical,first_stage_mount
-data   /data        f2fs    noatime     wait
+data   /data        f2fs    noatime     wait,latemount
 )fs";
 
     ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
@@ -1177,3 +1182,39 @@
     EXPECT_EQ("userdata_gsi", entry->blk_device);
     entry++;
 }
+
+TEST(fs_mgr, TransformFstabForDsu_synthesisAllMissingEntries) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    std::string fstab_contents = R"fs(
+data   /data        f2fs    noatime     wait,latemount
+vendor /vendor      ext4    ro  wait,logical,first_stage_mount
+)fs";
+
+    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+    Fstab fstab;
+    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
+    TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
+    ASSERT_EQ(4U, fstab.size());
+
+    auto entry = fstab.begin();
+
+    EXPECT_EQ("/data", entry->mount_point);
+    EXPECT_EQ("userdata_gsi", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/vendor", entry->mount_point);
+    EXPECT_EQ("vendor", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("ext4", entry->fs_type);
+    entry++;
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("erofs", entry->fs_type);
+    entry++;
+}
diff --git a/fs_mgr/tests/vts_fs_test.cpp b/fs_mgr/tests/vts_fs_test.cpp
index ae8e459..b8b34e2 100644
--- a/fs_mgr/tests/vts_fs_test.cpp
+++ b/fs_mgr/tests/vts_fs_test.cpp
@@ -23,6 +23,9 @@
 #include <gtest/gtest.h>
 #include <libdm/dm.h>
 
+using testing::Contains;
+using testing::Not;
+
 static int GetVsrLevel() {
     return android::base::GetIntProperty("ro.vendor.api_level", -1);
 }
@@ -117,3 +120,30 @@
     android::fs_mgr::Fstab fstab;
     EXPECT_FALSE(android::fs_mgr::ReadFstabFromDt(&fstab, false));
 }
+
+TEST(fs, NoLegacyVerifiedBoot) {
+    if (GetVsrLevel() < __ANDROID_API_T__) {
+        GTEST_SKIP();
+    }
+
+    const auto& default_fstab_path = android::fs_mgr::GetFstabPath();
+    EXPECT_FALSE(default_fstab_path.empty());
+
+    std::string fstab_str;
+    EXPECT_TRUE(android::base::ReadFileToString(default_fstab_path, &fstab_str,
+                                                /* follow_symlinks = */ true));
+
+    for (const auto& line : android::base::Split(fstab_str, "\n")) {
+        auto fields = android::base::Tokenize(line, " \t");
+        // Ignores empty lines and comments.
+        if (fields.empty() || android::base::StartsWith(fields.front(), '#')) {
+            continue;
+        }
+        // Each line in a fstab should have at least five entries.
+        //   <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
+        ASSERT_GE(fields.size(), 5);
+        EXPECT_THAT(android::base::Split(fields[4], ","), Not(Contains("verify")))
+                << "AVB 1.0 isn't supported now, but the 'verify' flag is found:\n"
+                << "  " << line;
+    }
+}
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index 9fe85d4..e305a86 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -308,30 +308,7 @@
         // If timeout and battery level is still not ready, draw unknown battery
     }
 
-    if (healthd_draw_ == nullptr) {
-        std::optional<bool> out_screen_on = configuration_->ChargerShouldKeepScreenOn();
-        if (out_screen_on.has_value()) {
-            if (!*out_screen_on) {
-                LOGV("[%" PRId64 "] leave screen off\n", now);
-                batt_anim_.run = false;
-                next_screen_transition_ = -1;
-                if (configuration_->ChargerIsOnline()) {
-                    RequestEnableSuspend();
-                }
-                return;
-            }
-        }
-
-        healthd_draw_ = HealthdDraw::Create(&batt_anim_);
-        if (healthd_draw_ == nullptr) return;
-
-#if !defined(__ANDROID_VNDK__)
-        if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
-            healthd_draw_->blank_screen(true, static_cast<int>(drm_));
-            screen_blanked_ = true;
-        }
-#endif
-    }
+    if (healthd_draw_ == nullptr) return;
 
     /* animation is over, blank screen and leave */
     if (batt_anim_.num_cycles > 0 && batt_anim_.cur_cycle == batt_anim_.num_cycles) {
@@ -736,6 +713,33 @@
     }
 }
 
+void Charger::InitHealthdDraw() {
+    if (healthd_draw_ == nullptr) {
+        std::optional<bool> out_screen_on = configuration_->ChargerShouldKeepScreenOn();
+        if (out_screen_on.has_value()) {
+            if (!*out_screen_on) {
+                LOGV("[%" PRId64 "] leave screen off\n", curr_time_ms());
+                batt_anim_.run = false;
+                next_screen_transition_ = -1;
+                if (configuration_->ChargerIsOnline()) {
+                    RequestEnableSuspend();
+                }
+                return;
+            }
+        }
+
+        healthd_draw_ = HealthdDraw::Create(&batt_anim_);
+        if (healthd_draw_ == nullptr) return;
+
+#if !defined(__ANDROID_VNDK__)
+        if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
+            healthd_draw_->blank_screen(true, static_cast<int>(drm_));
+            screen_blanked_ = true;
+        }
+#endif
+    }
+}
+
 void Charger::OnInit(struct healthd_config* config) {
     int ret;
     int i;
@@ -753,6 +757,7 @@
     }
 
     InitAnimation();
+    InitHealthdDraw();
 
     ret = CreateDisplaySurface(batt_anim_.fail_file, &surf_unknown_);
     if (ret < 0) {
diff --git a/healthd/include_charger/charger/healthd_mode_charger.h b/healthd/include_charger/charger/healthd_mode_charger.h
index 28e1fb5..82e4ddf 100644
--- a/healthd/include_charger/charger/healthd_mode_charger.h
+++ b/healthd/include_charger/charger/healthd_mode_charger.h
@@ -104,6 +104,7 @@
     void HandleInputState(int64_t now);
     void HandlePowerSupplyState(int64_t now);
     int InputCallback(int fd, unsigned int epevents);
+    void InitHealthdDraw();
     void InitAnimation();
     int RequestEnableSuspend();
     int RequestDisableSuspend();
diff --git a/init/Android.bp b/init/Android.bp
index dd67d04..856fe3e 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -164,6 +164,7 @@
         "libcgrouprc_format",
         "libfsverity_init",
         "liblmkd_utils",
+        "liblz4",
         "libmini_keyctl_static",
         "libmodprobe",
         "libprocinfo",
@@ -178,7 +179,6 @@
         "update_metadata-protos",
     ],
     shared_libs: [
-        "libbacktrace",
         "libbase",
         "libbootloader_message",
         "libcrypto",
@@ -195,6 +195,7 @@
         "libprocessgroup",
         "libprocessgroup_setup",
         "libselinux",
+        "libunwindstack",
         "libutils",
         "libziparchive",
     ],
@@ -202,6 +203,12 @@
     visibility: [":__subpackages__"],
 }
 
+cc_library_headers {
+    name: "libinit_headers",
+    export_include_dirs: ["."],
+    visibility: [":__subpackages__"],
+}
+
 cc_library_static {
     name: "libinit",
     recovery_available: true,
@@ -215,8 +222,8 @@
     ],
     whole_static_libs: [
         "libcap",
-        "com.android.sysprop.apex",
-        "com.android.sysprop.init",
+        "libcom.android.sysprop.apex",
+        "libcom.android.sysprop.init",
     ],
     header_libs: ["bootimg_headers"],
     proto: {
@@ -352,11 +359,11 @@
         "libgsi",
         "liblzma",
         "libunwindstack_no_dex",
-        "libbacktrace_no_dex",
         "libmodprobe",
         "libext2_uuid",
         "libprotobuf-cpp-lite",
         "libsnapshot_cow",
+        "liblz4",
         "libsnapshot_init",
         "update_metadata-protos",
         "libprocinfo",
diff --git a/init/README.md b/init/README.md
index 13c6ebd..fed81db 100644
--- a/init/README.md
+++ b/init/README.md
@@ -606,8 +606,12 @@
   group. If not provided, the directory is created with permissions 755 and
   owned by the root user and root group. If provided, the mode, owner and group
   will be updated if the directory exists already.
+  If the directory does not exist, it will receive the security context from
+  the current SELinux policy or its parent if not specified in the policy. If
+  the directory exists, its security context will not be changed (even if
+  different from the policy).
 
- > _action_ can be one of:
+  > _action_ can be one of:
   * `None`: take no encryption action; directory will be encrypted if parent is.
   * `Require`: encrypt directory, abort boot process if encryption fails
   * `Attempt`: try to set an encryption policy, but continue if it fails
diff --git a/init/TEST_MAPPING b/init/TEST_MAPPING
index fa1627c..36ca379 100644
--- a/init/TEST_MAPPING
+++ b/init/TEST_MAPPING
@@ -10,7 +10,7 @@
       "name": "MicrodroidHostTestCases"
     }
   ],
-  "hwasan-postsubmit": [
+  "hwasan-presubmit": [
     {
       "name": "CtsInitTestCases"
     },
diff --git a/init/action.h b/init/action.h
index 1534bf9..eddc384 100644
--- a/init/action.h
+++ b/init/action.h
@@ -22,6 +22,8 @@
 #include <variant>
 #include <vector>
 
+#include <android-base/strings.h>
+
 #include "builtins.h"
 #include "keyword_map.h"
 #include "result.h"
@@ -79,6 +81,7 @@
     static void set_function_map(const BuiltinFunctionMap* function_map) {
         function_map_ = function_map;
     }
+    bool IsFromApex() const { return base::StartsWith(filename_, "/apex/"); }
 
   private:
     void ExecuteCommand(const Command& command) const;
diff --git a/init/action_manager.h b/init/action_manager.h
index b6f93d9..2746a7c 100644
--- a/init/action_manager.h
+++ b/init/action_manager.h
@@ -37,6 +37,10 @@
     size_t CheckAllCommands();
 
     void AddAction(std::unique_ptr<Action> action);
+    template <class UnaryPredicate>
+    void RemoveActionIf(UnaryPredicate predicate) {
+        actions_.erase(std::remove_if(actions_.begin(), actions_.end(), predicate), actions_.end());
+    }
     void QueueEventTrigger(const std::string& trigger);
     void QueuePropertyChange(const std::string& name, const std::string& value);
     void QueueAllPropertyActions();
diff --git a/init/action_parser.cpp b/init/action_parser.cpp
index 52f6a1f..49fe24a 100644
--- a/init/action_parser.cpp
+++ b/init/action_parser.cpp
@@ -142,6 +142,14 @@
         action_subcontext = subcontext_;
     }
 
+    // We support 'on' for only Vendor APEXes from /{vendor, odm}.
+    // It is to prevent mainline modules from using 'on' triggers because events/properties are
+    // not stable for mainline modules.
+    // Note that this relies on Subcontext::PathMatchesSubcontext() to identify Vendor APEXes.
+    if (StartsWith(filename, "/apex/") && !action_subcontext) {
+        return Error() << "ParseSection() failed: 'on' is supported for only Vendor APEXes.";
+    }
+
     std::string event_trigger;
     std::map<std::string, std::string> property_triggers;
 
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 01db4f5..38f6f39 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -426,7 +426,7 @@
             return ErrnoError() << "fchmodat() failed on " << options.target;
         }
     }
-    if (fscrypt_is_native()) {
+    if (IsFbeEnabled()) {
         if (!FscryptSetDirectoryPolicy(ref_basename, options.fscrypt_action, options.target)) {
             return reboot_into_recovery(
                     {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + options.target});
@@ -1175,7 +1175,7 @@
     auto reboot = [reboot_reason, should_reboot_into_recovery](const std::string& message) {
         // TODO (b/122850122): support this in gsi
         if (should_reboot_into_recovery) {
-            if (fscrypt_is_native() && !android::gsi::IsGsiRunning()) {
+            if (IsFbeEnabled() && !android::gsi::IsGsiRunning()) {
                 LOG(ERROR) << message << ": Rebooting into recovery, reason: " << reboot_reason;
                 if (auto result = reboot_into_recovery(
                             {"--prompt_and_wipe_data", "--reason="s + reboot_reason});
@@ -1288,7 +1288,8 @@
         return Error() << "glob pattern '" << glob_pattern << "' failed";
     }
     std::vector<std::string> configs;
-    Parser parser = CreateServiceOnlyParser(ServiceList::GetInstance(), true);
+    Parser parser =
+            CreateApexConfigParser(ActionManager::GetInstance(), ServiceList::GetInstance());
     for (size_t i = 0; i < glob_result.gl_pathc; i++) {
         std::string path = glob_result.gl_pathv[i];
         // Filter-out /apex/<name>@<ver> paths. The paths are bind-mounted to
diff --git a/init/compare-bootcharts.py b/init/compare-bootcharts.py
index 2057b55..009b639 100755
--- a/init/compare-bootcharts.py
+++ b/init/compare-bootcharts.py
@@ -56,24 +56,24 @@
     ]
 
     jw = jiffy_record['jiffy_to_wallclock']
-    print "process: baseline experiment (delta)"
-    print " - Unit is ms (a jiffy is %d ms on the system)" % jw
-    print "------------------------------------"
+    print("process: baseline experiment (delta)")
+    print(" - Unit is ms (a jiffy is %d ms on the system)" % jw)
+    print("------------------------------------")
     for p in processes_of_interest:
         # e.g., 32-bit system doesn't have zygote64
         if p in process_map1 and p in process_map2:
-            print "%s: %d %d (%+d)" % (
+            print("%s: %d %d (%+d)" % (
                 p, process_map1[p]['start_time'] * jw,
                 process_map2[p]['start_time'] * jw,
                 (process_map2[p]['start_time'] -
-                 process_map1[p]['start_time']) * jw)
+                 process_map1[p]['start_time']) * jw))
 
     # Print the last tick for the bootanimation process
-    print "bootanimation ends at: %d %d (%+d)" % (
+    print("bootanimation ends at: %d %d (%+d)" % (
         process_map1['/system/bin/bootanimation']['last_tick'] * jw,
         process_map2['/system/bin/bootanimation']['last_tick'] * jw,
         (process_map2['/system/bin/bootanimation']['last_tick'] -
-            process_map1['/system/bin/bootanimation']['last_tick']) * jw)
+            process_map1['/system/bin/bootanimation']['last_tick']) * jw))
 
 def parse_proc_file(pathname, process_map, jiffy_record=None):
     # Uncompress bootchart.tgz
@@ -83,7 +83,7 @@
             f = tf.extractfile('proc_ps.log')
 
             # Break proc_ps into chunks based on timestamps
-            blocks = f.read().split('\n\n')
+            blocks = f.read().decode('utf-8').split('\n\n')
             for b in blocks:
                 lines = b.split('\n')
                 if not lines[0]:
@@ -133,7 +133,7 @@
 
 def main():
     if len(sys.argv) != 3:
-        print "Usage: %s base_bootchart_dir exp_bootchart_dir" % sys.argv[0]
+        print("Usage: %s base_bootchart_dir exp_bootchart_dir" % sys.argv[0])
         sys.exit(1)
 
     process_map1 = {}
diff --git a/init/devices.cpp b/init/devices.cpp
index d4a3cb9..28406f6 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -307,8 +307,8 @@
         PLOG(ERROR) << "setegid(" << gid << ") for " << path << " device failed";
         goto out;
     }
-    /* If the node already exists update its SELinux label to handle cases when
-     * it was created with the wrong context during coldboot procedure. */
+    /* If the node already exists update its SELinux label and the file mode to handle cases when
+     * it was created with the wrong context and file mode during coldboot procedure. */
     if (mknod(path.c_str(), mode, dev) && (errno == EEXIST) && !secontext.empty()) {
         char* fcon = nullptr;
         int rc = lgetfilecon(path.c_str(), &fcon);
@@ -330,6 +330,11 @@
             if (gid != s.st_gid) {
                 new_group = gid;
             }
+        if (mode != s.st_mode) {
+            if (chmod(path.c_str(), mode) != 0) {
+                PLOG(ERROR) << "Cannot chmod " << path << " to " << mode;
+            }
+        }
         } else {
             PLOG(ERROR) << "Cannot stat " << path;
         }
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index d050ed7..202a86a 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -119,19 +119,14 @@
 // Move snapuserd before switching root, so that it is available at the same path
 // after switching root.
 void PrepareSwitchRoot() {
-    constexpr const char* src = "/system/bin/snapuserd";
-    constexpr const char* dst = "/first_stage_ramdisk/system/bin/snapuserd";
+    static constexpr const auto& snapuserd = "/system/bin/snapuserd";
+    static constexpr const auto& snapuserd_ramdisk = "/system/bin/snapuserd_ramdisk";
+    static constexpr const auto& dst = "/first_stage_ramdisk/system/bin/snapuserd";
 
     if (access(dst, X_OK) == 0) {
         LOG(INFO) << dst << " already exists and it can be executed";
         return;
     }
-
-    if (access(src, F_OK) != 0) {
-        PLOG(INFO) << "Not moving " << src << " because it cannot be accessed";
-        return;
-    }
-
     auto dst_dir = android::base::Dirname(dst);
     std::error_code ec;
     if (access(dst_dir.c_str(), F_OK) != 0) {
@@ -139,7 +134,18 @@
             LOG(FATAL) << "Cannot create " << dst_dir << ": " << ec.message();
         }
     }
-    Copy(src, dst);
+
+    // prefer the generic ramdisk copy of snapuserd, because that's on system side of treble
+    // boundary, and therefore is more likely to be updated along with the Android platform.
+    // The vendor ramdisk copy might be under vendor freeze, or vendor might choose not to update
+    // it.
+    if (access(snapuserd_ramdisk, F_OK) == 0) {
+        LOG(INFO) << "Using generic ramdisk copy of snapuserd " << snapuserd_ramdisk;
+        Copy(snapuserd_ramdisk, dst);
+    } else if (access(snapuserd, F_OK) == 0) {
+        LOG(INFO) << "Using vendor ramdisk copy of snapuserd " << snapuserd;
+        Copy(snapuserd, dst);
+    }
 }
 }  // namespace
 
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 042988e..07ce458 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -507,16 +507,16 @@
         SaveRamdiskPathToSnapuserd();
     }
 
-    if (MountPartition(system_partition, false /* erase_same_mounts */)) {
-        if (dsu_not_on_userdata_ && fs_mgr_verity_is_check_at_most_once(*system_partition)) {
-            LOG(ERROR) << "check_most_at_once forbidden on external media";
-            return false;
-        }
-        SwitchRoot("/system");
-    } else {
+    if (!MountPartition(system_partition, false /* erase_same_mounts */)) {
         PLOG(ERROR) << "Failed to mount /system";
         return false;
     }
+    if (dsu_not_on_userdata_ && fs_mgr_verity_is_check_at_most_once(*system_partition)) {
+        LOG(ERROR) << "check_at_most_once forbidden on external media";
+        return false;
+    }
+
+    SwitchRoot("/system");
 
     return true;
 }
@@ -651,14 +651,9 @@
         return;
     }
 
-    std::string lp_names = "";
-    std::vector<std::string> dsu_partitions;
-    for (auto&& name : images->GetAllBackingImages()) {
-        dsu_partitions.push_back(name);
-        lp_names += name + ",";
-    }
-    // Publish the logical partition names for TransformFstabForDsu
-    WriteFile(gsi::kGsiLpNamesFile, lp_names);
+    // Publish the logical partition names for TransformFstabForDsu() and ReadFstabFromFile().
+    const auto dsu_partitions = images->GetAllBackingImages();
+    WriteFile(gsi::kGsiLpNamesFile, android::base::Join(dsu_partitions, ","));
     TransformFstabForDsu(&fstab_, active_dsu, dsu_partitions);
 }
 
diff --git a/init/init.cpp b/init/init.cpp
index 29f643e..be99a1c 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -51,16 +51,17 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <backtrace/Backtrace.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr_vendor_overlay.h>
 #include <keyutils.h>
 #include <libavb/libavb.h>
 #include <libgsi/libgsi.h>
 #include <libsnapshot/snapshot.h>
+#include <logwrap/logwrap.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/setup.h>
 #include <selinux/android.h>
+#include <unwindstack/AndroidUnwinder.h>
 
 #include "action_parser.h"
 #include "builtins.h"
@@ -88,6 +89,10 @@
 #include "system/core/init/property_service.pb.h"
 #include "util.h"
 
+#ifndef RECOVERY
+#include "com_android_apex.h"
+#endif  // RECOVERY
+
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 
@@ -256,12 +261,14 @@
 } shutdown_state;
 
 static void UnwindMainThreadStack() {
-    std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, 1));
-    if (!backtrace->Unwind(0)) {
-        LOG(ERROR) << __FUNCTION__ << "sys.powerctl: Failed to unwind callstack.";
+    unwindstack::AndroidLocalUnwinder unwinder;
+    unwindstack::AndroidUnwinderData data;
+    if (!unwinder.Unwind(data)) {
+        LOG(ERROR) << __FUNCTION__
+                   << "sys.powerctl: Failed to unwind callstack: " << data.GetErrorString();
     }
-    for (size_t i = 0; i < backtrace->NumFrames(); i++) {
-        LOG(ERROR) << "sys.powerctl: " << backtrace->FormatFrameData(i);
+    for (const auto& frame : data.frames) {
+        LOG(ERROR) << "sys.powerctl: " << unwinder.FormatFrame(frame);
     }
 }
 
@@ -294,13 +301,59 @@
     return parser;
 }
 
-// parser that only accepts new services
-Parser CreateServiceOnlyParser(ServiceList& service_list, bool from_apex) {
-    Parser parser;
+#ifndef RECOVERY
+template <typename T>
+struct LibXmlErrorHandler {
+    T handler_;
+    template <typename Handler>
+    LibXmlErrorHandler(Handler&& handler) : handler_(std::move(handler)) {
+        xmlSetGenericErrorFunc(nullptr, &ErrorHandler);
+    }
+    ~LibXmlErrorHandler() { xmlSetGenericErrorFunc(nullptr, nullptr); }
+    static void ErrorHandler(void*, const char* msg, ...) {
+        va_list args;
+        va_start(args, msg);
+        char* formatted;
+        if (vasprintf(&formatted, msg, args) >= 0) {
+            LOG(ERROR) << formatted;
+        }
+        free(formatted);
+        va_end(args);
+    }
+};
 
-    parser.AddSectionParser(
-            "service", std::make_unique<ServiceParser>(&service_list, GetSubcontext(), std::nullopt,
-                                                       from_apex));
+template <typename Handler>
+LibXmlErrorHandler(Handler&&) -> LibXmlErrorHandler<Handler>;
+#endif  // RECOVERY
+
+// Returns a Parser that accepts scripts from APEX modules. It supports `service` and `on`.
+Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list) {
+    Parser parser;
+    auto subcontext = GetSubcontext();
+#ifndef RECOVERY
+    if (subcontext) {
+        const auto apex_info_list_file = "/apex/apex-info-list.xml";
+        auto error_handler = LibXmlErrorHandler([&](const auto& error_message) {
+            LOG(ERROR) << "Failed to read " << apex_info_list_file << ":" << error_message;
+        });
+        const auto apex_info_list = com::android::apex::readApexInfoList(apex_info_list_file);
+        if (apex_info_list.has_value()) {
+            std::vector<std::string> subcontext_apexes;
+            for (const auto& info : apex_info_list->getApexInfo()) {
+                if (info.hasPreinstalledModulePath() &&
+                    subcontext->PathMatchesSubcontext(info.getPreinstalledModulePath())) {
+                    subcontext_apexes.push_back(info.getModuleName());
+                }
+            }
+            subcontext->SetApexList(std::move(subcontext_apexes));
+        }
+    }
+#endif  // RECOVERY
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, subcontext,
+                            std::nullopt));
+    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontext));
+
     return parser;
 }
 
@@ -393,6 +446,45 @@
     return {};
 }
 
+static Result<void> DoUnloadApex(const std::string& apex_name) {
+    std::string prop_name = "init.apex." + apex_name;
+    // TODO(b/232114573) remove services and actions read from the apex
+    // TODO(b/232799709) kill services from the apex
+    SetProperty(prop_name, "unloaded");
+    return {};
+}
+
+static Result<void> UpdateApexLinkerConfig(const std::string& apex_name) {
+    // Do not invoke linkerconfig when there's no bin/ in the apex.
+    const std::string bin_path = "/apex/" + apex_name + "/bin";
+    if (access(bin_path.c_str(), R_OK) != 0) {
+        return {};
+    }
+    const char* linkerconfig_binary = "/apex/com.android.runtime/bin/linkerconfig";
+    const char* linkerconfig_target = "/linkerconfig";
+    const char* arguments[] = {linkerconfig_binary, "--target", linkerconfig_target, "--apex",
+                               apex_name.c_str(),   "--strict"};
+
+    if (logwrap_fork_execvp(arraysize(arguments), arguments, nullptr, false, LOG_KLOG, false,
+                            nullptr) != 0) {
+        return ErrnoError() << "failed to execute linkerconfig";
+    }
+    LOG(INFO) << "Generated linker configuration for " << apex_name;
+    return {};
+}
+
+static Result<void> DoLoadApex(const std::string& apex_name) {
+    std::string prop_name = "init.apex." + apex_name;
+    // TODO(b/232799709) read .rc files from the apex
+
+    if (auto result = UpdateApexLinkerConfig(apex_name); !result.ok()) {
+        return result.error();
+    }
+
+    SetProperty(prop_name, "loaded");
+    return {};
+}
+
 enum class ControlTarget {
     SERVICE,    // function gets called for the named service
     INTERFACE,  // action gets called for every service that holds this interface
@@ -416,6 +508,17 @@
     return control_message_functions;
 }
 
+static Result<void> HandleApexControlMessage(std::string_view action, const std::string& name,
+                                             std::string_view message) {
+    if (action == "load") {
+        return DoLoadApex(name);
+    } else if (action == "unload") {
+        return DoUnloadApex(name);
+    } else {
+        return Error() << "Unknown control msg '" << message << "'";
+    }
+}
+
 static bool HandleControlMessage(std::string_view message, const std::string& name,
                                  pid_t from_pid) {
     std::string cmdline_path = StringPrintf("proc/%d/cmdline", from_pid);
@@ -427,8 +530,20 @@
         process_cmdline = "unknown process";
     }
 
-    Service* service = nullptr;
     auto action = message;
+    if (ConsumePrefix(&action, "apex_")) {
+        if (auto result = HandleApexControlMessage(action, name, message); !result.ok()) {
+            LOG(ERROR) << "Control message: Could not ctl." << message << " for '" << name
+                       << "' from pid: " << from_pid << " (" << process_cmdline
+                       << "): " << result.error();
+            return false;
+        }
+        LOG(INFO) << "Control message: Processed ctl." << message << " for '" << name
+                  << "' from pid: " << from_pid << " (" << process_cmdline << ")";
+        return true;
+    }
+
+    Service* service = nullptr;
     if (ConsumePrefix(&action, "interface_")) {
         service = ServiceList::GetInstance().FindInterface(name);
     } else {
diff --git a/init/init.h b/init/init.h
index 4f686cb..5220535 100644
--- a/init/init.h
+++ b/init/init.h
@@ -29,7 +29,7 @@
 namespace init {
 
 Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
-Parser CreateServiceOnlyParser(ServiceList& service_list, bool from_apex);
+Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list);
 
 bool start_waiting_for_property(const char *name, const char *value);
 
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 8550ec8..5651a83 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -35,6 +35,10 @@
 #include "util.h"
 
 using android::base::GetIntProperty;
+using android::base::GetProperty;
+using android::base::SetProperty;
+using android::base::WaitForProperty;
+using namespace std::literals;
 
 namespace android {
 namespace init {
@@ -42,34 +46,34 @@
 using ActionManagerCommand = std::function<void(ActionManager&)>;
 
 void TestInit(const std::string& init_script_file, const BuiltinFunctionMap& test_function_map,
-              const std::vector<ActionManagerCommand>& commands, ServiceList* service_list) {
-    ActionManager am;
-
+              const std::vector<ActionManagerCommand>& commands, ActionManager* action_manager,
+              ServiceList* service_list) {
     Action::set_function_map(&test_function_map);
 
     Parser parser;
     parser.AddSectionParser("service",
                             std::make_unique<ServiceParser>(service_list, nullptr, std::nullopt));
-    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
+    parser.AddSectionParser("on", std::make_unique<ActionParser>(action_manager, nullptr));
     parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
 
     ASSERT_TRUE(parser.ParseConfig(init_script_file));
 
     for (const auto& command : commands) {
-        command(am);
+        command(*action_manager);
     }
 
-    while (am.HasMoreCommands()) {
-        am.ExecuteOneCommand();
+    while (action_manager->HasMoreCommands()) {
+        action_manager->ExecuteOneCommand();
     }
 }
 
 void TestInitText(const std::string& init_script, const BuiltinFunctionMap& test_function_map,
-                  const std::vector<ActionManagerCommand>& commands, ServiceList* service_list) {
+                  const std::vector<ActionManagerCommand>& commands, ActionManager* action_manager,
+                  ServiceList* service_list) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     ASSERT_TRUE(android::base::WriteStringToFd(init_script, tf.fd));
-    TestInit(tf.path, test_function_map, commands, service_list);
+    TestInit(tf.path, test_function_map, commands, action_manager, service_list);
 }
 
 TEST(init, SimpleEventTrigger) {
@@ -91,8 +95,9 @@
     ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
     std::vector<ActionManagerCommand> commands{trigger_boot};
 
+    ActionManager action_manager;
     ServiceList service_list;
-    TestInitText(init_script, test_function_map, commands, &service_list);
+    TestInitText(init_script, test_function_map, commands, &action_manager, &service_list);
 
     EXPECT_TRUE(expect_true);
 }
@@ -154,8 +159,10 @@
     ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
     std::vector<ActionManagerCommand> commands{trigger_boot};
 
+    ActionManager action_manager;
     ServiceList service_list;
-    TestInitText(init_script, test_function_map, commands, &service_list);
+    TestInitText(init_script, test_function_map, commands, &action_manager, &service_list);
+    EXPECT_EQ(3, num_executed);
 }
 
 TEST(init, OverrideService) {
@@ -169,8 +176,9 @@
 
 )init";
 
+    ActionManager action_manager;
     ServiceList service_list;
-    TestInitText(init_script, BuiltinFunctionMap(), {}, &service_list);
+    TestInitText(init_script, BuiltinFunctionMap(), {}, &action_manager, &service_list);
     ASSERT_EQ(1, std::distance(service_list.begin(), service_list.end()));
 
     auto service = service_list.begin()->get();
@@ -236,13 +244,114 @@
     ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
     std::vector<ActionManagerCommand> commands{trigger_boot};
 
+    ActionManager action_manager;
     ServiceList service_list;
-
-    TestInit(start.path, test_function_map, commands, &service_list);
+    TestInit(start.path, test_function_map, commands, &action_manager, &service_list);
 
     EXPECT_EQ(6, num_executed);
 }
 
+BuiltinFunctionMap GetTestFunctionMapForLazyLoad(int& num_executed, ActionManager& action_manager) {
+    auto execute_command = [&num_executed](const BuiltinArguments& args) {
+        EXPECT_EQ(2U, args.size());
+        EXPECT_EQ(++num_executed, std::stoi(args[1]));
+        return Result<void>{};
+    };
+    auto load_command = [&action_manager](const BuiltinArguments& args) -> Result<void> {
+        EXPECT_EQ(2U, args.size());
+        Parser parser;
+        parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, nullptr));
+        if (!parser.ParseConfig(args[1])) {
+            return Error() << "Failed to load";
+        }
+        return Result<void>{};
+    };
+    auto trigger_command = [&action_manager](const BuiltinArguments& args) {
+        EXPECT_EQ(2U, args.size());
+        LOG(INFO) << "Queue event trigger: " << args[1];
+        action_manager.QueueEventTrigger(args[1]);
+        return Result<void>{};
+    };
+    BuiltinFunctionMap test_function_map = {
+            {"execute", {1, 1, {false, execute_command}}},
+            {"load", {1, 1, {false, load_command}}},
+            {"trigger", {1, 1, {false, trigger_command}}},
+    };
+    return test_function_map;
+}
+
+TEST(init, LazilyLoadedActionsCantBeTriggeredByTheSameTrigger) {
+    // "start" script loads "lazy" script. Even though "lazy" scripts
+    // defines "on boot" action, it's not executed by the current "boot"
+    // event because it's already processed.
+    TemporaryFile lazy;
+    ASSERT_TRUE(lazy.fd != -1);
+    ASSERT_TRUE(android::base::WriteStringToFd("on boot\nexecute 2", lazy.fd));
+
+    TemporaryFile start;
+    // clang-format off
+    std::string start_script = "on boot\n"
+                               "load " + std::string(lazy.path) + "\n"
+                               "execute 1";
+    // clang-format on
+    ASSERT_TRUE(android::base::WriteStringToFd(start_script, start.fd));
+
+    int num_executed = 0;
+    ActionManager action_manager;
+    ServiceList service_list;
+    BuiltinFunctionMap test_function_map =
+            GetTestFunctionMapForLazyLoad(num_executed, action_manager);
+
+    ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
+    std::vector<ActionManagerCommand> commands{trigger_boot};
+    TestInit(start.path, test_function_map, commands, &action_manager, &service_list);
+
+    EXPECT_EQ(1, num_executed);
+}
+
+TEST(init, LazilyLoadedActionsCanBeTriggeredByTheNextTrigger) {
+    // "start" script loads "lazy" script and then triggers "next" event
+    // which executes "on next" action loaded by the previous command.
+    TemporaryFile lazy;
+    ASSERT_TRUE(lazy.fd != -1);
+    ASSERT_TRUE(android::base::WriteStringToFd("on next\nexecute 2", lazy.fd));
+
+    TemporaryFile start;
+    // clang-format off
+    std::string start_script = "on boot\n"
+                               "load " + std::string(lazy.path) + "\n"
+                               "execute 1\n"
+                               "trigger next";
+    // clang-format on
+    ASSERT_TRUE(android::base::WriteStringToFd(start_script, start.fd));
+
+    int num_executed = 0;
+    ActionManager action_manager;
+    ServiceList service_list;
+    BuiltinFunctionMap test_function_map =
+            GetTestFunctionMapForLazyLoad(num_executed, action_manager);
+
+    ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
+    std::vector<ActionManagerCommand> commands{trigger_boot};
+    TestInit(start.path, test_function_map, commands, &action_manager, &service_list);
+
+    EXPECT_EQ(2, num_executed);
+}
+
+TEST(init, RespondToCtlApexMessages) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Skipping test, must be run as root.";
+        return;
+    }
+
+    std::string apex_name = "com.android.apex.cts.shim";
+    SetProperty("ctl.apex_unload", apex_name);
+    EXPECT_TRUE(WaitForProperty("init.apex." + apex_name, "unloaded", 10s));
+
+    SetProperty("ctl.apex_load", apex_name);
+    EXPECT_TRUE(WaitForProperty("init.apex." + apex_name, "loaded", 10s));
+}
+
 TEST(init, RejectsCriticalAndOneshotService) {
     if (GetIntProperty("ro.product.first_api_level", 10000) < 30) {
         GTEST_SKIP() << "Test only valid for devices launching with R or later";
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index bce1cc3..fead371 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -190,15 +190,33 @@
     return success;
 }
 
+// Switch the mount namespace of the current process from bootstrap to default OR from default to
+// bootstrap. If the current mount namespace is neither bootstrap nor default, keep it that way.
 Result<void> SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace) {
     if (IsRecoveryMode() || !IsApexUpdatable()) {
         // we don't have multiple namespaces in recovery mode or if apex is not updatable
         return {};
     }
-    const auto& ns_id = target_mount_namespace == NS_BOOTSTRAP ? bootstrap_ns_id : default_ns_id;
+
+    const std::string current_namespace_id = GetMountNamespaceId();
+    MountNamespace current_mount_namespace;
+    if (current_namespace_id == bootstrap_ns_id) {
+        current_mount_namespace = NS_BOOTSTRAP;
+    } else if (current_namespace_id == default_ns_id) {
+        current_mount_namespace = NS_DEFAULT;
+    } else {
+        // services with `namespace mnt` start in its own mount namespace. So we need to keep it.
+        return {};
+    }
+
+    // We're already in the target mount namespace.
+    if (current_mount_namespace == target_mount_namespace) {
+        return {};
+    }
+
     const auto& ns_fd = target_mount_namespace == NS_BOOTSTRAP ? bootstrap_ns_fd : default_ns_fd;
     const auto& ns_name = target_mount_namespace == NS_BOOTSTRAP ? "bootstrap" : "default";
-    if (ns_id != GetMountNamespaceId() && ns_fd.get() != -1) {
+    if (ns_fd.get() != -1) {
         if (setns(ns_fd.get(), CLONE_NEWNS) == -1) {
             return ErrnoError() << "Failed to switch to " << ns_name << " mount namespace.";
         }
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 9f7c215..7e92538 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -54,6 +54,7 @@
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/properties.h>
+#include <android-base/result.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <property_info_parser/property_info_parser.h>
@@ -76,9 +77,12 @@
 
 using namespace std::literals;
 
+using android::base::ErrnoError;
+using android::base::Error;
 using android::base::GetProperty;
 using android::base::ParseInt;
 using android::base::ReadFileToString;
+using android::base::Result;
 using android::base::Split;
 using android::base::StartsWith;
 using android::base::StringPrintf;
@@ -629,8 +633,8 @@
     return result;
 }
 
-static bool load_properties_from_file(const char*, const char*,
-                                      std::map<std::string, std::string>*);
+static Result<void> load_properties_from_file(const char*, const char*,
+                                              std::map<std::string, std::string>*);
 
 /*
  * Filter is used to decide which properties to load: NULL loads all keys,
@@ -691,7 +695,10 @@
                 continue;
             }
 
-            load_properties_from_file(expanded_filename->c_str(), key, properties);
+            if (auto res = load_properties_from_file(expanded_filename->c_str(), key, properties);
+                !res.ok()) {
+                LOG(WARNING) << res.error();
+            }
         } else {
             value = strchr(key, '=');
             if (!value) continue;
@@ -738,20 +745,19 @@
 
 // Filter is used to decide which properties to load: NULL loads all keys,
 // "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
-static bool load_properties_from_file(const char* filename, const char* filter,
-                                      std::map<std::string, std::string>* properties) {
+static Result<void> load_properties_from_file(const char* filename, const char* filter,
+                                              std::map<std::string, std::string>* properties) {
     Timer t;
     auto file_contents = ReadFile(filename);
     if (!file_contents.ok()) {
-        PLOG(WARNING) << "Couldn't load property file '" << filename
-                      << "': " << file_contents.error();
-        return false;
+        return Error() << "Couldn't load property file '" << filename
+                       << "': " << file_contents.error();
     }
     file_contents->push_back('\n');
 
     LoadProperties(file_contents->data(), filter, filename, properties);
     LOG(VERBOSE) << "(Loading properties from " << filename << " took " << t << ".)";
-    return true;
+    return {};
 }
 
 static void LoadPropertiesFromSecondStageRes(std::map<std::string, std::string>* properties) {
@@ -760,7 +766,9 @@
         CHECK(errno == ENOENT) << "Cannot access " << prop << ": " << strerror(errno);
         return;
     }
-    load_properties_from_file(prop.c_str(), nullptr, properties);
+    if (auto res = load_properties_from_file(prop.c_str(), nullptr, properties); !res.ok()) {
+        LOG(WARNING) << res.error();
+    }
 }
 
 // persist.sys.usb.config values can't be combined on build-time when property
@@ -1058,7 +1066,10 @@
     std::map<std::string, std::string> properties;
 
     if (IsRecoveryMode()) {
-        load_properties_from_file("/prop.default", nullptr, &properties);
+        if (auto res = load_properties_from_file("/prop.default", nullptr, &properties);
+            !res.ok()) {
+            LOG(ERROR) << res.error();
+        }
     }
 
     // /<part>/etc/build.prop is the canonical location of the build-time properties since S.
@@ -1067,7 +1078,7 @@
     const auto load_properties_from_partition = [&properties](const std::string& partition,
                                                               int support_legacy_path_until) {
         auto path = "/" + partition + "/etc/build.prop";
-        if (load_properties_from_file(path.c_str(), nullptr, &properties)) {
+        if (load_properties_from_file(path.c_str(), nullptr, &properties).ok()) {
             return;
         }
         // To read ro.<partition>.build.version.sdk, temporarily load the legacy paths into a
@@ -1105,7 +1116,13 @@
     // Order matters here. The more the partition is specific to a product, the higher its
     // precedence is.
     LoadPropertiesFromSecondStageRes(&properties);
-    load_properties_from_file("/system/build.prop", nullptr, &properties);
+
+    // system should have build.prop, unlike the other partitions
+    if (auto res = load_properties_from_file("/system/build.prop", nullptr, &properties);
+        !res.ok()) {
+        LOG(WARNING) << res.error();
+    }
+
     load_properties_from_partition("system_ext", /* support_legacy_path_until */ 30);
     load_properties_from_file("/system_dlkm/etc/build.prop", nullptr, &properties);
     // TODO(b/117892318): uncomment the following condition when vendor.imgs for aosp_* targets are
@@ -1121,7 +1138,10 @@
 
     if (access(kDebugRamdiskProp, R_OK) == 0) {
         LOG(INFO) << "Loading " << kDebugRamdiskProp;
-        load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
+        if (auto res = load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
+            !res.ok()) {
+            LOG(WARNING) << res.error();
+        }
     }
 
     for (const auto& [name, value] : properties) {
@@ -1331,6 +1351,11 @@
                 InitPropertySet(persistent_property_record.name(),
                                 persistent_property_record.value());
             }
+            // Apply debug ramdisk special settings after persistent properties are loaded.
+            if (android::base::GetBoolProperty("ro.force.debuggable", false)) {
+                // Always enable usb adb if device is booted with debug ramdisk.
+                update_sys_usb_config();
+            }
             InitPropertySet("ro.persistent_properties.ready", "true");
             persistent_properties_loaded = true;
             break;
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 41cf748..4e4bfd8 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -892,7 +892,16 @@
         sub_reason = "ns_switch";
         return Error() << "Failed to switch to bootstrap namespace";
     }
-    // Remove services that were defined in an APEX.
+    ActionManager::GetInstance().RemoveActionIf([](const auto& action) -> bool {
+        if (action->IsFromApex()) {
+            std::string trigger_name = action->BuildTriggersString();
+            LOG(INFO) << "Removing action (" << trigger_name << ") from (" << action->filename()
+                      << ":" << action->line() << ")";
+            return true;
+        }
+        return false;
+    });
+    // Remove services that were defined in an APEX
     ServiceList::GetInstance().RemoveServiceIf([](const std::unique_ptr<Service>& s) -> bool {
         if (s->is_from_apex()) {
             LOG(INFO) << "Removing service '" << s->name() << "' because it's defined in an APEX";
diff --git a/init/reboot_utils.cpp b/init/reboot_utils.cpp
index b3fa9fd..f8e1de0 100644
--- a/init/reboot_utils.cpp
+++ b/init/reboot_utils.cpp
@@ -26,8 +26,8 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
-#include <backtrace/Backtrace.h>
 #include <cutils/android_reboot.h>
+#include <unwindstack/AndroidUnwinder.h>
 
 #include "capabilities.h"
 #include "reboot_utils.h"
@@ -157,13 +157,13 @@
 
     // In the parent, let's try to get a backtrace then shutdown.
     LOG(ERROR) << __FUNCTION__ << ": signal " << signal_number;
-    std::unique_ptr<Backtrace> backtrace(
-            Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD));
-    if (!backtrace->Unwind(0)) {
-        LOG(ERROR) << __FUNCTION__ << ": Failed to unwind callstack.";
+    unwindstack::AndroidLocalUnwinder unwinder;
+    unwindstack::AndroidUnwinderData data;
+    if (!unwinder.Unwind(data)) {
+        LOG(ERROR) << __FUNCTION__ << ": Failed to unwind callstack: " << data.GetErrorString();
     }
-    for (size_t i = 0; i < backtrace->NumFrames(); i++) {
-        LOG(ERROR) << backtrace->FormatFrameData(i);
+    for (const auto& frame : data.frames) {
+        LOG(ERROR) << unwinder.FormatFrame(frame);
     }
     if (init_fatal_panic) {
         LOG(ERROR) << __FUNCTION__ << ": Trigger crash";
diff --git a/init/security.cpp b/init/security.cpp
index 970696e..0e9f6c2 100644
--- a/init/security.cpp
+++ b/init/security.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "security.h"
+#include "util.h"
 
 #include <errno.h>
 #include <fcntl.h>
@@ -89,7 +90,7 @@
 
 // Set /proc/sys/vm/mmap_rnd_bits and potentially
 // /proc/sys/vm/mmap_rnd_compat_bits to the maximum supported values.
-// Returns -1 if unable to set these to an acceptable value.
+// Returns an error if unable to set these to an acceptable value.
 //
 // To support this sysctl, the following upstream commits are needed:
 //
@@ -105,13 +106,20 @@
     // uml does not support mmap_rnd_bits
     return {};
 #elif defined(__aarch64__)
-    // arm64 supports 18 - 33 bits depending on pagesize and VA_SIZE
-    if (SetMmapRndBitsMin(33, 24, false) && SetMmapRndBitsMin(16, 16, true)) {
+    // arm64 architecture supports 18 - 33 rnd bits depending on pagesize and
+    // VA_SIZE. However the kernel might have been compiled with a narrower
+    // range using CONFIG_ARCH_MMAP_RND_BITS_MIN/MAX. To use the maximum
+    // supported number of bits, we start from the theoretical maximum of 33
+    // bits and try smaller values until we reach 24 bits which is the
+    // Android-specific minimum. Don't go lower even if the configured maximum
+    // is smaller than 24.
+    if (SetMmapRndBitsMin(33, 24, false) && (!Has32BitAbi() || SetMmapRndBitsMin(16, 16, true))) {
         return {};
     }
 #elif defined(__x86_64__)
-    // x86_64 supports 28 - 32 bits
-    if (SetMmapRndBitsMin(32, 32, false) && SetMmapRndBitsMin(16, 16, true)) {
+    // x86_64 supports 28 - 32 rnd bits, but Android wants to ensure that the
+    // theoretical maximum of 32 bits is always supported and used.
+    if (SetMmapRndBitsMin(32, 32, false) && (!Has32BitAbi() || SetMmapRndBitsMin(16, 16, true))) {
         return {};
     }
 #elif defined(__arm__) || defined(__i386__)
diff --git a/init/service.cpp b/init/service.cpp
index bd704cf..b36584b 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -131,13 +131,13 @@
 std::chrono::time_point<std::chrono::steady_clock> Service::exec_service_started_;
 
 Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
-                 const std::vector<std::string>& args, bool from_apex)
-    : Service(name, 0, 0, 0, {}, 0, "", subcontext_for_restart_commands, args, from_apex) {}
+                 const std::string& filename, const std::vector<std::string>& args)
+    : Service(name, 0, 0, 0, {}, 0, "", subcontext_for_restart_commands, filename, args) {}
 
 Service::Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
                  const std::vector<gid_t>& supp_gids, int namespace_flags,
                  const std::string& seclabel, Subcontext* subcontext_for_restart_commands,
-                 const std::vector<std::string>& args, bool from_apex)
+                 const std::string& filename, const std::vector<std::string>& args)
     : name_(name),
       classnames_({"default"}),
       flags_(flags),
@@ -157,7 +157,7 @@
       oom_score_adjust_(DEFAULT_OOM_SCORE_ADJUST),
       start_order_(0),
       args_(args),
-      from_apex_(from_apex) {}
+      filename_(filename) {}
 
 void Service::NotifyStateChange(const std::string& new_state) const {
     if ((flags_ & SVC_TEMPORARY) != 0) {
@@ -316,7 +316,9 @@
 #else
     static bool is_apex_updatable = false;
 #endif
-    const bool is_process_updatable = !use_bootstrap_ns_ && is_apex_updatable;
+    const bool use_default_mount_ns =
+            mount_namespace_.has_value() && *mount_namespace_ == NS_DEFAULT;
+    const bool is_process_updatable = use_default_mount_ns && is_apex_updatable;
 
     // If we crash > 4 times in 'fatal_crash_window_' minutes or before boot_completed,
     // reboot into bootloader or set crashing property
@@ -476,10 +478,9 @@
 }
 
 // Enters namespaces, sets environment variables, writes PID files and runs the service executable.
-void Service::RunService(const std::optional<MountNamespace>& override_mount_namespace,
-                         const std::vector<Descriptor>& descriptors,
+void Service::RunService(const std::vector<Descriptor>& descriptors,
                          std::unique_ptr<std::array<int, 2>, decltype(&ClosePipe)> pipefd) {
-    if (auto result = EnterNamespaces(namespaces_, name_, override_mount_namespace); !result.ok()) {
+    if (auto result = EnterNamespaces(namespaces_, name_, mount_namespace_); !result.ok()) {
         LOG(FATAL) << "Service '" << name_ << "' failed to set up namespaces: " << result.error();
     }
 
@@ -583,26 +584,9 @@
         scon = *result;
     }
 
-    // APEXd is always started in the "current" namespace because it is the process to set up
-    // the current namespace.
-    const bool is_apexd = args_[0] == "/system/bin/apexd";
-
-    if (!IsDefaultMountNamespaceReady() && !is_apexd) {
-        // If this service is started before APEXes and corresponding linker configuration
-        // get available, mark it as pre-apexd one. Note that this marking is
-        // permanent. So for example, if the service is re-launched (e.g., due
-        // to crash), it is still recognized as pre-apexd... for consistency.
-        use_bootstrap_ns_ = true;
-    }
-
-    // For pre-apexd services, override mount namespace as "bootstrap" one before starting.
-    // Note: "ueventd" is supposed to be run in "default" mount namespace even if it's pre-apexd
-    // to support loading firmwares from APEXes.
-    std::optional<MountNamespace> override_mount_namespace;
-    if (name_ == "ueventd") {
-        override_mount_namespace = NS_DEFAULT;
-    } else if (use_bootstrap_ns_) {
-        override_mount_namespace = NS_BOOTSTRAP;
+    if (!mount_namespace_.has_value()) {
+        // remember from which mount namespace the service should start
+        SetMountNamespace();
     }
 
     post_data_ = ServiceList::GetInstance().IsPostData();
@@ -635,7 +619,7 @@
 
     if (pid == 0) {
         umask(077);
-        RunService(override_mount_namespace, descriptors, std::move(pipefd));
+        RunService(descriptors, std::move(pipefd));
         _exit(127);
     }
 
@@ -686,6 +670,33 @@
     return {};
 }
 
+// Set mount namespace for the service.
+// The reason why remember the mount namespace:
+//   If this service is started before APEXes and corresponding linker configuration
+//   get available, mark it as pre-apexd one. Note that this marking is
+//   permanent. So for example, if the service is re-launched (e.g., due
+//   to crash), it is still recognized as pre-apexd... for consistency.
+void Service::SetMountNamespace() {
+    // APEXd is always started in the "current" namespace because it is the process to set up
+    // the current namespace. So, leave mount_namespace_ as empty.
+    if (args_[0] == "/system/bin/apexd") {
+        return;
+    }
+    // Services in the following list start in the "default" mount namespace.
+    // Note that they should use bootstrap bionic if they start before APEXes are ready.
+    static const std::set<std::string> kUseDefaultMountNamespace = {
+            "ueventd",           // load firmwares from APEXes
+            "hwservicemanager",  // load VINTF fragments from APEXes
+            "servicemanager",    // load VINTF fragments from APEXes
+    };
+    if (kUseDefaultMountNamespace.find(name_) != kUseDefaultMountNamespace.end()) {
+        mount_namespace_ = NS_DEFAULT;
+        return;
+    }
+    // Use the "default" mount namespace only if it's ready
+    mount_namespace_ = IsDefaultMountNamespaceReady() ? NS_DEFAULT : NS_BOOTSTRAP;
+}
+
 void Service::SetStartedInFirstStage(pid_t pid) {
     LOG(INFO) << "adding first-stage service '" << name_ << "'...";
 
@@ -851,7 +862,7 @@
     }
 
     return std::make_unique<Service>(name, flags, *uid, *gid, supp_gids, namespace_flags, seclabel,
-                                     nullptr, str_args, false);
+                                     nullptr, /*filename=*/"", str_args);
 }
 
 // This is used for snapuserd_proxy, which hands off a socket to snapuserd. It's
diff --git a/init/service.h b/init/service.h
index c314aa1..c14b312 100644
--- a/init/service.h
+++ b/init/service.h
@@ -32,6 +32,7 @@
 #include "action.h"
 #include "capabilities.h"
 #include "keyword_map.h"
+#include "mount_namespace.h"
 #include "parser.h"
 #include "service_utils.h"
 #include "subcontext.h"
@@ -65,12 +66,12 @@
 
   public:
     Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
-            const std::vector<std::string>& args, bool from_apex = false);
+            const std::string& filename, const std::vector<std::string>& args);
 
     Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
             const std::vector<gid_t>& supp_gids, int namespace_flags, const std::string& seclabel,
-            Subcontext* subcontext_for_restart_commands, const std::vector<std::string>& args,
-            bool from_apex = false);
+            Subcontext* subcontext_for_restart_commands, const std::string& filename,
+            const std::vector<std::string>& args);
 
     static Result<std::unique_ptr<Service>> MakeTemporaryOneshotService(
             const std::vector<std::string>& args);
@@ -133,7 +134,7 @@
     const std::vector<std::string>& args() const { return args_; }
     bool is_updatable() const { return updatable_; }
     bool is_post_data() const { return post_data_; }
-    bool is_from_apex() const { return from_apex_; }
+    bool is_from_apex() const { return base::StartsWith(filename_, "/apex/"); }
     void set_oneshot(bool value) {
         if (value) {
             flags_ |= SVC_ONESHOT;
@@ -152,10 +153,9 @@
     Result<void> CheckConsole();
     void ConfigureMemcg();
     void RunService(
-            const std::optional<MountNamespace>& override_mount_namespace,
             const std::vector<Descriptor>& descriptors,
             std::unique_ptr<std::array<int, 2>, void (*)(const std::array<int, 2>* pipe)> pipefd);
-
+    void SetMountNamespace();
     static unsigned long next_start_order_;
     static bool is_exec_service_running_;
     static std::chrono::time_point<std::chrono::steady_clock> exec_service_started_;
@@ -220,13 +220,13 @@
 
     std::vector<std::function<void(const siginfo_t& siginfo)>> reap_callbacks_;
 
-    bool use_bootstrap_ns_ = false;
+    std::optional<MountNamespace> mount_namespace_;
 
     bool post_data_ = false;
 
     std::optional<std::string> on_failure_reboot_target_;
 
-    bool from_apex_ = false;
+    std::string filename_;
 };
 
 }  // namespace init
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 9e914ee..32c57c4 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -647,7 +647,7 @@
         }
     }
 
-    service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args, from_apex_);
+    service_ = std::make_unique<Service>(name, restart_action_subcontext, filename, str_args);
     return {};
 }
 
diff --git a/init/service_parser.h b/init/service_parser.h
index 0fd2da5..54503dd 100644
--- a/init/service_parser.h
+++ b/init/service_parser.h
@@ -31,13 +31,11 @@
   public:
     ServiceParser(
             ServiceList* service_list, Subcontext* subcontext,
-            const std::optional<InterfaceInheritanceHierarchyMap>& interface_inheritance_hierarchy,
-            bool from_apex = false)
+            const std::optional<InterfaceInheritanceHierarchyMap>& interface_inheritance_hierarchy)
         : service_list_(service_list),
           subcontext_(subcontext),
           interface_inheritance_hierarchy_(interface_inheritance_hierarchy),
-          service_(nullptr),
-          from_apex_(from_apex) {}
+          service_(nullptr) {}
     Result<void> ParseSection(std::vector<std::string>&& args, const std::string& filename,
                               int line) override;
     Result<void> ParseLineSection(std::vector<std::string>&& args, int line) override;
@@ -92,7 +90,6 @@
     std::optional<InterfaceInheritanceHierarchyMap> interface_inheritance_hierarchy_;
     std::unique_ptr<Service> service_;
     std::string filename_;
-    bool from_apex_ = false;
 };
 
 }  // namespace init
diff --git a/init/service_test.cpp b/init/service_test.cpp
index 22ee844..87a2ce5 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -39,7 +39,7 @@
 
     std::vector<std::string> dummy_args{"/bin/test"};
     Service* service_in_old_memory =
-        new (old_memory) Service("test_old_memory", nullptr, dummy_args);
+        new (old_memory) Service("test_old_memory", nullptr, /*filename=*/"", dummy_args);
 
     EXPECT_EQ(0U, service_in_old_memory->flags());
     EXPECT_EQ(0, service_in_old_memory->pid());
@@ -58,7 +58,8 @@
     }
 
     Service* service_in_old_memory2 = new (old_memory) Service(
-            "test_old_memory", 0U, 0U, 0U, std::vector<gid_t>(), 0U, "", nullptr, dummy_args);
+            "test_old_memory", 0U, 0U, 0U, std::vector<gid_t>(), 0U, "",
+            nullptr, /*filename=*/"", dummy_args);
 
     EXPECT_EQ(0U, service_in_old_memory2->flags());
     EXPECT_EQ(0, service_in_old_memory2->pid());
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index 7aa4a9d..961e006 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -250,7 +250,11 @@
     Fork();
 }
 
-bool Subcontext::PathMatchesSubcontext(const std::string& path) {
+bool Subcontext::PathMatchesSubcontext(const std::string& path) const {
+    auto apex_name = GetApexNameFromFileName(path);
+    if (!apex_name.empty()) {
+        return std::find(apex_list_.begin(), apex_list_.end(), apex_name) != apex_list_.end();
+    }
     for (const auto& prefix : path_prefixes_) {
         if (StartsWith(path, prefix)) {
             return true;
@@ -259,6 +263,10 @@
     return false;
 }
 
+void Subcontext::SetApexList(std::vector<std::string>&& apex_list) {
+    apex_list_ = std::move(apex_list);
+}
+
 Result<SubcontextReply> Subcontext::TransmitMessage(const SubcontextCommand& subcontext_command) {
     if (auto result = SendMessage(socket_, subcontext_command); !result.ok()) {
         Restart();
@@ -370,6 +378,9 @@
 }
 
 void SubcontextTerminate() {
+    if (!subcontext) {
+        return;
+    }
     subcontext_terminated_by_shutdown = true;
     kill(subcontext->pid(), SIGTERM);
 }
diff --git a/init/subcontext.h b/init/subcontext.h
index cb4138e..8acc032 100644
--- a/init/subcontext.h
+++ b/init/subcontext.h
@@ -46,7 +46,8 @@
     Result<void> Execute(const std::vector<std::string>& args);
     Result<std::vector<std::string>> ExpandArgs(const std::vector<std::string>& args);
     void Restart();
-    bool PathMatchesSubcontext(const std::string& path);
+    bool PathMatchesSubcontext(const std::string& path) const;
+    void SetApexList(std::vector<std::string>&& apex_list);
 
     const std::string& context() const { return context_; }
     pid_t pid() const { return pid_; }
@@ -56,6 +57,7 @@
     Result<SubcontextReply> TransmitMessage(const SubcontextCommand& subcontext_command);
 
     std::vector<std::string> path_prefixes_;
+    std::vector<std::string> apex_list_;
     std::string context_;
     pid_t pid_;
     android::base::unique_fd socket_;
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index c6bf708..586e2cf 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -298,21 +298,31 @@
 
 static UeventdConfiguration GetConfiguration() {
     auto hardware = android::base::GetProperty("ro.hardware", "");
-    std::vector<std::string> legacy_paths{"/vendor/ueventd.rc", "/odm/ueventd.rc",
-                                          "/ueventd." + hardware + ".rc"};
+
+    struct LegacyPathInfo {
+        std::string legacy_path;
+        std::string preferred;
+    };
+    std::vector<LegacyPathInfo> legacy_paths{
+            {"/vendor/ueventd.rc", "/vendor/etc/ueventd.rc"},
+            {"/odm/ueventd.rc", "/odm/etc/ueventd.rc"},
+            {"/ueventd." + hardware + ".rc", "another ueventd.rc file"}};
 
     std::vector<std::string> canonical{"/system/etc/ueventd.rc"};
 
     if (android::base::GetIntProperty("ro.product.first_api_level", 10000) < __ANDROID_API_T__) {
         // TODO: Remove these legacy paths once Android S is no longer supported.
-        canonical.insert(canonical.end(), legacy_paths.begin(), legacy_paths.end());
+        for (const auto& info : legacy_paths) {
+            canonical.push_back(info.legacy_path);
+        }
     } else {
         // Warn if newer device is using legacy paths.
-        for (const auto& path : legacy_paths) {
-            if (access(path.c_str(), F_OK) == 0) {
+        for (const auto& info : legacy_paths) {
+            if (access(info.legacy_path.c_str(), F_OK) == 0) {
                 LOG(FATAL_WITHOUT_ABORT)
                         << "Legacy ueventd configuration file detected and will not be parsed: "
-                        << path;
+                        << info.legacy_path << ". Please move your configuration to "
+                        << info.preferred << " instead.";
             }
         }
     }
diff --git a/init/util.cpp b/init/util.cpp
index d1e518b..bfc3fb6 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -61,6 +61,8 @@
 
 const std::string kDefaultAndroidDtDir("/proc/device-tree/firmware/android/");
 
+const std::string kDataDirPrefix("/data/");
+
 void (*trigger_shutdown)(const std::string& command) = nullptr;
 
 // DecodeUid() - decodes and returns the given string, which can be either the
@@ -458,58 +460,34 @@
     return {};
 }
 
-static FscryptAction FscryptInferAction(const std::string& dir) {
-    const std::string prefix = "/data/";
-
-    if (!android::base::StartsWith(dir, prefix)) {
-        return FscryptAction::kNone;
-    }
-
-    // Special-case /data/media/obb per b/64566063
-    if (dir == "/data/media/obb") {
-        // Try to set policy on this directory, but if it is non-empty this may fail.
-        return FscryptAction::kAttempt;
-    }
-
-    // Only set policy on first level /data directories
-    // To make this less restrictive, consider using a policy file.
-    // However this is overkill for as long as the policy is simply
-    // to apply a global policy to all /data folders created via makedir
-    if (dir.find_first_of('/', prefix.size()) != std::string::npos) {
-        return FscryptAction::kNone;
-    }
-
-    // Special case various directories that must not be encrypted,
-    // often because their subdirectories must be encrypted.
-    // This isn't a nice way to do this, see b/26641735
-    std::vector<std::string> directories_to_exclude = {
-            "lost+found", "system_ce", "system_de", "misc_ce",     "misc_de",
-            "vendor_ce",  "vendor_de", "media",     "data",        "user",
-            "user_de",    "apex",      "preloads",  "app-staging", "gsi",
-    };
-    for (const auto& d : directories_to_exclude) {
-        if ((prefix + d) == dir) {
-            return FscryptAction::kNone;
+// Remove unnecessary slashes so that any later checks (e.g., the check for
+// whether the path is a top-level directory in /data) don't get confused.
+std::string CleanDirPath(const std::string& path) {
+    std::string result;
+    result.reserve(path.length());
+    // Collapse duplicate slashes, e.g. //data//foo// => /data/foo/
+    for (char c : path) {
+        if (c != '/' || result.empty() || result.back() != '/') {
+            result += c;
         }
     }
-    // Empty these directories if policy setting fails.
-    std::vector<std::string> wipe_on_failure = {
-            "rollback", "rollback-observer",  // b/139193659
-    };
-    for (const auto& d : wipe_on_failure) {
-        if ((prefix + d) == dir) {
-            return FscryptAction::kDeleteIfNecessary;
-        }
+    // Remove trailing slash, e.g. /data/foo/ => /data/foo
+    if (result.length() > 1 && result.back() == '/') {
+        result.pop_back();
     }
-    return FscryptAction::kRequire;
+    return result;
 }
 
 Result<MkdirOptions> ParseMkdir(const std::vector<std::string>& args) {
+    std::string path = CleanDirPath(args[1]);
+    const bool is_toplevel_data_dir =
+            StartsWith(path, kDataDirPrefix) &&
+            path.find_first_of('/', kDataDirPrefix.size()) == std::string::npos;
+    FscryptAction fscrypt_action =
+            is_toplevel_data_dir ? FscryptAction::kRequire : FscryptAction::kNone;
     mode_t mode = 0755;
     Result<uid_t> uid = -1;
     Result<gid_t> gid = -1;
-    FscryptAction fscrypt_inferred_action = FscryptInferAction(args[1]);
-    FscryptAction fscrypt_action = fscrypt_inferred_action;
     std::string ref_option = "ref";
     bool set_option_encryption = false;
     bool set_option_key = false;
@@ -574,24 +552,17 @@
     if (set_option_key && fscrypt_action == FscryptAction::kNone) {
         return Error() << "Key option set but encryption action is none";
     }
-    const std::string prefix = "/data/";
-    if (StartsWith(args[1], prefix) &&
-        args[1].find_first_of('/', prefix.size()) == std::string::npos) {
+    if (is_toplevel_data_dir) {
         if (!set_option_encryption) {
-            LOG(WARNING) << "Top-level directory needs encryption action, eg mkdir " << args[1]
+            LOG(WARNING) << "Top-level directory needs encryption action, eg mkdir " << path
                          << " <mode> <uid> <gid> encryption=Require";
         }
         if (fscrypt_action == FscryptAction::kNone) {
-            LOG(INFO) << "Not setting encryption policy on: " << args[1];
+            LOG(INFO) << "Not setting encryption policy on: " << path;
         }
     }
-    if (fscrypt_action != fscrypt_inferred_action) {
-        LOG(WARNING) << "Inferred action different from explicit one, expected "
-                     << static_cast<int>(fscrypt_inferred_action) << " but got "
-                     << static_cast<int>(fscrypt_action);
-    }
 
-    return MkdirOptions{args[1], mode, *uid, *gid, fscrypt_action, ref_option};
+    return MkdirOptions{path, mode, *uid, *gid, fscrypt_action, ref_option};
 }
 
 Result<MountAllOptions> ParseMountAll(const std::vector<std::string>& args) {
@@ -762,5 +733,20 @@
     return is_microdroid;
 }
 
+bool Has32BitAbi() {
+    static bool has = !android::base::GetProperty("ro.product.cpu.abilist32", "").empty();
+    return has;
+}
+
+std::string GetApexNameFromFileName(const std::string& path) {
+    static const std::string kApexDir = "/apex/";
+    if (StartsWith(path, kApexDir)) {
+        auto begin = kApexDir.size();
+        auto end = path.find('/', begin);
+        return path.substr(begin, end - begin);
+    }
+    return "";
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/util.h b/init/util.h
index bf53675..daec470 100644
--- a/init/util.h
+++ b/init/util.h
@@ -69,6 +69,7 @@
 
 bool IsLegalPropertyName(const std::string& name);
 Result<void> IsLegalPropertyValue(const std::string& name, const std::string& value);
+std::string CleanDirPath(const std::string& path);
 
 struct MkdirOptions {
     std::string target;
@@ -105,5 +106,8 @@
 void SetDefaultMountNamespaceReady();
 
 bool IsMicrodroid();
+bool Has32BitAbi();
+
+std::string GetApexNameFromFileName(const std::string& path);
 }  // namespace init
 }  // namespace android
diff --git a/init/util_test.cpp b/init/util_test.cpp
index 565e7d4..e8144c3 100644
--- a/init/util_test.cpp
+++ b/init/util_test.cpp
@@ -170,5 +170,18 @@
     EXPECT_TRUE(is_dir(path1.c_str()));
 }
 
+TEST(util, CleanDirPath) {
+    EXPECT_EQ("", CleanDirPath(""));
+    EXPECT_EQ("/", CleanDirPath("/"));
+    EXPECT_EQ("/", CleanDirPath("//"));
+    EXPECT_EQ("/foo", CleanDirPath("/foo"));
+    EXPECT_EQ("/foo", CleanDirPath("//foo"));
+    EXPECT_EQ("/foo", CleanDirPath("/foo/"));
+    EXPECT_EQ("/foo/bar", CleanDirPath("/foo/bar"));
+    EXPECT_EQ("/foo/bar", CleanDirPath("/foo/bar/"));
+    EXPECT_EQ("/foo/bar", CleanDirPath("/foo/bar////"));
+    EXPECT_EQ("/foo/bar", CleanDirPath("//foo//bar"));
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/libcutils/TEST_MAPPING b/libcutils/TEST_MAPPING
index cca7d93..6477502 100644
--- a/libcutils/TEST_MAPPING
+++ b/libcutils/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "libcutils_test"
     }
   ],
-  "hwasan-postsubmit": [
+  "hwasan-presubmit": [
     {
       "name": "libcutils_test"
     }
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index 98ae0d4..3867f34 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -214,6 +214,7 @@
  * provided, which is the name of the row where this async event should be
  * recorded. The track name, name, and cookie used to begin an event must be
  * used to end it.
+ * The cookie here must be unique on the track_name level, not the name level.
  */
 #define ATRACE_ASYNC_FOR_TRACK_BEGIN(track_name, name, cookie) \
     atrace_async_for_track_begin(ATRACE_TAG, track_name, name, cookie)
@@ -229,13 +230,13 @@
  * Trace the end of an asynchronous event.
  * This should correspond to a previous ATRACE_ASYNC_FOR_TRACK_BEGIN.
  */
-#define ATRACE_ASYNC_FOR_TRACK_END(track_name, name, cookie) \
-    atrace_async_for_track_end(ATRACE_TAG, track_name, name, cookie)
+#define ATRACE_ASYNC_FOR_TRACK_END(track_name, cookie) \
+    atrace_async_for_track_end(ATRACE_TAG, track_name, cookie)
 static inline void atrace_async_for_track_end(uint64_t tag, const char* track_name,
-                                              const char* name, int32_t cookie) {
+                                              int32_t cookie) {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
-        void atrace_async_for_track_end_body(const char*, const char*, int32_t);
-        atrace_async_for_track_end_body(track_name, name, cookie);
+        void atrace_async_for_track_end_body(const char*, int32_t);
+        atrace_async_for_track_end_body(track_name, cookie);
     }
 }
 
diff --git a/libcutils/trace-container.cpp b/libcutils/trace-container.cpp
index 6202662..eae6155 100644
--- a/libcutils/trace-container.cpp
+++ b/libcutils/trace-container.cpp
@@ -240,15 +240,15 @@
     WRITE_MSG("G|%d|", "|%" PRId32, track_name, name, cookie);
 }
 
-void atrace_async_for_track_end_body(const char* track_name, const char* name, int32_t cookie) {
+void atrace_async_for_track_end_body(const char* track_name, int32_t cookie) {
     if (CC_LIKELY(atrace_use_container_sock)) {
-        WRITE_MSG_IN_CONTAINER("H", "|", "|%d", track_name, name, cookie);
+        WRITE_MSG_IN_CONTAINER("H", "|", "|%d", "", track_name, cookie);
         return;
     }
 
     if (atrace_marker_fd < 0) return;
 
-    WRITE_MSG("H|%d|", "|%" PRId32, track_name, name, cookie);
+    WRITE_MSG("H|%d|", "|%" PRId32, "", track_name, cookie);
 }
 
 void atrace_instant_body(const char* name) {
diff --git a/libcutils/trace-dev.cpp b/libcutils/trace-dev.cpp
index e9583fb..1827e32 100644
--- a/libcutils/trace-dev.cpp
+++ b/libcutils/trace-dev.cpp
@@ -93,8 +93,8 @@
     WRITE_MSG("G|%d|", "|%" PRId32, track_name, name, cookie);
 }
 
-void atrace_async_for_track_end_body(const char* track_name, const char* name, int32_t cookie) {
-    WRITE_MSG("H|%d|", "|%" PRId32, track_name, name, cookie);
+void atrace_async_for_track_end_body(const char* track_name, int32_t cookie) {
+    WRITE_MSG("H|%d|", "|%" PRId32, "", track_name, cookie);
 }
 
 void atrace_instant_body(const char* name) {
diff --git a/libcutils/trace-dev_test.cpp b/libcutils/trace-dev_test.cpp
index d4a907d..3dea5ff 100644
--- a/libcutils/trace-dev_test.cpp
+++ b/libcutils/trace-dev_test.cpp
@@ -306,112 +306,52 @@
 }
 
 TEST_F(TraceDevTest, atrace_async_for_track_end_body_normal) {
-    atrace_async_for_track_end_body("fake_track", "fake_name", 12345);
+    atrace_async_for_track_end_body("fake_track", 12345);
 
     ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
 
     std::string actual;
     ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
-    std::string expected = android::base::StringPrintf("H|%d|fake_track|fake_name|12345", getpid());
+    std::string expected = android::base::StringPrintf("H|%d|fake_track|12345", getpid());
     ASSERT_STREQ(expected.c_str(), actual.c_str());
 }
 
-TEST_F(TraceDevTest, atrace_async_for_track_end_body_exact_track_name) {
-    const int name_size = 5;
+TEST_F(TraceDevTest, atrace_async_for_track_end_body_exact) {
     std::string expected = android::base::StringPrintf("H|%d|", getpid());
     std::string track_name =
-            MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - name_size - 6);
-    atrace_async_for_track_end_body(track_name.c_str(), "name", 12345);
+            MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 7);
+    atrace_async_for_track_end_body(track_name.c_str(), 12345);
 
     ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
     ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
 
     std::string actual;
     ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
-    expected += track_name + "|name|12345";
+    expected += track_name + "|12345";
     ASSERT_STREQ(expected.c_str(), actual.c_str());
 
-    // Add a single character and verify name truncation
+    // Add a single character and verify we get the exact same value as before.
     ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
     track_name += '*';
-    expected = android::base::StringPrintf("H|%d|", getpid());
-    expected += track_name + "|nam|12345";
-    atrace_async_for_track_end_body(track_name.c_str(), "name", 12345);
+    atrace_async_for_track_end_body(track_name.c_str(), 12345);
     EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
     ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
     ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
     ASSERT_STREQ(expected.c_str(), actual.c_str());
 }
 
-TEST_F(TraceDevTest, atrace_async_for_track_end_body_truncated_track_name) {
+TEST_F(TraceDevTest, atrace_async_for_track_end_body_truncated) {
     std::string expected = android::base::StringPrintf("H|%d|", getpid());
     std::string track_name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
-    atrace_async_for_track_end_body(track_name.c_str(), "name", 12345);
+    atrace_async_for_track_end_body(track_name.c_str(), 12345);
 
     ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
     ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
 
     std::string actual;
     ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
-    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 9;
-    expected += android::base::StringPrintf("%.*s|n|12345", expected_len, track_name.c_str());
-    ASSERT_STREQ(expected.c_str(), actual.c_str());
-}
-
-TEST_F(TraceDevTest, atrace_async_for_track_end_body_exact_name) {
-    const int track_name_size = 11;
-    std::string expected = android::base::StringPrintf("H|%d|", getpid());
-    std::string name =
-            MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - track_name_size - 6);
-    atrace_async_for_track_end_body("track_name", name.c_str(), 12345);
-
-    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
-    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
-
-    std::string actual;
-    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
-    expected += "track_name|" + name + "|12345";
-    ASSERT_STREQ(expected.c_str(), actual.c_str());
-
-    // Add a single character and verify we get the same value as before.
-    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
-    name += '*';
-    atrace_async_for_track_end_body("track_name", name.c_str(), 12345);
-    EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
-    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
-    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
-    ASSERT_STREQ(expected.c_str(), actual.c_str());
-}
-
-TEST_F(TraceDevTest, atrace_async_for_track_end_body_truncated_name) {
-    std::string expected = android::base::StringPrintf("H|%d|track_name|", getpid());
-    std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
-    atrace_async_for_track_end_body("track_name", name.c_str(), 12345);
-
-    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
-    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
-
-    std::string actual;
-    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
-    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 1 - 6;
-    expected += android::base::StringPrintf("%.*s|12345", expected_len, name.c_str());
-    ASSERT_STREQ(expected.c_str(), actual.c_str());
-}
-
-TEST_F(TraceDevTest, atrace_async_for_track_end_body_truncated_both) {
-    std::string expected = android::base::StringPrintf("H|%d|", getpid());
-    std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
-    std::string track_name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
-    atrace_async_for_track_end_body(track_name.c_str(), name.c_str(), 12345);
-
-    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
-    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
-
-    std::string actual;
-    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
-    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 3 - 6;
-    expected += android::base::StringPrintf("%.*s|%.1s|12345", expected_len, track_name.c_str(),
-                                            name.c_str());
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 7;
+    expected += android::base::StringPrintf("%.*s|12345", expected_len, track_name.c_str());
     ASSERT_STREQ(expected.c_str(), actual.c_str());
 }
 
diff --git a/libcutils/trace-host.cpp b/libcutils/trace-host.cpp
index c2a379b..e9f58c3 100644
--- a/libcutils/trace-host.cpp
+++ b/libcutils/trace-host.cpp
@@ -30,8 +30,7 @@
 void atrace_async_end_body(const char* /*name*/, int32_t /*cookie*/) {}
 void atrace_async_for_track_begin_body(const char* /*track_name*/, const char* /*name*/,
                                        int32_t /*cookie*/) {}
-void atrace_async_for_track_end_body(const char* /*track_name*/, const char* /*name*/,
-                                     int32_t /*cookie*/) {}
+void atrace_async_for_track_end_body(const char* /*track_name*/, int32_t /*cookie*/) {}
 void atrace_instant_body(const char* /*name*/) {}
 void atrace_instant_for_track_body(const char* /*track_name*/, const char* /*name*/) {}
 void atrace_int_body(const char* /*name*/, int32_t /*value*/) {}
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 3054d2b..b2ace34 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -440,12 +440,11 @@
 }
 
 // Another option to load kernel modules. load in independent modules in parallel
-// and then load modules which only have soft dependency, third update dependency list of other
-// remaining modules, repeat these steps until all modules are loaded.
+// and then update dependency list of other remaining modules, repeat these steps
+// until all modules are loaded.
 bool Modprobe::LoadModulesParallel(int num_threads) {
     bool ret = true;
     std::map<std::string, std::set<std::string>> mod_with_deps;
-    std::map<std::string, std::set<std::string>> mod_with_softdeps;
 
     // Get dependencies
     for (const auto& module : module_load_) {
@@ -458,26 +457,33 @@
 
     // Get soft dependencies
     for (const auto& [it_mod, it_softdep] : module_pre_softdep_) {
-        mod_with_softdeps[MakeCanonical(it_mod)].emplace(it_softdep);
+        if (mod_with_deps.find(MakeCanonical(it_softdep)) != mod_with_deps.end()) {
+            mod_with_deps[MakeCanonical(it_mod)].emplace(
+                GetDependencies(MakeCanonical(it_softdep))[0]);
+        }
     }
 
     // Get soft post dependencies
     for (const auto& [it_mod, it_softdep] : module_post_softdep_) {
-        mod_with_softdeps[MakeCanonical(it_mod)].emplace(it_softdep);
+        if (mod_with_deps.find(MakeCanonical(it_softdep)) != mod_with_deps.end()) {
+            mod_with_deps[MakeCanonical(it_softdep)].emplace(
+                GetDependencies(MakeCanonical(it_mod))[0]);
+        }
     }
 
     while (!mod_with_deps.empty()) {
         std::vector<std::thread> threads;
         std::vector<std::string> mods_path_to_load;
-        std::vector<std::string> mods_with_softdep_to_load;
         std::mutex vector_lock;
 
-        // Find independent modules and modules only having soft dependencies
+        // Find independent modules
         for (const auto& [it_mod, it_dep] : mod_with_deps) {
-            if (it_dep.size() == 1 && mod_with_softdeps[it_mod].empty()) {
-                mods_path_to_load.emplace_back(*(it_dep.begin()));
-            } else if (it_dep.size() == 1) {
-                mods_with_softdep_to_load.emplace_back(it_mod);
+            if (it_dep.size() == 1) {
+                if (module_options_[it_mod].find("load_sequential=1") != std::string::npos) {
+                    LoadWithAliases(it_mod, true);
+                } else {
+                    mods_path_to_load.emplace_back(*(it_dep.begin()));
+                }
             }
         }
 
@@ -502,21 +508,10 @@
             thread.join();
         }
 
-        // Since we cannot assure if these soft dependencies tree are overlap,
-        // we loaded these modules one by one.
-        for (auto dep = mods_with_softdep_to_load.rbegin(); dep != mods_with_softdep_to_load.rend();
-             dep++) {
-            ret &= LoadWithAliases(*dep, true);
-        }
-
         std::lock_guard guard(module_loaded_lock_);
         // Remove loaded module form mod_with_deps and soft dependencies of other modules
         for (const auto& module_loaded : module_loaded_) {
             mod_with_deps.erase(module_loaded);
-
-            for (auto& [mod, softdeps] : mod_with_softdeps) {
-                softdeps.erase(module_loaded);
-            }
         }
 
         // Remove loaded module form dependencies of other modules which are not loaded yet
diff --git a/libnetutils/dhcpclient.c b/libnetutils/dhcpclient.c
index 11c116a..ad6898c 100644
--- a/libnetutils/dhcpclient.c
+++ b/libnetutils/dhcpclient.c
@@ -45,7 +45,7 @@
 
 typedef unsigned long long msecs_t;
 #if VERBOSE
-void dump_dhcp_msg();
+void dump_dhcp_msg(dhcp_msg *msg, int len);
 #endif
 
 msecs_t get_msecs(void)
diff --git a/libpackagelistparser/TEST_MAPPING b/libpackagelistparser/TEST_MAPPING
index d69a7fb..f4a7761 100644
--- a/libpackagelistparser/TEST_MAPPING
+++ b/libpackagelistparser/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "libpackagelistparser_test"
     }
   ],
-  "hwasan-postsubmit": [
+  "hwasan-presubmit": [
     {
       "name": "libpackagelistparser_test"
     }
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 267e62c..51c810e 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -187,10 +187,6 @@
     auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
     auto uid_path = ConvertUidToPath(cgroup, uid);
 
-    if (retries == 0) {
-        retries = 1;
-    }
-
     while (retries--) {
         ret = rmdir(uid_pid_path.c_str());
         if (!ret || errno != EBUSY) break;
@@ -463,12 +459,13 @@
                       << " in " << static_cast<int>(ms) << "ms";
         }
 
-        int err = RemoveProcessGroup(cgroup, uid, initialPid, retries);
+        // 400 retries correspond to 2 secs max timeout
+        int err = RemoveProcessGroup(cgroup, uid, initialPid, 400);
 
         if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
             std::string memcg_apps_path;
             if (CgroupGetMemcgAppsPath(&memcg_apps_path) &&
-                RemoveProcessGroup(memcg_apps_path.c_str(), uid, initialPid, retries) < 0) {
+                RemoveProcessGroup(memcg_apps_path.c_str(), uid, initialPid, 400) < 0) {
                 return -1;
             }
         }
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 4092c1a..8589a8d 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -647,7 +647,7 @@
       "Profiles": [ "ServicePerformance", "LowIoPriority", "TimerSlackNormal" ]
     },
     {
-      "Name": "VMCompilationPerformance",
+      "Name": "SCHED_SP_COMPUTE",
       "Profiles": [ "HighPerformance", "ProcessCapacityHigh", "LowIoPriority", "TimerSlackNormal" ]
     },
     {
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 3831ef2..304248a 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -410,7 +410,7 @@
     // Make sure we do this only one time. No need for std::call_once because
     // init is a single-threaded process
     if (access(CGROUPS_RC_PATH, F_OK) == 0) {
-        LOG(WARNING) << "Attempt to call SetupCgroups more than once";
+        LOG(WARNING) << "Attempt to call CgroupSetup() more than once";
         return true;
     }
 
diff --git a/libsparse/img2simg.cpp b/libsparse/img2simg.cpp
index 3e24cc0..51580f7 100644
--- a/libsparse/img2simg.cpp
+++ b/libsparse/img2simg.cpp
@@ -38,24 +38,41 @@
 #endif
 
 void usage() {
-  fprintf(stderr, "Usage: img2simg <raw_image_file> <sparse_image_file> [<block_size>]\n");
+  fprintf(stderr, "Usage: img2simg [-s] <raw_image_file> <sparse_image_file> [<block_size>]\n");
 }
 
 int main(int argc, char* argv[]) {
+  char *arg_in;
+  char *arg_out;
+  enum sparse_read_mode mode = SPARSE_READ_MODE_NORMAL;
+  int extra;
   int in;
+  int opt;
   int out;
   int ret;
   struct sparse_file* s;
   unsigned int block_size = 4096;
   off64_t len;
 
-  if (argc < 3 || argc > 4) {
+  while ((opt = getopt(argc, argv, "s")) != -1) {
+    switch (opt) {
+      case 's':
+        mode = SPARSE_READ_MODE_HOLE;
+        break;
+      default:
+        usage();
+        exit(-1);
+    }
+  }
+
+  extra = argc - optind;
+  if (extra < 2 || extra > 3) {
     usage();
     exit(-1);
   }
 
-  if (argc == 4) {
-    block_size = atoi(argv[3]);
+  if (extra == 3) {
+    block_size = atoi(argv[optind + 2]);
   }
 
   if (block_size < 1024 || block_size % 4 != 0) {
@@ -63,22 +80,24 @@
     exit(-1);
   }
 
-  if (strcmp(argv[1], "-") == 0) {
+  arg_in = argv[optind];
+  if (strcmp(arg_in, "-") == 0) {
     in = STDIN_FILENO;
   } else {
-    in = open(argv[1], O_RDONLY | O_BINARY);
+    in = open(arg_in, O_RDONLY | O_BINARY);
     if (in < 0) {
-      fprintf(stderr, "Cannot open input file %s\n", argv[1]);
+      fprintf(stderr, "Cannot open input file %s\n", arg_in);
       exit(-1);
     }
   }
 
-  if (strcmp(argv[2], "-") == 0) {
+  arg_out = argv[optind + 1];
+  if (strcmp(arg_out, "-") == 0) {
     out = STDOUT_FILENO;
   } else {
-    out = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
+    out = open(arg_out, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0664);
     if (out < 0) {
-      fprintf(stderr, "Cannot open output file %s\n", argv[2]);
+      fprintf(stderr, "Cannot open output file %s\n", arg_out);
       exit(-1);
     }
   }
@@ -93,7 +112,7 @@
   }
 
   sparse_file_verbose(s);
-  ret = sparse_file_read(s, in, SPARSE_READ_MODE_NORMAL, false);
+  ret = sparse_file_read(s, in, mode, false);
   if (ret) {
     fprintf(stderr, "Failed to read file\n");
     exit(-1);
diff --git a/libsparse/sparse_fuzzer.cpp b/libsparse/sparse_fuzzer.cpp
index 235d15d..663c812 100644
--- a/libsparse/sparse_fuzzer.cpp
+++ b/libsparse/sparse_fuzzer.cpp
@@ -23,5 +23,7 @@
   if (!file) {
       return 0;
   }
-  return sparse_file_callback(file, false, false, WriteCallback, nullptr);
+  int32_t result = sparse_file_callback(file, false, false, WriteCallback, nullptr);
+  sparse_file_destroy(file);
+  return result;
 }
diff --git a/libsparse/sparse_read.cpp b/libsparse/sparse_read.cpp
index 028b6be..44f7557 100644
--- a/libsparse/sparse_read.cpp
+++ b/libsparse/sparse_read.cpp
@@ -670,7 +670,7 @@
   int64_t len;
   int ret;
 
-  s = sparse_file_import(fd, verbose, crc);
+  s = sparse_file_import(fd, false, crc);
   if (s) {
     return s;
   }
@@ -686,6 +686,9 @@
   if (!s) {
     return nullptr;
   }
+  if (verbose) {
+    sparse_file_verbose(s);
+  }
 
   ret = sparse_file_read_normal(s, fd);
   if (ret < 0) {
diff --git a/libstats/pull_lazy/TEST_MAPPING b/libstats/pull_lazy/TEST_MAPPING
index 92f1e6a..0282a03 100644
--- a/libstats/pull_lazy/TEST_MAPPING
+++ b/libstats/pull_lazy/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name" : "libstatspull_lazy_test"
     }
   ],
-  "hwasan-postsubmit" : [
+  "hwasan-presubmit" : [
     {
       "name" : "libstatspull_lazy_test"
     }
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index 4ffa98d..a3ef131 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -22,6 +22,10 @@
     name: "libstatspull_bindgen",
     wrapper_src: "statslog.h",
     crate_name: "statspull_bindgen",
+    visibility: [
+        "//frameworks/proto_logging/stats/stats_log_api_gen",
+        "//packages/modules/Virtualization/libs/statslog_virtualization",
+    ],
     source_stem: "bindings",
     bindgen_flags: [
         "--size_t-is-usize",
@@ -48,7 +52,7 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.virt",
-    ]
+    ],
 }
 
 rust_library {
diff --git a/libstats/push_compat/statsd_writer.c b/libstats/push_compat/statsd_writer.c
index 04d3b46..4818d11 100644
--- a/libstats/push_compat/statsd_writer.c
+++ b/libstats/push_compat/statsd_writer.c
@@ -61,7 +61,7 @@
 static int statsdOpen();
 static void statsdClose();
 static int statsdWrite(struct timespec* ts, struct iovec* vec, size_t nr);
-static void statsdNoteDrop();
+static void statsdNoteDrop(int error, int tag);
 
 struct android_log_transport_write statsdLoggerWrite = {
         .name = "statsd",
diff --git a/libstats/socket_lazy/TEST_MAPPING b/libstats/socket_lazy/TEST_MAPPING
index b182660..03506cd 100644
--- a/libstats/socket_lazy/TEST_MAPPING
+++ b/libstats/socket_lazy/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name" : "libstatssocket_lazy_test"
     }
   ],
-  "hwasan-postsubmit" : [
+  "hwasan-presubmit" : [
     {
       "name" : "libstatssocket_lazy_test"
     }
diff --git a/libsync/libsync.map.txt b/libsync/libsync.map.txt
index aac6b57..32df91e 100644
--- a/libsync/libsync.map.txt
+++ b/libsync/libsync.map.txt
@@ -19,7 +19,7 @@
     sync_merge; # introduced=26
     sync_file_info; # introduced=26
     sync_file_info_free; # introduced=26
-    sync_wait; # llndk apex
+    sync_wait; # llndk systemapi
     sync_fence_info; # llndk
     sync_pt_info; # llndk
     sync_fence_info_free; # llndk
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 019a368..7939e82 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -44,14 +44,6 @@
     export_include_dirs: ["include"],
 
     target: {
-        android: {
-            header_libs: ["libbacktrace_headers"],
-            export_header_lib_headers: ["libbacktrace_headers"],
-        },
-        host_linux: {
-            header_libs: ["libbacktrace_headers"],
-            export_header_lib_headers: ["libbacktrace_headers"],
-        },
         linux_bionic: {
             enabled: true,
         },
@@ -201,7 +193,7 @@
 
     shared_libs: [
         "libutils",
-        "libbacktrace",
+        "libunwindstack",
     ],
 
     target: {
diff --git a/libutils/CallStack.cpp b/libutils/CallStack.cpp
index fe6f33d..f19ba6a 100644
--- a/libutils/CallStack.cpp
+++ b/libutils/CallStack.cpp
@@ -20,7 +20,7 @@
 #include <utils/Errors.h>
 #include <utils/Log.h>
 
-#include <backtrace/Backtrace.h>
+#include <unwindstack/AndroidUnwinder.h>
 
 #define CALLSTACK_WEAK  // Don't generate weak definitions.
 #include <utils/CallStack.h>
@@ -39,14 +39,25 @@
 }
 
 void CallStack::update(int32_t ignoreDepth, pid_t tid) {
+    if (ignoreDepth < 0) {
+        ignoreDepth = 0;
+    }
+
     mFrameLines.clear();
 
-    std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, tid));
-    if (!backtrace->Unwind(ignoreDepth)) {
-        ALOGW("%s: Failed to unwind callstack.", __FUNCTION__);
+    unwindstack::AndroidLocalUnwinder unwinder;
+    unwindstack::AndroidUnwinderData data;
+    std::optional<pid_t> tid_val;
+    if (tid != -1) {
+        *tid_val = tid;
     }
-    for (size_t i = 0; i < backtrace->NumFrames(); i++) {
-      mFrameLines.push_back(String8(backtrace->FormatFrameData(i).c_str()));
+    if (!unwinder.Unwind(tid_val, data)) {
+        ALOGW("%s: Failed to unwind callstack: %s", __FUNCTION__, data.GetErrorString().c_str());
+    }
+    for (size_t i = ignoreDepth; i < data.frames.size(); i++) {
+        auto& frame = data.frames[i];
+        frame.num -= ignoreDepth;
+        mFrameLines.push_back(String8(unwinder.FormatFrame(frame).c_str()));
     }
 }
 
diff --git a/libutils/RefBase.cpp b/libutils/RefBase.cpp
index 4ddac3d..ed5b2a9 100644
--- a/libutils/RefBase.cpp
+++ b/libutils/RefBase.cpp
@@ -149,6 +149,29 @@
 // Same for weak counts.
 #define BAD_WEAK(c) ((c) == 0 || ((c) & (~MAX_COUNT)) != 0)
 
+// name kept because prebuilts used to use it from inlining sp<> code
+void sp_report_stack_pointer() { LOG_ALWAYS_FATAL("RefBase used with stack pointer argument"); }
+
+// Check whether address is definitely on the calling stack.  We actually check whether it is on
+// the same 4K page as the frame pointer.
+//
+// Assumptions:
+// - Pages are never smaller than 4K (MIN_PAGE_SIZE)
+// - Malloced memory never shares a page with a stack.
+//
+// It does not appear safe to broaden this check to include adjacent pages; apparently this code
+// is used in environments where there may not be a guard page below (at higher addresses than)
+// the bottom of the stack.
+static void check_not_on_stack(const void* ptr) {
+    static constexpr int MIN_PAGE_SIZE = 0x1000;  // 4K. Safer than including sys/user.h.
+    static constexpr uintptr_t MIN_PAGE_MASK = ~static_cast<uintptr_t>(MIN_PAGE_SIZE - 1);
+    uintptr_t my_frame_address =
+            reinterpret_cast<uintptr_t>(__builtin_frame_address(0 /* this frame */));
+    if (((reinterpret_cast<uintptr_t>(ptr) ^ my_frame_address) & MIN_PAGE_MASK) == 0) {
+        sp_report_stack_pointer();
+    }
+}
+
 // ---------------------------------------------------------------------------
 
 class RefBase::weakref_impl : public RefBase::weakref_type
@@ -432,6 +455,8 @@
         return;
     }
 
+    check_not_on_stack(this);
+
     int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
     // A decStrong() must still happen after us.
     ALOG_ASSERT(old > INITIAL_STRONG_VALUE, "0x%x too small", old);
@@ -744,21 +769,27 @@
         if (mRefs->mWeak.load(std::memory_order_relaxed) == 0) {
             delete mRefs;
         }
-    } else if (mRefs->mStrong.load(std::memory_order_relaxed) == INITIAL_STRONG_VALUE) {
-        // We never acquired a strong reference on this object.
+    } else {
+        int32_t strongs = mRefs->mStrong.load(std::memory_order_relaxed);
 
-        // TODO: make this fatal, but too much code in Android manages RefBase with
-        // new/delete manually (or using other mechanisms such as std::make_unique).
-        // However, this is dangerous because it's also common for code to use the
-        // sp<T>(T*) constructor, assuming that if the object is around, it is already
-        // owned by an sp<>.
-        ALOGW("RefBase: Explicit destruction, weak count = %d (in %p). Use sp<> to manage this "
-              "object.",
-              mRefs->mWeak.load(), this);
+        if (strongs == INITIAL_STRONG_VALUE) {
+            // We never acquired a strong reference on this object.
+
+            // It would be nice to make this fatal, but many places use RefBase on the stack.
+            // However, this is dangerous because it's also common for code to use the
+            // sp<T>(T*) constructor, assuming that if the object is around, it is already
+            // owned by an sp<>.
+            ALOGW("RefBase: Explicit destruction, weak count = %d (in %p). Use sp<> to manage this "
+                  "object.",
+                  mRefs->mWeak.load(), this);
 
 #if CALLSTACK_ENABLED
-        CallStack::logStack(LOG_TAG);
+            CallStack::logStack(LOG_TAG);
 #endif
+        } else if (strongs != 0) {
+            LOG_ALWAYS_FATAL("RefBase: object %p with strong count %d deleted. Double owned?", this,
+                             strongs);
+        }
     }
     // For debugging purposes, clear mRefs.  Ineffective against outstanding wp's.
     const_cast<weakref_impl*&>(mRefs) = nullptr;
@@ -766,6 +797,8 @@
 
 void RefBase::extendObjectLifetime(int32_t mode)
 {
+    check_not_on_stack(this);
+
     // Must be happens-before ordered with respect to construction or any
     // operation that could destroy the object.
     mRefs->mFlags.fetch_or(mode, std::memory_order_relaxed);
diff --git a/libutils/RefBase_test.cpp b/libutils/RefBase_test.cpp
index 93f9654..aed3b9b 100644
--- a/libutils/RefBase_test.cpp
+++ b/libutils/RefBase_test.cpp
@@ -265,6 +265,21 @@
     delete foo;
 }
 
+TEST(RefBase, DoubleOwnershipDeath) {
+    bool isDeleted;
+    auto foo = sp<Foo>::make(&isDeleted);
+
+    // if something else thinks it owns foo, should die
+    EXPECT_DEATH(delete foo.get(), "");
+
+    EXPECT_FALSE(isDeleted);
+}
+
+TEST(RefBase, StackOwnershipDeath) {
+    bool isDeleted;
+    EXPECT_DEATH({ Foo foo(&isDeleted); foo.incStrong(nullptr); }, "");
+}
+
 // Set up a situation in which we race with visit2AndRremove() to delete
 // 2 strong references.  Bar destructor checks that there are no early
 // deletions and prior updates are visible to destructor.
diff --git a/libutils/StrongPointer.cpp b/libutils/StrongPointer.cpp
index ef46723..ba52502 100644
--- a/libutils/StrongPointer.cpp
+++ b/libutils/StrongPointer.cpp
@@ -21,7 +21,4 @@
 namespace android {
 
 void sp_report_race() { LOG_ALWAYS_FATAL("sp<> assignment detected data race"); }
-
-void sp_report_stack_pointer() { LOG_ALWAYS_FATAL("sp<> constructed with stack pointer argument"); }
-
 }
diff --git a/libutils/include/utils/CallStack.h b/libutils/include/utils/CallStack.h
index 7a4a345..fe4d4f5 100644
--- a/libutils/include/utils/CallStack.h
+++ b/libutils/include/utils/CallStack.h
@@ -20,7 +20,6 @@
 #include <memory>
 
 #include <android/log.h>
-#include <backtrace/backtrace_constants.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
 
@@ -59,7 +58,7 @@
 
     // Immediately collect the stack traces for the specified thread.
     // The default is to dump the stack of the current call.
-    void update(int32_t ignoreDepth = 1, pid_t tid = BACKTRACE_CURRENT_THREAD);
+    void update(int32_t ignoreDepth = 1, pid_t tid = -1);
 
     // Dump a stack trace to the log using the supplied logtag.
     void log(const char* logtag,
diff --git a/libutils/include/utils/RefBase.h b/libutils/include/utils/RefBase.h
index e07f574..f3acd6f 100644
--- a/libutils/include/utils/RefBase.h
+++ b/libutils/include/utils/RefBase.h
@@ -779,6 +779,40 @@
 
 }  // namespace android
 
+namespace libutilsinternal {
+template <typename T, typename = void>
+struct is_complete_type : std::false_type {};
+
+template <typename T>
+struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
+}  // namespace libutilsinternal
+
+namespace std {
+
+// Define `RefBase` specific versions of `std::make_shared` and
+// `std::make_unique` to block people from using them. Using them to allocate
+// `RefBase` objects results in double ownership. Use
+// `sp<T>::make(...)` instead.
+//
+// Note: We exclude incomplete types because `std::is_base_of` is undefined in
+// that case.
+
+template <typename T, typename... Args,
+          typename std::enable_if<libutilsinternal::is_complete_type<T>::value, bool>::value = true,
+          typename std::enable_if<std::is_base_of<android::RefBase, T>::value, bool>::value = true>
+shared_ptr<T> make_shared(Args...) {  // SEE COMMENT ABOVE.
+    static_assert(!std::is_base_of<android::RefBase, T>::value, "Must use RefBase with sp<>");
+}
+
+template <typename T, typename... Args,
+          typename std::enable_if<libutilsinternal::is_complete_type<T>::value, bool>::value = true,
+          typename std::enable_if<std::is_base_of<android::RefBase, T>::value, bool>::value = true>
+unique_ptr<T> make_unique(Args...) {  // SEE COMMENT ABOVE.
+    static_assert(!std::is_base_of<android::RefBase, T>::value, "Must use RefBase with sp<>");
+}
+
+}  // namespace std
+
 // ---------------------------------------------------------------------------
 
 #endif // ANDROID_REF_BASE_H
diff --git a/libutils/include/utils/StrongPointer.h b/libutils/include/utils/StrongPointer.h
index bb1941b..54aa691 100644
--- a/libutils/include/utils/StrongPointer.h
+++ b/libutils/include/utils/StrongPointer.h
@@ -120,7 +120,6 @@
     template<typename Y> friend class sp;
     template<typename Y> friend class wp;
     void set_pointer(T* ptr);
-    static inline void check_not_on_stack(const void* ptr);
     T* m_ptr;
 };
 
@@ -185,32 +184,10 @@
 
 // For code size reasons, we do not want these inlined or templated.
 void sp_report_race();
-void sp_report_stack_pointer();
 
 // ---------------------------------------------------------------------------
 // No user serviceable parts below here.
 
-// Check whether address is definitely on the calling stack.  We actually check whether it is on
-// the same 4K page as the frame pointer.
-//
-// Assumptions:
-// - Pages are never smaller than 4K (MIN_PAGE_SIZE)
-// - Malloced memory never shares a page with a stack.
-//
-// It does not appear safe to broaden this check to include adjacent pages; apparently this code
-// is used in environments where there may not be a guard page below (at higher addresses than)
-// the bottom of the stack.
-template <typename T>
-void sp<T>::check_not_on_stack(const void* ptr) {
-    static constexpr int MIN_PAGE_SIZE = 0x1000;  // 4K. Safer than including sys/user.h.
-    static constexpr uintptr_t MIN_PAGE_MASK = ~static_cast<uintptr_t>(MIN_PAGE_SIZE - 1);
-    uintptr_t my_frame_address =
-            reinterpret_cast<uintptr_t>(__builtin_frame_address(0 /* this frame */));
-    if (((reinterpret_cast<uintptr_t>(ptr) ^ my_frame_address) & MIN_PAGE_MASK) == 0) {
-        sp_report_stack_pointer();
-    }
-}
-
 // TODO: Ideally we should find a way to increment the reference count before running the
 // constructor, so that generating an sp<> to this in the constructor is no longer dangerous.
 template <typename T>
@@ -219,14 +196,13 @@
     T* t = new T(std::forward<Args>(args)...);
     sp<T> result;
     result.m_ptr = t;
-    t->incStrong(t);  // bypass check_not_on_stack for heap allocation
+    t->incStrong(t);
     return result;
 }
 
 template <typename T>
 sp<T> sp<T>::fromExisting(T* other) {
     if (other) {
-        check_not_on_stack(other);
         other->incStrongRequireStrong(other);
         sp<T> result;
         result.m_ptr = other;
@@ -240,7 +216,6 @@
 sp<T>::sp(T* other)
         : m_ptr(other) {
     if (other) {
-        check_not_on_stack(other);
         other->incStrong(this);
     }
 }
@@ -249,7 +224,6 @@
 template <typename U>
 sp<T>::sp(U* other) : m_ptr(other) {
     if (other) {
-        check_not_on_stack(other);
         (static_cast<T*>(other))->incStrong(this);
     }
 }
@@ -258,7 +232,6 @@
 sp<T>& sp<T>::operator=(T* other) {
     T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
     if (other) {
-        check_not_on_stack(other);
         other->incStrong(this);
     }
     if (oldPtr) oldPtr->decStrong(this);
diff --git a/libutils/include/utils/Thread.h b/libutils/include/utils/Thread.h
index fc67656..5cf6b47 100644
--- a/libutils/include/utils/Thread.h
+++ b/libutils/include/utils/Thread.h
@@ -42,7 +42,9 @@
 {
 public:
     // Create a Thread object, but doesn't create or start the associated
-    // thread. See the run() method.
+    // thread. See the run() method. This object must be used with RefBase/sp,
+    // like any other RefBase object, because they are conventionally promoted
+    // from bare pointers (Thread::run is particularly problematic here).
     explicit            Thread(bool canCallJava = true);
     virtual             ~Thread();
 
diff --git a/libvndksupport/libvndksupport.map.txt b/libvndksupport/libvndksupport.map.txt
index a44ed18..1d94b9d 100644
--- a/libvndksupport/libvndksupport.map.txt
+++ b/libvndksupport/libvndksupport.map.txt
@@ -1,8 +1,8 @@
 LIBVNDKSUPPORT {
   global:
-    android_is_in_vendor_process; # llndk apex
-    android_load_sphal_library; # llndk apex
-    android_unload_sphal_library; # llndk apex
+    android_is_in_vendor_process; # llndk systemapi
+    android_load_sphal_library; # llndk systemapi
+    android_unload_sphal_library; # llndk systemapi
   local:
     *;
 };
diff --git a/mkbootfs/mkbootfs.c b/mkbootfs/mkbootfs.c
index 58153f3..05d1940 100644
--- a/mkbootfs/mkbootfs.c
+++ b/mkbootfs/mkbootfs.c
@@ -24,8 +24,7 @@
 ** - device notes, pipes, etc are not supported (error)
 */
 
-void die(const char *why, ...)
-{
+static void die(const char* why, ...) {
     va_list ap;
 
     va_start(ap, why);
@@ -42,7 +41,7 @@
 };
 
 static struct fs_config_entry* canned_config = NULL;
-static char *target_out_path = NULL;
+static const char* target_out_path = NULL;
 
 /* Each line in the canned file should be a path plus three ints (uid,
  * gid, mode). */
@@ -273,8 +272,7 @@
     }
 }
 
-void archive(const char *start, const char *prefix)
-{
+static void archive(const char* start, const char* prefix) {
     char in[8192];
     char out[8192];
 
@@ -294,7 +292,7 @@
 
     char line[CANNED_LINE_LENGTH];
     FILE* f = fopen(filename, "r");
-    if (f == NULL) die("failed to open canned file");
+    if (f == NULL) die("failed to open canned file '%s'", filename);
 
     while (fgets(line, CANNED_LINE_LENGTH, f) != NULL) {
         if (!line[0]) break;
@@ -332,6 +330,13 @@
 
 int main(int argc, char *argv[])
 {
+    if (argc == 1) {
+        fprintf(stderr,
+                "usage: %s [-d TARGET_OUTPUT_PATH] [-f CANNED_CONFIGURATION_PATH] DIRECTORIES...\n",
+                argv[0]);
+        exit(1);
+    }
+
     argc--;
     argv++;
 
diff --git a/property_service/TEST_MAPPING b/property_service/TEST_MAPPING
index 20f6c84..7b02b51 100644
--- a/property_service/TEST_MAPPING
+++ b/property_service/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name": "propertyinfoserializer_tests"
     }
   ],
-  "hwasan-postsubmit": [
+  "hwasan-presubmit": [
     {
       "name": "propertyinfoserializer_tests"
     }
diff --git a/rootdir/init.rc b/rootdir/init.rc
index cd71aa8..660f18c 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -86,31 +86,9 @@
     mkdir /dev/sys/fs 0755 system system
     mkdir /dev/sys/block 0755 system system
 
-# Run boringssl self test for each ABI so that later processes can skip it. http://b/139348610
-on early-init && property:ro.product.cpu.abilist32=*
-    exec_start boringssl_self_test32
-on early-init && property:ro.product.cpu.abilist64=*
-    exec_start boringssl_self_test64
-on property:apexd.status=ready && property:ro.product.cpu.abilist32=*
-    exec_start boringssl_self_test_apex32
-on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
-    exec_start boringssl_self_test_apex64
-
-service boringssl_self_test32 /system/bin/boringssl_self_test32
-    reboot_on_failure reboot,boringssl-self-check-failed
-    stdio_to_kmsg
-
-service boringssl_self_test64 /system/bin/boringssl_self_test64
-    reboot_on_failure reboot,boringssl-self-check-failed
-    stdio_to_kmsg
-
-service boringssl_self_test_apex32 /apex/com.android.conscrypt/bin/boringssl_self_test32
-    reboot_on_failure reboot,boringssl-self-check-failed
-    stdio_to_kmsg
-
-service boringssl_self_test_apex64 /apex/com.android.conscrypt/bin/boringssl_self_test64
-    reboot_on_failure reboot,boringssl-self-check-failed
-    stdio_to_kmsg
+    # Create location for fs_mgr to store abbreviated output from filesystem
+    # checker programs.
+    mkdir /dev/fscklogs 0770 root system
 
 on init
     sysclktz 0
@@ -443,10 +421,6 @@
 
     mount bpf bpf /sys/fs/bpf nodev noexec nosuid
 
-    # Create location for fs_mgr to store abbreviated output from filesystem
-    # checker programs.
-    mkdir /dev/fscklogs 0770 root system
-
     # pstore/ramoops previous console log
     mount pstore pstore /sys/fs/pstore nodev noexec nosuid
     chown system log /sys/fs/pstore
@@ -502,6 +476,33 @@
     start hwservicemanager
     start vndservicemanager
 
+# Run boringssl self test for each ABI.  Any failures trigger reboot to firmware.
+on init && property:ro.product.cpu.abilist32=*
+    exec_start boringssl_self_test32
+on init && property:ro.product.cpu.abilist64=*
+    exec_start boringssl_self_test64
+on property:apexd.status=ready && property:ro.product.cpu.abilist32=*
+    exec_start boringssl_self_test_apex32
+on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
+    exec_start boringssl_self_test_apex64
+
+service boringssl_self_test32 /system/bin/boringssl_self_test32
+    reboot_on_failure reboot,boringssl-self-check-failed
+    stdio_to_kmsg
+
+service boringssl_self_test64 /system/bin/boringssl_self_test64
+    reboot_on_failure reboot,boringssl-self-check-failed
+    stdio_to_kmsg
+
+service boringssl_self_test_apex32 /apex/com.android.conscrypt/bin/boringssl_self_test32
+    reboot_on_failure reboot,boringssl-self-check-failed
+    stdio_to_kmsg
+
+service boringssl_self_test_apex64 /apex/com.android.conscrypt/bin/boringssl_self_test64
+    reboot_on_failure reboot,boringssl-self-check-failed
+    stdio_to_kmsg
+
+
 # Healthd can trigger a full boot from charger mode by signaling this
 # property when the power button is held.
 on property:sys.boot_from_charger_mode=1
@@ -534,6 +535,10 @@
     # /data, which in turn can only be loaded when system properties are present.
     trigger post-fs-data
 
+    # APEXes are ready to use. apex-ready is a public trigger similar to apexd.status=ready which
+    # is a system-private property.
+    trigger apex-ready
+
     # Should be before netd, but after apex, properties and logging is available.
     trigger load_bpf_programs
 
@@ -684,8 +689,6 @@
     copy /data/system/entropy.dat /dev/urandom
 
     mkdir /data/vendor 0771 root root encryption=Require
-    mkdir /data/vendor_ce 0771 root root encryption=None
-    mkdir /data/vendor_de 0771 root root encryption=None
     mkdir /data/vendor/hardware 0771 root root
 
     # Start tombstoned early to be able to store tombstones.
@@ -734,6 +737,13 @@
     # To handle userspace reboots as well as devices that use FDE, make sure
     # that apexd is started cleanly here (set apexd.status="") and that it is
     # restarted if it's already running.
+    #
+    # /data/apex uses encryption=None because direct I/O support is needed on
+    # APEX files, but some devices don't support direct I/O on encrypted files.
+    # Also, APEXes are public information, similar to the system image.
+    # /data/apex/decompressed and /data/apex/ota_reserved override this setting;
+    # they are encrypted so that files in them can be hard-linked into
+    # /data/rollback which is encrypted.
     mkdir /data/apex 0755 root system encryption=None
     mkdir /data/apex/active 0755 root system
     mkdir /data/apex/backup 0700 root system
@@ -777,7 +787,6 @@
     mkdir /data/misc/carrierid 0770 system radio
     mkdir /data/misc/apns 0770 system radio
     mkdir /data/misc/emergencynumberdb 0770 system radio
-    mkdir /data/misc/zoneinfo 0775 system system
     mkdir /data/misc/network_watchlist 0774 system system
     mkdir /data/misc/textclassifier 0771 system system
     mkdir /data/misc/vpn 0770 system vpn
@@ -830,13 +839,14 @@
     exec - virtualizationservice system -- /bin/rm -rf /data/misc/virtualizationservice
     mkdir /data/misc/virtualizationservice 0770 system system
 
+    # /data/preloads uses encryption=None because it only contains preloaded
+    # files that are public information, similar to the system image.
     mkdir /data/preloads 0775 system system encryption=None
 
     # For security reasons, /data/local/tmp should always be empty.
     # Do not place files or directories in /data/local/tmp
     mkdir /data/local/tmp 0771 shell shell
     mkdir /data/local/traces 0777 shell shell
-    mkdir /data/data 0771 system system encryption=None
     mkdir /data/app-private 0771 system system encryption=Require
     mkdir /data/app-ephemeral 0771 system system encryption=Require
     mkdir /data/app-asec 0700 root root encryption=Require
@@ -874,7 +884,10 @@
     chown system system /data/resource-cache
     chmod 0771 /data/resource-cache
 
-    # create the lost+found directories, so as to enforce our permissions
+    # Ensure that lost+found exists and has the correct permissions.  Linux
+    # filesystems expect this directory to exist; it's where the fsck tool puts
+    # any recovered files that weren't present in any directory.  It must be
+    # unencrypted, as fsck must be able to write to it.
     mkdir /data/lost+found 0770 root root encryption=None
 
     # create directory for DRM plug-ins - give drm the read/write access to
@@ -902,21 +915,26 @@
     mkdir /data/system/heapdump 0700 system system
     mkdir /data/system/users 0775 system system
 
-    mkdir /data/system_de 0770 system system encryption=None
-    mkdir /data/system_ce 0770 system system encryption=None
-
-    mkdir /data/misc_de 01771 system misc encryption=None
+    # Create the parent directories of the user CE and DE storage directories.
+    # These parent directories must use encryption=None, since each of their
+    # subdirectories uses a different encryption policy (a per-user one), and
+    # encryption policies apply recursively.  These directories should never
+    # contain any subdirectories other than the per-user ones.  /data/media/obb
+    # is an exception that exists for legacy reasons.
+    mkdir /data/media 0770 media_rw media_rw encryption=None
     mkdir /data/misc_ce 01771 system misc encryption=None
-
+    mkdir /data/misc_de 01771 system misc encryption=None
+    mkdir /data/system_ce 0770 system system encryption=None
+    mkdir /data/system_de 0770 system system encryption=None
     mkdir /data/user 0711 system system encryption=None
     mkdir /data/user_de 0711 system system encryption=None
+    mkdir /data/vendor_ce 0771 root root encryption=None
+    mkdir /data/vendor_de 0771 root root encryption=None
 
-    # Unlink /data/user/0 if we previously symlink it to /data/data
-    rm /data/user/0
-
-    # Bind mount /data/user/0 to /data/data
-    mkdir /data/user/0 0700 system system encryption=None
-    mount none /data/data /data/user/0 bind rec
+    # 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
+    exec - media_rw media_rw -- /system/bin/chattr +F /data/media
 
     # A tmpfs directory, which will contain all apps CE DE data directory that
     # bind mount from the original source.
@@ -929,8 +947,10 @@
     mkdir /data_mirror/data_ce/null 0700 root root
     mkdir /data_mirror/data_de/null 0700 root root
 
-    # Bind mount CE and DE data directory to mirror's default volume directory
-    mount none /data/user /data_mirror/data_ce/null bind rec
+    # Bind mount CE and DE data directory to mirror's default volume directory.
+    # The 'slave' option (MS_SLAVE) is needed to cause the later bind mount of
+    # /data/data onto /data/user/0 to propagate to /data_mirror/data_ce/null/0.
+    mount none /data/user /data_mirror/data_ce/null bind rec slave
     mount none /data/user_de /data_mirror/data_de/null bind rec
 
     # Create mirror directory for jit profiles
@@ -963,11 +983,6 @@
     wait_for_prop apexd.status activated
     perform_apex_config
 
-    # Special-case /data/media/obb per b/64566063
-    mkdir /data/media 0770 media_rw media_rw encryption=None
-    exec - media_rw media_rw -- /system/bin/chattr +F /data/media
-    mkdir /data/media/obb 0770 media_rw media_rw encryption=Attempt
-
     # Create directories for boot animation.
     mkdir /data/bootanim 0755 system system encryption=DeleteIfNecessary
 
@@ -1006,10 +1021,6 @@
     # completed and apexd.status becomes "ready".
     exec_start apexd-snapshotde
 
-    # Check any timezone data in /data is newer than the copy in the time zone data
-    # module, delete if not.
-    exec - system system -- /system/bin/tzdatacheck /apex/com.android.tzdata/etc/tz /data/misc/zoneinfo
-
     # sys.memfd_use set to false by default, which keeps it disabled
     # until it is confirmed that apps and vendor processes don't make
     # IOCTLs on ashmem fds any more.
@@ -1131,10 +1142,6 @@
     chown system system /sys/devices/system/cpu/cpufreq/interactive/io_is_busy
     chmod 0660 /sys/devices/system/cpu/cpufreq/interactive/io_is_busy
 
-    # Assume SMP uses shared cpufreq policy for all CPUs
-    chown system system /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
-    chmod 0660 /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
-
     chown system system /sys/class/leds/vibrator/trigger
     chown system system /sys/class/leds/vibrator/activate
     chown system system /sys/class/leds/vibrator/brightness
@@ -1159,6 +1166,7 @@
     chown system system /sys/kernel/ipv4/tcp_rmem_def
     chown system system /sys/kernel/ipv4/tcp_rmem_max
     chown root radio /proc/cmdline
+    chown root system /proc/bootconfig
 
     # Define default initial receive window size in segments.
     setprop net.tcp_def_init_rwnd 60
@@ -1302,6 +1310,7 @@
 on userspace-reboot-resume
   trigger userspace-reboot-fs-remount
   trigger post-fs-data
+  trigger apex-ready
   trigger zygote-start
   trigger early-boot
   trigger boot
diff --git a/set-verity-state/Android.bp b/set-verity-state/Android.bp
index 2f0cb10..f40118b 100644
--- a/set-verity-state/Android.bp
+++ b/set-verity-state/Android.bp
@@ -9,19 +9,31 @@
     srcs: ["set-verity-state.cpp"],
     shared_libs: [
         "libbase",
+        "libbinder",
         "libcrypto",
         "libcrypto_utils",
-        "libcutils",
-        "libfec",
         "libfs_mgr_binder",
-        "liblog",
         "libutils",
     ],
     static_libs: [
         "libavb_user",
     ],
+    header_libs: [
+        "libcutils_headers",
+    ],
 
     cflags: ["-Werror"],
+    cppflags: [
+        "-DALLOW_DISABLE_VERITY=0",
+    ],
+    product_variables: {
+        debuggable: {
+            cppflags: [
+                "-UALLOW_DISABLE_VERITY",
+                "-DALLOW_DISABLE_VERITY=1",
+            ],
+        },
+    },
     symlinks: [
         "enable-verity",
         "disable-verity",
diff --git a/set-verity-state/OWNERS b/set-verity-state/OWNERS
new file mode 100644
index 0000000..e849450
--- /dev/null
+++ b/set-verity-state/OWNERS
@@ -0,0 +1,3 @@
+dvander@google.com
+yochiang@google.com
+bowgotsai@google.com
diff --git a/set-verity-state/set-verity-state.cpp b/set-verity-state/set-verity-state.cpp
index 52a7f74..de9a452 100644
--- a/set-verity-state/set-verity-state.cpp
+++ b/set-verity-state/set-verity-state.cpp
@@ -14,242 +14,230 @@
  * limitations under the License.
  */
 
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <libavb_user/libavb_user.h>
-#include <stdarg.h>
+#include <getopt.h>
 #include <stdio.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <unistd.h>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <fs_mgr.h>
+#include <binder/ProcessState.h>
+#include <cutils/android_reboot.h>
 #include <fs_mgr_overlayfs.h>
-#include <fstab/fstab.h>
-#include <log/log_properties.h>
+#include <libavb_user/libavb_user.h>
 
-#include "fec/io.h"
+using namespace std::string_literals;
+
+namespace {
+
+void print_usage() {
+  printf(
+      "Usage:\n"
+      "\tdisable-verity\n"
+      "\tenable-verity\n"
+      "\tset-verity-state [0|1]\n"
+      "Options:\n"
+      "\t-h --help\tthis help\n"
+      "\t-R --reboot\tautomatic reboot if needed for new settings to take effect\n"
+      "\t-v --verbose\tbe noisy\n");
+}
 
 #ifdef ALLOW_DISABLE_VERITY
-static const bool kAllowDisableVerity = true;
+const bool kAllowDisableVerity = true;
 #else
-static const bool kAllowDisableVerity = false;
+const bool kAllowDisableVerity = false;
 #endif
 
-using android::base::unique_fd;
-
-static void suggest_run_adb_root() {
-  if (getuid() != 0) printf("Maybe run adb root?\n");
-}
-
-static bool make_block_device_writable(const std::string& dev) {
-  unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC));
-  if (fd == -1) {
-    return false;
-  }
-
-  int OFF = 0;
-  bool result = (ioctl(fd.get(), BLKROSET, &OFF) != -1);
-  return result;
-}
-
-/* Turn verity on/off */
-static bool set_verity_enabled_state(const char* block_device, const char* mount_point,
-                                     bool enable) {
-  if (!make_block_device_writable(block_device)) {
-    printf("Could not make block device %s writable (%s).\n", block_device, strerror(errno));
-    return false;
-  }
-
-  fec::io fh(block_device, O_RDWR);
-
-  if (!fh) {
-    printf("Could not open block device %s (%s).\n", block_device, strerror(errno));
-    suggest_run_adb_root();
-    return false;
-  }
-
-  fec_verity_metadata metadata;
-
-  if (!fh.get_verity_metadata(metadata)) {
-    printf("Couldn't find verity metadata!\n");
-    return false;
-  }
-
-  if (!enable && metadata.disabled) {
-    printf("Verity already disabled on %s\n", mount_point);
-    return false;
-  }
-
-  if (enable && !metadata.disabled) {
-    printf("Verity already enabled on %s\n", mount_point);
-    return false;
-  }
-
-  if (!fh.set_verity_status(enable)) {
-    printf("Could not set verity %s flag on device %s with error %s\n",
-           enable ? "enabled" : "disabled", block_device, strerror(errno));
-    return false;
-  }
-
-  auto change = false;
-  errno = 0;
-  if (enable ? fs_mgr_overlayfs_teardown(mount_point, &change)
-             : fs_mgr_overlayfs_setup(nullptr, mount_point, &change)) {
-    if (change) {
-      printf("%s overlayfs for %s\n", enable ? "disabling" : "using", mount_point);
-    }
-  } else if (errno) {
-    int expected_errno = enable ? EBUSY : ENOENT;
-    if (errno != expected_errno) {
-      printf("Overlayfs %s for %s failed with error %s\n", enable ? "teardown" : "setup",
-             mount_point, strerror(errno));
-    }
-  }
-  printf("Verity %s on %s\n", enable ? "enabled" : "disabled", mount_point);
-  return true;
-}
-
 /* Helper function to get A/B suffix, if any. If the device isn't
  * using A/B the empty string is returned. Otherwise either "_a",
  * "_b", ... is returned.
  */
-static std::string get_ab_suffix() {
+std::string get_ab_suffix() {
   return android::base::GetProperty("ro.boot.slot_suffix", "");
 }
 
-static bool is_avb_device_locked() {
+bool is_avb_device_locked() {
   return android::base::GetProperty("ro.boot.vbmeta.device_state", "") == "locked";
 }
 
-static bool overlayfs_setup(bool enable) {
+bool is_debuggable() {
+  return android::base::GetBoolProperty("ro.debuggable", false);
+}
+
+bool is_using_avb() {
+  // Figure out if we're using VB1.0 or VB2.0 (aka AVB) - by
+  // contract, androidboot.vbmeta.digest is set by the bootloader
+  // when using AVB).
+  return !android::base::GetProperty("ro.boot.vbmeta.digest", "").empty();
+}
+
+[[noreturn]] void reboot(const std::string& name) {
+  LOG(INFO) << "Rebooting device for new settings to take effect";
+  ::sync();
+  android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot," + name);
+  ::sleep(60);
+  LOG(ERROR) << "Failed to reboot";
+  ::exit(1);
+}
+
+bool overlayfs_setup(bool enable) {
   auto change = false;
   errno = 0;
-  if (enable ? fs_mgr_overlayfs_teardown(nullptr, &change)
-             : fs_mgr_overlayfs_setup(nullptr, nullptr, &change)) {
+  if (enable ? fs_mgr_overlayfs_setup(nullptr, &change)
+             : fs_mgr_overlayfs_teardown(nullptr, &change)) {
     if (change) {
-      printf("%s overlayfs\n", enable ? "disabling" : "using");
+      LOG(INFO) << (enable ? "Enabled" : "Disabled") << " overlayfs";
     }
-  } else if (errno) {
-    printf("Overlayfs %s failed with error %s\n", enable ? "teardown" : "setup", strerror(errno));
-    suggest_run_adb_root();
+  } else {
+    LOG(ERROR) << "Failed to " << (enable ? "enable" : "disable") << " overlayfs";
   }
   return change;
 }
 
+struct SetVerityStateResult {
+  bool success = false;
+  bool want_reboot = false;
+};
+
 /* Use AVB to turn verity on/off */
-static bool set_avb_verity_enabled_state(AvbOps* ops, bool enable_verity) {
+SetVerityStateResult SetVerityState(bool enable_verity) {
   std::string ab_suffix = get_ab_suffix();
-  bool verity_enabled;
+  bool verity_enabled = false;
 
   if (is_avb_device_locked()) {
-    printf("Device is locked. Please unlock the device first\n");
-    return false;
+    LOG(ERROR) << "Device must be bootloader unlocked to change verity state";
+    return {};
   }
 
-  if (!avb_user_verity_get(ops, ab_suffix.c_str(), &verity_enabled)) {
-    printf("Error getting verity state. Try adb root first?\n");
-    return false;
+  std::unique_ptr<AvbOps, decltype(&avb_ops_user_free)> ops(avb_ops_user_new(), &avb_ops_user_free);
+  if (!ops) {
+    LOG(ERROR) << "Error getting AVB ops";
+    return {};
+  }
+
+  if (!avb_user_verity_get(ops.get(), ab_suffix.c_str(), &verity_enabled)) {
+    LOG(ERROR) << "Error getting verity state";
+    return {};
   }
 
   if ((verity_enabled && enable_verity) || (!verity_enabled && !enable_verity)) {
-    printf("verity is already %s\n", verity_enabled ? "enabled" : "disabled");
-    return false;
+    LOG(INFO) << "Verity is already " << (verity_enabled ? "enabled" : "disabled");
+    return {.success = true, .want_reboot = false};
   }
 
-  if (!avb_user_verity_set(ops, ab_suffix.c_str(), enable_verity)) {
-    printf("Error setting verity\n");
-    return false;
+  if (!avb_user_verity_set(ops.get(), ab_suffix.c_str(), enable_verity)) {
+    LOG(ERROR) << "Error setting verity state";
+    return {};
   }
 
-  overlayfs_setup(enable_verity);
-  printf("Successfully %s verity\n", enable_verity ? "enabled" : "disabled");
-  return true;
+  LOG(INFO) << "Successfully " << (enable_verity ? "enabled" : "disabled") << " verity";
+  return {.success = true, .want_reboot = true};
 }
 
+class MyLogger {
+ public:
+  explicit MyLogger(bool verbose) : verbose_(verbose) {}
+
+  void operator()(android::base::LogId id, android::base::LogSeverity severity, const char* tag,
+                  const char* file, unsigned int line, const char* message) {
+    // Hide log starting with '[fs_mgr]' unless it's an error.
+    if (verbose_ || severity >= android::base::ERROR || message[0] != '[') {
+      fprintf(stderr, "%s\n", message);
+    }
+    logd_(id, severity, tag, file, line, message);
+  }
+
+ private:
+  android::base::LogdLogger logd_;
+  bool verbose_;
+};
+
+}  // namespace
+
 int main(int argc, char* argv[]) {
-  if (argc == 0) {
-    LOG(FATAL) << "set-verity-state called with empty argv";
-  }
+  bool auto_reboot = false;
+  bool verbose = false;
 
-  std::optional<bool> enable_opt;
-  std::string procname = android::base::Basename(argv[0]);
-  if (procname == "enable-verity") {
-    enable_opt = true;
-  } else if (procname == "disable-verity") {
-    enable_opt = false;
-  }
-
-  if (!enable_opt.has_value()) {
-    if (argc != 2) {
-      printf("usage: %s [1|0]\n", argv[0]);
-      return 1;
-    }
-
-    if (strcmp(argv[1], "1") == 0) {
-      enable_opt = true;
-    } else if (strcmp(argv[1], "0") == 0) {
-      enable_opt = false;
-    } else {
-      printf("usage: %s [1|0]\n", argv[0]);
-      return 1;
+  struct option longopts[] = {
+      {"help", no_argument, nullptr, 'h'},
+      {"reboot", no_argument, nullptr, 'R'},
+      {"verbose", no_argument, nullptr, 'v'},
+      {0, 0, nullptr, 0},
+  };
+  for (int opt; (opt = ::getopt_long(argc, argv, "hRv", longopts, nullptr)) != -1;) {
+    switch (opt) {
+      case 'h':
+        print_usage();
+        return 0;
+      case 'R':
+        auto_reboot = true;
+        break;
+      case 'v':
+        verbose = true;
+        break;
+      default:
+        print_usage();
+        return 1;
     }
   }
 
-  bool enable = enable_opt.value();
+  android::base::InitLogging(argv, MyLogger(verbose));
 
-  bool any_changed = false;
-
-  // Figure out if we're using VB1.0 or VB2.0 (aka AVB) - by
-  // contract, androidboot.vbmeta.digest is set by the bootloader
-  // when using AVB).
-  bool using_avb = !android::base::GetProperty("ro.boot.vbmeta.digest", "").empty();
-
-  // If using AVB, dm-verity is used on any build so we want it to
-  // be possible to disable/enable on any build (except USER). For
-  // VB1.0 dm-verity is only enabled on certain builds.
-  if (!using_avb) {
-    if (!kAllowDisableVerity) {
-      printf("%s only works for userdebug builds\n", argv[0]);
-    }
-
-    if (!android::base::GetBoolProperty("ro.secure", false)) {
-      overlayfs_setup(enable);
-      printf("verity not enabled - ENG build\n");
-      return 0;
-    }
+  bool enable_verity = false;
+  const std::string progname = getprogname();
+  if (progname == "enable-verity") {
+    enable_verity = true;
+  } else if (progname == "disable-verity") {
+    enable_verity = false;
+  } else if (optind < argc && (argv[optind] == "1"s || argv[optind] == "0"s)) {
+    // progname "set-verity-state"
+    enable_verity = (argv[optind] == "1"s);
+  } else {
+    print_usage();
+    return 1;
   }
 
-  // Should never be possible to disable dm-verity on a USER build
-  // regardless of using AVB or VB1.0.
-  if (!__android_log_is_debuggable()) {
-    printf("verity cannot be disabled/enabled - USER build\n");
-    return 0;
+  if (!kAllowDisableVerity || !is_debuggable()) {
+    errno = EPERM;
+    PLOG(ERROR) << "Cannot disable/enable verity on user build";
+    return 1;
   }
 
-  if (using_avb) {
-    // Yep, the system is using AVB.
-    AvbOps* ops = avb_ops_user_new();
-    if (ops == nullptr) {
-      printf("Error getting AVB ops\n");
-      return 1;
+  if (getuid() != 0) {
+    errno = EACCES;
+    PLOG(ERROR) << "Must be running as root (adb root)";
+    return 1;
+  }
+
+  if (!is_using_avb()) {
+    LOG(ERROR) << "Expected AVB device, VB1.0 is no longer supported";
+    return 1;
+  }
+
+  int exit_code = 0;
+  bool want_reboot = false;
+
+  auto ret = SetVerityState(enable_verity);
+  if (ret.success) {
+    want_reboot |= ret.want_reboot;
+  } else {
+    exit_code = 1;
+  }
+
+  // Disable any overlayfs unconditionally if we want verity enabled.
+  // Enable overlayfs only if verity is successfully disabled or is already disabled.
+  if (enable_verity || ret.success) {
+    // Start a threadpool to service waitForService() callbacks as
+    // fs_mgr_overlayfs_* might call waitForService() to get the image service.
+    android::ProcessState::self()->startThreadPool();
+    want_reboot |= overlayfs_setup(!enable_verity);
+  }
+
+  if (want_reboot) {
+    if (auto_reboot) {
+      reboot(progname);
     }
-    if (set_avb_verity_enabled_state(ops, enable)) {
-      any_changed = true;
-    }
-    avb_ops_user_free(ops);
-  }
-  if (!any_changed) any_changed = overlayfs_setup(enable);
-
-  if (any_changed) {
-    printf("Now reboot your device for settings to take effect\n");
+    printf("Reboot the device for new settings to take effect\n");
   }
 
-  return 0;
+  return exit_code;
 }
diff --git a/trusty/OWNERS b/trusty/OWNERS
index 5c4e03a..61b97c6 100644
--- a/trusty/OWNERS
+++ b/trusty/OWNERS
@@ -1,9 +1,11 @@
 armellel@google.com
 arve@android.com
+danielangell@google.com
 gmar@google.com
 marcone@google.com
 mmaurer@google.com
 ncbray@google.com
 swillden@google.com
+thurston@google.com
 trong@google.com
 wenhaowang@google.com
diff --git a/trusty/keymaster/keymint/TEST_MAPPING b/trusty/keymaster/keymint/TEST_MAPPING
index ae24fb4..6671355 100644
--- a/trusty/keymaster/keymint/TEST_MAPPING
+++ b/trusty/keymaster/keymint/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name" : "vts_treble_vintf_framework_test"
     }
   ],
-  "hwasan-postsubmit" : [
+  "hwasan-presubmit" : [
     {
       "name" : "vts_treble_vintf_framework_test"
     }
diff --git a/trusty/libtrusty-rs/Android.bp b/trusty/libtrusty-rs/Android.bp
index bc1dcf6..4fc162b 100644
--- a/trusty/libtrusty-rs/Android.bp
+++ b/trusty/libtrusty-rs/Android.bp
@@ -19,6 +19,7 @@
 rust_library {
     name: "libtrusty-rs",
     crate_name: "trusty",
+    vendor_available: true,
     srcs: [
         "src/lib.rs"
     ],
diff --git a/trusty/metrics/metrics_test.cpp b/trusty/metrics/metrics_test.cpp
index 407ddf2..0c6db7f 100644
--- a/trusty/metrics/metrics_test.cpp
+++ b/trusty/metrics/metrics_test.cpp
@@ -31,7 +31,7 @@
 using android::base::unique_fd;
 
 static void TriggerCrash() {
-    size_t num_retries = 3;
+    size_t num_retries = 6;
     int fd = -1;
 
     for (size_t i = 0; i < num_retries; i++) {
@@ -61,6 +61,18 @@
     virtual void SetUp() override {
         auto ret = Open();
         ASSERT_TRUE(ret.ok()) << ret.error();
+
+        /* Drain events (if any) and reset state */
+        DrainEvents();
+        crashed_app_.clear();
+        event_drop_count_ = 0;
+    }
+
+    void DrainEvents() {
+        while (WaitForEvent(1000 /* 1 second timeout */).ok()) {
+            auto ret = HandleEvent();
+            ASSERT_TRUE(ret.ok()) << ret.error();
+        }
     }
 
     void WaitForAndHandleEvent() {
@@ -79,6 +91,9 @@
     TriggerCrash();
     WaitForAndHandleEvent();
 
+    /* Check that no event was dropped. */
+    ASSERT_EQ(event_drop_count_, 0);
+
     /* Check that correct TA crashed. */
     ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
 }
@@ -110,6 +125,9 @@
     auto ret = HandleEvent();
     ASSERT_TRUE(ret.ok()) << ret.error();
 
+    /* Check that no event was dropped. */
+    ASSERT_EQ(event_drop_count_, 0);
+
     /* Check that correct TA crashed. */
     ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
 }
diff --git a/trusty/storage/interface/include/trusty/interface/storage.h b/trusty/storage/interface/include/trusty/interface/storage.h
index 3f1dcb8..255ade1 100644
--- a/trusty/storage/interface/include/trusty/interface/storage.h
+++ b/trusty/storage/interface/include/trusty/interface/storage.h
@@ -70,6 +70,9 @@
  * @STORAGE_ERR_TRANSACT        returned by various operations to indicate that current transaction
  *                              is in error state. Such state could be only cleared by sending
  *                              STORAGE_END_TRANSACTION message.
+ * @STORAGE_ERR_SYNC_FAILURE    indicates that the current operation failed to sync
+ *                              to disk. Only returned if STORAGE_MSG_FLAG_PRE_COMMIT or
+ *                              STORAGE_MSG_FLAG_POST_COMMIT was set for the request.
  */
 enum storage_err {
 	STORAGE_NO_ERROR          = 0,
@@ -80,6 +83,7 @@
 	STORAGE_ERR_NOT_FOUND     = 5,
 	STORAGE_ERR_EXIST         = 6,
 	STORAGE_ERR_TRANSACT      = 7,
+	STORAGE_ERR_SYNC_FAILURE  = 8,
 };
 
 /**
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index 2620034..b970406 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -70,56 +70,14 @@
     exit(code);
 }
 
-static int drop_privs(void) {
-    struct __user_cap_header_struct capheader;
-    struct __user_cap_data_struct capdata[2];
-
-    if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
-        return -1;
-    }
-
-    /*
-     * ensure we're running as the system user
-     */
-    if (setgid(AID_SYSTEM) != 0) {
-        return -1;
-    }
-
-    if (setuid(AID_SYSTEM) != 0) {
-        return -1;
-    }
-
-    /*
-     * drop all capabilities except SYS_RAWIO
-     */
-    memset(&capheader, 0, sizeof(capheader));
-    memset(&capdata, 0, sizeof(capdata));
-    capheader.version = _LINUX_CAPABILITY_VERSION_3;
-    capheader.pid = 0;
-
-    capdata[CAP_TO_INDEX(CAP_SYS_RAWIO)].permitted = CAP_TO_MASK(CAP_SYS_RAWIO);
-    capdata[CAP_TO_INDEX(CAP_SYS_RAWIO)].effective = CAP_TO_MASK(CAP_SYS_RAWIO);
-
-    if (capset(&capheader, &capdata[0]) < 0) {
-        return -1;
-    }
-
-    /*
-     * No access for group and other. We need execute access for user to create
-     * an accessible directory.
-     */
-    umask(S_IRWXG | S_IRWXO);
-
-    return 0;
-}
-
 static int handle_req(struct storage_msg* msg, const void* req, size_t req_len) {
     int rc;
 
-    if ((msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) && (msg->cmd != STORAGE_RPMB_SEND)) {
+    if ((msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) && msg->cmd != STORAGE_RPMB_SEND &&
+        msg->cmd != STORAGE_FILE_WRITE) {
         /*
-         * handling post commit messages on non rpmb commands are not
-         * implemented as there is no use case for this yet.
+         * handling post commit messages on commands other than rpmb and write
+         * operations are not implemented as there is no use case for this yet.
          */
         ALOGE("cmd 0x%x: post commit option is not implemented\n", msg->cmd);
         msg->result = STORAGE_ERR_UNIMPLEMENTED;
@@ -129,7 +87,7 @@
     if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT) {
         rc = storage_sync_checkpoint();
         if (rc < 0) {
-            msg->result = STORAGE_ERR_GENERIC;
+            msg->result = STORAGE_ERR_SYNC_FAILURE;
             return ipc_respond(msg, NULL, 0);
         }
     }
@@ -260,8 +218,11 @@
 int main(int argc, char* argv[]) {
     int rc;
 
-    /* drop privileges */
-    if (drop_privs() < 0) return EXIT_FAILURE;
+    /*
+     * No access for group and other. We need execute access for user to create
+     * an accessible directory.
+     */
+    umask(S_IRWXG | S_IRWXO);
 
     /* parse arguments */
     parse_args(argc, argv);
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index c00c399..c531cfd 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -407,6 +407,14 @@
         goto err_response;
     }
 
+    if (msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) {
+        rc = storage_sync_checkpoint();
+        if (rc < 0) {
+            msg->result = STORAGE_ERR_SYNC_FAILURE;
+            goto err_response;
+        }
+    }
+
     msg->result = STORAGE_NO_ERROR;
 
 err_response:
diff --git a/trusty/test/binder/aidl/ByteEnum.aidl b/trusty/test/binder/aidl/ByteEnum.aidl
new file mode 100644
index 0000000..d3a13ac
--- /dev/null
+++ b/trusty/test/binder/aidl/ByteEnum.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Hello, world!
+ */
+@Backing(type="byte")
+enum ByteEnum {
+    // Comment about FOO.
+    FOO = 1,
+    BAR = 2,
+    BAZ,
+}
diff --git a/trusty/test/binder/aidl/ITestService.aidl b/trusty/test/binder/aidl/ITestService.aidl
new file mode 100644
index 0000000..c6a99c8
--- /dev/null
+++ b/trusty/test/binder/aidl/ITestService.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import ByteEnum;
+import IntEnum;
+import LongEnum;
+
+interface ITestService {
+    const @utf8InCpp String PORT = "com.android.trusty.binder.test.service";
+
+    const int TEST_CONSTANT = 42;
+    const int TEST_CONSTANT2 = -42;
+    const int TEST_CONSTANT3 = +42;
+    const int TEST_CONSTANT4 = +4;
+    const int TEST_CONSTANT5 = -4;
+    const int TEST_CONSTANT6 = -0;
+    const int TEST_CONSTANT7 = +0;
+    const int TEST_CONSTANT8 = 0;
+    const int TEST_CONSTANT9 = 0x56;
+    const int TEST_CONSTANT10 = 0xa5;
+    const int TEST_CONSTANT11 = 0xFA;
+    const int TEST_CONSTANT12 = 0xffffffff;
+
+    const byte BYTE_TEST_CONSTANT = 17;
+    const long LONG_TEST_CONSTANT = 1L << 40;
+
+    const String STRING_TEST_CONSTANT = "foo";
+    const String STRING_TEST_CONSTANT2 = "bar";
+
+    // Test that primitives work as parameters and return types.
+    boolean RepeatBoolean(boolean token);
+    byte RepeatByte(byte token);
+    char RepeatChar(char token);
+    int RepeatInt(int token);
+    long RepeatLong(long token);
+    float RepeatFloat(float token);
+    double RepeatDouble(double token);
+    String RepeatString(String token);
+    ByteEnum RepeatByteEnum(ByteEnum token);
+    IntEnum RepeatIntEnum(IntEnum token);
+    LongEnum RepeatLongEnum(LongEnum token);
+
+    // Test that arrays work as parameters and return types.
+    boolean[] ReverseBoolean(in boolean[] input, out boolean[] repeated);
+    byte[] ReverseByte(in byte[] input, out byte[] repeated);
+    char[] ReverseChar(in char[] input, out char[] repeated);
+    int[] ReverseInt(in int[] input, out int[] repeated);
+    long[] ReverseLong(in long[] input, out long[] repeated);
+    float[] ReverseFloat(in float[] input, out float[] repeated);
+    double[] ReverseDouble(in double[] input, out double[] repeated);
+    String[] ReverseString(in String[] input, out String[] repeated);
+    ByteEnum[] ReverseByteEnum(in ByteEnum[] input, out ByteEnum[] repeated);
+    IntEnum[] ReverseIntEnum(in IntEnum[] input, out IntEnum[] repeated);
+    LongEnum[] ReverseLongEnum(in LongEnum[] input, out LongEnum[] repeated);
+}
diff --git a/trusty/test/binder/aidl/IntEnum.aidl b/trusty/test/binder/aidl/IntEnum.aidl
new file mode 100644
index 0000000..120e44f
--- /dev/null
+++ b/trusty/test/binder/aidl/IntEnum.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@JavaDerive(toString=true)
+@Backing(type="int")
+enum IntEnum {
+    FOO = 1000,
+    BAR = 2000,
+    BAZ,
+    /** @deprecated do not use this */
+    QUX,
+}
diff --git a/trusty/test/binder/aidl/LongEnum.aidl b/trusty/test/binder/aidl/LongEnum.aidl
new file mode 100644
index 0000000..0e9e933
--- /dev/null
+++ b/trusty/test/binder/aidl/LongEnum.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@Backing(type="long")
+enum LongEnum {
+    FOO = 100000000000,
+    BAR = 200000000000,
+    BAZ,
+}
diff --git a/trusty/test/binder/aidl/rules.mk b/trusty/test/binder/aidl/rules.mk
new file mode 100644
index 0000000..6154abb
--- /dev/null
+++ b/trusty/test/binder/aidl/rules.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_AIDLS := \
+	$(LOCAL_DIR)/ByteEnum.aidl \
+	$(LOCAL_DIR)/IntEnum.aidl \
+	$(LOCAL_DIR)/ITestService.aidl \
+	$(LOCAL_DIR)/LongEnum.aidl \
+
+include make/aidl.mk