Merge "Diced: Added AID for Android's dice daemon diced."
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 0ff047f..edcea44 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -283,6 +283,7 @@
         "libdebuggerd/test/log_fake.cpp",
         "libdebuggerd/test/open_files_list_test.cpp",
         "libdebuggerd/test/tombstone_test.cpp",
+        "libdebuggerd/test/utility_test.cpp",
     ],
 
     target: {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 4394274..b107767 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -18,6 +18,7 @@
 #include <dlfcn.h>
 #include <err.h>
 #include <fcntl.h>
+#include <linux/prctl.h>
 #include <malloc.h>
 #include <stdlib.h>
 #include <sys/capability.h>
@@ -31,6 +32,7 @@
 
 #include <chrono>
 #include <regex>
+#include <set>
 #include <string>
 #include <thread>
 
@@ -54,6 +56,9 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <unwindstack/Elf.h>
+#include <unwindstack/Memory.h>
+
 #include <libminijail.h>
 #include <scoped_minijail.h>
 
@@ -340,11 +345,17 @@
 
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
-  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0xdead)");
+#ifdef __LP64__
+  ASSERT_MATCH(result,
+               R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x000000000000dead)");
+#else
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0000dead)");
+#endif
 
   if (mte_supported()) {
     // Test that the default TAGGED_ADDR_CTRL value is set.
-    ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)");
+    ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)"
+                         R"( \(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe\))");
   }
 }
 
@@ -370,8 +381,7 @@
 
   // The address can either be tagged (new kernels) or untagged (old kernels).
   ASSERT_MATCH(
-      result,
-      R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr (0x100000000000dead|0xdead))");
+      result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x[01]00000000000dead)");
 }
 
 // Marked as weak to prevent the compiler from removing the malloc in the caller. In theory, the
@@ -422,6 +432,12 @@
     abort();
   }
 }
+
+static void SetTagCheckingLevelAsync() {
+  if (mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, M_HEAP_TAGGING_LEVEL_ASYNC) == 0) {
+    abort();
+  }
+}
 #endif
 
 // Number of iterations required to reliably guarantee a GWP-ASan crash.
@@ -653,6 +669,36 @@
 #endif
 }
 
+TEST_F(CrasherTest, mte_async) {
+#if defined(__aarch64__)
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&]() {
+    SetTagCheckingLevelAsync();
+    volatile int* p = (volatile int*)malloc(16);
+    p[-1] = 42;
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 8 \(SEGV_MTEAERR\), fault addr --------)");
+#else
+  GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
 TEST_F(CrasherTest, mte_multiple_causes) {
 #if defined(__aarch64__)
   if (!mte_supported()) {
@@ -703,7 +749,7 @@
   for (const auto& result : log_sources) {
     ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
     ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, "
-                                  "listing them in decreasing order of probability."));
+                                  "listing them in decreasing order of likelihood."));
     // Adjacent untracked allocations may cause us to see the wrong underflow here (or only
     // overflows), so we can't match explicitly for an underflow message.
     ASSERT_MATCH(result,
@@ -890,7 +936,7 @@
 
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
-  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0xdead)");
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+dead)");
 }
 
 TEST_F(CrasherTest, abort) {
@@ -1940,7 +1986,7 @@
 
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
-  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x1024)");
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+1024)");
 
   ASSERT_MATCH(result, R"(\nmemory map \(.*\):\n)");
 
@@ -1970,8 +2016,8 @@
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
 
-  std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr )";
-  match_str += android::base::StringPrintf("0x%" PRIxPTR, crash_uptr);
+  std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x)";
+  match_str += format_full_pointer(crash_uptr);
   ASSERT_MATCH(result, match_str);
 
   ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
@@ -2018,8 +2064,8 @@
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
 
-  std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr )";
-  match_str += android::base::StringPrintf("%p", middle_ptr);
+  std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x)";
+  match_str += format_full_pointer(reinterpret_cast<uintptr_t>(middle_ptr));
   ASSERT_MATCH(result, match_str);
 
   ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
@@ -2056,8 +2102,8 @@
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
 
-  std::string match_str = R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\), fault addr )";
-  match_str += android::base::StringPrintf("%p", ptr);
+  std::string match_str = R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\), fault addr 0x)";
+  match_str += format_full_pointer(reinterpret_cast<uintptr_t>(ptr));
   ASSERT_MATCH(result, match_str);
 
   ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
@@ -2181,8 +2227,214 @@
   ConsumeFd(std::move(output_fd), &result);
 
   // Verify the process crashed properly.
-  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0)");
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0*)");
 
   // Now verify that the dex_pc frame includes a proper function name.
   ASSERT_MATCH(result, R"( \[anon:dex\] \(Main\.\<init\>\+2)");
 }
+
+static std::string format_map_pointer(uintptr_t ptr) {
+#if defined(__LP64__)
+  return android::base::StringPrintf("%08x'%08x", static_cast<uint32_t>(ptr >> 32),
+                                     static_cast<uint32_t>(ptr & 0xffffffff));
+#else
+  return android::base::StringPrintf("%08x", ptr);
+#endif
+}
+
+// Verify that map data is properly formatted.
+TEST_F(CrasherTest, verify_map_format) {
+  // Create multiple maps to make sure that the map data is formatted properly.
+  void* none_map = mmap(nullptr, getpagesize(), 0, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  ASSERT_NE(MAP_FAILED, none_map);
+  void* r_map = mmap(nullptr, getpagesize(), PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  ASSERT_NE(MAP_FAILED, r_map);
+  void* w_map = mmap(nullptr, getpagesize(), PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  ASSERT_NE(MAP_FAILED, w_map);
+  void* x_map = mmap(nullptr, getpagesize(), PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  ASSERT_NE(MAP_FAILED, x_map);
+
+  TemporaryFile tf;
+  ASSERT_EQ(0x2000, lseek(tf.fd, 0x2000, SEEK_SET));
+  char c = 'f';
+  ASSERT_EQ(1, write(tf.fd, &c, 1));
+  ASSERT_EQ(0x5000, lseek(tf.fd, 0x5000, SEEK_SET));
+  ASSERT_EQ(1, write(tf.fd, &c, 1));
+  ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET));
+  void* file_map = mmap(nullptr, 0x3001, PROT_READ, MAP_PRIVATE, tf.fd, 0x2000);
+  ASSERT_NE(MAP_FAILED, file_map);
+
+  StartProcess([]() { abort(); });
+
+  ASSERT_EQ(0, munmap(none_map, getpagesize()));
+  ASSERT_EQ(0, munmap(r_map, getpagesize()));
+  ASSERT_EQ(0, munmap(w_map, getpagesize()));
+  ASSERT_EQ(0, munmap(x_map, getpagesize()));
+  ASSERT_EQ(0, munmap(file_map, 0x3001));
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  std::string match_str;
+  // Verify none.
+  match_str = android::base::StringPrintf(
+      "    %s-%s ---         0      1000\\n",
+      format_map_pointer(reinterpret_cast<uintptr_t>(none_map)).c_str(),
+      format_map_pointer(reinterpret_cast<uintptr_t>(none_map) + getpagesize() - 1).c_str());
+  ASSERT_MATCH(result, match_str);
+
+  // Verify read-only.
+  match_str = android::base::StringPrintf(
+      "    %s-%s r--         0      1000\\n",
+      format_map_pointer(reinterpret_cast<uintptr_t>(r_map)).c_str(),
+      format_map_pointer(reinterpret_cast<uintptr_t>(r_map) + getpagesize() - 1).c_str());
+  ASSERT_MATCH(result, match_str);
+
+  // Verify write-only.
+  match_str = android::base::StringPrintf(
+      "    %s-%s -w-         0      1000\\n",
+      format_map_pointer(reinterpret_cast<uintptr_t>(w_map)).c_str(),
+      format_map_pointer(reinterpret_cast<uintptr_t>(w_map) + getpagesize() - 1).c_str());
+  ASSERT_MATCH(result, match_str);
+
+  // Verify exec-only.
+  match_str = android::base::StringPrintf(
+      "    %s-%s --x         0      1000\\n",
+      format_map_pointer(reinterpret_cast<uintptr_t>(x_map)).c_str(),
+      format_map_pointer(reinterpret_cast<uintptr_t>(x_map) + getpagesize() - 1).c_str());
+  ASSERT_MATCH(result, match_str);
+
+  // Verify file map with non-zero offset and a name.
+  match_str = android::base::StringPrintf(
+      "    %s-%s r--      2000      4000  %s\\n",
+      format_map_pointer(reinterpret_cast<uintptr_t>(file_map)).c_str(),
+      format_map_pointer(reinterpret_cast<uintptr_t>(file_map) + 0x3fff).c_str(), tf.path);
+  ASSERT_MATCH(result, match_str);
+}
+
+// Verify that the tombstone map data is correct.
+TEST_F(CrasherTest, verify_header) {
+  StartProcess([]() { abort(); });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  std::string match_str = android::base::StringPrintf(
+      "Build fingerprint: '%s'\\nRevision: '%s'\\n",
+      android::base::GetProperty("ro.build.fingerprint", "unknown").c_str(),
+      android::base::GetProperty("ro.revision", "unknown").c_str());
+  match_str += android::base::StringPrintf("ABI: '%s'\n", ABI_STRING);
+  ASSERT_MATCH(result, match_str);
+}
+
+// Verify that the thread header is formatted properly.
+TEST_F(CrasherTest, verify_thread_header) {
+  void* shared_map =
+      mmap(nullptr, sizeof(pid_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+  ASSERT_NE(MAP_FAILED, shared_map);
+  memset(shared_map, 0, sizeof(pid_t));
+
+  StartProcess([&shared_map]() {
+    std::atomic_bool tid_written;
+    std::thread thread([&tid_written, &shared_map]() {
+      pid_t tid = gettid();
+      memcpy(shared_map, &tid, sizeof(pid_t));
+      tid_written = true;
+      volatile bool done = false;
+      while (!done)
+        ;
+    });
+    thread.detach();
+    while (!tid_written.load(std::memory_order_acquire))
+      ;
+    abort();
+  });
+
+  pid_t primary_pid = crasher_pid;
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  // Read the tid data out.
+  pid_t tid;
+  memcpy(&tid, shared_map, sizeof(pid_t));
+  ASSERT_NE(0, tid);
+
+  ASSERT_EQ(0, munmap(shared_map, sizeof(pid_t)));
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  // Verify that there are two headers, one where the tid is "primary_pid"
+  // and the other where the tid is "tid".
+  std::string match_str = android::base::StringPrintf("pid: %d, tid: %d, name: .*  >>> .* <<<\\n",
+                                                      primary_pid, primary_pid);
+  ASSERT_MATCH(result, match_str);
+
+  match_str =
+      android::base::StringPrintf("pid: %d, tid: %d, name: .*  >>> .* <<<\\n", primary_pid, tid);
+  ASSERT_MATCH(result, match_str);
+}
+
+// Verify that there is a BuildID present in the map section and set properly.
+TEST_F(CrasherTest, verify_build_id) {
+  StartProcess([]() { abort(); });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  // Find every /system or /apex lib and verify the BuildID is displayed
+  // properly.
+  bool found_valid_elf = false;
+  std::smatch match;
+  std::regex build_id_regex(R"(  ((/system/|/apex/)\S+) \(BuildId: ([^\)]+)\))");
+  for (std::string prev_file; std::regex_search(result, match, build_id_regex);
+       result = match.suffix()) {
+    if (prev_file == match[1]) {
+      // Already checked this file.
+      continue;
+    }
+
+    prev_file = match[1];
+    unwindstack::Elf elf(unwindstack::Memory::CreateFileMemory(prev_file, 0).release());
+    if (!elf.Init() || !elf.valid()) {
+      // Skipping invalid elf files.
+      continue;
+    }
+    ASSERT_EQ(match[3], elf.GetPrintableBuildID());
+
+    found_valid_elf = true;
+  }
+  ASSERT_TRUE(found_valid_elf) << "Did not find any elf files with valid BuildIDs to check.";
+}
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 24ae169..002321f 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -92,6 +92,7 @@
 void get_signal_sender(char* buf, size_t n, const siginfo_t*);
 const char* get_signame(const siginfo_t*);
 const char* get_sigcode(const siginfo_t*);
+std::string describe_tagged_addr_ctrl(long ctrl);
 
 // Number of bytes per MTE granule.
 constexpr size_t kTagGranuleSize = 16;
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index f4690ba..a89f385 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -135,7 +135,7 @@
   if (error_info_.reports[1].error_type != UNKNOWN) {
     _LOG(log, logtype::HEADER,
          "\nNote: multiple potential causes for this crash were detected, listing them in "
-         "decreasing order of probability.\n");
+         "decreasing order of likelihood.\n");
   }
 
   size_t report_num = 0;
diff --git a/debuggerd/libdebuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp
index a14dcb0..1cbfb56 100644
--- a/debuggerd/libdebuggerd/test/tombstone_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp
@@ -32,9 +32,6 @@
 #include "host_signal_fixup.h"
 #include "log_fake.h"
 
-// Include tombstone.cpp to define log_tag before GWP-ASan includes log.
-#include "tombstone.cpp"
-
 #include "gwp_asan.cpp"
 
 using ::testing::MatchesRegex;
@@ -82,283 +79,6 @@
   std::string amfd_data_;
 };
 
-TEST_F(TombstoneTest, single_map) {
-#if defined(__LP64__)
-  unwinder_mock_->MockAddMap(0x123456789abcd000UL, 0x123456789abdf000UL, 0, 0, "", 0);
-#else
-  unwinder_mock_->MockAddMap(0x1234000, 0x1235000, 0, 0, "", 0);
-#endif
-
-  dump_all_maps(&log_, unwinder_mock_.get(), 0);
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory map (1 entry):\n"
-#if defined(__LP64__)
-"    12345678'9abcd000-12345678'9abdefff ---         0     12000\n";
-#else
-"    01234000-01234fff ---         0      1000\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
-  ASSERT_STREQ("", amfd_data_.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, single_map_elf_build_id) {
-  uint64_t build_id_offset;
-#if defined(__LP64__)
-  build_id_offset = 0x123456789abcd000UL;
-  unwinder_mock_->MockAddMap(build_id_offset, 0x123456789abdf000UL, 0, PROT_READ,
-                             "/system/lib/libfake.so", 0);
-#else
-  build_id_offset = 0x1234000;
-  unwinder_mock_->MockAddMap(0x1234000, 0x1235000, 0, PROT_READ, "/system/lib/libfake.so", 0);
-#endif
-
-  unwinder_mock_->MockSetBuildID(
-      build_id_offset,
-      std::string{static_cast<char>(0xab), static_cast<char>(0xcd), static_cast<char>(0xef),
-                  static_cast<char>(0x12), static_cast<char>(0x34), static_cast<char>(0x56),
-                  static_cast<char>(0x78), static_cast<char>(0x90), static_cast<char>(0xab),
-                  static_cast<char>(0xcd), static_cast<char>(0xef), static_cast<char>(0x12),
-                  static_cast<char>(0x34), static_cast<char>(0x56), static_cast<char>(0x78),
-                  static_cast<char>(0x90)});
-  dump_all_maps(&log_, unwinder_mock_.get(), 0);
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump = \
-"\nmemory map (1 entry):\n"
-#if defined(__LP64__)
-"    12345678'9abcd000-12345678'9abdefff r--         0     12000  /system/lib/libfake.so (BuildId: abcdef1234567890abcdef1234567890)\n";
-#else
-"    01234000-01234fff r--         0      1000  /system/lib/libfake.so (BuildId: abcdef1234567890abcdef1234567890)\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
-  ASSERT_STREQ("", amfd_data_.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps) {
-  unwinder_mock_->MockAddMap(0xa234000, 0xa235000, 0, 0, "", 0);
-  unwinder_mock_->MockAddMap(0xa334000, 0xa335000, 0xf000, PROT_READ, "", 0);
-  unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
-  unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
-  unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
-                             "/system/lib/fake.so", 0);
-
-  dump_all_maps(&log_, unwinder_mock_.get(), 0);
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump =
-      "\nmemory map (5 entries):\n"
-#if defined(__LP64__)
-      "    00000000'0a234000-00000000'0a234fff ---         0      1000\n"
-      "    00000000'0a334000-00000000'0a334fff r--      f000      1000\n"
-      "    00000000'0a434000-00000000'0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "    00000000'0a534000-00000000'0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    00000000'0a634000-00000000'0a634fff rwx         0      1000  /system/lib/fake.so\n";
-#else
-      "    0a234000-0a234fff ---         0      1000\n"
-      "    0a334000-0a334fff r--      f000      1000\n"
-      "    0a434000-0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "    0a534000-0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    0a634000-0a634fff rwx         0      1000  /system/lib/fake.so\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
-  ASSERT_STREQ("", amfd_data_.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps_fault_address_before) {
-  unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
-  unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
-  unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
-                             "/system/lib/fake.so", 0);
-
-  dump_all_maps(&log_, unwinder_mock_.get(), 0x1000);
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump =
-      "\nmemory map (3 entries):\n"
-#if defined(__LP64__)
-      "--->Fault address falls at 00000000'00001000 before any mapped regions\n"
-      "    00000000'0a434000-00000000'0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "    00000000'0a534000-00000000'0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    00000000'0a634000-00000000'0a634fff rwx         0      1000  /system/lib/fake.so\n";
-#else
-      "--->Fault address falls at 00001000 before any mapped regions\n"
-      "    0a434000-0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "    0a534000-0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    0a634000-0a634fff rwx         0      1000  /system/lib/fake.so\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
-  ASSERT_STREQ("", amfd_data_.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps_fault_address_between) {
-  unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
-  unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
-  unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
-                             "/system/lib/fake.so", 0);
-
-  dump_all_maps(&log_, unwinder_mock_.get(), 0xa533000);
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump =
-      "\nmemory map (3 entries): (fault address prefixed with --->)\n"
-#if defined(__LP64__)
-      "    00000000'0a434000-00000000'0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "--->Fault address falls at 00000000'0a533000 between mapped regions\n"
-      "    00000000'0a534000-00000000'0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    00000000'0a634000-00000000'0a634fff rwx         0      1000  /system/lib/fake.so\n";
-#else
-      "    0a434000-0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "--->Fault address falls at 0a533000 between mapped regions\n"
-      "    0a534000-0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    0a634000-0a634fff rwx         0      1000  /system/lib/fake.so\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
-  ASSERT_STREQ("", amfd_data_.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps_fault_address_in_map) {
-  unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
-  unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
-  unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
-                             "/system/lib/fake.so", 0);
-
-  dump_all_maps(&log_, unwinder_mock_.get(), 0xa534040);
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump =
-      "\nmemory map (3 entries): (fault address prefixed with --->)\n"
-#if defined(__LP64__)
-      "    00000000'0a434000-00000000'0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "--->00000000'0a534000-00000000'0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    00000000'0a634000-00000000'0a634fff rwx         0      1000  /system/lib/fake.so\n";
-#else
-      "    0a434000-0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "--->0a534000-0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    0a634000-0a634fff rwx         0      1000  /system/lib/fake.so\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
-  ASSERT_STREQ("", amfd_data_.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps_fault_address_after) {
-  unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
-  unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
-  unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
-                             "/system/lib/fake.so", 0);
-
-#if defined(__LP64__)
-  uint64_t addr = 0x12345a534040UL;
-#else
-  uint64_t addr = 0xf534040UL;
-#endif
-  dump_all_maps(&log_, unwinder_mock_.get(), addr);
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  const char* expected_dump =
-      "\nmemory map (3 entries): (fault address prefixed with --->)\n"
-#if defined(__LP64__)
-      "    00000000'0a434000-00000000'0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "    00000000'0a534000-00000000'0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    00000000'0a634000-00000000'0a634fff rwx         0      1000  /system/lib/fake.so\n"
-      "--->Fault address falls at 00001234'5a534040 after any mapped regions\n";
-#else
-      "    0a434000-0a434fff -w-      1000      1000  (load bias 0xd000)\n"
-      "    0a534000-0a534fff --x      3000      1000  (load bias 0x2000)\n"
-      "    0a634000-0a634fff rwx         0      1000  /system/lib/fake.so\n"
-      "--->Fault address falls at 0f534040 after any mapped regions\n";
-#endif
-  ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
-  ASSERT_STREQ("", amfd_data_.c_str());
-
-  // Verify that the log buf is empty, and no error messages.
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, dump_log_file_error) {
-  log_.should_retrieve_logcat = true;
-  dump_log_file(&log_, 123, "/fake/filename", 10);
-
-  std::string tombstone_contents;
-  ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
-  ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_STREQ("", tombstone_contents.c_str());
-
-  ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("6 DEBUG Unable to open /fake/filename: Permission denied\n\n",
-               getFakeLogPrint().c_str());
-
-  ASSERT_STREQ("", amfd_data_.c_str());
-}
-
-TEST_F(TombstoneTest, dump_header_info) {
-  dump_header_info(&log_);
-
-  std::string expected = android::base::StringPrintf(
-      "Build fingerprint: '%s'\nRevision: '%s'\n",
-      android::base::GetProperty("ro.build.fingerprint", "unknown").c_str(),
-      android::base::GetProperty("ro.revision", "unknown").c_str());
-  expected += android::base::StringPrintf("ABI: '%s'\n", ABI_STRING);
-  ASSERT_STREQ(expected.c_str(), amfd_data_.c_str());
-}
-
-TEST_F(TombstoneTest, dump_thread_info_uid) {
-  std::vector<std::string> cmdline = {"some_process"};
-  dump_thread_info(
-      &log_,
-      ThreadInfo{
-          .uid = 1, .tid = 3, .thread_name = "some_thread", .pid = 2, .command_line = cmdline});
-  std::string expected = "pid: 2, tid: 3, name: some_thread  >>> some_process <<<\nuid: 1\n";
-  ASSERT_STREQ(expected.c_str(), amfd_data_.c_str());
-}
-
 class GwpAsanCrashDataTest : public GwpAsanCrashData {
 public:
   GwpAsanCrashDataTest(
@@ -483,4 +203,3 @@
           "Cause: \\[GWP-ASan\\]: Invalid \\(Wild\\) Free, 33 bytes right of a 32-byte "
           "allocation at 0x[a-fA-F0-9]+\n"));
 }
-
diff --git a/debuggerd/libdebuggerd/test/utility_test.cpp b/debuggerd/libdebuggerd/test/utility_test.cpp
new file mode 100644
index 0000000..97328b7
--- /dev/null
+++ b/debuggerd/libdebuggerd/test/utility_test.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <sys/prctl.h>
+
+#include "libdebuggerd/utility.h"
+
+TEST(UtilityTest, describe_tagged_addr_ctrl) {
+  EXPECT_EQ("", describe_tagged_addr_ctrl(0));
+  EXPECT_EQ(" (PR_TAGGED_ADDR_ENABLE)", describe_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE));
+  EXPECT_EQ(" (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe)",
+            describe_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+                                      (0xfffe << PR_MTE_TAG_SHIFT)));
+  EXPECT_EQ(
+      " (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, PR_MTE_TCF_ASYNC, mask 0xfffe, unknown "
+      "0xf0000000)",
+      describe_tagged_addr_ctrl(0xf0000000 | PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+                                PR_MTE_TCF_ASYNC | (0xfffe << PR_MTE_TAG_SHIFT)));
+}
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 1835f0e..f21a203 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -18,565 +18,38 @@
 
 #include "libdebuggerd/tombstone.h"
 
-#include <dirent.h>
 #include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
 #include <signal.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/ptrace.h>
-#include <sys/stat.h>
-#include <time.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include <memory>
 #include <string>
 
 #include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <android/log.h>
 #include <async_safe/log.h>
-#include <bionic/macros.h>
 #include <log/log.h>
-#include <log/log_read.h>
-#include <log/logprint.h>
 #include <private/android_filesystem_config.h>
-#include <unwindstack/DexFiles.h>
-#include <unwindstack/JitDebug.h>
-#include <unwindstack/Maps.h>
 #include <unwindstack/Memory.h>
 #include <unwindstack/Regs.h>
 #include <unwindstack/Unwinder.h>
 
 #include "libdebuggerd/backtrace.h"
-#include "libdebuggerd/gwp_asan.h"
 #include "libdebuggerd/open_files_list.h"
 #include "libdebuggerd/utility.h"
 #include "util.h"
 
-#if defined(USE_SCUDO)
-#include "libdebuggerd/scudo.h"
-#endif
-
-#include "gwp_asan/common.h"
-#include "gwp_asan/crash_handler.h"
-
 #include "tombstone.pb.h"
 
-using android::base::GetBoolProperty;
-using android::base::GetProperty;
-using android::base::StringPrintf;
 using android::base::unique_fd;
 
 using namespace std::literals::string_literals;
 
-#define STACK_WORDS 16
-
-static void dump_header_info(log_t* log) {
-  auto fingerprint = GetProperty("ro.build.fingerprint", "unknown");
-  auto revision = GetProperty("ro.revision", "unknown");
-
-  _LOG(log, logtype::HEADER, "Build fingerprint: '%s'\n", fingerprint.c_str());
-  _LOG(log, logtype::HEADER, "Revision: '%s'\n", revision.c_str());
-  _LOG(log, logtype::HEADER, "ABI: '%s'\n", ABI_STRING);
-}
-
-static std::string get_stack_overflow_cause(uint64_t fault_addr, uint64_t sp,
-                                            unwindstack::Maps* maps) {
-  static constexpr uint64_t kMaxDifferenceBytes = 256;
-  uint64_t difference;
-  if (sp >= fault_addr) {
-    difference = sp - fault_addr;
-  } else {
-    difference = fault_addr - sp;
-  }
-  if (difference <= kMaxDifferenceBytes) {
-    // The faulting address is close to the current sp, check if the sp
-    // indicates a stack overflow.
-    // On arm, the sp does not get updated when the instruction faults.
-    // In this case, the sp will still be in a valid map, which is the
-    // last case below.
-    // On aarch64, the sp does get updated when the instruction faults.
-    // In this case, the sp will be in either an invalid map if triggered
-    // on the main thread, or in a guard map if in another thread, which
-    // will be the first case or second case from below.
-    auto map_info = maps->Find(sp);
-    if (map_info == nullptr) {
-      return "stack pointer is in a non-existent map; likely due to stack overflow.";
-    } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
-      return "stack pointer is not in a rw map; likely due to stack overflow.";
-    } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) {
-      return "stack pointer is close to top of stack; likely stack overflow.";
-    }
-  }
-  return "";
-}
-
-static void dump_probable_cause(log_t* log, unwindstack::Unwinder* unwinder,
-                                const ProcessInfo& process_info, const ThreadInfo& main_thread) {
-#if defined(USE_SCUDO)
-  ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
-  if (scudo_crash_data.CrashIsMine()) {
-    scudo_crash_data.DumpCause(log, unwinder);
-    return;
-  }
-#endif
-
-  GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
-                                       main_thread);
-  if (gwp_asan_crash_data.CrashIsMine()) {
-    gwp_asan_crash_data.DumpCause(log);
-    return;
-  }
-
-  unwindstack::Maps* maps = unwinder->GetMaps();
-  unwindstack::Regs* regs = main_thread.registers.get();
-  const siginfo_t* si = main_thread.siginfo;
-  std::string cause;
-  if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) {
-    if (si->si_addr < reinterpret_cast<void*>(4096)) {
-      cause = StringPrintf("null pointer dereference");
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0ffc)) {
-      cause = "call to kuser_helper_version";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fe0)) {
-      cause = "call to kuser_get_tls";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fc0)) {
-      cause = "call to kuser_cmpxchg";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fa0)) {
-      cause = "call to kuser_memory_barrier";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0f60)) {
-      cause = "call to kuser_cmpxchg64";
-    } else {
-      cause = get_stack_overflow_cause(reinterpret_cast<uint64_t>(si->si_addr), regs->sp(), maps);
-    }
-  } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
-    uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
-    auto map_info = maps->Find(fault_addr);
-    if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
-      cause = "execute-only (no-read) memory access error; likely due to data in .text.";
-    } else {
-      cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps);
-    }
-  } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
-    cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
-                         si->si_syscall);
-  }
-
-  if (!cause.empty()) _LOG(log, logtype::HEADER, "Cause: %s\n", cause.c_str());
-}
-
-static void dump_signal_info(log_t* log, const ThreadInfo& thread_info,
-                             const ProcessInfo& process_info, unwindstack::Memory* process_memory) {
-  char addr_desc[64];  // ", fault addr 0x1234"
-  if (process_info.has_fault_address) {
-    // SIGILL faults will never have tagged addresses, so okay to
-    // indiscriminately use the tagged address here.
-    size_t addr = process_info.maybe_tagged_fault_address;
-    if (thread_info.siginfo->si_signo == SIGILL) {
-      uint32_t instruction = {};
-      process_memory->Read(addr, &instruction, sizeof(instruction));
-      snprintf(addr_desc, sizeof(addr_desc), "0x%zx (*pc=%#08x)", addr, instruction);
-    } else {
-      snprintf(addr_desc, sizeof(addr_desc), "0x%zx", addr);
-    }
-  } else {
-    snprintf(addr_desc, sizeof(addr_desc), "--------");
-  }
-
-  char sender_desc[32] = {};  // " from pid 1234, uid 666"
-  if (signal_has_sender(thread_info.siginfo, thread_info.pid)) {
-    get_signal_sender(sender_desc, sizeof(sender_desc), thread_info.siginfo);
-  }
-
-  _LOG(log, logtype::HEADER, "signal %d (%s), code %d (%s%s), fault addr %s\n",
-       thread_info.siginfo->si_signo, get_signame(thread_info.siginfo),
-       thread_info.siginfo->si_code, get_sigcode(thread_info.siginfo), sender_desc, addr_desc);
-}
-
-static void dump_thread_info(log_t* log, const ThreadInfo& thread_info) {
-  // Don't try to collect logs from the threads that implement the logging system itself.
-  if (thread_info.uid == AID_LOGD) log->should_retrieve_logcat = false;
-
-  const char* process_name = "<unknown>";
-  if (!thread_info.command_line.empty()) {
-    process_name = thread_info.command_line[0].c_str();
-  }
-
-  _LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s  >>> %s <<<\n", thread_info.pid,
-       thread_info.tid, thread_info.thread_name.c_str(), process_name);
-  _LOG(log, logtype::HEADER, "uid: %d\n", thread_info.uid);
-  if (thread_info.tagged_addr_ctrl != -1) {
-    _LOG(log, logtype::HEADER, "tagged_addr_ctrl: %016lx\n", thread_info.tagged_addr_ctrl);
-  }
-}
-
-static std::string get_addr_string(uint64_t addr) {
-  std::string addr_str;
-#if defined(__LP64__)
-  addr_str = StringPrintf("%08x'%08x", static_cast<uint32_t>(addr >> 32),
-                          static_cast<uint32_t>(addr & 0xffffffff));
-#else
-  addr_str = StringPrintf("%08x", static_cast<uint32_t>(addr));
-#endif
-  return addr_str;
-}
-
-static void dump_abort_message(log_t* log, unwindstack::Memory* process_memory, uint64_t address) {
-  if (address == 0) {
-    return;
-  }
-
-  size_t length;
-  if (!process_memory->ReadFully(address, &length, sizeof(length))) {
-    _LOG(log, logtype::HEADER, "Failed to read abort message header: %s\n", strerror(errno));
-    return;
-  }
-
-  // The length field includes the length of the length field itself.
-  if (length < sizeof(size_t)) {
-    _LOG(log, logtype::HEADER, "Abort message header malformed: claimed length = %zd\n", length);
-    return;
-  }
-
-  length -= sizeof(size_t);
-
-  // The abort message should be null terminated already, but reserve a spot for NUL just in case.
-  std::vector<char> msg(length + 1);
-  if (!process_memory->ReadFully(address + sizeof(length), &msg[0], length)) {
-    _LOG(log, logtype::HEADER, "Failed to read abort message: %s\n", strerror(errno));
-    return;
-  }
-
-  // Remove any trailing newlines.
-  size_t index = length;
-  while (index > 0 && (msg[index - 1] == '\0' || msg[index - 1] == '\n')) {
-    --index;
-  }
-  msg[index] = '\0';
-  _LOG(log, logtype::HEADER, "Abort message: '%s'\n", &msg[0]);
-}
-
-static void dump_all_maps(log_t* log, unwindstack::Unwinder* unwinder, uint64_t addr) {
-  bool print_fault_address_marker = addr;
-
-  unwindstack::Maps* maps = unwinder->GetMaps();
-  _LOG(log, logtype::MAPS,
-       "\n"
-       "memory map (%zu entr%s):",
-       maps->Total(), maps->Total() == 1 ? "y" : "ies");
-  if (print_fault_address_marker) {
-    if (maps->Total() != 0 && addr < maps->Get(0)->start()) {
-      _LOG(log, logtype::MAPS, "\n--->Fault address falls at %s before any mapped regions\n",
-           get_addr_string(addr).c_str());
-      print_fault_address_marker = false;
-    } else {
-      _LOG(log, logtype::MAPS, " (fault address prefixed with --->)\n");
-    }
-  } else {
-    _LOG(log, logtype::MAPS, "\n");
-  }
-
-  std::shared_ptr<unwindstack::Memory>& process_memory = unwinder->GetProcessMemory();
-
-  std::string line;
-  for (auto const& map_info : *maps) {
-    line = "    ";
-    if (print_fault_address_marker) {
-      if (addr < map_info->start()) {
-        _LOG(log, logtype::MAPS, "--->Fault address falls at %s between mapped regions\n",
-             get_addr_string(addr).c_str());
-        print_fault_address_marker = false;
-      } else if (addr >= map_info->start() && addr < map_info->end()) {
-        line = "--->";
-        print_fault_address_marker = false;
-      }
-    }
-    line += get_addr_string(map_info->start()) + '-' + get_addr_string(map_info->end() - 1) + ' ';
-    if (map_info->flags() & PROT_READ) {
-      line += 'r';
-    } else {
-      line += '-';
-    }
-    if (map_info->flags() & PROT_WRITE) {
-      line += 'w';
-    } else {
-      line += '-';
-    }
-    if (map_info->flags() & PROT_EXEC) {
-      line += 'x';
-    } else {
-      line += '-';
-    }
-    line += StringPrintf("  %8" PRIx64 "  %8" PRIx64, map_info->offset(),
-                         map_info->end() - map_info->start());
-    bool space_needed = true;
-    if (!map_info->name().empty()) {
-      space_needed = false;
-      line += "  " + map_info->name();
-      std::string build_id = map_info->GetPrintableBuildID();
-      if (!build_id.empty()) {
-        line += " (BuildId: " + build_id + ")";
-      }
-    }
-    uint64_t load_bias = map_info->GetLoadBias(process_memory);
-    if (load_bias != 0) {
-      if (space_needed) {
-        line += ' ';
-      }
-      line += StringPrintf(" (load bias 0x%" PRIx64 ")", load_bias);
-    }
-    _LOG(log, logtype::MAPS, "%s\n", line.c_str());
-  }
-  if (print_fault_address_marker) {
-    _LOG(log, logtype::MAPS, "--->Fault address falls at %s after any mapped regions\n",
-         get_addr_string(addr).c_str());
-  }
-}
-
-static void print_register_row(log_t* log,
-                               const std::vector<std::pair<std::string, uint64_t>>& registers) {
-  std::string output;
-  for (auto& [name, value] : registers) {
-    output += android::base::StringPrintf("  %-3s %0*" PRIx64, name.c_str(),
-                                          static_cast<int>(2 * sizeof(void*)),
-                                          static_cast<uint64_t>(value));
-  }
-
-  _LOG(log, logtype::REGISTERS, "  %s\n", output.c_str());
-}
-
-void dump_registers(log_t* log, unwindstack::Regs* regs) {
-  // Split lr/sp/pc into their own special row.
-  static constexpr size_t column_count = 4;
-  std::vector<std::pair<std::string, uint64_t>> current_row;
-  std::vector<std::pair<std::string, uint64_t>> special_row;
-
-#if defined(__arm__) || defined(__aarch64__)
-  static constexpr const char* special_registers[] = {"ip", "lr", "sp", "pc", "pst"};
-#elif defined(__i386__)
-  static constexpr const char* special_registers[] = {"ebp", "esp", "eip"};
-#elif defined(__x86_64__)
-  static constexpr const char* special_registers[] = {"rbp", "rsp", "rip"};
-#else
-  static constexpr const char* special_registers[] = {};
-#endif
-
-  regs->IterateRegisters([log, &current_row, &special_row](const char* name, uint64_t value) {
-    auto row = &current_row;
-    for (const char* special_name : special_registers) {
-      if (strcmp(special_name, name) == 0) {
-        row = &special_row;
-        break;
-      }
-    }
-
-    row->emplace_back(name, value);
-    if (current_row.size() == column_count) {
-      print_register_row(log, current_row);
-      current_row.clear();
-    }
-  });
-
-  if (!current_row.empty()) {
-    print_register_row(log, current_row);
-  }
-
-  print_register_row(log, special_row);
-}
-
-void dump_memory_and_code(log_t* log, unwindstack::Maps* maps, unwindstack::Memory* memory,
-                          unwindstack::Regs* regs) {
-  regs->IterateRegisters([log, maps, memory](const char* reg_name, uint64_t reg_value) {
-    std::string label{"memory near "s + reg_name};
-    if (maps) {
-      auto map_info = maps->Find(untag_address(reg_value));
-      if (map_info != nullptr && !map_info->name().empty()) {
-        label += " (" + map_info->name() + ")";
-      }
-    }
-    dump_memory(log, memory, reg_value, label);
-  });
-}
-
-static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const ThreadInfo& thread_info,
-                        const ProcessInfo& process_info, bool primary_thread) {
-  log->current_tid = thread_info.tid;
-  if (!primary_thread) {
-    _LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
-  }
-  dump_thread_info(log, thread_info);
-
-  if (thread_info.siginfo) {
-    dump_signal_info(log, thread_info, process_info, unwinder->GetProcessMemory().get());
-  }
-
-  if (primary_thread) {
-    // The main thread must have a valid siginfo.
-    CHECK(thread_info.siginfo != nullptr);
-    dump_probable_cause(log, unwinder, process_info, thread_info);
-
-    dump_abort_message(log, unwinder->GetProcessMemory().get(), process_info.abort_msg_address);
-  }
-
-  dump_registers(log, thread_info.registers.get());
-
-  // Unwind will mutate the registers, so make a copy first.
-  std::unique_ptr<unwindstack::Regs> regs_copy(thread_info.registers->Clone());
-  unwinder->SetRegs(regs_copy.get());
-  unwinder->Unwind();
-  if (unwinder->NumFrames() == 0) {
-    _LOG(log, logtype::THREAD, "Failed to unwind\n");
-    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());
-    }
-  } else {
-    _LOG(log, logtype::BACKTRACE, "\nbacktrace:\n");
-    log_backtrace(log, unwinder, "    ");
-  }
-
-  if (primary_thread) {
-    GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
-                                         thread_info);
-
-    if (gwp_asan_crash_data.HasDeallocationTrace()) {
-      gwp_asan_crash_data.DumpDeallocationTrace(log, unwinder);
-    }
-
-    if (gwp_asan_crash_data.HasAllocationTrace()) {
-      gwp_asan_crash_data.DumpAllocationTrace(log, unwinder);
-    }
-
-    unwindstack::Maps* maps = unwinder->GetMaps();
-    dump_memory_and_code(log, maps, unwinder->GetProcessMemory().get(),
-                         thread_info.registers.get());
-    if (maps != nullptr) {
-      uint64_t addr = 0;
-      if (process_info.has_fault_address) {
-        addr = process_info.untagged_fault_address;
-      }
-      dump_all_maps(log, unwinder, addr);
-    }
-  }
-
-  log->current_tid = log->crashed_tid;
-  return true;
-}
-
-// Reads the contents of the specified log device, filters out the entries
-// that don't match the specified pid, and writes them to the tombstone file.
-//
-// If "tail" is non-zero, log the last "tail" number of lines.
-static void dump_log_file(log_t* log, pid_t pid, const char* filename, unsigned int tail) {
-  bool first = true;
-  logger_list* logger_list;
-
-  if (!log->should_retrieve_logcat) {
-    return;
-  }
-
-  logger_list =
-      android_logger_list_open(android_name_to_log_id(filename), ANDROID_LOG_NONBLOCK, tail, pid);
-
-  if (!logger_list) {
-    ALOGE("Unable to open %s: %s\n", filename, strerror(errno));
-    return;
-  }
-
-  while (true) {
-    log_msg log_entry;
-    ssize_t actual = android_logger_list_read(logger_list, &log_entry);
-
-    if (actual < 0) {
-      if (actual == -EINTR) {
-        // interrupted by signal, retry
-        continue;
-      } else if (actual == -EAGAIN) {
-        // non-blocking EOF; we're done
-        break;
-      } else {
-        ALOGE("Error while reading log: %s\n", strerror(-actual));
-        break;
-      }
-    } else if (actual == 0) {
-      ALOGE("Got zero bytes while reading log: %s\n", strerror(errno));
-      break;
-    }
-
-    // NOTE: if you ALOGV something here, this will spin forever,
-    // because you will be writing as fast as you're reading.  Any
-    // high-frequency debug diagnostics should just be written to
-    // the tombstone file.
-
-    if (first) {
-      _LOG(log, logtype::LOGS, "--------- %slog %s\n", tail ? "tail end of " : "", filename);
-      first = false;
-    }
-
-    // Msg format is: <priority:1><tag:N>\0<message:N>\0
-    //
-    // We want to display it in the same format as "logcat -v threadtime"
-    // (although in this case the pid is redundant).
-    char timeBuf[32];
-    time_t sec = static_cast<time_t>(log_entry.entry.sec);
-    tm tm;
-    localtime_r(&sec, &tm);
-    strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", &tm);
-
-    char* msg = log_entry.msg();
-    if (msg == nullptr) {
-      continue;
-    }
-    unsigned char prio = msg[0];
-    char* tag = msg + 1;
-    msg = tag + strlen(tag) + 1;
-
-    // consume any trailing newlines
-    char* nl = msg + strlen(msg) - 1;
-    while (nl >= msg && *nl == '\n') {
-      *nl-- = '\0';
-    }
-
-    static const char* kPrioChars = "!.VDIWEFS";
-    char prioChar = (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?');
-
-    // Look for line breaks ('\n') and display each text line
-    // on a separate line, prefixed with the header, like logcat does.
-    do {
-      nl = strchr(msg, '\n');
-      if (nl != nullptr) {
-        *nl = '\0';
-        ++nl;
-      }
-
-      _LOG(log, logtype::LOGS, "%s.%03d %5d %5d %c %-8s: %s\n", timeBuf,
-           log_entry.entry.nsec / 1000000, log_entry.entry.pid, log_entry.entry.tid, prioChar, tag,
-           msg);
-    } while ((msg = nl));
-  }
-
-  android_logger_list_free(logger_list);
-}
-
-// Dumps the logs generated by the specified pid to the tombstone, from both
-// "system" and "main" log devices.  Ideally we'd interleave the output.
-static void dump_logs(log_t* log, pid_t pid, unsigned int tail) {
-  if (pid == getpid()) {
-    // Cowardly refuse to dump logs while we're running in-process.
-    return;
-  }
-
-  dump_log_file(log, pid, "system", tail);
-  dump_log_file(log, pid, "main", tail);
-}
-
 void engrave_tombstone_ucontext(int tombstone_fd, int proto_fd, uint64_t abort_msg_address,
                                 siginfo_t* siginfo, ucontext_t* ucontext) {
   pid_t uid = getuid();
@@ -645,45 +118,7 @@
   log.tfd = output_fd.get();
   log.amfd_data = amfd_data;
 
-  bool translate_proto = GetBoolProperty("debug.debuggerd.translate_proto_to_text", true);
-  if (translate_proto) {
-    tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) {
-      _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str());
-    });
-  } else {
-    bool want_logs = GetBoolProperty("ro.debuggable", false);
-
-    _LOG(&log, logtype::HEADER,
-         "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
-    dump_header_info(&log);
-    _LOG(&log, logtype::HEADER, "Timestamp: %s\n", get_timestamp().c_str());
-
-    auto it = threads.find(target_thread);
-    if (it == threads.end()) {
-      async_safe_fatal("failed to find target thread");
-    }
-
-    dump_thread(&log, unwinder, it->second, process_info, true);
-
-    if (want_logs) {
-      dump_logs(&log, it->second.pid, 50);
-    }
-
-    for (auto& [tid, thread_info] : threads) {
-      if (tid == target_thread) {
-        continue;
-      }
-
-      dump_thread(&log, unwinder, thread_info, process_info, false);
-    }
-
-    if (open_files) {
-      _LOG(&log, logtype::OPEN_FILES, "\nopen files:\n");
-      dump_open_files_list(&log, *open_files, "    ");
-    }
-
-    if (want_logs) {
-      dump_logs(&log, it->second.pid, 0);
-    }
-  }
+  tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) {
+    _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str());
+  });
 }
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 681b963..de86b0a 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -82,7 +82,8 @@
      thread.name().c_str(), process_name);
   CB(should_log, "uid: %d", tombstone.uid());
   if (thread.tagged_addr_ctrl() != -1) {
-    CB(should_log, "tagged_addr_ctrl: %016" PRIx64, thread.tagged_addr_ctrl());
+    CB(should_log, "tagged_addr_ctrl: %016" PRIx64 "%s", thread.tagged_addr_ctrl(),
+       describe_tagged_addr_ctrl(thread.tagged_addr_ctrl()).c_str());
   }
 }
 
@@ -292,6 +293,7 @@
 
 static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
                               const Thread& thread) {
+  int word_size = pointer_width(tombstone);
   print_thread_header(callback, tombstone, thread, true);
 
   const Signal& signal_info = tombstone.signal_info();
@@ -307,7 +309,7 @@
   } else {
     std::string fault_addr_desc;
     if (signal_info.has_fault_address()) {
-      fault_addr_desc = StringPrintf("0x%" PRIx64, signal_info.fault_address());
+      fault_addr_desc = StringPrintf("0x%0*" PRIx64, 2 * word_size, signal_info.fault_address());
     } else {
       fault_addr_desc = "--------";
     }
@@ -331,7 +333,7 @@
   if (tombstone.causes_size() > 1) {
     CBS("");
     CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing "
-        "order of probability.");
+        "order of likelihood.");
   }
 
   for (const Cause& cause : tombstone.causes()) {
@@ -369,7 +371,6 @@
     return;
   }
 
-  int word_size = pointer_width(tombstone);
   const auto format_pointer = [word_size](uint64_t ptr) -> std::string {
     if (word_size == 8) {
       uint64_t top = ptr >> 32;
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index a7506b7..71f0c09 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -41,6 +41,7 @@
 #include <unwindstack/Memory.h>
 #include <unwindstack/Unwinder.h>
 
+using android::base::StringPrintf;
 using android::base::unique_fd;
 
 bool is_allowed_in_logcat(enum logtype ltype) {
@@ -275,9 +276,10 @@
     case SIGBUS:
     case SIGFPE:
     case SIGILL:
-    case SIGSEGV:
     case SIGTRAP:
       return true;
+    case SIGSEGV:
+      return si->si_code != SEGV_MTEAERR;
     default:
       return false;
   }
@@ -444,6 +446,33 @@
   return "?";
 }
 
+std::string describe_tagged_addr_ctrl(long ctrl) {
+  std::string desc;
+  if (ctrl & PR_TAGGED_ADDR_ENABLE) {
+    desc += ", PR_TAGGED_ADDR_ENABLE";
+    ctrl &= ~PR_TAGGED_ADDR_ENABLE;
+  }
+  if (ctrl & PR_MTE_TCF_SYNC) {
+    desc += ", PR_MTE_TCF_SYNC";
+    ctrl &= ~PR_MTE_TCF_SYNC;
+  }
+  if (ctrl & PR_MTE_TCF_ASYNC) {
+    desc += ", PR_MTE_TCF_ASYNC";
+    ctrl &= ~PR_MTE_TCF_ASYNC;
+  }
+  if (ctrl & PR_MTE_TAG_MASK) {
+    desc += StringPrintf(", mask 0x%04lx", (ctrl & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
+    ctrl &= ~PR_MTE_TAG_MASK;
+  }
+  if (ctrl) {
+    desc += StringPrintf(", unknown 0x%lx", ctrl);
+  }
+  if (desc.empty()) {
+    return "";
+  }
+  return " (" + desc.substr(2) + ")";
+}
+
 void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix) {
   if (unwinder->elf_from_memory_not_file()) {
     _LOG(log, logtype::BACKTRACE,
diff --git a/diagnose_usb/Android.bp b/diagnose_usb/Android.bp
index cb79ffe..d7d87b5 100644
--- a/diagnose_usb/Android.bp
+++ b/diagnose_usb/Android.bp
@@ -7,6 +7,7 @@
     cflags: ["-Wall", "-Wextra", "-Werror"],
     host_supported: true,
     recovery_available: true,
+    min_sdk_version: "apex_inherit",
     apex_available: [
         "com.android.adbd",
         // TODO(b/151398197) remove the below
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 339f392..708a677 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -166,8 +166,10 @@
         "android.hardware.boot@1.1",
         "android.hardware.fastboot@1.1",
         "android.hardware.health@2.0",
+        "android.hardware.health-V1-ndk",
         "libasyncio",
         "libbase",
+        "libbinder_ndk",
         "libbootloader_message",
         "libcutils",
         "libext2_uuid",
@@ -183,8 +185,10 @@
     ],
 
     static_libs: [
+        "android.hardware.health-translate-ndk",
         "libc++fs",
         "libhealthhalutils",
+        "libhealthshim",
         "libsnapshot_cow",
         "libsnapshot_nobinder",
         "update_metadata-protos",
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 64a934d..e6a834e 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -21,10 +21,12 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
+#include <android/binder_manager.h>
 #include <android/hardware/boot/1.0/IBootControl.h>
 #include <android/hardware/fastboot/1.1/IFastboot.h>
 #include <fs_mgr.h>
 #include <fs_mgr/roots.h>
+#include <health-shim/shim.h>
 #include <healthhalutils/HealthHalUtils.h>
 
 #include "constants.h"
@@ -32,16 +34,36 @@
 #include "tcp_client.h"
 #include "usb_client.h"
 
+using std::string_literals::operator""s;
 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 ::android::hardware::health::V2_0::get_health_service;
 
 namespace sph = std::placeholders;
 
+std::shared_ptr<aidl::android::hardware::health::IHealth> get_health_service() {
+    using aidl::android::hardware::health::IHealth;
+    using HidlHealth = android::hardware::health::V2_0::IHealth;
+    using aidl::android::hardware::health::HealthShim;
+    auto service_name = IHealth::descriptor + "/default"s;
+    if (AServiceManager_isDeclared(service_name.c_str())) {
+        ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+        std::shared_ptr<IHealth> health = IHealth::fromBinder(binder);
+        if (health != nullptr) return health;
+        LOG(WARNING) << "AIDL health service is declared, but it cannot be retrieved.";
+    }
+    LOG(INFO) << "Unable to get AIDL health service, trying HIDL...";
+    android::sp<HidlHealth> hidl_health = android::hardware::health::V2_0::get_health_service();
+    if (hidl_health != nullptr) {
+        return ndk::SharedRefBase::make<HealthShim>(hidl_health);
+    }
+    LOG(WARNING) << "No health implementation is found.";
+    return nullptr;
+}
+
 FastbootDevice::FastbootDevice()
     : kCommandMap({
               {FB_CMD_SET_ACTIVE, SetActiveHandler},
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index 3536136..91ffce3 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -22,10 +22,10 @@
 #include <utility>
 #include <vector>
 
+#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 <android/hardware/health/2.0/IHealth.h>
 
 #include "commands.h"
 #include "transport.h"
@@ -57,7 +57,7 @@
     android::sp<android::hardware::fastboot::V1_1::IFastboot> fastboot_hal() {
         return fastboot_hal_;
     }
-    android::sp<android::hardware::health::V2_0::IHealth> health_hal() { return health_hal_; }
+    std::shared_ptr<aidl::android::hardware::health::IHealth> health_hal() { return health_hal_; }
 
     void set_active_slot(const std::string& active_slot) { active_slot_ = active_slot; }
 
@@ -67,7 +67,7 @@
     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_;
-    android::sp<android::hardware::health::V2_0::IHealth> health_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_;
     std::string active_slot_;
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index ee1eed8..76e9889 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -26,7 +26,6 @@
 #include <android/hardware/boot/1.1/IBootControl.h>
 #include <ext4_utils/ext4_utils.h>
 #include <fs_mgr.h>
-#include <healthhalutils/HealthHalUtils.h>
 #include <liblp/liblp.h>
 
 #include "fastboot_device.h"
@@ -120,23 +119,17 @@
 }
 
 bool GetBatteryVoltageHelper(FastbootDevice* device, int32_t* battery_voltage) {
-    using android::hardware::health::V2_0::HealthInfo;
-    using android::hardware::health::V2_0::Result;
+    using aidl::android::hardware::health::HealthInfo;
 
     auto health_hal = device->health_hal();
     if (!health_hal) {
         return false;
     }
 
-    Result ret;
-    auto ret_val = health_hal->getHealthInfo([&](Result result, HealthInfo info) {
-        *battery_voltage = info.legacy.batteryVoltage;
-        ret = result;
-    });
-    if (!ret_val.isOk() || (ret != Result::SUCCESS)) {
-        return false;
-    }
-
+    HealthInfo health_info;
+    auto res = health_hal->getHealthInfo(&health_info);
+    if (!res.isOk()) return false;
+    *battery_voltage = health_info.batteryVoltageMillivolts;
     return true;
 }
 
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 07e1e6b..a320c0e 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -75,9 +75,6 @@
 #include "blockdev.h"
 #include "fs_mgr_priv.h"
 
-#define KEY_LOC_PROP   "ro.crypto.keyfile.userdata"
-#define KEY_IN_FOOTER  "footer"
-
 #define E2FSCK_BIN      "/system/bin/e2fsck"
 #define F2FS_FSCK_BIN   "/system/bin/fsck.f2fs"
 #define MKSWAP_BIN      "/system/bin/mkswap"
@@ -907,7 +904,7 @@
                    << "(): skipping mount due to invalid magic, mountpoint=" << fstab[i].mount_point
                    << " blk_dev=" << realpath(fstab[i].blk_device) << " rec[" << i
                    << "].fs_type=" << fstab[i].fs_type;
-            mount_errno = EINVAL;  // continue bootup for FDE
+            mount_errno = EINVAL;  // continue bootup for metadata encryption
             continue;
         }
 
@@ -1005,50 +1002,22 @@
     return false;
 }
 
-static bool needs_block_encryption(const FstabEntry& entry) {
-    if (android::base::GetBoolProperty("ro.vold.forceencryption", false) && entry.is_encryptable())
-        return true;
-    if (entry.fs_mgr_flags.force_crypt) return true;
-    if (entry.fs_mgr_flags.crypt) {
-        // Check for existence of convert_fde breadcrumb file.
-        auto convert_fde_name = entry.mount_point + "/misc/vold/convert_fde";
-        if (access(convert_fde_name.c_str(), F_OK) == 0) return true;
-    }
-    if (entry.fs_mgr_flags.force_fde_or_fbe) {
-        // Check for absence of convert_fbe breadcrumb file.
-        auto convert_fbe_name = entry.mount_point + "/convert_fbe";
-        if (access(convert_fbe_name.c_str(), F_OK) != 0) return true;
-    }
-    return false;
-}
-
 static bool should_use_metadata_encryption(const FstabEntry& entry) {
-    return !entry.metadata_key_dir.empty() &&
-           (entry.fs_mgr_flags.file_encryption || entry.fs_mgr_flags.force_fde_or_fbe);
+    return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption;
 }
 
 // Check to see if a mountable volume has encryption requirements
 static int handle_encryptable(const FstabEntry& entry) {
-    // If this is block encryptable, need to trigger encryption.
-    if (needs_block_encryption(entry)) {
-        if (umount(entry.mount_point.c_str()) == 0) {
-            return FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION;
-        } else {
-            PWARNING << "Could not umount " << entry.mount_point << " - allow continue unencrypted";
-            return FS_MGR_MNTALL_DEV_NOT_ENCRYPTED;
-        }
-    } else if (should_use_metadata_encryption(entry)) {
+    if (should_use_metadata_encryption(entry)) {
         if (umount(entry.mount_point.c_str()) == 0) {
             return FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION;
         } else {
             PERROR << "Could not umount " << entry.mount_point << " - fail since can't encrypt";
             return FS_MGR_MNTALL_FAIL;
         }
-    } else if (entry.fs_mgr_flags.file_encryption || entry.fs_mgr_flags.force_fde_or_fbe) {
+    } else if (entry.fs_mgr_flags.file_encryption) {
         LINFO << entry.mount_point << " is file encrypted";
         return FS_MGR_MNTALL_DEV_FILE_ENCRYPTED;
-    } else if (entry.is_encryptable()) {
-        return FS_MGR_MNTALL_DEV_NOT_ENCRYPTED;
     } else {
         return FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE;
     }
@@ -1056,9 +1025,6 @@
 
 static void set_type_property(int status) {
     switch (status) {
-        case FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED:
-            SetProperty("ro.crypto.type", "block");
-            break;
         case FS_MGR_MNTALL_DEV_FILE_ENCRYPTED:
         case FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED:
         case FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION:
@@ -1532,7 +1498,6 @@
 
         // Mounting failed, understand why and retry.
         wiped = partition_wiped(current_entry.blk_device.c_str());
-        bool crypt_footer = false;
         if (mount_errno != EBUSY && mount_errno != EACCES &&
             current_entry.fs_mgr_flags.formattable && wiped) {
             // current_entry and attempted_entry point at the same partition, but sometimes
@@ -1544,19 +1509,6 @@
 
             checkpoint_manager.Revert(&current_entry);
 
-            if (current_entry.is_encryptable() && current_entry.key_loc != KEY_IN_FOOTER) {
-                unique_fd fd(TEMP_FAILURE_RETRY(
-                        open(current_entry.key_loc.c_str(), O_WRONLY | O_CLOEXEC)));
-                if (fd >= 0) {
-                    LINFO << __FUNCTION__ << "(): also wipe " << current_entry.key_loc;
-                    wipe_block_device(fd, get_file_size(fd));
-                } else {
-                    PERROR << __FUNCTION__ << "(): " << current_entry.key_loc << " wouldn't open";
-                }
-            } else if (current_entry.is_encryptable() && current_entry.key_loc == KEY_IN_FOOTER) {
-                crypt_footer = true;
-            }
-
             // EncryptInplace will be used when vdc gives an error or needs to format partitions
             // other than /data
             if (should_use_metadata_encryption(current_entry) &&
@@ -1577,7 +1529,7 @@
                 }
             }
 
-            if (fs_mgr_do_format(current_entry, crypt_footer) == 0) {
+            if (fs_mgr_do_format(current_entry) == 0) {
                 // Let's replay the mount actions.
                 i = top_idx - 1;
                 continue;
@@ -1590,27 +1542,8 @@
         }
 
         // mount(2) returned an error, handle the encryptable/formattable case.
-        if (mount_errno != EBUSY && mount_errno != EACCES && attempted_entry.is_encryptable()) {
-            if (wiped) {
-                LERROR << __FUNCTION__ << "(): " << attempted_entry.blk_device << " is wiped and "
-                       << attempted_entry.mount_point << " " << attempted_entry.fs_type
-                       << " is encryptable. Suggest recovery...";
-                encryptable = FS_MGR_MNTALL_DEV_NEEDS_RECOVERY;
-                continue;
-            } else {
-                // Need to mount a tmpfs at this mountpoint for now, and set
-                // properties that vold will query later for decrypting
-                LERROR << __FUNCTION__ << "(): possibly an encryptable blkdev "
-                       << attempted_entry.blk_device << " for mount " << attempted_entry.mount_point
-                       << " type " << attempted_entry.fs_type;
-                if (fs_mgr_do_tmpfs_mount(attempted_entry.mount_point.c_str()) < 0) {
-                    ++error_count;
-                    continue;
-                }
-            }
-            encryptable = FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED;
-        } else if (mount_errno != EBUSY && mount_errno != EACCES &&
-                   should_use_metadata_encryption(attempted_entry)) {
+        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},
                           nullptr)) {
diff --git a/fs_mgr/fs_mgr_format.cpp b/fs_mgr/fs_mgr_format.cpp
index 301c907..bb49873 100644
--- a/fs_mgr/fs_mgr_format.cpp
+++ b/fs_mgr/fs_mgr_format.cpp
@@ -34,7 +34,6 @@
 #include <selinux/selinux.h>
 
 #include "fs_mgr_priv.h"
-#include "cryptfs.h"
 
 using android::base::unique_fd;
 
@@ -58,7 +57,7 @@
 }
 
 static int format_ext4(const std::string& fs_blkdev, const std::string& fs_mnt_point,
-                       bool crypt_footer, bool needs_projid, bool needs_metadata_csum) {
+                       bool needs_projid, bool needs_metadata_csum) {
     uint64_t dev_sz;
     int rc = 0;
 
@@ -68,9 +67,6 @@
     }
 
     /* Format the partition using the calculated length */
-    if (crypt_footer) {
-        dev_sz -= CRYPT_FOOTER_OFFSET;
-    }
 
     std::string size_str = std::to_string(dev_sz / 4096);
 
@@ -120,8 +116,8 @@
     return rc;
 }
 
-static int format_f2fs(const std::string& fs_blkdev, uint64_t dev_sz, bool crypt_footer,
-                       bool needs_projid, bool needs_casefold, bool fs_compress) {
+static int format_f2fs(const std::string& fs_blkdev, uint64_t dev_sz, bool needs_projid,
+                       bool needs_casefold, bool fs_compress) {
     if (!dev_sz) {
         int rc = get_dev_sz(fs_blkdev, &dev_sz);
         if (rc) {
@@ -130,9 +126,6 @@
     }
 
     /* Format the partition using the calculated length */
-    if (crypt_footer) {
-        dev_sz -= CRYPT_FOOTER_OFFSET;
-    }
 
     std::string size_str = std::to_string(dev_sz / 4096);
 
@@ -159,7 +152,7 @@
     return logwrap_fork_execvp(args.size(), args.data(), nullptr, false, LOG_KLOG, false, nullptr);
 }
 
-int fs_mgr_do_format(const FstabEntry& entry, bool crypt_footer) {
+int fs_mgr_do_format(const FstabEntry& entry) {
     LERROR << __FUNCTION__ << ": Format " << entry.blk_device << " as '" << entry.fs_type << "'";
 
     bool needs_casefold = false;
@@ -171,10 +164,10 @@
     }
 
     if (entry.fs_type == "f2fs") {
-        return format_f2fs(entry.blk_device, entry.length, crypt_footer, needs_projid,
-                           needs_casefold, entry.fs_mgr_flags.fs_compress);
+        return format_f2fs(entry.blk_device, entry.length, needs_projid, needs_casefold,
+                           entry.fs_mgr_flags.fs_compress);
     } else if (entry.fs_type == "ext4") {
-        return format_ext4(entry.blk_device, entry.mount_point, crypt_footer, needs_projid,
+        return format_ext4(entry.blk_device, entry.mount_point, needs_projid,
                            entry.fs_mgr_flags.ext_meta_csum);
     } else {
         LERROR << "File system type '" << entry.fs_type << "' is not supported";
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 609bd11..07b533b 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -138,7 +138,7 @@
                         entry->reserved_size = size_in_4k_blocks << 12;
                     }
                 } else if (StartsWith(flag, "lowerdir=")) {
-                    entry->lowerdir = std::move(arg);
+                    entry->lowerdir = arg;
                 }
             }
         }
@@ -146,7 +146,7 @@
     entry->fs_options = std::move(fs_options);
 }
 
-void ParseFsMgrFlags(const std::string& flags, FstabEntry* entry) {
+bool ParseFsMgrFlags(const std::string& flags, FstabEntry* entry) {
     for (const auto& flag : Split(flags, ",")) {
         if (flag.empty() || flag == "defaults") continue;
         std::string arg;
@@ -189,9 +189,18 @@
 
         // Then handle flags that take an argument.
         if (StartsWith(flag, "encryptable=")) {
-            // The encryptable flag is followed by an = and the  location of the keys.
+            // The "encryptable" flag identifies adoptable storage volumes.  The
+            // argument to this flag is ignored, but it should be "userdata".
+            //
+            // Historical note: this flag was originally meant just for /data,
+            // to indicate that FDE (full disk encryption) can be enabled.
+            // Unfortunately, it was also overloaded to identify adoptable
+            // storage volumes.  Today, FDE is no longer supported, leaving only
+            // the adoptable storage volume meaning for this flag.
             entry->fs_mgr_flags.crypt = true;
-            entry->key_loc = arg;
+        } else if (StartsWith(flag, "forceencrypt=") || StartsWith(flag, "forcefdeorfbe=")) {
+            LERROR << "flag no longer supported: " << flag;
+            return false;
         } else if (StartsWith(flag, "voldmanaged=")) {
             // The voldmanaged flag is followed by an = and the label, a colon and the partition
             // number or the word "auto", e.g. voldmanaged=sdcard:3
@@ -235,18 +244,8 @@
                     LWARNING << "Warning: zramsize= flag malformed: " << arg;
                 }
             }
-        } else if (StartsWith(flag, "forceencrypt=")) {
-            // The forceencrypt flag is followed by an = and the location of the keys.
-            entry->fs_mgr_flags.force_crypt = true;
-            entry->key_loc = arg;
         } else if (StartsWith(flag, "fileencryption=")) {
             ParseFileEncryption(arg, entry);
-        } else if (StartsWith(flag, "forcefdeorfbe=")) {
-            // The forcefdeorfbe flag is followed by an = and the location of the keys.  Get it and
-            // return it.
-            entry->fs_mgr_flags.force_fde_or_fbe = true;
-            entry->key_loc = arg;
-            entry->encryption_options = "aes-256-xts:aes-256-cts";
         } else if (StartsWith(flag, "max_comp_streams=")) {
             if (!ParseInt(arg, &entry->max_comp_streams)) {
                 LWARNING << "Warning: max_comp_streams= flag malformed: " << arg;
@@ -306,6 +305,19 @@
             LWARNING << "Warning: unknown flag: " << flag;
         }
     }
+
+    // FDE is no longer supported, so reject "encryptable" when used without
+    // "vold_managed".  For now skip this check when in recovery mode, since
+    // some recovery fstabs still contain the FDE options since they didn't do
+    // anything in recovery mode anyway (except possibly to cause the
+    // reservation of a crypto footer) and thus never got removed.
+    if (entry->fs_mgr_flags.crypt && !entry->fs_mgr_flags.vold_managed &&
+        access("/system/bin/recovery", F_OK) != 0) {
+        LERROR << "FDE is no longer supported; 'encryptable' can only be used for adoptable "
+                  "storage";
+        return false;
+    }
+    return true;
 }
 
 std::string InitAndroidDtDir() {
@@ -518,66 +530,39 @@
 
 }  // namespace
 
-bool ReadFstabFromFp(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out) {
-    ssize_t len;
-    size_t alloc_len = 0;
-    char *line = NULL;
-    const char *delim = " \t";
-    char *save_ptr, *p;
+bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out) {
+    const int expected_fields = proc_mounts ? 4 : 5;
+
     Fstab fstab;
 
-    while ((len = getline(&line, &alloc_len, fstab_file)) != -1) {
-        /* if the last character is a newline, shorten the string by 1 byte */
-        if (line[len - 1] == '\n') {
-            line[len - 1] = '\0';
+    for (const auto& line : android::base::Split(fstab_str, "\n")) {
+        auto fields = android::base::Tokenize(line, " \t");
+
+        // Ignore empty lines and comments.
+        if (fields.empty() || android::base::StartsWith(fields.front(), '#')) {
+            continue;
         }
 
-        /* Skip any leading whitespace */
-        p = line;
-        while (isspace(*p)) {
-            p++;
+        if (fields.size() < expected_fields) {
+            LERROR << "Error parsing fstab: expected " << expected_fields << " fields, got "
+                   << fields.size();
+            return false;
         }
-        /* ignore comments or empty lines */
-        if (*p == '#' || *p == '\0')
-            continue;
 
         FstabEntry entry;
+        auto it = fields.begin();
 
-        if (!(p = strtok_r(line, delim, &save_ptr))) {
-            LERROR << "Error parsing mount source";
-            goto err;
-        }
-        entry.blk_device = p;
-
-        if (!(p = strtok_r(NULL, delim, &save_ptr))) {
-            LERROR << "Error parsing mount_point";
-            goto err;
-        }
-        entry.mount_point = p;
-
-        if (!(p = strtok_r(NULL, delim, &save_ptr))) {
-            LERROR << "Error parsing fs_type";
-            goto err;
-        }
-        entry.fs_type = p;
-
-        if (!(p = strtok_r(NULL, delim, &save_ptr))) {
-            LERROR << "Error parsing mount_flags";
-            goto err;
-        }
-
-        ParseMountFlags(p, &entry);
+        entry.blk_device = std::move(*it++);
+        entry.mount_point = std::move(*it++);
+        entry.fs_type = std::move(*it++);
+        ParseMountFlags(std::move(*it++), &entry);
 
         // For /proc/mounts, ignore everything after mnt_freq and mnt_passno
-        if (proc_mounts) {
-            p += strlen(p);
-        } else if (!(p = strtok_r(NULL, delim, &save_ptr))) {
-            LERROR << "Error parsing fs_mgr_options";
-            goto err;
+        if (!proc_mounts && !ParseFsMgrFlags(std::move(*it++), &entry)) {
+            LERROR << "Error parsing fs_mgr_flags";
+            return false;
         }
 
-        ParseFsMgrFlags(p, &entry);
-
         if (entry.fs_mgr_flags.logical) {
             entry.logical_partition_name = entry.blk_device;
         }
@@ -587,21 +572,17 @@
 
     if (fstab.empty()) {
         LERROR << "No entries found in fstab";
-        goto err;
+        return false;
     }
 
     /* If an A/B partition, modify block device to be the real block device */
     if (!fs_mgr_update_for_slotselect(&fstab)) {
         LERROR << "Error updating for slotselect";
-        goto err;
+        return false;
     }
-    free(line);
+
     *fstab_out = std::move(fstab);
     return true;
-
-err:
-    free(line);
-    return false;
 }
 
 void TransformFstabForDsu(Fstab* fstab, const std::string& dsu_slot,
@@ -685,9 +666,11 @@
 }
 
 void EnableMandatoryFlags(Fstab* fstab) {
-    // Devices launched in R and after should enable fs_verity on userdata. The flag causes tune2fs
-    // to enable the feature. A better alternative would be to enable on mkfs at the beginning.
+    // Devices launched in R and after must support fs_verity. Set flag to cause tune2fs
+    // to enable the feature on userdata and metadata partitions.
     if (android::base::GetIntProperty("ro.product.first_api_level", 0) >= 30) {
+        // Devices launched in R and after should enable fs_verity on userdata.
+        // A better alternative would be to enable on mkfs at the beginning.
         std::vector<FstabEntry*> data_entries = GetEntriesForMountPoint(fstab, "/data");
         for (auto&& entry : data_entries) {
             // Besides ext4, f2fs is also supported. But the image is already created with verity
@@ -696,20 +679,26 @@
                 entry->fs_mgr_flags.fs_verity = true;
             }
         }
+        // Devices shipping with S and earlier likely do not already have fs_verity enabled via
+        // mkfs, so enable it here.
+        std::vector<FstabEntry*> metadata_entries = GetEntriesForMountPoint(fstab, "/metadata");
+        for (auto&& entry : metadata_entries) {
+            entry->fs_mgr_flags.fs_verity = true;
+        }
     }
 }
 
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab_out) {
-    auto fstab_file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
-    if (!fstab_file) {
-        PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'";
+    const bool is_proc_mounts = (path == "/proc/mounts");
+
+    std::string fstab_str;
+    if (!android::base::ReadFileToString(path, &fstab_str, /* follow_symlinks = */ true)) {
+        PERROR << __FUNCTION__ << "(): failed to read file: '" << path << "'";
         return false;
     }
 
-    bool is_proc_mounts = path == "/proc/mounts";
-
     Fstab fstab;
-    if (!ReadFstabFromFp(fstab_file.get(), is_proc_mounts, &fstab)) {
+    if (!ParseFstabFromString(fstab_str, is_proc_mounts, &fstab)) {
         LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'";
         return false;
     }
@@ -756,15 +745,7 @@
         return false;
     }
 
-    std::unique_ptr<FILE, decltype(&fclose)> fstab_file(
-        fmemopen(static_cast<void*>(const_cast<char*>(fstab_buf.c_str())),
-                 fstab_buf.length(), "r"), fclose);
-    if (!fstab_file) {
-        if (verbose) PERROR << __FUNCTION__ << "(): failed to create a file stream for fstab dt";
-        return false;
-    }
-
-    if (!ReadFstabFromFp(fstab_file.get(), false, fstab)) {
+    if (!ParseFstabFromString(fstab_buf, /* proc_mounts = */ false, fstab)) {
         if (verbose) {
             LERROR << __FUNCTION__ << "(): failed to load fstab from kernel:" << std::endl
                    << fstab_buf;
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 0522ac5..2b31119 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -322,6 +322,17 @@
 const auto kLowerdirOption = "lowerdir="s;
 const auto kUpperdirOption = "upperdir="s;
 
+static inline bool KernelSupportsUserXattrs() {
+    struct utsname uts;
+    uname(&uts);
+
+    int major, minor;
+    if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) {
+        return false;
+    }
+    return major > 5 || (major == 5 && minor >= 15);
+}
+
 // default options for mount_point, returns empty string for none available.
 std::string fs_mgr_get_overlayfs_options(const std::string& mount_point) {
     auto candidate = fs_mgr_get_overlayfs_candidate(mount_point);
@@ -331,6 +342,9 @@
     if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kOverrideCredsRequired) {
         ret += ",override_creds=off";
     }
+    if (KernelSupportsUserXattrs()) {
+        ret += ",userxattr";
+    }
     return ret;
 }
 
diff --git a/fs_mgr/fs_mgr_roots.cpp b/fs_mgr/fs_mgr_roots.cpp
index d275320..2ad8125 100644
--- a/fs_mgr/fs_mgr_roots.cpp
+++ b/fs_mgr/fs_mgr_roots.cpp
@@ -125,8 +125,7 @@
     int result = fs_mgr_do_mount_one(*rec, mount_point);
     if (result == -1 && rec->fs_mgr_flags.formattable) {
         PERROR << "Failed to mount " << mount_point << "; formatting";
-        bool crypt_footer = rec->is_encryptable() && rec->key_loc == "footer";
-        if (fs_mgr_do_format(*rec, crypt_footer) != 0) {
+        if (fs_mgr_do_format(*rec) != 0) {
             PERROR << "Failed to format " << mount_point;
             return false;
         }
diff --git a/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
index 1fddbf8..6a8a191 100644
--- a/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
+++ b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
@@ -19,12 +19,8 @@
 #include <fstab/fstab.h>
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    std::unique_ptr<FILE, decltype(&fclose)> fstab_file(
-            fmemopen(static_cast<void*>(const_cast<uint8_t*>(data)), size, "r"), fclose);
-    if (fstab_file == nullptr) {
-        return 0;
-    }
+    std::string make_fstab_str(reinterpret_cast<const char*>(data), size);
     android::fs_mgr::Fstab fstab;
-    android::fs_mgr::ReadFstabFromFp(fstab_file.get(), /* proc_mounts= */ false, &fstab);
+    android::fs_mgr::ParseFstabFromString(make_fstab_str, /* proc_mounts = */ false, &fstab);
     return 0;
 }
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 21c9989..29a5e60 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -56,9 +56,6 @@
 #define FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION 6
 #define FS_MGR_MNTALL_DEV_FILE_ENCRYPTED 5
 #define FS_MGR_MNTALL_DEV_NEEDS_RECOVERY 4
-#define FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION 3
-#define FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED 2
-#define FS_MGR_MNTALL_DEV_NOT_ENCRYPTED 1
 #define FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE 0
 #define FS_MGR_MNTALL_FAIL (-1)
 
@@ -107,7 +104,7 @@
 // device is in "check_at_most_once" mode.
 bool fs_mgr_verity_is_check_at_most_once(const android::fs_mgr::FstabEntry& entry);
 
-int fs_mgr_do_format(const android::fs_mgr::FstabEntry& entry, bool reserve_footer);
+int fs_mgr_do_format(const android::fs_mgr::FstabEntry& entry);
 
 #define FS_MGR_SETUP_VERITY_SKIPPED  (-3)
 #define FS_MGR_SETUP_VERITY_DISABLED (-2)
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index d0f32a3..054300e 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -37,7 +37,6 @@
     unsigned long flags = 0;
     std::string fs_options;
     std::string fs_checkpoint_opts;
-    std::string key_loc;
     std::string metadata_key_dir;
     std::string metadata_encryption;
     off64_t length = 0;
@@ -60,19 +59,17 @@
     struct FsMgrFlags {
         bool wait : 1;
         bool check : 1;
-        bool crypt : 1;
+        bool crypt : 1;  // Now only used to identify adoptable storage volumes
         bool nonremovable : 1;
         bool vold_managed : 1;
         bool recovery_only : 1;
         bool verify : 1;
-        bool force_crypt : 1;
         bool no_emulated_sd : 1;  // No emulated sdcard daemon; sd card is the only external
                                   // storage.
         bool no_trim : 1;
         bool file_encryption : 1;
         bool formattable : 1;
         bool slot_select : 1;
-        bool force_fde_or_fbe : 1;
         bool late_mount : 1;
         bool no_fail : 1;
         bool verify_at_boot : 1;
@@ -89,9 +86,7 @@
         bool overlayfs_remove_missing_lowerdir : 1;
     } fs_mgr_flags = {};
 
-    bool is_encryptable() const {
-        return fs_mgr_flags.crypt || fs_mgr_flags.force_crypt || fs_mgr_flags.force_fde_or_fbe;
-    }
+    bool is_encryptable() const { return fs_mgr_flags.crypt; }
 };
 
 // An Fstab is a collection of FstabEntry structs.
@@ -99,8 +94,8 @@
 // Unless explicitly requested, a lookup on mount point should always return the 1st one.
 using Fstab = std::vector<FstabEntry>;
 
-// Exported for testability. Regular users should use ReadFstabFromfile().
-bool ReadFstabFromFp(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out);
+// Exported for testability. Regular users should use ReadFstabFromFile().
+bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out);
 
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
 bool ReadFstabFromDt(Fstab* fstab, bool verbose = true);
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 5ab2ce2..8b269cd 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -236,6 +236,7 @@
         "libbrotli",
         "libc++fs",
         "libfs_mgr_binder",
+        "libgflags",
         "libgsi",
         "libgmock",
         "liblp",
@@ -263,6 +264,17 @@
     defaults: ["libsnapshot_test_defaults"],
 }
 
+sh_test {
+    name: "run_snapshot_tests",
+    src: "run_snapshot_tests.sh",
+    test_suites: [
+        "device-tests",
+    ],
+    required: [
+        "vts_libsnapshot_test",
+    ],
+}
+
 cc_binary {
     name: "snapshotctl",
     srcs: [
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index e2abdba..532f66d 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -194,6 +194,9 @@
 
     // Source build fingerprint.
     string source_build_fingerprint = 8;
+
+    // user-space snapshots
+    bool userspace_snapshots = 9;
 }
 
 // Next: 10
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index ec58cca..ba62330 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -35,6 +35,7 @@
                 (override));
     MOCK_METHOD(UpdateState, GetUpdateState, (double* progress), (override));
     MOCK_METHOD(bool, UpdateUsesCompression, (), (override));
+    MOCK_METHOD(bool, UpdateUsesUserSnapshots, (), (override));
     MOCK_METHOD(Return, CreateUpdateSnapshots,
                 (const chromeos_update_engine::DeltaArchiveManifest& manifest), (override));
     MOCK_METHOD(bool, MapUpdateSnapshot,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index a49b026..41c6ef5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -193,6 +193,9 @@
     // UpdateState is None, or no snapshots have been created.
     virtual bool UpdateUsesCompression() = 0;
 
+    // Returns true if userspace snapshots is enabled for the current update.
+    virtual bool UpdateUsesUserSnapshots() = 0;
+
     // Create necessary COW device / files for OTA clients. New logical partitions will be added to
     // group "cow" in target_metadata. Regions of partitions of current_metadata will be
     // "write-protected" and snapshotted.
@@ -352,6 +355,7 @@
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
     bool UpdateUsesCompression() override;
+    bool UpdateUsesUserSnapshots() override;
     Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
                            std::string* snapshot_path) override;
@@ -387,6 +391,11 @@
     // first-stage to decide whether to launch snapuserd.
     bool IsSnapuserdRequired();
 
+    enum class SnapshotDriver {
+        DM_SNAPSHOT,
+        DM_USER,
+    };
+
   private:
     FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
     FRIEND_TEST(SnapshotTest, CreateSnapshot);
@@ -456,6 +465,8 @@
     };
     static std::unique_ptr<LockedFile> OpenFile(const std::string& file, int lock_flags);
 
+    SnapshotDriver GetSnapshotDriver(LockedFile* lock);
+
     // Create a new snapshot record. This creates the backing COW store and
     // persists information needed to map the device. The device can be mapped
     // with MapSnapshot().
@@ -491,8 +502,8 @@
 
     // Create a dm-user device for a given snapshot.
     bool MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file,
-                      const std::string& base_device, const std::chrono::milliseconds& timeout_ms,
-                      std::string* path);
+                      const std::string& base_device, const std::string& base_path_merge,
+                      const std::chrono::milliseconds& timeout_ms, std::string* path);
 
     // Map the source device used for dm-user.
     bool MapSourceDevice(LockedFile* lock, const std::string& name,
@@ -591,7 +602,8 @@
     // Internal callback for when merging is complete.
     bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
                                  const SnapshotStatus& status);
-    bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
+    bool CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
+                                const SnapshotStatus& status);
 
     struct MergeResult {
         explicit MergeResult(UpdateState state,
@@ -689,7 +701,10 @@
     bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
 
     // Unmap a dm-user device through snapuserd.
-    bool UnmapDmUserDevice(const std::string& snapshot_name);
+    bool UnmapDmUserDevice(const std::string& dm_user_name);
+
+    // Unmap a dm-user device for user space snapshots
+    bool UnmapUserspaceSnapshotDevice(LockedFile* lock, const std::string& snapshot_name);
 
     // If there isn't a previous update, return true. |needs_merge| is set to false.
     // If there is a previous update but the device has not boot into it, tries to cancel the
@@ -778,6 +793,9 @@
 
     // Helper of UpdateUsesCompression
     bool UpdateUsesCompression(LockedFile* lock);
+    // Locked and unlocked functions to test whether the current update uses
+    // userspace snapshots.
+    bool UpdateUsesUserSnapshots(LockedFile* lock);
 
     // Wrapper around libdm, with diagnostics.
     bool DeleteDeviceIfExists(const std::string& name,
@@ -792,6 +810,7 @@
     std::function<bool(const std::string&)> uevent_regen_callback_;
     std::unique_ptr<SnapuserdClient> snapuserd_client_;
     std::unique_ptr<LpMetadata> old_partition_metadata_;
+    std::optional<bool> is_snapshot_userspace_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index 74b78c5..318e525 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -35,6 +35,7 @@
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
     bool UpdateUsesCompression() override;
+    bool UpdateUsesUserSnapshots() override;
     Return CreateUpdateSnapshots(
             const chromeos_update_engine::DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
diff --git a/fs_mgr/libsnapshot/run_snapshot_tests.sh b/fs_mgr/libsnapshot/run_snapshot_tests.sh
new file mode 100644
index 0000000..b03a4e0
--- /dev/null
+++ b/fs_mgr/libsnapshot/run_snapshot_tests.sh
@@ -0,0 +1,35 @@
+#!/system/bin/sh
+
+# Detect host or AOSP.
+getprop ro.build.version.sdk > /dev/null 2>&1
+if [ $? -eq 0 ]; then
+    cmd_prefix=""
+    local_root=""
+else
+    cmd_prefix="adb shell"
+    local_root="${ANDROID_PRODUCT_OUT}"
+    set -e
+    set -x
+    adb root
+    adb sync data
+    set +x
+    set +e
+fi
+
+testpath64="/data/nativetest64/vts_libsnapshot_test/vts_libsnapshot_test"
+testpath32="/data/nativetest/vts_libsnapshot_test/vts_libsnapshot_test"
+if [ -f "${local_root}/${testpath64}" ]; then
+    testpath="${testpath64}"
+elif [ -f "${local_root}/${testpath32}" ]; then
+    testpath="${testpath32}"
+else
+    echo "ERROR: vts_libsnapshot_test not found." 1>&2
+    echo "Make sure to build vts_libsnapshot_test or snapshot_tests first." 1>&2
+    exit 1
+fi
+
+# Verbose, error on failure.
+set -x
+set -e
+
+time ${cmd_prefix} ${testpath}
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 3d8ae29..e6e17bd 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -95,6 +95,7 @@
     if (!info) {
         info = new DeviceInfo();
     }
+
     return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
 }
 
@@ -121,8 +122,34 @@
     return snapshot_name + "-cow";
 }
 
-static std::string GetDmUserCowName(const std::string& snapshot_name) {
-    return snapshot_name + "-user-cow";
+SnapshotManager::SnapshotDriver SnapshotManager::GetSnapshotDriver(LockedFile* lock) {
+    if (UpdateUsesUserSnapshots(lock)) {
+        return SnapshotManager::SnapshotDriver::DM_USER;
+    } else {
+        return SnapshotManager::SnapshotDriver::DM_SNAPSHOT;
+    }
+}
+
+static std::string GetDmUserCowName(const std::string& snapshot_name,
+                                    SnapshotManager::SnapshotDriver driver) {
+    // dm-user block device will act as a snapshot device. We identify it with
+    // the same partition name so that when partitions can be mounted off
+    // dm-user.
+
+    switch (driver) {
+        case SnapshotManager::SnapshotDriver::DM_USER: {
+            return snapshot_name;
+        }
+
+        case SnapshotManager::SnapshotDriver::DM_SNAPSHOT: {
+            return snapshot_name + "-user-cow";
+        }
+
+        default: {
+            LOG(ERROR) << "Invalid snapshot driver";
+            return "";
+        }
+    }
 }
 
 static std::string GetCowImageDeviceName(const std::string& snapshot_name) {
@@ -398,9 +425,33 @@
 
 bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
                                    const std::string& cow_file, const std::string& base_device,
+                                   const std::string& base_path_merge,
                                    const std::chrono::milliseconds& timeout_ms, std::string* path) {
     CHECK(lock);
 
+    if (UpdateUsesUserSnapshots(lock)) {
+        SnapshotStatus status;
+        if (!ReadSnapshotStatus(lock, name, &status)) {
+            LOG(ERROR) << "MapDmUserCow: ReadSnapshotStatus failed...";
+            return false;
+        }
+
+        if (status.state() == SnapshotState::NONE ||
+            status.state() == SnapshotState::MERGE_COMPLETED) {
+            LOG(ERROR) << "Should not create a snapshot device for " << name
+                       << " after merging has completed.";
+            return false;
+        }
+
+        SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+        if (update_status.state() == UpdateState::MergeCompleted ||
+            update_status.state() == UpdateState::MergeNeedsReboot) {
+            LOG(ERROR) << "Should not create a snapshot device for " << name
+                       << " after global merging has completed.";
+            return false;
+        }
+    }
+
     // Use an extra decoration for first-stage init, so we can transition
     // to a new table entry in second-stage.
     std::string misc_name = name;
@@ -412,18 +463,41 @@
         return false;
     }
 
-    uint64_t base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
-    if (base_sectors == 0) {
-        LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
-        return false;
+    uint64_t base_sectors = 0;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
+        if (base_sectors == 0) {
+            LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
+            return false;
+        }
+    } else {
+        // For userspace snapshots, the size of the base device is taken as the
+        // size of the dm-user block device. Since there is no pseudo mapping
+        // created in the daemon, we no longer need to rely on the daemon for
+        // sizing the dm-user block device.
+        unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge.c_str(), O_RDONLY | O_CLOEXEC)));
+        if (fd < 0) {
+            LOG(ERROR) << "Cannot open block device: " << base_path_merge;
+            return false;
+        }
+
+        uint64_t dev_sz = get_block_device_size(fd.get());
+        if (!dev_sz) {
+            LOG(ERROR) << "Failed to find block device size: " << base_path_merge;
+            return false;
+        }
+
+        base_sectors = dev_sz >> 9;
     }
 
     DmTable table;
     table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
     if (!dm_.CreateDevice(name, table, path, timeout_ms)) {
+        LOG(ERROR) << " dm-user: CreateDevice failed... ";
         return false;
     }
     if (!WaitForDevice(*path, timeout_ms)) {
+        LOG(ERROR) << " dm-user: timeout: Failed to create block device for: " << name;
         return false;
     }
 
@@ -432,6 +506,15 @@
         return false;
     }
 
+    if (UpdateUsesUserSnapshots(lock)) {
+        // Now that the dm-user device is created, initialize the daemon and
+        // spin up the worker threads.
+        if (!snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device, base_path_merge)) {
+            LOG(ERROR) << "InitDmUserCow failed";
+            return false;
+        }
+    }
+
     return snapuserd_client_->AttachDmUser(misc_name);
 }
 
@@ -585,9 +668,15 @@
 bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
     CHECK(lock);
 
-    if (!DeleteDeviceIfExists(name)) {
-        LOG(ERROR) << "Could not delete snapshot device: " << name;
-        return false;
+    if (UpdateUsesUserSnapshots(lock)) {
+        if (!UnmapUserspaceSnapshotDevice(lock, name)) {
+            return false;
+        }
+    } else {
+        if (!DeleteDeviceIfExists(name)) {
+            LOG(ERROR) << "Could not delete snapshot device: " << name;
+            return false;
+        }
     }
     return true;
 }
@@ -698,13 +787,15 @@
 
     DmTargetSnapshot::Status initial_target_values = {};
     for (const auto& snapshot : snapshots) {
-        DmTargetSnapshot::Status current_status;
-        if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
-            return false;
+        if (!UpdateUsesUserSnapshots(lock.get())) {
+            DmTargetSnapshot::Status current_status;
+            if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
+                return false;
+            }
+            initial_target_values.sectors_allocated += current_status.sectors_allocated;
+            initial_target_values.total_sectors += current_status.total_sectors;
+            initial_target_values.metadata_sectors += current_status.metadata_sectors;
         }
-        initial_target_values.sectors_allocated += current_status.sectors_allocated;
-        initial_target_values.total_sectors += current_status.total_sectors;
-        initial_target_values.metadata_sectors += current_status.metadata_sectors;
 
         SnapshotStatus snapshot_status;
         if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
@@ -719,11 +810,14 @@
 
     SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
     initial_status.set_state(UpdateState::Merging);
-    initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
-    initial_status.set_total_sectors(initial_target_values.total_sectors);
-    initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
     initial_status.set_compression_enabled(compression_enabled);
 
+    if (!UpdateUsesUserSnapshots(lock.get())) {
+        initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
+        initial_status.set_total_sectors(initial_target_values.total_sectors);
+        initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
+    }
+
     // If any partitions shrunk, we need to merge them before we merge any other
     // partitions (see b/177935716). Otherwise, a merge from another partition
     // may overwrite the source block of a copy operation.
@@ -777,20 +871,36 @@
                      << " has unexpected state: " << SnapshotState_Name(status.state());
     }
 
-    // After this, we return true because we technically did switch to a merge
-    // target. Everything else we do here is just informational.
-    if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
-        return code;
+    if (UpdateUsesUserSnapshots(lock)) {
+        if (EnsureSnapuserdConnected()) {
+            // This is the point where we inform the daemon to initiate/resume
+            // the merge
+            if (!snapuserd_client_->InitiateMerge(name)) {
+                return MergeFailureCode::UnknownTable;
+            }
+        } else {
+            LOG(ERROR) << "Failed to connect to snapuserd daemon to initiate merge";
+            return MergeFailureCode::UnknownTable;
+        }
+    } else {
+        // After this, we return true because we technically did switch to a merge
+        // target. Everything else we do here is just informational.
+        if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
+            return code;
+        }
     }
 
     status.set_state(SnapshotState::MERGING);
 
-    DmTargetSnapshot::Status dm_status;
-    if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
-        LOG(ERROR) << "Could not query merge status for snapshot: " << name;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        DmTargetSnapshot::Status dm_status;
+        if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
+            LOG(ERROR) << "Could not query merge status for snapshot: " << name;
+        }
+        status.set_sectors_allocated(dm_status.sectors_allocated);
+        status.set_metadata_sectors(dm_status.metadata_sectors);
     }
-    status.set_sectors_allocated(dm_status.sectors_allocated);
-    status.set_metadata_sectors(dm_status.metadata_sectors);
+
     if (!WriteSnapshotStatus(lock, status)) {
         LOG(ERROR) << "Could not update status file for snapshot: " << name;
     }
@@ -856,9 +966,15 @@
         return false;
     }
     auto type = DeviceMapper::GetTargetType(snap_target.spec);
-    if (type != "snapshot" && type != "snapshot-merge") {
-        return false;
+
+    // If this is not a user-snapshot device then it should either
+    // be a dm-snapshot or dm-snapshot-merge target
+    if (type != "user") {
+        if (type != "snapshot" && type != "snapshot-merge") {
+            return false;
+        }
     }
+
     if (target) {
         *target = std::move(snap_target);
     }
@@ -1094,34 +1210,86 @@
     DCHECK((current_metadata = ReadCurrentMetadata()) &&
            GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated);
 
-    std::string target_type;
-    DmTargetSnapshot::Status status;
-    if (!QuerySnapshotStatus(name, &target_type, &status)) {
-        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
-    }
-    if (target_type == "snapshot" &&
-        DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
-        update_status.merge_phase() == MergePhase::FIRST_PHASE) {
-        // The snapshot is not being merged because it's in the wrong phase.
-        return MergeResult(UpdateState::None);
-    }
-    if (target_type != "snapshot-merge") {
-        // We can get here if we failed to rewrite the target type in
-        // InitiateMerge(). If we failed to create the target in first-stage
-        // init, boot would not succeed.
-        LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
-        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+    if (UpdateUsesUserSnapshots(lock)) {
+        std::string merge_status;
+        if (EnsureSnapuserdConnected()) {
+            // Query the snapshot status from the daemon
+            merge_status = snapuserd_client_->QuerySnapshotStatus(name);
+        } else {
+            MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
+        }
+
+        if (merge_status == "snapshot-merge-failed") {
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
+        }
+
+        // This is the case when device reboots during merge. Once the device boots,
+        // snapuserd daemon will not resume merge immediately in first stage init.
+        // This is slightly different as compared to dm-snapshot-merge; In this
+        // case, metadata file will have "MERGING" state whereas the daemon will be
+        // waiting to resume the merge. Thus, we resume the merge at this point.
+        if (merge_status == "snapshot" && snapshot_status.state() == SnapshotState::MERGING) {
+            if (!snapuserd_client_->InitiateMerge(name)) {
+                return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
+
+        if (merge_status == "snapshot" &&
+            DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
+            update_status.merge_phase() == MergePhase::FIRST_PHASE) {
+            // The snapshot is not being merged because it's in the wrong phase.
+            return MergeResult(UpdateState::None);
+        }
+
+        if (merge_status == "snapshot-merge") {
+            if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
+                LOG(ERROR) << "Snapshot " << name
+                           << " is merging after being marked merge-complete.";
+                return MergeResult(UpdateState::MergeFailed,
+                                   MergeFailureCode::UnmergedSectorsAfterCompletion);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
+
+        if (merge_status != "snapshot-merge-complete") {
+            LOG(ERROR) << "Snapshot " << name << " has incorrect status: " << merge_status;
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+        }
+    } else {
+        // dm-snapshot in the kernel
+        std::string target_type;
+        DmTargetSnapshot::Status status;
+        if (!QuerySnapshotStatus(name, &target_type, &status)) {
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
+        }
+        if (target_type == "snapshot" &&
+            DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
+            update_status.merge_phase() == MergePhase::FIRST_PHASE) {
+            // The snapshot is not being merged because it's in the wrong phase.
+            return MergeResult(UpdateState::None);
+        }
+        if (target_type != "snapshot-merge") {
+            // We can get here if we failed to rewrite the target type in
+            // InitiateMerge(). If we failed to create the target in first-stage
+            // init, boot would not succeed.
+            LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+        }
+
+        // These two values are equal when merging is complete.
+        if (status.sectors_allocated != status.metadata_sectors) {
+            if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
+                LOG(ERROR) << "Snapshot " << name
+                           << " is merging after being marked merge-complete.";
+                return MergeResult(UpdateState::MergeFailed,
+                                   MergeFailureCode::UnmergedSectorsAfterCompletion);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
     }
 
-    // These two values are equal when merging is complete.
-    if (status.sectors_allocated != status.metadata_sectors) {
-        if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
-            LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
-            return MergeResult(UpdateState::MergeFailed,
-                               MergeFailureCode::UnmergedSectorsAfterCompletion);
-        }
-        return MergeResult(UpdateState::Merging);
-    }
+    // Merge is complete at this point
 
     auto code = CheckMergeConsistency(lock, name, snapshot_status);
     if (code != MergeFailureCode::Ok) {
@@ -1311,30 +1479,40 @@
 
 bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
                                               const SnapshotStatus& status) {
-    if (IsSnapshotDevice(name)) {
-        // We are extra-cautious here, to avoid deleting the wrong table.
-        std::string target_type;
-        DmTargetSnapshot::Status dm_status;
-        if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
-            return false;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        if (IsSnapshotDevice(name)) {
+            // We are extra-cautious here, to avoid deleting the wrong table.
+            std::string target_type;
+            DmTargetSnapshot::Status dm_status;
+            if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
+                return false;
+            }
+            if (target_type != "snapshot-merge") {
+                LOG(ERROR) << "Unexpected target type " << target_type
+                           << " for snapshot device: " << name;
+                return false;
+            }
+            if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
+                LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
+                return false;
+            }
+            if (!CollapseSnapshotDevice(lock, name, status)) {
+                LOG(ERROR) << "Unable to collapse snapshot: " << name;
+                return false;
+            }
         }
-        if (target_type != "snapshot-merge") {
-            LOG(ERROR) << "Unexpected target type " << target_type
-                       << " for snapshot device: " << name;
-            return false;
-        }
-        if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
-            LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
-            return false;
-        }
-        if (!CollapseSnapshotDevice(name, status)) {
+    } else {
+        // Just collapse the device - no need to query again as we just did
+        // prior to calling this function
+        if (!CollapseSnapshotDevice(lock, name, status)) {
             LOG(ERROR) << "Unable to collapse snapshot: " << name;
             return false;
         }
-        // Note that collapsing is implicitly an Unmap, so we don't need to
-        // unmap the snapshot.
     }
 
+    // Note that collapsing is implicitly an Unmap, so we don't need to
+    // unmap the snapshot.
+
     if (!DeleteSnapshot(lock, name)) {
         LOG(ERROR) << "Could not delete snapshot: " << name;
         return false;
@@ -1342,23 +1520,26 @@
     return true;
 }
 
-bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
+bool SnapshotManager::CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
                                              const SnapshotStatus& status) {
-    // Verify we have a snapshot-merge device.
-    DeviceMapper::TargetInfo target;
-    if (!GetSingleTarget(name, TableQuery::Table, &target)) {
-        return false;
-    }
-    if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
-        // This should be impossible, it was checked earlier.
-        LOG(ERROR) << "Snapshot device has invalid target type: " << name;
-        return false;
-    }
+    if (!UpdateUsesUserSnapshots(lock)) {
+        // Verify we have a snapshot-merge device.
+        DeviceMapper::TargetInfo target;
+        if (!GetSingleTarget(name, TableQuery::Table, &target)) {
+            return false;
+        }
+        if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
+            // This should be impossible, it was checked earlier.
+            LOG(ERROR) << "Snapshot device has invalid target type: " << name;
+            return false;
+        }
 
-    std::string base_device, cow_device;
-    if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
-        LOG(ERROR) << "Could not parse snapshot device " << name << " parameters: " << target.data;
-        return false;
+        std::string base_device, cow_device;
+        if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
+            LOG(ERROR) << "Could not parse snapshot device " << name
+                       << " parameters: " << target.data;
+            return false;
+        }
     }
 
     uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
@@ -1386,14 +1567,32 @@
         return false;
     }
 
-    // Attempt to delete the snapshot device if one still exists. Nothing
-    // should be depending on the device, and device-mapper should have
-    // flushed remaining I/O. We could in theory replace with dm-zero (or
-    // re-use the table above), but for now it's better to know why this
-    // would fail.
-    if (status.compression_enabled()) {
-        UnmapDmUserDevice(name);
+    if (!UpdateUsesUserSnapshots(lock)) {
+        // Attempt to delete the snapshot device if one still exists. Nothing
+        // should be depending on the device, and device-mapper should have
+        // flushed remaining I/O. We could in theory replace with dm-zero (or
+        // re-use the table above), but for now it's better to know why this
+        // would fail.
+        //
+        // Furthermore, we should not be trying to unmap for userspace snapshot
+        // 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()) {
+            auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
+            UnmapDmUserDevice(dm_user_name);
+        }
     }
+
+    // We can't delete base device immediately as daemon holds a reference.
+    // Make sure we wait for all the worker threads to terminate and release
+    // the reference
+    if (UpdateUsesUserSnapshots(lock) && EnsureSnapuserdConnected()) {
+        if (!snapuserd_client_->WaitForDeviceDelete(name)) {
+            LOG(ERROR) << "Failed to wait for " << name << " control device to delete";
+        }
+    }
+
     auto base_name = GetBaseDeviceName(name);
     if (!DeleteDeviceIfExists(base_name)) {
         LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
@@ -1464,10 +1663,15 @@
         return false;
     }
 
+    if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) {
+        snapuserd_argv->emplace_back("-user_snapshot");
+    }
+
     size_t num_cows = 0;
     size_t ok_cows = 0;
     for (const auto& snapshot : snapshots) {
-        std::string user_cow_name = GetDmUserCowName(snapshot);
+        std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get()));
+
         if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) {
             continue;
         }
@@ -1513,6 +1717,12 @@
             continue;
         }
 
+        std::string base_path_merge;
+        if (!dm_.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &base_path_merge)) {
+            LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
+            continue;
+        }
+
         std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status);
 
         std::string cow_image_device;
@@ -1529,8 +1739,14 @@
         }
 
         if (transition == InitTransition::SELINUX_DETACH) {
-            auto message = misc_name + "," + cow_image_device + "," + source_device;
-            snapuserd_argv->emplace_back(std::move(message));
+            if (!UpdateUsesUserSnapshots(lock.get())) {
+                auto message = misc_name + "," + cow_image_device + "," + source_device;
+                snapuserd_argv->emplace_back(std::move(message));
+            } else {
+                auto message = misc_name + "," + cow_image_device + "," + source_device + "," +
+                               base_path_merge;
+                snapuserd_argv->emplace_back(std::move(message));
+            }
 
             // Do not attempt to connect to the new snapuserd yet, it hasn't
             // been started. We do however want to wait for the misc device
@@ -1539,8 +1755,15 @@
             continue;
         }
 
-        uint64_t base_sectors =
-                snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
+        uint64_t base_sectors;
+        if (!UpdateUsesUserSnapshots(lock.get())) {
+            base_sectors =
+                    snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
+        } else {
+            base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device,
+                                                            source_device, base_path_merge);
+        }
+
         if (base_sectors == 0) {
             // Unrecoverable as metadata reads from cow device failed
             LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
@@ -1775,30 +1998,36 @@
         return state;
     }
 
-    // Sum all the snapshot states as if the system consists of a single huge
-    // snapshots device, then compute the merge completion percentage of that
-    // device.
-    std::vector<std::string> snapshots;
-    if (!ListSnapshots(lock.get(), &snapshots)) {
-        LOG(ERROR) << "Could not list snapshots";
-        return state;
+    if (!UpdateUsesUserSnapshots(lock.get())) {
+        // Sum all the snapshot states as if the system consists of a single huge
+        // snapshots device, then compute the merge completion percentage of that
+        // device.
+        std::vector<std::string> snapshots;
+        if (!ListSnapshots(lock.get(), &snapshots)) {
+            LOG(ERROR) << "Could not list snapshots";
+            return state;
+        }
+
+        DmTargetSnapshot::Status fake_snapshots_status = {};
+        for (const auto& snapshot : snapshots) {
+            DmTargetSnapshot::Status current_status;
+
+            if (!IsSnapshotDevice(snapshot)) continue;
+            if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
+
+            fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
+            fake_snapshots_status.total_sectors += current_status.total_sectors;
+            fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
+        }
+
+        *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
+                                                   update_status.sectors_allocated());
+    } else {
+        if (EnsureSnapuserdConnected()) {
+            *progress = snapuserd_client_->GetMergePercent();
+        }
     }
 
-    DmTargetSnapshot::Status fake_snapshots_status = {};
-    for (const auto& snapshot : snapshots) {
-        DmTargetSnapshot::Status current_status;
-
-        if (!IsSnapshotDevice(snapshot)) continue;
-        if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
-
-        fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
-        fake_snapshots_status.total_sectors += current_status.total_sectors;
-        fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
-    }
-
-    *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
-                                               update_status.sectors_allocated());
-
     return state;
 }
 
@@ -1813,6 +2042,38 @@
     return update_status.compression_enabled();
 }
 
+bool SnapshotManager::UpdateUsesUserSnapshots() {
+    // This and the following function is constantly
+    // invoked during snapshot merge. We want to avoid
+    // constantly reading from disk. Hence, store this
+    // value in memory.
+    //
+    // Furthermore, this value in the disk is set
+    // only when OTA is applied and doesn't change
+    // during merge phase. Hence, once we know that
+    // the value is read from disk the very first time,
+    // it is safe to read successive checks from memory.
+    if (is_snapshot_userspace_.has_value()) {
+        return is_snapshot_userspace_.value();
+    }
+
+    auto lock = LockShared();
+    if (!lock) return false;
+
+    return UpdateUsesUserSnapshots(lock.get());
+}
+
+bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) {
+    // See UpdateUsesUserSnapshots()
+    if (is_snapshot_userspace_.has_value()) {
+        return is_snapshot_userspace_.value();
+    }
+
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    is_snapshot_userspace_ = update_status.userspace_snapshots();
+    return is_snapshot_userspace_.value();
+}
+
 bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
                                     const std::string& suffix) {
     CHECK(lock);
@@ -2040,6 +2301,16 @@
         paths->target_device = base_path;
     }
 
+    auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+    if (remaining_time.count() < 0) {
+        return false;
+    }
+
+    // Wait for the base device to appear
+    if (!WaitForDevice(base_path, remaining_time)) {
+        return false;
+    }
+
     if (!live_snapshot_status.has_value()) {
         created_devices.Release();
         return true;
@@ -2053,7 +2324,7 @@
         return false;
     }
 
-    auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+    remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
     std::string cow_name;
@@ -2109,10 +2380,10 @@
             return false;
         }
 
-        auto name = GetDmUserCowName(params.GetPartitionName());
+        auto name = GetDmUserCowName(params.GetPartitionName(), GetSnapshotDriver(lock));
 
         std::string new_cow_device;
-        if (!MapDmUserCow(lock, name, cow_path, source_device_path, remaining_time,
+        if (!MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time,
                           &new_cow_device)) {
             LOG(ERROR) << "Could not map dm-user device for partition "
                        << params.GetPartitionName();
@@ -2126,21 +2397,37 @@
         cow_device = new_cow_device;
     }
 
-    std::string path;
-    if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
-                     &path)) {
-        LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
-        return false;
-    }
-    // No need to add params.GetPartitionName() to created_devices since it is immediately released.
+    // For userspace snapshots, dm-user block device itself will act as a
+    // snapshot device. There is one subtle difference - MapSnapshot will create
+    // either snapshot target or snapshot-merge target based on the underlying
+    // state of the snapshot device. If snapshot-merge target is created, merge
+    // will immediately start in the kernel.
+    //
+    // This is no longer true with respect to userspace snapshots. When dm-user
+    // block device is created, we just have the snapshots ready but daemon in
+    // the user-space will not start the merge. We have to explicitly inform the
+    // daemon to resume the merge. Check ProcessUpdateState() call stack.
+    if (!UpdateUsesUserSnapshots(lock)) {
+        std::string path;
+        if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
+                         &path)) {
+            LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
+            return false;
+        }
+        // No need to add params.GetPartitionName() to created_devices since it is immediately
+        // released.
 
-    if (paths) {
-        paths->snapshot_device = path;
+        if (paths) {
+            paths->snapshot_device = path;
+        }
+        LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
+    } else {
+        LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at "
+                  << cow_device;
     }
 
     created_devices.Release();
 
-    LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
     return true;
 }
 
@@ -2247,8 +2534,11 @@
     CHECK(lock);
     if (!EnsureImageManager()) return false;
 
-    if (UpdateUsesCompression(lock) && !UnmapDmUserDevice(name)) {
-        return false;
+    if (UpdateUsesCompression(lock) && !UpdateUsesUserSnapshots(lock)) {
+        auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
+        if (!UnmapDmUserDevice(dm_user_name)) {
+            return false;
+        }
     }
 
     if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
@@ -2264,8 +2554,7 @@
     return true;
 }
 
-bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
-    auto dm_user_name = GetDmUserCowName(snapshot_name);
+bool SnapshotManager::UnmapDmUserDevice(const std::string& dm_user_name) {
     if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
         return true;
     }
@@ -2291,6 +2580,46 @@
     return true;
 }
 
+bool SnapshotManager::UnmapUserspaceSnapshotDevice(LockedFile* lock,
+                                                   const std::string& snapshot_name) {
+    auto dm_user_name = GetDmUserCowName(snapshot_name, GetSnapshotDriver(lock));
+    if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
+        return true;
+    }
+
+    CHECK(lock);
+
+    SnapshotStatus snapshot_status;
+
+    if (!ReadSnapshotStatus(lock, snapshot_name, &snapshot_status)) {
+        return false;
+    }
+    // If the merge is complete, then we switch dm tables which is equivalent
+    // to unmap; hence, we can't be deleting the device
+    // as the table would be mounted off partitions and will fail.
+    if (snapshot_status.state() != SnapshotState::MERGE_COMPLETED) {
+        if (!DeleteDeviceIfExists(dm_user_name)) {
+            LOG(ERROR) << "Cannot unmap " << dm_user_name;
+            return false;
+        }
+    }
+
+    if (EnsureSnapuserdConnected()) {
+        if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
+            LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
+            return false;
+        }
+    }
+
+    // Ensure the control device is gone so we don't run into ABA problems.
+    auto control_device = "/dev/dm-user/" + dm_user_name;
+    if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
+        LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
+        return false;
+    }
+    return true;
+}
+
 bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) {
     auto lock = LockExclusive();
     if (!lock) return false;
@@ -2527,6 +2856,7 @@
         status.set_compression_enabled(old_status.compression_enabled());
         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());
     }
     return WriteSnapshotUpdateStatus(lock, status);
 }
@@ -2844,6 +3174,43 @@
     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()) {
+            // Userspace snapshots is enabled only if compression is enabled
+            status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
+            if (IsUserspaceSnapshotsEnabled()) {
+                is_snapshot_userspace_ = true;
+                LOG(INFO) << "User-space snapshots enabled";
+            } else {
+                is_snapshot_userspace_ = false;
+                LOG(INFO) << "User-space snapshots disabled";
+            }
+
+            // Terminate stale daemon if any
+            std::unique_ptr<SnapuserdClient> snapuserd_client =
+                    SnapuserdClient::Connect(kSnapuserdSocket, 10s);
+            if (snapuserd_client) {
+                snapuserd_client->DetachSnapuserd();
+                snapuserd_client->CloseConnection();
+                snapuserd_client = nullptr;
+            }
+
+            // Clear the cached client if any
+            if (snapuserd_client_) {
+                snapuserd_client_->CloseConnection();
+                snapuserd_client_ = nullptr;
+            }
+        } else {
+            status.set_userspace_snapshots(!IsDmSnapshotTestingEnabled());
+            if (IsDmSnapshotTestingEnabled()) {
+                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 (!WriteSnapshotUpdateStatus(lock.get(), status)) {
         LOG(ERROR) << "Unable to write new update state";
         return Return::Error();
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index acee2f4..54c6a00 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -488,7 +488,7 @@
             .fs_type = "ext4",
             .mount_point = mount_point,
     };
-    CHECK(0 == fs_mgr_do_format(entry, false /* crypt_footer */));
+    CHECK(0 == fs_mgr_do_format(entry));
     CHECK(0 == fs_mgr_do_mount_one(entry));
     return std::make_unique<AutoUnmount>(mount_point);
 }
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 078f16e..9adc655 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -155,8 +155,8 @@
     }
 
     std::string MakeXorBlockString() {
-        std::string data(100, -1);
-        data.resize(kBlockSize, 0);
+        std::string data(kBlockSize, 0);
+        memset(data.data(), 0xff, 100);
         return data;
     }
 
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index a8d5b8a..4af5367 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -121,6 +121,11 @@
     return false;
 }
 
+bool SnapshotManagerStub::UpdateUsesUserSnapshots() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
 class SnapshotMergeStatsStub : public ISnapshotMergeStats {
     bool Start() override { return false; }
     void set_state(android::snapshot::UpdateState, bool) override {}
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index d78ba0a..14f2d45 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -34,6 +34,7 @@
 #include <fs_mgr/file_wait.h>
 #include <fs_mgr/roots.h>
 #include <fs_mgr_dm_linear.h>
+#include <gflags/gflags.h>
 #include <gtest/gtest.h>
 #include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
@@ -45,11 +46,15 @@
 #include "partition_cow_creator.h"
 #include "utility.h"
 
+#include <android-base/properties.h>
+
 // Mock classes are not used. Header included to ensure mocked class definition aligns with the
 // class itself.
 #include <libsnapshot/mock_device_info.h>
 #include <libsnapshot/mock_snapshot.h>
 
+DEFINE_string(force_config, "", "Force testing mode (dmsnap, vab, vabc) ignoring device config.");
+
 namespace android {
 namespace snapshot {
 
@@ -85,6 +90,8 @@
 std::string fake_super;
 
 void MountMetadata();
+bool ShouldUseCompression();
+bool ShouldUseUserspaceSnapshots();
 
 class SnapshotTest : public ::testing::Test {
   public:
@@ -137,11 +144,7 @@
         std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
                                               "test_partition_b"};
         for (const auto& snapshot : snapshots) {
-            ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
-            DeleteBackingImage(image_manager_, snapshot + "-cow-img");
-
-            auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
-            android::base::RemoveFileIfExists(status_file);
+            CleanupSnapshotArtifacts(snapshot);
         }
 
         // Remove stale partitions in fake super.
@@ -149,7 +152,7 @@
                 "base-device",
                 "test_partition_b",
                 "test_partition_b-base",
-                "test_partition_b-base",
+                "test_partition_b-cow",
         };
         for (const auto& partition : partitions) {
             DeleteDevice(partition);
@@ -161,6 +164,32 @@
         }
     }
 
+    void CleanupSnapshotArtifacts(const std::string& snapshot) {
+        // The device-mapper stack may have been collapsed to dm-linear, so it's
+        // necessary to check what state it's in before attempting a cleanup.
+        // SnapshotManager has no path like this because we'd never remove a
+        // merged snapshot (a live partition).
+        bool is_dm_user = false;
+        DeviceMapper::TargetInfo target;
+        if (sm->IsSnapshotDevice(snapshot, &target)) {
+            is_dm_user = (DeviceMapper::GetTargetType(target.spec) == "user");
+        }
+
+        if (is_dm_user) {
+            ASSERT_TRUE(sm->EnsureSnapuserdConnected());
+            ASSERT_TRUE(AcquireLock());
+
+            auto local_lock = std::move(lock_);
+            ASSERT_TRUE(sm->UnmapUserspaceSnapshotDevice(local_lock.get(), snapshot));
+        }
+
+        ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
+        DeleteBackingImage(image_manager_, snapshot + "-cow-img");
+
+        auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
+        android::base::RemoveFileIfExists(status_file);
+    }
+
     bool AcquireLock() {
         lock_ = sm->LockExclusive();
         return !!lock_;
@@ -272,7 +301,7 @@
     AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
         AssertionResult res = AssertionSuccess();
         if (!(res = DeleteDevice(snapshot))) return res;
-        if (!sm->UnmapDmUserDevice(snapshot)) {
+        if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) {
             return AssertionFailure() << "Cannot delete dm-user device for " << snapshot;
         }
         if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
@@ -426,7 +455,7 @@
     ASSERT_TRUE(AcquireLock());
 
     PartitionCowCreator cow_creator;
-    cow_creator.compression_enabled = IsCompressionEnabled();
+    cow_creator.compression_enabled = ShouldUseCompression();
     if (cow_creator.compression_enabled) {
         cow_creator.compression_algorithm = "gz";
     } else {
@@ -467,7 +496,7 @@
     ASSERT_TRUE(AcquireLock());
 
     PartitionCowCreator cow_creator;
-    cow_creator.compression_enabled = IsCompressionEnabled();
+    cow_creator.compression_enabled = ShouldUseCompression();
 
     static const uint64_t kDeviceSize = 1024 * 1024;
     SnapshotStatus status;
@@ -525,6 +554,8 @@
     std::unique_ptr<ISnapshotWriter> writer;
     ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
 
+    bool userspace_snapshots = sm->UpdateUsesUserSnapshots(lock_.get());
+
     // Release the lock.
     lock_ = nullptr;
 
@@ -546,7 +577,11 @@
     // The device should have been switched to a snapshot-merge target.
     DeviceMapper::TargetInfo target;
     ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+    if (userspace_snapshots) {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+    } else {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+    }
 
     // We should not be able to cancel an update now.
     ASSERT_FALSE(sm->CancelUpdate());
@@ -582,11 +617,13 @@
 
     ASSERT_TRUE(AcquireLock());
 
+    bool userspace_snapshots = init->UpdateUsesUserSnapshots(lock_.get());
+
     // Validate that we have a snapshot device.
     SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
     ASSERT_EQ(status.state(), SnapshotState::CREATED);
-    if (IsCompressionEnabled()) {
+    if (ShouldUseCompression()) {
         ASSERT_EQ(status.compression_algorithm(), "gz");
     } else {
         ASSERT_EQ(status.compression_algorithm(), "none");
@@ -594,7 +631,11 @@
 
     DeviceMapper::TargetInfo target;
     ASSERT_TRUE(init->IsSnapshotDevice("test_partition_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    if (userspace_snapshots) {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+    } else {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    }
 }
 
 TEST_F(SnapshotTest, FlashSuperDuringUpdate) {
@@ -856,7 +897,7 @@
         opener_ = std::make_unique<TestPartitionOpener>(fake_super);
 
         auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
-        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+        dynamic_partition_metadata->set_vabc_enabled(ShouldUseCompression());
         dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
 
         // Create a fake update package metadata.
@@ -895,9 +936,9 @@
         ASSERT_NE(nullptr, metadata);
         ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
 
-        // Map source partitions. Additionally, map sys_b to simulate system_other after flashing.
+        // Map source partitions.
         std::string path;
-        for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) {
+        for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
             ASSERT_TRUE(CreateLogicalPartition(
                     CreateLogicalPartitionParams{
                             .block_device = fake_super,
@@ -989,7 +1030,7 @@
     }
 
     AssertionResult MapOneUpdateSnapshot(const std::string& name) {
-        if (IsCompressionEnabled()) {
+        if (ShouldUseCompression()) {
             std::unique_ptr<ISnapshotWriter> writer;
             return MapUpdateSnapshot(name, &writer);
         } else {
@@ -999,7 +1040,7 @@
     }
 
     AssertionResult WriteSnapshotAndHash(const std::string& name) {
-        if (IsCompressionEnabled()) {
+        if (ShouldUseCompression()) {
             std::unique_ptr<ISnapshotWriter> writer;
             auto res = MapUpdateSnapshot(name, &writer);
             if (!res) {
@@ -1167,7 +1208,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+    ASSERT_EQ(init->IsSnapuserdRequired(), ShouldUseUserspaceSnapshots());
     {
         // We should have started in SECOND_PHASE since nothing shrinks.
         ASSERT_TRUE(AcquireLock());
@@ -1194,7 +1235,7 @@
 }
 
 TEST_F(SnapshotUpdateTest, DuplicateOps) {
-    if (!IsCompressionEnabled()) {
+    if (!ShouldUseCompression()) {
         GTEST_SKIP() << "Compression-only test";
     }
 
@@ -1238,7 +1279,7 @@
 // Test that shrinking and growing partitions at the same time is handled
 // correctly in VABC.
 TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
-    if (!IsCompressionEnabled()) {
+    if (!ShouldUseCompression()) {
         // b/179111359
         GTEST_SKIP() << "Skipping Virtual A/B Compression test";
     }
@@ -1301,7 +1342,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+    ASSERT_EQ(init->IsSnapuserdRequired(), ShouldUseUserspaceSnapshots());
     {
         // Check that the merge phase is FIRST_PHASE until at least one call
         // to ProcessUpdateState() occurs.
@@ -1318,11 +1359,21 @@
     // Check that we used the correct types after rebooting mid-merge.
     DeviceMapper::TargetInfo target;
     ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
-    ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
-    ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+
+    bool userspace_snapshots = init->UpdateUsesUserSnapshots();
+    if (userspace_snapshots) {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+        ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+        ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+    } else {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+        ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+        ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    }
 
     // Complete the merge.
     ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
@@ -1800,6 +1851,8 @@
 
     ASSERT_TRUE(new_sm->FinishMergeInRecovery());
 
+    ASSERT_TRUE(UnmapAll());
+
     auto mount = new_sm->EnsureMetadataMounted();
     ASSERT_TRUE(mount && mount->HasDevice());
     ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
@@ -1892,6 +1945,8 @@
     ASSERT_FALSE(test_device->IsSlotUnbootable(1));
     ASSERT_FALSE(test_device->IsSlotUnbootable(0));
 
+    ASSERT_TRUE(UnmapAll());
+
     // Now reboot into new slot.
     test_device = new TestDeviceInfo(fake_super, "_b");
     auto init = NewManagerForFirstStageMount(test_device);
@@ -1920,8 +1975,8 @@
         ASSERT_TRUE(AcquireLock());
 
         PartitionCowCreator cow_creator = {
-                .compression_enabled = IsCompressionEnabled(),
-                .compression_algorithm = IsCompressionEnabled() ? "gz" : "none",
+                .compression_enabled = ShouldUseCompression(),
+                .compression_algorithm = ShouldUseCompression() ? "gz" : "none",
         };
         SnapshotStatus status;
         status.set_name("sys_a");
@@ -1953,6 +2008,8 @@
     ASSERT_FALSE(test_device->IsSlotUnbootable(1));
     ASSERT_FALSE(test_device->IsSlotUnbootable(0));
 
+    ASSERT_TRUE(UnmapAll());
+
     // Now reboot into new slot.
     test_device = new TestDeviceInfo(fake_super, "_b");
     auto init = NewManagerForFirstStageMount(test_device);
@@ -2015,7 +2072,7 @@
 
 // Test for overflow bit after update
 TEST_F(SnapshotUpdateTest, Overflow) {
-    if (IsCompressionEnabled()) {
+    if (ShouldUseCompression()) {
         GTEST_SKIP() << "No overflow bit set for userspace COWs";
     }
 
@@ -2150,7 +2207,7 @@
 };
 
 TEST_F(SnapshotUpdateTest, DaemonTransition) {
-    if (!IsCompressionEnabled()) {
+    if (!ShouldUseCompression()) {
         GTEST_SKIP() << "Skipping Virtual A/B Compression test";
     }
 
@@ -2176,21 +2233,38 @@
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
 
-    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow-init", F_OK), 0);
-    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), -1);
+    bool userspace_snapshots = init->UpdateUsesUserSnapshots();
+
+    if (userspace_snapshots) {
+        ASSERT_EQ(access("/dev/dm-user/sys_b-init", F_OK), 0);
+        ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), -1);
+    } else {
+        ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow-init", F_OK), 0);
+        ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), -1);
+    }
 
     ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
 
     // :TODO: this is a workaround to ensure the handler list stays empty. We
     // should make this test more like actual init, and spawn two copies of
     // snapuserd, given how many other tests we now have for normal snapuserd.
-    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init"));
-    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init"));
-    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init"));
+    if (userspace_snapshots) {
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-init"));
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-init"));
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-init"));
 
-    // The control device should have been renamed.
-    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s));
-    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), 0);
+        // The control device should have been renamed.
+        ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-init", 10s));
+        ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), 0);
+    } else {
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init"));
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init"));
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init"));
+
+        // The control device should have been renamed.
+        ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s));
+        ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), 0);
+    }
 }
 
 TEST_F(SnapshotUpdateTest, MapAllSnapshots) {
@@ -2213,6 +2287,8 @@
 TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) {
     AddOperationForPartitions();
 
+    ASSERT_TRUE(UnmapAll());
+
     // Execute the update from B->A.
     test_device->set_slot_suffix("_b");
     ASSERT_TRUE(sm->BeginUpdate());
@@ -2229,8 +2305,13 @@
             },
             &path));
 
-    // Hold sys_a open so it can't be unmapped.
-    unique_fd fd(open(path.c_str(), O_RDONLY));
+    bool userspace_snapshots = sm->UpdateUsesUserSnapshots();
+
+    unique_fd fd;
+    if (!userspace_snapshots) {
+        // Hold sys_a open so it can't be unmapped.
+        fd.reset(open(path.c_str(), O_RDONLY));
+    }
 
     // Switch back to "A", make sure we can cancel. Instead of unmapping sys_a
     // we should simply delete the old snapshots.
@@ -2249,6 +2330,11 @@
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    if (sm->UpdateUsesUserSnapshots()) {
+        GTEST_SKIP() << "Test does not apply to userspace snapshots";
+    }
+
     ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
     ASSERT_TRUE(UnmapAll());
@@ -2553,11 +2639,53 @@
     }
 }
 
+bool ShouldUseUserspaceSnapshots() {
+    if (FLAGS_force_config == "dmsnap") {
+        return false;
+    }
+    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();
+}
+
 }  // namespace snapshot
 }  // namespace android
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
     ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
-    return RUN_ALL_TESTS();
+    gflags::ParseCommandLineFlags(&argc, &argv, false);
+
+    android::base::SetProperty("ctl.stop", "snapuserd");
+
+    std::unordered_set<std::string> configs = {"", "dmsnap", "vab", "vabc"};
+    if (configs.count(FLAGS_force_config) == 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";
+        }
+    }
+
+    int ret = RUN_ALL_TESTS();
+
+    if (FLAGS_force_config == "dmsnap") {
+        android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
+    }
+    return ret;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index c9b0512..84bcb94 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -61,12 +61,12 @@
         "dm-snapshot-merge/snapuserd_worker.cpp",
         "dm-snapshot-merge/snapuserd_readahead.cpp",
         "snapuserd_daemon.cpp",
-	"snapuserd_buffer.cpp",
-	"user-space-merge/snapuserd_core.cpp",
-	"user-space-merge/snapuserd_dm_user.cpp",
-	"user-space-merge/snapuserd_merge.cpp",
-	"user-space-merge/snapuserd_readahead.cpp",
-	"user-space-merge/snapuserd_transitions.cpp",
+        "snapuserd_buffer.cpp",
+        "user-space-merge/snapuserd_core.cpp",
+        "user-space-merge/snapuserd_dm_user.cpp",
+        "user-space-merge/snapuserd_merge.cpp",
+        "user-space-merge/snapuserd_readahead.cpp",
+        "user-space-merge/snapuserd_transitions.cpp",
         "user-space-merge/snapuserd_server.cpp",
     ],
 
@@ -95,11 +95,24 @@
     init_rc: [
         "snapuserd.rc",
     ],
+
+    // 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
+    // snapuserd, which would lead to deadlock if we had to handle page
+    // 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_test {
@@ -111,7 +124,45 @@
         "dm-snapshot-merge/cow_snapuserd_test.cpp",
         "dm-snapshot-merge/snapuserd.cpp",
         "dm-snapshot-merge/snapuserd_worker.cpp",
-	"snapuserd_buffer.cpp",
+        "snapuserd_buffer.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    static_libs: [
+        "libbrotli",
+        "libgtest",
+        "libsnapshot_cow",
+        "libsnapshot_snapuserd",
+        "libcutils_sockets",
+        "libz",
+        "libfs_mgr",
+        "libdm",
+        "libext4_utils",
+    ],
+    header_libs: [
+        "libstorage_literals_headers",
+        "libfiemap_headers",
+    ],
+    test_options: {
+        min_shipping_api_level: 30,
+    },
+    auto_gen_config: true,
+    require_root: false,
+}
+
+cc_test {
+    name: "snapuserd_test",
+    defaults: [
+        "fs_mgr_defaults",
+    ],
+    srcs: [
+        "user-space-merge/snapuserd_test.cpp",
     ],
     cflags: [
         "-Wall",
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index 3bb7a0a..c201b23 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -246,9 +246,15 @@
     int num_ops = 0;
     int total_blocks_merged = 0;
 
+    // This memcpy is important as metadata_buffer_ will be an unaligned address and will fault
+    // on 32-bit systems
+    std::unique_ptr<uint8_t[]> metadata_buffer =
+            std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+    memcpy(metadata_buffer.get(), metadata_buffer_, snapuserd_->GetBufferMetadataSize());
+
     while (true) {
         struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
-                (char*)metadata_buffer_ + metadata_offset);
+                (char*)metadata_buffer.get() + metadata_offset);
 
         // Done reading metadata
         if (bm->new_block == 0 && bm->file_offset == 0) {
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index 6ed55af..cebda1c 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -63,7 +63,8 @@
     // The misc_name must be the "misc_name" given to dm-user in step 2.
     //
     uint64_t InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
-                           const std::string& backing_device);
+                           const std::string& backing_device,
+                           const std::string& base_path_merge = "");
     bool AttachDmUser(const std::string& misc_name);
 
     // Wait for snapuserd to disassociate with a dm-user control device. This
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index e345269..7b1c7a3 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -195,8 +195,16 @@
 }
 
 uint64_t SnapuserdClient::InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
-                                        const std::string& backing_device) {
-    std::vector<std::string> parts = {"init", misc_name, cow_device, backing_device};
+                                        const std::string& backing_device,
+                                        const std::string& base_path_merge) {
+    std::vector<std::string> parts;
+
+    if (base_path_merge.empty()) {
+        parts = {"init", misc_name, cow_device, backing_device};
+    } else {
+        // For userspace snapshots
+        parts = {"init", misc_name, cow_device, backing_device, base_path_merge};
+    }
     std::string msg = android::base::Join(parts, ",");
     if (!Sendmsg(msg)) {
         LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon";
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index 912884f..ddb1f79 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -14,25 +14,95 @@
  * limitations under the License.
  */
 
-#include "snapuserd_daemon.h"
-
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <gflags/gflags.h>
 #include <snapuserd/snapuserd_client.h>
 
+#include "snapuserd_daemon.h"
+
 DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
 DEFINE_bool(no_socket, false,
             "If true, no socket is used. Each additional argument is an INIT message.");
 DEFINE_bool(socket_handoff, false,
             "If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
+DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
 
 namespace android {
 namespace snapshot {
 
-bool Daemon::StartServer(int argc, char** argv) {
+bool Daemon::IsUserspaceSnapshotsEnabled() {
+    return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+}
+
+bool Daemon::IsDmSnapshotTestingEnabled() {
+    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
+}
+
+bool Daemon::StartDaemon(int argc, char** argv) {
     int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true);
 
+    // Daemon launched from first stage init and during selinux transition
+    // will have the command line "-user_snapshot" flag set if the user-space
+    // snapshots are enabled.
+    //
+    // Daemon launched as a init service during "socket-handoff" and when OTA
+    // is applied will check for the property. This is ok as the system
+    // properties are valid at this point. We can't do this during first
+    // stage init and hence use the command line flags to get the information.
+    if (!IsDmSnapshotTestingEnabled() && (FLAGS_user_snapshot || IsUserspaceSnapshotsEnabled())) {
+        LOG(INFO) << "Starting daemon for user-space snapshots.....";
+        return StartServerForUserspaceSnapshots(arg_start, argc, argv);
+    } else {
+        LOG(INFO) << "Starting daemon for dm-snapshots.....";
+        return StartServerForDmSnapshot(arg_start, argc, argv);
+    }
+}
+
+bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv) {
+    sigfillset(&signal_mask_);
+    sigdelset(&signal_mask_, SIGINT);
+    sigdelset(&signal_mask_, SIGTERM);
+    sigdelset(&signal_mask_, SIGUSR1);
+
+    // Masking signals here ensure that after this point, we won't handle INT/TERM
+    // until after we call into ppoll()
+    signal(SIGINT, Daemon::SignalHandler);
+    signal(SIGTERM, Daemon::SignalHandler);
+    signal(SIGPIPE, Daemon::SignalHandler);
+    signal(SIGUSR1, Daemon::SignalHandler);
+
+    MaskAllSignalsExceptIntAndTerm();
+
+    if (FLAGS_socket_handoff) {
+        return user_server_.RunForSocketHandoff();
+    }
+    if (!FLAGS_no_socket) {
+        if (!user_server_.Start(FLAGS_socket)) {
+            return false;
+        }
+        return user_server_.Run();
+    }
+
+    for (int i = arg_start; i < argc; i++) {
+        auto parts = android::base::Split(argv[i], ",");
+        if (parts.size() != 4) {
+            LOG(ERROR) << "Malformed message, expected three sub-arguments.";
+            return false;
+        }
+        auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
+        if (!handler || !user_server_.StartHandler(handler)) {
+            return false;
+        }
+    }
+
+    // Skip the accept() call to avoid spurious log spam. The server will still
+    // run until all handlers have completed.
+    return user_server_.WaitForSocket();
+}
+
+bool Daemon::StartServerForDmSnapshot(int arg_start, int argc, char** argv) {
     sigfillset(&signal_mask_);
     sigdelset(&signal_mask_, SIGINT);
     sigdelset(&signal_mask_, SIGTERM);
@@ -95,11 +165,19 @@
 }
 
 void Daemon::Interrupt() {
-    server_.Interrupt();
+    if (IsUserspaceSnapshotsEnabled()) {
+        user_server_.Interrupt();
+    } else {
+        server_.Interrupt();
+    }
 }
 
 void Daemon::ReceivedSocketSignal() {
-    server_.ReceivedSocketSignal();
+    if (IsUserspaceSnapshotsEnabled()) {
+        user_server_.ReceivedSocketSignal();
+    } else {
+        server_.ReceivedSocketSignal();
+    }
 }
 
 void Daemon::SignalHandler(int signal) {
@@ -133,9 +211,10 @@
 
     android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance();
 
-    if (!daemon.StartServer(argc, argv)) {
-        LOG(ERROR) << "Snapuserd daemon failed to start.";
+    if (!daemon.StartDaemon(argc, argv)) {
+        LOG(ERROR) << "Snapuserd daemon failed to start";
         exit(EXIT_FAILURE);
     }
+
     return 0;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
index fbf57d9..cf3b917 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "dm-snapshot-merge/snapuserd_server.h"
+#include "user-space-merge/snapuserd_server.h"
 
 namespace android {
 namespace snapshot {
@@ -35,9 +36,13 @@
         return instance;
     }
 
-    bool StartServer(int argc, char** argv);
+    bool StartServerForDmSnapshot(int arg_start, int argc, char** argv);
+    bool StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv);
     void Interrupt();
     void ReceivedSocketSignal();
+    bool IsUserspaceSnapshotsEnabled();
+    bool IsDmSnapshotTestingEnabled();
+    bool StartDaemon(int argc, char** argv);
 
   private:
     // Signal mask used with ppoll()
@@ -47,6 +52,7 @@
     void operator=(Daemon const&) = delete;
 
     SnapuserdServer server_;
+    UserSnapshotServer user_server_;
     void MaskAllSignalsExceptIntAndTerm();
     void MaskAllSignals();
     static void SignalHandler(int signal);
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 57e47e7..95d95cd 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -35,7 +35,7 @@
 }
 
 bool SnapshotHandler::InitializeWorkers() {
-    for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
+    for (int i = 0; i < kNumWorkerThreads; i++) {
         std::unique_ptr<Worker> wt =
                 std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
                                          misc_name_, base_path_merge_, GetSharedPtr());
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 13b56fa..1953316 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -48,10 +48,10 @@
 using android::base::unique_fd;
 using namespace std::chrono_literals;
 
-static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
-static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
+static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20);
+static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ);
 
-static constexpr int NUM_THREADS_PER_PARTITION = 1;
+static constexpr int kNumWorkerThreads = 4;
 
 #define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
 #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
index bfbacf9..1e300d2 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -231,8 +231,8 @@
     // Allocate the buffer which is used to communicate between
     // daemon and dm-user. The buffer comprises of header and a fixed payload.
     // If the dm-user requests a big IO, the IO will be broken into chunks
-    // of PAYLOAD_SIZE.
-    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
+    // of PAYLOAD_BUFFER_SZ.
+    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_BUFFER_SZ;
     bufsink_.Initialize(buf_size);
 }
 
@@ -326,7 +326,7 @@
 
     do {
         // Process 1MB payload at a time
-        size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+        size_t read_size = std::min(PAYLOAD_BUFFER_SZ, remaining_size);
 
         header->type = DM_USER_RESP_SUCCESS;
         size_t total_bytes_read = 0;
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 47fc7db..fa055b7 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -81,11 +81,11 @@
     // Why 2048 ops ? We can probably increase this to bigger value but just
     // need to ensure that merge makes forward progress if there are
     // crashes repeatedly which is highly unlikely.
-    int total_ops_merged_per_commit = (PAYLOAD_SIZE / BLOCK_SZ) * 8;
+    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 8;
     int num_ops_merged = 0;
 
     while (!cowop_iter->Done()) {
-        int num_ops = PAYLOAD_SIZE / BLOCK_SZ;
+        int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
         std::vector<const CowOperation*> replace_zero_vec;
         uint64_t source_offset;
 
@@ -292,6 +292,7 @@
 
     if (!Init()) {
         SNAP_LOG(ERROR) << "Merge thread initialization failed...";
+        snapuserd_->MergeFailed();
         return false;
     }
 
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 0bcf26e..9e8ccfb 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -121,9 +121,15 @@
     int num_ops = 0;
     int total_blocks_merged = 0;
 
+    // This memcpy is important as metadata_buffer_ will be an unaligned address and will fault
+    // on 32-bit systems
+    std::unique_ptr<uint8_t[]> metadata_buffer =
+            std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+    memcpy(metadata_buffer.get(), metadata_buffer_, snapuserd_->GetBufferMetadataSize());
+
     while (true) {
         struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
-                (char*)metadata_buffer_ + metadata_offset);
+                (char*)metadata_buffer.get() + metadata_offset);
 
         // Done reading metadata
         if (bm->new_block == 0 && bm->file_offset == 0) {
@@ -429,7 +435,7 @@
             static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
     read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
     // For xor ops
-    bufsink_.Initialize(PAYLOAD_SIZE);
+    bufsink_.Initialize(PAYLOAD_BUFFER_SZ);
 }
 
 }  // namespace snapshot
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 a4fd5a0..a79e3e1 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -44,7 +44,7 @@
 using android::base::borrowed_fd;
 using android::base::unique_fd;
 
-DaemonOps SnapuserServer::Resolveop(std::string& input) {
+DaemonOps UserSnapshotServer::Resolveop(std::string& input) {
     if (input == "init") return DaemonOps::INIT;
     if (input == "start") return DaemonOps::START;
     if (input == "stop") return DaemonOps::STOP;
@@ -59,14 +59,14 @@
     return DaemonOps::INVALID;
 }
 
-SnapuserServer::~SnapuserServer() {
+UserSnapshotServer::~UserSnapshotServer() {
     // Close any client sockets that were added via AcceptClient().
     for (size_t i = 1; i < watched_fds_.size(); i++) {
         close(watched_fds_[i].fd);
     }
 }
 
-std::string SnapuserServer::GetDaemonStatus() {
+std::string UserSnapshotServer::GetDaemonStatus() {
     std::string msg = "";
 
     if (IsTerminating())
@@ -77,8 +77,8 @@
     return msg;
 }
 
-void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
-                              std::vector<std::string>& out) {
+void UserSnapshotServer::Parsemsg(std::string const& msg, const char delim,
+                                  std::vector<std::string>& out) {
     std::stringstream ss(msg);
     std::string s;
 
@@ -87,15 +87,15 @@
     }
 }
 
-void SnapuserServer::ShutdownThreads() {
+void UserSnapshotServer::ShutdownThreads() {
     terminating_ = true;
     JoinAllThreads();
 }
 
-DmUserHandler::DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
+UserSnapshotDmUserHandler::UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
     : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
 
-bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
+bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
     ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
     if (ret < 0) {
         PLOG(ERROR) << "Snapuserd:server: send() failed";
@@ -109,8 +109,8 @@
     return true;
 }
 
-bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
-    char msg[MAX_PACKET_SIZE];
+bool UserSnapshotServer::Recv(android::base::borrowed_fd fd, std::string* data) {
+    char msg[kMaxPacketSize];
     ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
     if (rv < 0) {
         PLOG(ERROR) << "recv failed";
@@ -120,7 +120,7 @@
     return true;
 }
 
-bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
+bool UserSnapshotServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
     const char delim = ',';
 
     std::vector<std::string> out;
@@ -290,7 +290,7 @@
     }
 }
 
-void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
+void UserSnapshotServer::RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler) {
     LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
     handler->snapuserd()->SetSocketPresent(is_socket_present_);
@@ -337,7 +337,7 @@
     }
 }
 
-bool SnapuserServer::Start(const std::string& socketname) {
+bool UserSnapshotServer::Start(const std::string& socketname) {
     bool start_listening = true;
 
     sockfd_.reset(android_get_control_socket(socketname.c_str()));
@@ -353,7 +353,7 @@
     return StartWithSocket(start_listening);
 }
 
-bool SnapuserServer::StartWithSocket(bool start_listening) {
+bool UserSnapshotServer::StartWithSocket(bool start_listening) {
     if (start_listening && listen(sockfd_.get(), 4) < 0) {
         PLOG(ERROR) << "listen socket failed";
         return false;
@@ -374,7 +374,7 @@
     return true;
 }
 
-bool SnapuserServer::Run() {
+bool UserSnapshotServer::Run() {
     LOG(INFO) << "Now listening on snapuserd socket";
 
     while (!IsTerminating()) {
@@ -406,9 +406,9 @@
     return true;
 }
 
-void SnapuserServer::JoinAllThreads() {
+void UserSnapshotServer::JoinAllThreads() {
     // Acquire the thread list within the lock.
-    std::vector<std::shared_ptr<DmUserHandler>> dm_users;
+    std::vector<std::shared_ptr<UserSnapshotDmUserHandler>> dm_users;
     {
         std::lock_guard<std::mutex> guard(lock_);
         dm_users = std::move(dm_users_);
@@ -421,14 +421,14 @@
     }
 }
 
-void SnapuserServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
+void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
     struct pollfd p = {};
     p.fd = fd.get();
     p.events = events;
     watched_fds_.emplace_back(std::move(p));
 }
 
-void SnapuserServer::AcceptClient() {
+void UserSnapshotServer::AcceptClient() {
     int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC));
     if (fd < 0) {
         PLOG(ERROR) << "accept4 failed";
@@ -438,7 +438,7 @@
     AddWatchedFd(fd, POLLIN);
 }
 
-bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
+bool UserSnapshotServer::HandleClient(android::base::borrowed_fd fd, int revents) {
     if (revents & POLLHUP) {
         LOG(DEBUG) << "Snapuserd client disconnected";
         return false;
@@ -455,16 +455,15 @@
     return true;
 }
 
-void SnapuserServer::Interrupt() {
+void UserSnapshotServer::Interrupt() {
     // Force close the socket so poll() fails.
     sockfd_ = {};
     SetTerminating();
 }
 
-std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& misc_name,
-                                                          const std::string& cow_device_path,
-                                                          const std::string& backing_device,
-                                                          const std::string& base_path_merge) {
+std::shared_ptr<UserSnapshotDmUserHandler> UserSnapshotServer::AddHandler(
+        const std::string& misc_name, const std::string& cow_device_path,
+        const std::string& backing_device, const std::string& base_path_merge) {
     auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
                                                        base_path_merge);
     if (!snapuserd->InitCowDevice()) {
@@ -477,7 +476,7 @@
         return nullptr;
     }
 
-    auto handler = std::make_shared<DmUserHandler>(snapuserd);
+    auto handler = std::make_shared<UserSnapshotDmUserHandler>(snapuserd);
     {
         std::lock_guard<std::mutex> lock(lock_);
         if (FindHandler(&lock, misc_name) != dm_users_.end()) {
@@ -489,7 +488,7 @@
     return handler;
 }
 
-bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
+bool UserSnapshotServer::StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
     if (handler->snapuserd()->IsAttached()) {
         LOG(ERROR) << "Handler already attached";
         return false;
@@ -497,11 +496,11 @@
 
     handler->snapuserd()->AttachControlDevice();
 
-    handler->thread() = std::thread(std::bind(&SnapuserServer::RunThread, this, handler));
+    handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler));
     return true;
 }
 
-bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
+bool UserSnapshotServer::StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
     if (!handler->snapuserd()->IsAttached()) {
         LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
         return false;
@@ -511,8 +510,8 @@
     return true;
 }
 
-auto SnapuserServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                 const std::string& misc_name) -> HandlerList::iterator {
+auto UserSnapshotServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                     const std::string& misc_name) -> HandlerList::iterator {
     CHECK(proof_of_lock);
 
     for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@@ -523,7 +522,7 @@
     return dm_users_.end();
 }
 
-void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
+void UserSnapshotServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
     CHECK(proof_of_lock);
 
     for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@@ -533,11 +532,12 @@
     }
 }
 
-std::string SnapuserServer::GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler) {
+std::string UserSnapshotServer::GetMergeStatus(
+        const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
     return handler->snapuserd()->GetMergeStatus();
 }
 
-double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
+double UserSnapshotServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
     CHECK(proof_of_lock);
     double percentage = 0.0;
     int n = 0;
@@ -567,8 +567,8 @@
     return percentage;
 }
 
-bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
-    std::shared_ptr<DmUserHandler> handler;
+bool UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) {
+    std::shared_ptr<UserSnapshotDmUserHandler> handler;
     {
         std::lock_guard<std::mutex> lock(lock_);
 
@@ -588,7 +588,7 @@
     return true;
 }
 
-bool SnapuserServer::WaitForSocket() {
+bool UserSnapshotServer::WaitForSocket() {
     auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
 
     auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
@@ -642,7 +642,7 @@
     return Run();
 }
 
-bool SnapuserServer::RunForSocketHandoff() {
+bool UserSnapshotServer::RunForSocketHandoff() {
     unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
     if (proxy_fd < 0) {
         PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;
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 e93621c..c645456 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -33,7 +33,7 @@
 namespace android {
 namespace snapshot {
 
-static constexpr uint32_t MAX_PACKET_SIZE = 512;
+static constexpr uint32_t kMaxPacketSize = 512;
 
 enum class DaemonOps {
     INIT,
@@ -49,9 +49,9 @@
     INVALID,
 };
 
-class DmUserHandler {
+class UserSnapshotDmUserHandler {
   public:
-    explicit DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
+    explicit UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
 
     void FreeResources() {
         // Each worker thread holds a reference to snapuserd.
@@ -76,7 +76,7 @@
     bool thread_terminated_ = false;
 };
 
-class SnapuserServer {
+class UserSnapshotServer {
   private:
     android::base::unique_fd sockfd_;
     bool terminating_;
@@ -87,7 +87,7 @@
 
     std::mutex lock_;
 
-    using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
+    using HandlerList = std::vector<std::shared_ptr<UserSnapshotDmUserHandler>>;
     HandlerList dm_users_;
 
     void AddWatchedFd(android::base::borrowed_fd fd, int events);
@@ -105,11 +105,11 @@
 
     bool IsTerminating() { return terminating_; }
 
-    void RunThread(std::shared_ptr<DmUserHandler> handler);
+    void RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler);
     void JoinAllThreads();
     bool StartWithSocket(bool start_listening);
 
-    // Find a DmUserHandler within a lock.
+    // Find a UserSnapshotDmUserHandler within a lock.
     HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
                                       const std::string& misc_name);
 
@@ -117,8 +117,8 @@
     void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
 
   public:
-    SnapuserServer() { terminating_ = false; }
-    ~SnapuserServer();
+    UserSnapshotServer() { terminating_ = false; }
+    ~UserSnapshotServer();
 
     bool Start(const std::string& socketname);
     bool Run();
@@ -126,13 +126,13 @@
     bool RunForSocketHandoff();
     bool WaitForSocket();
 
-    std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
-                                              const std::string& cow_device_path,
-                                              const std::string& backing_device,
-                                              const std::string& base_path_merge);
-    bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
-    bool StartMerge(const std::shared_ptr<DmUserHandler>& handler);
-    std::string GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler);
+    std::shared_ptr<UserSnapshotDmUserHandler> AddHandler(const std::string& misc_name,
+                                                          const std::string& cow_device_path,
+                                                          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);
+    std::string GetMergeStatus(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
 
     void SetTerminating() { terminating_ = true; }
     void ReceivedSocketSignal() { received_socket_signal_ = true; }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
new file mode 100644
index 0000000..1c3e04b
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -0,0 +1,861 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <linux/memfd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <fs_mgr/file_wait.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <libdm/loop_control.h>
+#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_client.h>
+#include <storage_literals/storage_literals.h>
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android::storage_literals;
+using android::base::unique_fd;
+using LoopDevice = android::dm::LoopDevice;
+using namespace std::chrono_literals;
+using namespace android::dm;
+using namespace std;
+
+static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
+
+class Tempdevice {
+  public:
+    Tempdevice(const std::string& name, const DmTable& table)
+        : dm_(DeviceMapper::Instance()), name_(name), valid_(false) {
+        valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5));
+    }
+    Tempdevice(Tempdevice&& other) noexcept
+        : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) {
+        other.valid_ = false;
+    }
+    ~Tempdevice() {
+        if (valid_) {
+            dm_.DeleteDevice(name_);
+        }
+    }
+    bool Destroy() {
+        if (!valid_) {
+            return false;
+        }
+        valid_ = false;
+        return dm_.DeleteDevice(name_);
+    }
+    const std::string& path() const { return path_; }
+    const std::string& name() const { return name_; }
+    bool valid() const { return valid_; }
+
+    Tempdevice(const Tempdevice&) = delete;
+    Tempdevice& operator=(const Tempdevice&) = delete;
+
+    Tempdevice& operator=(Tempdevice&& other) noexcept {
+        name_ = other.name_;
+        valid_ = other.valid_;
+        other.valid_ = false;
+        return *this;
+    }
+
+  private:
+    DeviceMapper& dm_;
+    std::string name_;
+    std::string path_;
+    bool valid_;
+};
+
+class SnapuserTest final {
+  public:
+    bool Setup();
+    bool SetupOrderedOps();
+    bool SetupOrderedOpsInverted();
+    bool SetupCopyOverlap_1();
+    bool SetupCopyOverlap_2();
+    bool Merge();
+    void ValidateMerge();
+    void ReadSnapshotDeviceAndValidate();
+    void Shutdown();
+    void MergeInterrupt();
+    void MergeInterruptFixed(int duration);
+    void MergeInterruptRandomly(int max_duration);
+    void StartMerge();
+    void CheckMergeCompletion();
+
+    static const uint64_t kSectorSize = 512;
+
+  private:
+    void SetupImpl();
+
+    void SimulateDaemonRestart();
+
+    void CreateCowDevice();
+    void CreateCowDeviceOrderedOps();
+    void CreateCowDeviceOrderedOpsInverted();
+    void CreateCowDeviceWithCopyOverlap_1();
+    void CreateCowDeviceWithCopyOverlap_2();
+    bool SetupDaemon();
+    void CreateBaseDevice();
+    void InitCowDevice();
+    void SetDeviceControlName();
+    void InitDaemon();
+    void CreateDmUserDevice();
+    void StartSnapuserdDaemon();
+
+    unique_ptr<LoopDevice> base_loop_;
+    unique_ptr<Tempdevice> dmuser_dev_;
+
+    std::string system_device_ctrl_name_;
+    std::string system_device_name_;
+
+    unique_fd base_fd_;
+    std::unique_ptr<TemporaryFile> cow_system_;
+    std::unique_ptr<SnapuserdClient> client_;
+    std::unique_ptr<uint8_t[]> orig_buffer_;
+    std::unique_ptr<uint8_t[]> merged_buffer_;
+    bool setup_ok_ = false;
+    bool merge_ok_ = false;
+    size_t size_ = 100_MiB;
+    int cow_num_sectors_;
+    int total_base_size_;
+};
+
+static unique_fd CreateTempFile(const std::string& name, size_t size) {
+    unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING));
+    if (fd < 0) {
+        return {};
+    }
+    if (size) {
+        if (ftruncate(fd, size) < 0) {
+            perror("ftruncate");
+            return {};
+        }
+        if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
+            perror("fcntl");
+            return {};
+        }
+    }
+    return fd;
+}
+
+void SnapuserTest::Shutdown() {
+    ASSERT_TRUE(dmuser_dev_->Destroy());
+
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
+    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
+    ASSERT_TRUE(client_->DetachSnapuserd());
+}
+
+bool SnapuserTest::Setup() {
+    SetupImpl();
+    return setup_ok_;
+}
+
+bool SnapuserTest::SetupOrderedOps() {
+    CreateBaseDevice();
+    CreateCowDeviceOrderedOps();
+    return SetupDaemon();
+}
+
+bool SnapuserTest::SetupOrderedOpsInverted() {
+    CreateBaseDevice();
+    CreateCowDeviceOrderedOpsInverted();
+    return SetupDaemon();
+}
+
+bool SnapuserTest::SetupCopyOverlap_1() {
+    CreateBaseDevice();
+    CreateCowDeviceWithCopyOverlap_1();
+    return SetupDaemon();
+}
+
+bool SnapuserTest::SetupCopyOverlap_2() {
+    CreateBaseDevice();
+    CreateCowDeviceWithCopyOverlap_2();
+    return SetupDaemon();
+}
+
+bool SnapuserTest::SetupDaemon() {
+    SetDeviceControlName();
+
+    StartSnapuserdDaemon();
+
+    CreateDmUserDevice();
+    InitCowDevice();
+    InitDaemon();
+
+    setup_ok_ = true;
+
+    return setup_ok_;
+}
+
+void SnapuserTest::StartSnapuserdDaemon() {
+    pid_t pid = fork();
+    ASSERT_GE(pid, 0);
+    if (pid == 0) {
+        std::string arg0 = "/system/bin/snapuserd";
+        std::string arg1 = "-socket="s + kSnapuserdSocketTest;
+        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
+        ASSERT_GE(execv(arg0.c_str(), argv), 0);
+    } else {
+        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
+        ASSERT_NE(client_, nullptr);
+    }
+}
+
+void SnapuserTest::CreateBaseDevice() {
+    unique_fd rnd_fd;
+
+    total_base_size_ = (size_ * 5);
+    base_fd_ = CreateTempFile("base_device", total_base_size_);
+    ASSERT_GE(base_fd_, 0);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+    for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
+        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true);
+    }
+
+    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+
+    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
+    ASSERT_TRUE(base_loop_->valid());
+}
+
+void SnapuserTest::ReadSnapshotDeviceAndValidate() {
+    unique_fd fd(open(dmuser_dev_->path().c_str(), O_RDONLY));
+    ASSERT_GE(fd, 0);
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
+
+    // COPY
+    loff_t offset = 0;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0);
+
+    // ZERO
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
+
+    // XOR
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
+}
+
+void SnapuserTest::CreateCowDeviceWithCopyOverlap_2() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t blk_src_copy = 0;
+
+    // Create overlapping copy operations
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
+        x -= 1;
+        if (x == 1) {
+            break;
+        }
+        blk_src_copy += 1;
+    }
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // Merged operations required for validation
+    int block_size = 4096;
+    x = num_blocks;
+    loff_t src_offset = block_size;
+    loff_t dest_offset = 0;
+
+    while (1) {
+        memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset,
+                block_size);
+        x -= 1;
+        if (x == 1) {
+            break;
+        }
+        src_offset += block_size;
+        dest_offset += block_size;
+    }
+}
+
+void SnapuserTest::CreateCowDeviceWithCopyOverlap_1() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t blk_src_copy = num_blocks - 1;
+
+    // Create overlapping copy operations
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            ASSERT_EQ(blk_src_copy, 0);
+            break;
+        }
+        blk_src_copy -= 1;
+    }
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // Merged operations
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
+              true);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(
+                      base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
+              true);
+}
+
+void SnapuserTest::CreateCowDeviceOrderedOpsInverted() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+
+    // Fill random data
+    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
+                  true);
+
+        offset += 1_MiB;
+    }
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t blk_end_copy = num_blocks * 3;
+    size_t source_blk = num_blocks - 1;
+    size_t blk_src_copy = blk_end_copy - 1;
+    uint16_t xor_offset = 5;
+
+    size_t x = num_blocks;
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            break;
+        }
+        source_blk -= 1;
+        blk_src_copy -= 1;
+    }
+
+    for (size_t i = num_blocks; i > 0; i--) {
+        ASSERT_TRUE(writer.AddXorBlocks(num_blocks + i - 1,
+                                        &random_buffer_1_.get()[options.block_size * (i - 1)],
+                                        options.block_size, 2 * num_blocks + i - 1, xor_offset));
+    }
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+    // Merged Buffer
+    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
+    memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
+    }
+}
+
+void SnapuserTest::CreateCowDeviceOrderedOps() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+
+    // Fill random data
+    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
+                  true);
+
+        offset += 1_MiB;
+    }
+    memset(random_buffer_1_.get(), 0, size_);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t source_blk = 0;
+    size_t blk_src_copy = 2 * num_blocks;
+    uint16_t xor_offset = 5;
+
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+
+        x -= 1;
+        if (x == 0) {
+            break;
+        }
+        source_blk += 1;
+        blk_src_copy += 1;
+    }
+
+    ASSERT_TRUE(writer.AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
+                                    xor_offset));
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+    // Merged Buffer
+    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
+    memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
+    }
+}
+
+void SnapuserTest::CreateCowDevice() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+
+    // Fill random data
+    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
+                  true);
+
+        offset += 1_MiB;
+    }
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t blk_end_copy = num_blocks * 2;
+    size_t source_blk = num_blocks - 1;
+    size_t blk_src_copy = blk_end_copy - 1;
+
+    uint32_t sequence[num_blocks * 2];
+    // Sequence for Copy ops
+    for (int i = 0; i < num_blocks; i++) {
+        sequence[i] = num_blocks - 1 - i;
+    }
+    // Sequence for Xor ops
+    for (int i = 0; i < num_blocks; i++) {
+        sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
+    }
+    ASSERT_TRUE(writer.AddSequenceData(2 * num_blocks, sequence));
+
+    size_t x = num_blocks;
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            break;
+        }
+        source_blk -= 1;
+        blk_src_copy -= 1;
+    }
+
+    source_blk = num_blocks;
+    blk_src_copy = blk_end_copy;
+
+    ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+
+    size_t blk_zero_copy_start = source_blk + num_blocks;
+    size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
+
+    ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
+
+    size_t blk_random2_replace_start = blk_zero_copy_end;
+
+    ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
+
+    size_t blk_xor_start = blk_random2_replace_start + num_blocks;
+    size_t xor_offset = BLOCK_SZ / 2;
+    ASSERT_TRUE(writer.AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
+                                    xor_offset));
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    std::string zero_buffer(size_, 0);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true);
+    memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_,
+                                               size_ + xor_offset),
+              true);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[(size_ * 4) + i] =
+                (uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]);
+    }
+}
+
+void SnapuserTest::InitCowDevice() {
+    uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
+                                                  base_loop_->device(), base_loop_->device());
+    ASSERT_NE(num_sectors, 0);
+}
+
+void SnapuserTest::SetDeviceControlName() {
+    system_device_name_.clear();
+    system_device_ctrl_name_.clear();
+
+    std::string str(cow_system_->path);
+    std::size_t found = str.find_last_of("/\\");
+    ASSERT_NE(found, std::string::npos);
+    system_device_name_ = str.substr(found + 1);
+
+    system_device_ctrl_name_ = system_device_name_ + "-ctrl";
+}
+
+void SnapuserTest::CreateDmUserDevice() {
+    unique_fd fd(TEMP_FAILURE_RETRY(open(base_loop_->device().c_str(), O_RDONLY | O_CLOEXEC)));
+    ASSERT_TRUE(fd > 0);
+
+    uint64_t dev_sz = get_block_device_size(fd.get());
+    ASSERT_TRUE(dev_sz > 0);
+
+    cow_num_sectors_ = dev_sz >> 9;
+
+    DmTable dmuser_table;
+    ASSERT_TRUE(dmuser_table.AddTarget(
+            std::make_unique<DmTargetUser>(0, cow_num_sectors_, system_device_ctrl_name_)));
+    ASSERT_TRUE(dmuser_table.valid());
+
+    dmuser_dev_ = std::make_unique<Tempdevice>(system_device_name_, dmuser_table);
+    ASSERT_TRUE(dmuser_dev_->valid());
+    ASSERT_FALSE(dmuser_dev_->path().empty());
+
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
+}
+
+void SnapuserTest::InitDaemon() {
+    bool ok = client_->AttachDmUser(system_device_ctrl_name_);
+    ASSERT_TRUE(ok);
+}
+
+void SnapuserTest::CheckMergeCompletion() {
+    while (true) {
+        double percentage = client_->GetMergePercent();
+        if ((int)percentage == 100) {
+            break;
+        }
+
+        std::this_thread::sleep_for(1s);
+    }
+}
+
+void SnapuserTest::SetupImpl() {
+    CreateBaseDevice();
+    CreateCowDevice();
+
+    SetDeviceControlName();
+
+    StartSnapuserdDaemon();
+
+    CreateDmUserDevice();
+    InitCowDevice();
+    InitDaemon();
+
+    setup_ok_ = true;
+}
+
+bool SnapuserTest::Merge() {
+    StartMerge();
+    CheckMergeCompletion();
+    merge_ok_ = true;
+    return merge_ok_;
+}
+
+void SnapuserTest::StartMerge() {
+    bool ok = client_->InitiateMerge(system_device_ctrl_name_);
+    ASSERT_TRUE(ok);
+}
+
+void SnapuserTest::ValidateMerge() {
+    merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
+              true);
+    ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
+}
+
+void SnapuserTest::SimulateDaemonRestart() {
+    Shutdown();
+    std::this_thread::sleep_for(500ms);
+    SetDeviceControlName();
+    StartSnapuserdDaemon();
+    CreateDmUserDevice();
+    InitCowDevice();
+    InitDaemon();
+}
+
+void SnapuserTest::MergeInterruptRandomly(int max_duration) {
+    std::srand(std::time(nullptr));
+    StartMerge();
+
+    for (int i = 0; i < 20; i++) {
+        int duration = std::rand() % max_duration;
+        std::this_thread::sleep_for(std::chrono::milliseconds(duration));
+        SimulateDaemonRestart();
+        StartMerge();
+    }
+
+    SimulateDaemonRestart();
+    ASSERT_TRUE(Merge());
+}
+
+void SnapuserTest::MergeInterruptFixed(int duration) {
+    StartMerge();
+
+    for (int i = 0; i < 25; i++) {
+        std::this_thread::sleep_for(std::chrono::milliseconds(duration));
+        SimulateDaemonRestart();
+        StartMerge();
+    }
+
+    SimulateDaemonRestart();
+    ASSERT_TRUE(Merge());
+}
+
+void SnapuserTest::MergeInterrupt() {
+    // Interrupt merge at various intervals
+    StartMerge();
+    std::this_thread::sleep_for(250ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(250ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(150ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(100ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(800ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(600ms);
+    SimulateDaemonRestart();
+
+    ASSERT_TRUE(Merge());
+}
+
+TEST(Snapuserd_Test, Snapshot_IO_TEST) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.Setup());
+    // I/O before merge
+    harness.ReadSnapshotDeviceAndValidate();
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    // I/O after merge - daemon should read directly
+    // from base device
+    harness.ReadSnapshotDeviceAndValidate();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.Setup());
+    // Issue I/O before merge begins
+    std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
+    // Start the merge
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST_1) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.Setup());
+    // Start the merge
+    harness.StartMerge();
+    // Issue I/O in parallel when merge is in-progress
+    std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
+    harness.CheckMergeCompletion();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Resume) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.Setup());
+    harness.MergeInterrupt();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_1());
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_2());
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_1());
+    harness.MergeInterrupt();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Ordered) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupOrderedOps());
+    harness.MergeInterruptFixed(300);
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Ordered) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupOrderedOps());
+    harness.MergeInterruptRandomly(500);
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Inverted) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupOrderedOpsInverted());
+    harness.MergeInterruptFixed(50);
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Inverted) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupOrderedOpsInverted());
+    harness.MergeInterruptRandomly(50);
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+}  // namespace snapshot
+}  // namespace android
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
index 6c91fde..6dec1e2 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -570,7 +570,6 @@
     {
         std::unique_lock<std::mutex> lock(blk_state->m_lock);
 
-        CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
         blk_state->num_ios_in_progress -= 1;
         if (blk_state->num_ios_in_progress == 0) {
             pending_ios = false;
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 4a2af1c..89d6145 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -22,6 +22,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <fs_mgr/roots.h>
@@ -187,6 +188,10 @@
     return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
 }
 
+bool IsUserspaceSnapshotsEnabled() {
+    return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+}
+
 std::string GetOtherPartitionName(const std::string& name) {
     auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
     CHECK(suffix == "_a" || suffix == "_b");
@@ -195,5 +200,9 @@
     return name.substr(0, name.size() - suffix.size()) + other_suffix;
 }
 
+bool IsDmSnapshotTestingEnabled() {
+    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index e97afed..a032b68 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -131,8 +131,11 @@
 
 bool IsCompressionEnabled();
 
+bool IsUserspaceSnapshotsEnabled();
+
+bool IsDmSnapshotTestingEnabled();
+
 // Swap the suffix of a partition name.
 std::string GetOtherPartitionName(const std::string& name);
-
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 94e1abb..d631d7a 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -193,13 +193,11 @@
            lhs.vold_managed == rhs.vold_managed &&
            lhs.recovery_only == rhs.recovery_only &&
            lhs.verify == rhs.verify &&
-           lhs.force_crypt == rhs.force_crypt &&
            lhs.no_emulated_sd == rhs.no_emulated_sd &&
            lhs.no_trim == rhs.no_trim &&
            lhs.file_encryption == rhs.file_encryption &&
            lhs.formattable == rhs.formattable &&
            lhs.slot_select == rhs.slot_select &&
-           lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
            lhs.late_mount == rhs.late_mount &&
            lhs.no_fail == rhs.no_fail &&
            lhs.verify_at_boot == rhs.verify_at_boot &&
@@ -488,18 +486,16 @@
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     std::string fstab_contents = R"fs(
-source none0       swap   defaults      encryptable,forceencrypt,fileencryption,forcefdeorfbe,keydirectory,length,swapprio,zramsize,max_comp_streams,reservedsize,eraseblk,logicalblk,sysfs_path,zram_backingdev_size
+source none0       swap   defaults      fileencryption,keydirectory,length,swapprio,zramsize,max_comp_streams,reservedsize,eraseblk,logicalblk,sysfs_path,zram_backingdev_size
 
-source none1       swap   defaults      encryptable=,forceencrypt=,fileencryption=,keydirectory=,length=,swapprio=,zramsize=,max_comp_streams=,avb=,reservedsize=,eraseblk=,logicalblk=,sysfs_path=,zram_backingdev_size=
-
-source none2       swap   defaults      forcefdeorfbe=
+source none1       swap   defaults      fileencryption=,keydirectory=,length=,swapprio=,zramsize=,max_comp_streams=,avb=,reservedsize=,eraseblk=,logicalblk=,sysfs_path=,zram_backingdev_size=
 
 )fs";
     ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_LE(3U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -507,7 +503,6 @@
         FstabEntry::FsMgrFlags flags = {};
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    EXPECT_EQ("", entry->key_loc);
     EXPECT_EQ("", entry->metadata_key_dir);
     EXPECT_EQ(0, entry->length);
     EXPECT_EQ("", entry->label);
@@ -526,13 +521,10 @@
     EXPECT_EQ("none1", entry->mount_point);
     {
         FstabEntry::FsMgrFlags flags = {};
-        flags.crypt = true;
-        flags.force_crypt = true;
         flags.file_encryption = true;
         flags.avb = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    EXPECT_EQ("", entry->key_loc);
     EXPECT_EQ("", entry->metadata_key_dir);
     EXPECT_EQ(0, entry->length);
     EXPECT_EQ("", entry->label);
@@ -546,24 +538,26 @@
     EXPECT_EQ(0, entry->logical_blk_size);
     EXPECT_EQ("", entry->sysfs_path);
     EXPECT_EQ(0U, entry->zram_backingdev_size);
-    entry++;
-
-    // forcefdeorfbe has its own encryption_options defaults, so test it separately.
-    EXPECT_EQ("none2", entry->mount_point);
-    {
-        FstabEntry::FsMgrFlags flags = {};
-        flags.force_fde_or_fbe = true;
-        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
-    }
-    EXPECT_EQ("aes-256-xts:aes-256-cts", entry->encryption_options);
-    EXPECT_EQ("", entry->key_loc);
 }
 
-TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Encryptable) {
+// FDE is no longer supported, so an fstab with FDE enabled should be rejected.
+TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FDE) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     std::string fstab_contents = R"fs(
-source none0       swap   defaults      encryptable=/dir/key
+source /data        ext4    noatime    forceencrypt=footer
+)fs";
+    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+    Fstab fstab;
+    EXPECT_FALSE(ReadFstabFromFile(tf.path, &fstab));
+}
+
+TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_AdoptableStorage) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    std::string fstab_contents = R"fs(
+source none0       swap   defaults      encryptable=userdata,voldmanaged=sdcard:auto
 )fs";
     ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
 
@@ -573,11 +567,11 @@
 
     FstabEntry::FsMgrFlags flags = {};
     flags.crypt = true;
+    flags.vold_managed = true;
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
     EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
-    EXPECT_EQ("/dir/key", entry->key_loc);
 }
 
 TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_VoldManaged) {
@@ -725,53 +719,6 @@
     EXPECT_EQ(0, entry->zram_size);
 }
 
-TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ForceEncrypt) {
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
-    std::string fstab_contents = R"fs(
-source none0       swap   defaults      forceencrypt=/dir/key
-)fs";
-
-    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
-
-    Fstab fstab;
-    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_LE(1U, fstab.size());
-
-    auto entry = fstab.begin();
-    EXPECT_EQ("none0", entry->mount_point);
-
-    FstabEntry::FsMgrFlags flags = {};
-    flags.force_crypt = true;
-    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
-
-    EXPECT_EQ("/dir/key", entry->key_loc);
-}
-
-TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ForceFdeOrFbe) {
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
-    std::string fstab_contents = R"fs(
-source none0       swap   defaults      forcefdeorfbe=/dir/key
-)fs";
-
-    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
-
-    Fstab fstab;
-    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_LE(1U, fstab.size());
-
-    auto entry = fstab.begin();
-    EXPECT_EQ("none0", entry->mount_point);
-
-    FstabEntry::FsMgrFlags flags = {};
-    flags.force_fde_or_fbe = true;
-    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
-
-    EXPECT_EQ("/dir/key", entry->key_loc);
-    EXPECT_EQ("aes-256-xts:aes-256-cts", entry->encryption_options);
-}
-
 TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FileEncryption) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index a7f0c0e..0aedc58 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -29,7 +29,9 @@
     srcs: [
         "gatekeeperd.cpp",
     ],
-
+    defaults: [
+        "keymint_use_latest_hal_aidl_ndk_shared",
+    ],
     shared_libs: [
         "libbinder",
         "libbinder_ndk",
@@ -43,7 +45,6 @@
         "libhidlbase",
         "android.hardware.gatekeeper@1.0",
         "libgatekeeper_aidl",
-        "android.hardware.security.keymint-V1-ndk",
         "android.security.authorization-ndk",
     ],
 
diff --git a/healthd/Android.bp b/healthd/Android.bp
index eaa8e5b..24777c8 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -216,8 +216,6 @@
 
     shared_libs: [
         // common
-        "android.hardware.health@2.0",
-        "android.hardware.health@2.1",
         "libbase",
         "libcutils",
         "libhidlbase",
@@ -255,6 +253,10 @@
         "charger.cpp",
         "charger_utils.cpp",
     ],
+    shared_libs: [
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
+    ],
 
     target: {
         recovery: {
@@ -280,6 +282,11 @@
     name: "charger_test",
     defaults: ["charger_defaults"],
     srcs: ["charger_test.cpp"],
+    static_libs: [
+        "android.hardware.health@1.0",
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
+    ],
 }
 
 cc_test {
@@ -290,6 +297,9 @@
         "healthd_mode_charger_test.cpp"
     ],
     static_libs: [
+        "android.hardware.health@1.0",
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
         "libgmock",
     ],
     test_suites: [
diff --git a/init/README.md b/init/README.md
index 6c29b07..c102b1f 100644
--- a/init/README.md
+++ b/init/README.md
@@ -487,11 +487,6 @@
   not already running.  See the start entry for more information on
   starting services.
 
-`class_start_post_data <serviceclass>`
-> Like `class_start`, but only considers services that were started
-  after /data was mounted, and that were running at the time
- `class_reset_post_data` was called. Only used for FDE devices.
-
 `class_stop <serviceclass>`
 > Stop and disable all services of the specified class if they are
   currently running.
@@ -501,12 +496,9 @@
   currently running, without disabling them. They can be restarted
   later using `class_start`.
 
-`class_reset_post_data <serviceclass>`
-> Like `class_reset`, but only considers services that were started
-  after /data was mounted. Only used for FDE devices.
-
-`class_restart <serviceclass>`
-> Restarts all services of the specified class.
+`class_restart [--only-enabled] <serviceclass>`
+> Restarts all services of the specified class. If `--only-enabled` is
+  specified, then disabled services are skipped.
 
 `copy <src> <dst>`
 > Copies a file. Similar to write, but useful for binary/large
@@ -607,8 +599,7 @@
   Properties are expanded within _level_.
 
 `mark_post_data`
-> Used to mark the point right after /data is mounted. Used to implement the
-  `class_reset_post_data` and `class_start_post_data` commands.
+> Used to mark the point right after /data is mounted.
 
 `mkdir <path> [<mode>] [<owner>] [<group>] [encryption=<action>] [key=<key>]`
 > Create a directory at _path_, optionally with the given mode, owner, and
@@ -650,9 +641,10 @@
   configurations. Intended to be used only once when apexd notifies the mount
   event by setting `apexd.status` to ready.
 
-`restart <service>`
+`restart [--only-if-running] <service>`
 > Stops and restarts a running service, does nothing if the service is currently
-  restarting, otherwise, it just starts the service.
+  restarting, otherwise, it just starts the service. If "--only-if-running" is
+  specified, the service is only restarted if it is already running.
 
 `restorecon <path> [ <path>\* ]`
 > Restore the file named by _path_ to the security context specified
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 763a147..8045c71 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -46,7 +46,6 @@
 #include <map>
 #include <memory>
 
-#include <ApexProperties.sysprop.h>
 #include <InitProperties.sysprop.h>
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
@@ -177,28 +176,6 @@
     return {};
 }
 
-static Result<void> do_class_start_post_data(const BuiltinArguments& args) {
-    if (args.context != kInitContext) {
-        return Error() << "command 'class_start_post_data' only available in init context";
-    }
-    static bool is_apex_updatable = android::sysprop::ApexProperties::updatable().value_or(false);
-
-    if (!is_apex_updatable) {
-        // No need to start these on devices that don't support APEX, since they're not
-        // stopped either.
-        return {};
-    }
-    for (const auto& service : ServiceList::GetInstance()) {
-        if (service->classnames().count(args[1])) {
-            if (auto result = service->StartIfPostData(); !result.ok()) {
-                LOG(ERROR) << "Could not start service '" << service->name()
-                           << "' as part of class '" << args[1] << "': " << result.error();
-            }
-        }
-    }
-    return {};
-}
-
 static Result<void> do_class_stop(const BuiltinArguments& args) {
     ForEachServiceInClass(args[1], &Service::Stop);
     return {};
@@ -209,24 +186,35 @@
     return {};
 }
 
-static Result<void> do_class_reset_post_data(const BuiltinArguments& args) {
-    if (args.context != kInitContext) {
-        return Error() << "command 'class_reset_post_data' only available in init context";
-    }
-    static bool is_apex_updatable = android::sysprop::ApexProperties::updatable().value_or(false);
-    if (!is_apex_updatable) {
-        // No need to stop these on devices that don't support APEX.
-        return {};
-    }
-    ForEachServiceInClass(args[1], &Service::ResetIfPostData);
-    return {};
-}
-
 static Result<void> do_class_restart(const BuiltinArguments& args) {
     // Do not restart a class if it has a property persist.dont_start_class.CLASS set to 1.
     if (android::base::GetBoolProperty("persist.init.dont_start_class." + args[1], false))
         return {};
-    ForEachServiceInClass(args[1], &Service::Restart);
+
+    std::string classname;
+
+    CHECK(args.size() == 2 || args.size() == 3);
+
+    bool only_enabled = false;
+    if (args.size() == 3) {
+        if (args[1] != "--only-enabled") {
+            return Error() << "Unexpected argument: " << args[1];
+        }
+        only_enabled = true;
+        classname = args[2];
+    } else if (args.size() == 2) {
+        classname = args[1];
+    }
+
+    for (const auto& service : ServiceList::GetInstance()) {
+        if (!service->classnames().count(classname)) {
+            continue;
+        }
+        if (only_enabled && !service->IsEnabled()) {
+            continue;
+        }
+        service->Restart();
+    }
     return {};
 }
 
@@ -586,32 +574,7 @@
  * return code is processed based on input code
  */
 static Result<void> queue_fs_event(int code, bool userdata_remount) {
-    if (code == FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION) {
-        if (userdata_remount) {
-            // FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION should only happen on FDE devices. Since we don't
-            // support userdata remount on FDE devices, this should never been triggered. Time to
-            // panic!
-            LOG(ERROR) << "Userdata remount is not supported on FDE devices. How did you get here?";
-            trigger_shutdown("reboot,requested-userdata-remount-on-fde-device");
-        }
-        ActionManager::GetInstance().QueueEventTrigger("encrypt");
-        return {};
-    } else if (code == FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED) {
-        if (userdata_remount) {
-            // FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED should only happen on FDE devices. Since we
-            // don't support userdata remount on FDE devices, this should never been triggered.
-            // Time to panic!
-            LOG(ERROR) << "Userdata remount is not supported on FDE devices. How did you get here?";
-            trigger_shutdown("reboot,requested-userdata-remount-on-fde-device");
-        }
-        SetProperty("ro.crypto.state", "encrypted");
-        ActionManager::GetInstance().QueueEventTrigger("defaultcrypto");
-        return {};
-    } else if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
-        SetProperty("ro.crypto.state", "unencrypted");
-        ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
-        return {};
-    } else if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
+    if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
         SetProperty("ro.crypto.state", "unsupported");
         ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
         return {};
@@ -811,8 +774,21 @@
 }
 
 static Result<void> do_restart(const BuiltinArguments& args) {
-    Service* svc = ServiceList::GetInstance().FindService(args[1]);
-    if (!svc) return Error() << "service " << args[1] << " not found";
+    bool only_if_running = false;
+    if (args.size() == 3) {
+        if (args[1] == "--only-if-running") {
+            only_if_running = true;
+        } else {
+            return Error() << "Unknown argument to restart: " << args[1];
+        }
+    }
+
+    const auto& classname = args[args.size() - 1];
+    Service* svc = ServiceList::GetInstance().FindService(classname);
+    if (!svc) return Error() << "service " << classname << " not found";
+    if (only_if_running && !svc->IsRunning()) {
+        return {};
+    }
     svc->Restart();
     return {};
 }
@@ -1119,17 +1095,6 @@
 }
 
 static Result<void> do_load_persist_props(const BuiltinArguments& args) {
-    // Devices with FDE have load_persist_props called twice; the first time when the temporary
-    // /data partition is mounted and then again once /data is truly mounted.  We do not want to
-    // read persistent properties from the temporary /data partition or mark persistent properties
-    // as having been loaded during the first call, so we return in that case.
-    std::string crypto_state = android::base::GetProperty("ro.crypto.state", "");
-    std::string crypto_type = android::base::GetProperty("ro.crypto.type", "");
-    if (crypto_state == "encrypted" && crypto_type == "block") {
-        static size_t num_calls = 0;
-        if (++num_calls == 1) return {};
-    }
-
     SendLoadPersistentPropertiesMessage();
 
     start_waiting_for_property("ro.persistent_properties.ready", "true");
@@ -1464,10 +1429,8 @@
         {"chmod",                   {2,     2,    {true,   do_chmod}}},
         {"chown",                   {2,     3,    {true,   do_chown}}},
         {"class_reset",             {1,     1,    {false,  do_class_reset}}},
-        {"class_reset_post_data",   {1,     1,    {false,  do_class_reset_post_data}}},
-        {"class_restart",           {1,     1,    {false,  do_class_restart}}},
+        {"class_restart",           {1,     2,    {false,  do_class_restart}}},
         {"class_start",             {1,     1,    {false,  do_class_start}}},
-        {"class_start_post_data",   {1,     1,    {false,  do_class_start_post_data}}},
         {"class_stop",              {1,     1,    {false,  do_class_stop}}},
         {"copy",                    {2,     2,    {true,   do_copy}}},
         {"copy_per_line",           {2,     2,    {true,   do_copy_per_line}}},
@@ -1503,7 +1466,7 @@
         {"update_linker_config",    {0,     0,    {false,  do_update_linker_config}}},
         {"readahead",               {1,     2,    {true,   do_readahead}}},
         {"remount_userdata",        {0,     0,    {false,  do_remount_userdata}}},
-        {"restart",                 {1,     1,    {false,  do_restart}}},
+        {"restart",                 {1,     2,    {false,  do_restart}}},
         {"restorecon",              {1,     kMax, {true,   do_restorecon}}},
         {"restorecon_recursive",    {1,     kMax, {true,   do_restorecon_recursive}}},
         {"rm",                      {1,     1,    {true,   do_rm}}},
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index f5c10bb..ada5a47 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -81,8 +81,7 @@
     FirstStageMount(Fstab fstab);
     virtual ~FirstStageMount() = default;
 
-    // The factory method to create either FirstStageMountVBootV1 or FirstStageMountVBootV2
-    // based on device tree configurations.
+    // The factory method to create a FirstStageMountVBootV2 instance.
     static Result<std::unique_ptr<FirstStageMount>> Create();
     bool DoCreateDevices();    // Creates devices and logical partitions from storage devices
     bool DoFirstStageMount();  // Mounts fstab entries read from device tree.
@@ -125,16 +124,6 @@
     std::map<std::string, std::vector<std::string>> preload_avb_key_blobs_;
 };
 
-class FirstStageMountVBootV1 : public FirstStageMount {
-  public:
-    FirstStageMountVBootV1(Fstab fstab) : FirstStageMount(std::move(fstab)) {}
-    ~FirstStageMountVBootV1() override = default;
-
-  protected:
-    bool GetDmVerityDevices(std::set<std::string>* devices) override;
-    bool SetUpDmVerity(FstabEntry* fstab_entry) override;
-};
-
 class FirstStageMountVBootV2 : public FirstStageMount {
   public:
     friend void SetInitAvbVersionInRecovery();
@@ -243,11 +232,7 @@
         return fstab.error();
     }
 
-    if (IsDtVbmetaCompatible(*fstab)) {
-        return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
-    } else {
-        return std::make_unique<FirstStageMountVBootV1>(std::move(*fstab));
-    }
+    return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
 }
 
 bool FirstStageMount::DoCreateDevices() {
@@ -391,7 +376,11 @@
 
     use_snapuserd_ = sm->IsSnapuserdRequired();
     if (use_snapuserd_) {
-        LaunchFirstStageSnapuserd();
+        if (sm->UpdateUsesUserSnapshots()) {
+            LaunchFirstStageSnapuserd(SnapshotDriver::DM_USER);
+        } else {
+            LaunchFirstStageSnapuserd(SnapshotDriver::DM_SNAPSHOT);
+        }
     }
 
     sm->SetUeventRegenCallback([this](const std::string& device) -> bool {
@@ -675,56 +664,6 @@
     TransformFstabForDsu(&fstab_, active_dsu, dsu_partitions);
 }
 
-bool FirstStageMountVBootV1::GetDmVerityDevices(std::set<std::string>* devices) {
-    need_dm_verity_ = false;
-
-    for (const auto& fstab_entry : fstab_) {
-        // Don't allow verifyatboot in the first stage.
-        if (fstab_entry.fs_mgr_flags.verify_at_boot) {
-            LOG(ERROR) << "Partitions can't be verified at boot";
-            return false;
-        }
-        // Checks for verified partitions.
-        if (fstab_entry.fs_mgr_flags.verify) {
-            need_dm_verity_ = true;
-        }
-    }
-
-    // Includes the partition names of fstab records.
-    // Notes that fstab_rec->blk_device has A/B suffix updated by fs_mgr when A/B is used.
-    for (const auto& fstab_entry : fstab_) {
-        // Skip pseudo filesystems.
-        if (fstab_entry.fs_type == "overlay") {
-            continue;
-        }
-        if (!fstab_entry.fs_mgr_flags.logical) {
-            devices->emplace(basename(fstab_entry.blk_device.c_str()));
-        }
-    }
-
-    return true;
-}
-
-bool FirstStageMountVBootV1::SetUpDmVerity(FstabEntry* fstab_entry) {
-    if (fstab_entry->fs_mgr_flags.verify) {
-        int ret = fs_mgr_setup_verity(fstab_entry, false /* wait_for_verity_dev */);
-        switch (ret) {
-            case FS_MGR_SETUP_VERITY_SKIPPED:
-            case FS_MGR_SETUP_VERITY_DISABLED:
-                LOG(INFO) << "Verity disabled/skipped for '" << fstab_entry->mount_point << "'";
-                return true;
-            case FS_MGR_SETUP_VERITY_SUCCESS:
-                // The exact block device name (fstab_rec->blk_device) is changed to
-                // "/dev/block/dm-XX". Needs to create it because ueventd isn't started in init
-                // first stage.
-                return block_dev_init_.InitDmDevice(fstab_entry->blk_device);
-            default:
-                return false;
-        }
-    }
-    return true;  // Returns true to mount the partition.
-}
-
 // First retrieve any vbmeta partitions from device tree (legacy) then read through the fstab
 // for any further vbmeta partitions.
 FirstStageMountVBootV2::FirstStageMountVBootV2(Fstab fstab)
diff --git a/init/service.cpp b/init/service.cpp
index 489dd67..f7318cb 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -661,25 +661,6 @@
     StopOrReset(SVC_RESET);
 }
 
-void Service::ResetIfPostData() {
-    if (post_data_) {
-        if (flags_ & SVC_RUNNING) {
-            running_at_post_data_reset_ = true;
-        }
-        StopOrReset(SVC_RESET);
-    }
-}
-
-Result<void> Service::StartIfPostData() {
-    // Start the service, but only if it was started after /data was mounted,
-    // and it was still running when we reset the post-data services.
-    if (running_at_post_data_reset_) {
-        return Start();
-    }
-
-    return {};
-}
-
 void Service::Stop() {
     StopOrReset(SVC_DISABLED);
 }
diff --git a/init/service.h b/init/service.h
index ccf6899..3289f54 100644
--- a/init/service.h
+++ b/init/service.h
@@ -80,10 +80,8 @@
     Result<void> ExecStart();
     Result<void> Start();
     Result<void> StartIfNotDisabled();
-    Result<void> StartIfPostData();
     Result<void> Enable();
     void Reset();
-    void ResetIfPostData();
     void Stop();
     void Terminate();
     void Timeout();
@@ -214,8 +212,6 @@
 
     bool post_data_ = false;
 
-    bool running_at_post_data_reset_ = false;
-
     std::optional<std::string> on_failure_reboot_target_;
 
     bool from_apex_ = false;
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
index b8c2fd2..e11510e 100644
--- a/init/snapuserd_transition.cpp
+++ b/init/snapuserd_transition.cpp
@@ -58,7 +58,7 @@
 static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0";
 static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0";
 
-void LaunchFirstStageSnapuserd() {
+void LaunchFirstStageSnapuserd(SnapshotDriver driver) {
     SocketDescriptor socket_desc;
     socket_desc.name = android::snapshot::kSnapuserdSocket;
     socket_desc.type = SOCK_STREAM;
@@ -80,12 +80,23 @@
     }
     if (pid == 0) {
         socket->Publish();
-        char arg0[] = "/system/bin/snapuserd";
-        char* const argv[] = {arg0, nullptr};
-        if (execv(arg0, argv) < 0) {
-            PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+
+        if (driver == SnapshotDriver::DM_USER) {
+            char arg0[] = "/system/bin/snapuserd";
+            char arg1[] = "-user_snapshot";
+            char* const argv[] = {arg0, arg1, nullptr};
+            if (execv(arg0, argv) < 0) {
+                PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+            }
+            _exit(127);
+        } else {
+            char arg0[] = "/system/bin/snapuserd";
+            char* const argv[] = {arg0, nullptr};
+            if (execv(arg0, argv) < 0) {
+                PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+            }
+            _exit(127);
         }
-        _exit(127);
     }
 
     auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 10s);
diff --git a/init/snapuserd_transition.h b/init/snapuserd_transition.h
index 62aee83..be22afd 100644
--- a/init/snapuserd_transition.h
+++ b/init/snapuserd_transition.h
@@ -29,8 +29,13 @@
 namespace android {
 namespace init {
 
+enum class SnapshotDriver {
+    DM_SNAPSHOT,
+    DM_USER,
+};
+
 // Fork and exec a new copy of snapuserd.
-void LaunchFirstStageSnapuserd();
+void LaunchFirstStageSnapuserd(SnapshotDriver driver);
 
 class SnapuserdSelinuxHelper final {
     using SnapshotManager = android::snapshot::SnapshotManager;
diff --git a/init/ueventd_test.cpp b/init/ueventd_test.cpp
index fc3cdfb..1ac6d8e 100644
--- a/init/ueventd_test.cpp
+++ b/init/ueventd_test.cpp
@@ -99,7 +99,7 @@
     const char* const contexts[] = {
         "u:object_r:audio_device:s0",
         "u:object_r:sensors_device:s0",
-        "u:object_r:video_device:s0"
+        "u:object_r:video_device:s0",
         "u:object_r:zero_device:s0",
     };
 
diff --git a/libasyncio/Android.bp b/libasyncio/Android.bp
index 692e223..296e207 100644
--- a/libasyncio/Android.bp
+++ b/libasyncio/Android.bp
@@ -32,6 +32,7 @@
     defaults: ["libasyncio_defaults"],
     vendor_available: true,
     recovery_available: true,
+    min_sdk_version: "apex_inherit",
     apex_available: [
         "//apex_available:platform",
         "com.android.adbd",
diff --git a/libcrypto_utils/Android.bp b/libcrypto_utils/Android.bp
index a9f6958..2559137 100644
--- a/libcrypto_utils/Android.bp
+++ b/libcrypto_utils/Android.bp
@@ -44,6 +44,7 @@
             enabled: true,
         },
     },
+    min_sdk_version: "apex_inherit",
     apex_available: [
         "//apex_available:platform",
         "com.android.adbd",
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0d9f2c7..c8bfb01 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -156,7 +156,6 @@
     },
     srcs: [
         "config_utils.cpp",
-        "canned_fs_config.cpp",
         "iosched_policy.cpp",
         "load_file.cpp",
         "native_handle.cpp",
@@ -173,6 +172,7 @@
         not_windows: {
             srcs: libcutils_nonwindows_sources + [
                 "ashmem-host.cpp",
+                "canned_fs_config.cpp",
                 "fs_config.cpp",
                 "trace-host.cpp",
             ],
@@ -193,6 +193,7 @@
             srcs: libcutils_nonwindows_sources + [
                 "android_reboot.cpp",
                 "ashmem-dev.cpp",
+                "canned_fs_config.cpp",
                 "fs_config.cpp",
                 "klog.cpp",
                 "partition_utils.cpp",
diff --git a/libcutils/canned_fs_config.cpp b/libcutils/canned_fs_config.cpp
index 2772ef0..b677949 100644
--- a/libcutils/canned_fs_config.cpp
+++ b/libcutils/canned_fs_config.cpp
@@ -18,6 +18,8 @@
 #include <private/canned_fs_config.h>
 #include <private/fs_config.h>
 
+#include <android-base/strings.h>
+
 #include <errno.h>
 #include <inttypes.h>
 #include <limits.h>
@@ -25,104 +27,107 @@
 #include <stdlib.h>
 #include <string.h>
 
-typedef struct {
-    const char* path;
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+using android::base::ConsumePrefix;
+using android::base::StartsWith;
+using android::base::Tokenize;
+
+struct Entry {
+    std::string path;
     unsigned uid;
     unsigned gid;
     unsigned mode;
     uint64_t capabilities;
-} Path;
+};
 
-static Path* canned_data = NULL;
-static int canned_alloc = 0;
-static int canned_used = 0;
-
-static int path_compare(const void* a, const void* b) {
-    return strcmp(((Path*)a)->path, ((Path*)b)->path);
-}
+static std::vector<Entry> canned_data;
 
 int load_canned_fs_config(const char* fn) {
-    char buf[PATH_MAX + 200];
-    FILE* f;
-
-    f = fopen(fn, "r");
-    if (f == NULL) {
-        fprintf(stderr, "failed to open %s: %s\n", fn, strerror(errno));
-        return -1;
-    }
-
-    while (fgets(buf, sizeof(buf), f)) {
-        Path* p;
-        char* token;
-        char* line = buf;
-        bool rootdir;
-
-        while (canned_used >= canned_alloc) {
-            canned_alloc = (canned_alloc+1) * 2;
-            canned_data = (Path*) realloc(canned_data, canned_alloc * sizeof(Path));
+    std::ifstream input(fn);
+    for (std::string line; std::getline(input, line);) {
+        // Historical: the root dir can be represented as a space character.
+        // e.g. " 1000 1000 0755" is parsed as
+        // path = " ", uid = 1000, gid = 1000, mode = 0755.
+        // But at the same time, we also have accepted
+        // "/ 1000 1000 0755".
+        if (StartsWith(line, " ")) {
+            line.insert(line.begin(), '/');
         }
-        p = canned_data + canned_used;
-        if (line[0] == '/') line++;
-        rootdir = line[0] == ' ';
-        p->path = strdup(rootdir ? "" : strtok(line, " "));
-        p->uid = atoi(strtok(rootdir ? line : NULL, " "));
-        p->gid = atoi(strtok(NULL, " "));
-        p->mode = strtol(strtok(NULL, " "), NULL, 8);   // mode is in octal
-        p->capabilities = 0;
 
-        do {
-            token = strtok(NULL, " ");
-            if (token && strncmp(token, "capabilities=", 13) == 0) {
-                p->capabilities = strtoll(token+13, NULL, 0);
+        std::vector<std::string> tokens = Tokenize(line, " ");
+        if (tokens.size() < 4) {
+            std::cerr << "Ill-formed line: " << line << " in " << fn << std::endl;
+            return -1;
+        }
+
+        // Historical: remove the leading '/' if exists.
+        std::string path(tokens[0].front() == '/' ? std::string(tokens[0], 1) : tokens[0]);
+
+        Entry e{
+                .path = std::move(path),
+                .uid = static_cast<unsigned int>(atoi(tokens[1].c_str())),
+                .gid = static_cast<unsigned int>(atoi(tokens[2].c_str())),
+                // mode is in octal
+                .mode = static_cast<unsigned int>(strtol(tokens[3].c_str(), nullptr, 8)),
+                .capabilities = 0,
+        };
+
+        for (size_t i = 4; i < tokens.size(); i++) {
+            std::string_view sv = tokens[i];
+            if (ConsumePrefix(&sv, "capabilities=")) {
+                e.capabilities = strtoll(std::string(sv).c_str(), nullptr, 0);
                 break;
             }
-        } while (token);
+            // Historical: there can be tokens like "selabel=..." here. They have been ignored.
+            // It's not an error because selabels are applied separately in e2fsdroid using the
+            // file_contexts files set via -S option.
+            std::cerr << "info: ignored token \"" << sv << "\" in " << fn << std::endl;
+        }
 
-        canned_used++;
+        canned_data.emplace_back(std::move(e));
     }
 
-    fclose(f);
+    // Note: we used to sort the entries by path names. This was to improve the lookup performance
+    // by doing binary search. However, this is no longer the case. The lookup performance is not
+    // critical because this tool runs on the host, not on the device. Now, there can be multiple
+    // entries for the same path. Then the one that comes the last wins. This is to allow overriding
+    // platform provided fs_config with a user provided fs_config by appending the latter to the
+    // former.
+    //
+    // To implement the strategy, reverse the entries order, and search from the top.
+    std::reverse(canned_data.begin(), canned_data.end());
 
-    qsort(canned_data, canned_used, sizeof(Path), path_compare);
-    printf("loaded %d fs_config entries\n", canned_used);
-
+    std::cout << "loaded " << canned_data.size() << " fs_config entries" << std::endl;
     return 0;
 }
 
-static const int kDebugCannedFsConfig = 0;
+void canned_fs_config(const char* path, [[maybe_unused]] int dir,
+                      [[maybe_unused]] const char* target_out_path, unsigned* uid, unsigned* gid,
+                      unsigned* mode, uint64_t* capabilities) {
+    if (path != nullptr && path[0] == '/') path++;  // canned paths lack the leading '/'
 
-void canned_fs_config(const char* path, int dir, const char* target_out_path,
-                      unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities) {
-    Path key, *p;
+    const Entry* found = nullptr;
+    // canned_data is already reversed. First match wins.
+    for (const auto& entry : canned_data) {
+        if (path == entry.path) {
+            found = &entry;
+            break;
+        }
+        continue;
+    }
 
-    key.path = path;
-    if (path[0] == '/') key.path++; // canned paths lack the leading '/'
-    p = (Path*) bsearch(&key, canned_data, canned_used, sizeof(Path), path_compare);
-    if (p == NULL) {
-        fprintf(stderr, "failed to find [%s] in canned fs_config\n", path);
+    if (found == nullptr) {
+        std::cerr << "failed to find " << path << " in canned fs_config" << std::endl;
         exit(1);
     }
-    *uid = p->uid;
-    *gid = p->gid;
-    *mode = p->mode;
-    *capabilities = p->capabilities;
 
-    if (kDebugCannedFsConfig) {
-        // for debugging, run the built-in fs_config and compare the results.
-
-        unsigned c_uid, c_gid, c_mode;
-        uint64_t c_capabilities;
-
-        fs_config(path, dir, target_out_path, &c_uid, &c_gid, &c_mode, &c_capabilities);
-
-        if (c_uid != *uid) printf("%s uid %d %d\n", path, *uid, c_uid);
-        if (c_gid != *gid) printf("%s gid %d %d\n", path, *gid, c_gid);
-        if (c_mode != *mode) printf("%s mode 0%o 0%o\n", path, *mode, c_mode);
-        if (c_capabilities != *capabilities) {
-            printf("%s capabilities %" PRIx64 " %" PRIx64 "\n",
-                path,
-                *capabilities,
-                c_capabilities);
-        }
-    }
+    *uid = found->uid;
+    *gid = found->gid;
+    *mode = found->mode;
+    *capabilities = found->capabilities;
 }
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index 24c6ae6..97e93f8 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -208,6 +208,39 @@
 }
 
 /**
+ * Trace an instantaneous context. name is used to identify the context.
+ *
+ * An "instant" is an event with no defined duration. Visually is displayed like a single marker
+ * in the timeline (rather than a span, in the case of begin/end events).
+ *
+ * By default, instant events are added into a dedicated track that has the same name of the event.
+ * Use atrace_instant_for_track to put different instant events into the same timeline track/row.
+ */
+#define ATRACE_INSTANT(name) atrace_instant(ATRACE_TAG, name)
+static inline void atrace_instant(uint64_t tag, const char* name) {
+    if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+        void atrace_instant_body(const char*);
+        atrace_instant_body(name);
+    }
+}
+
+/**
+ * Trace an instantaneous context. name is used to identify the context.
+ * trackName is the name of the row where the event should be recorded.
+ *
+ * An "instant" is an event with no defined duration. Visually is displayed like a single marker
+ * in the timeline (rather than a span, in the case of begin/end events).
+ */
+#define ATRACE_INSTANT_FOR_TRACK(trackName, name) \
+    atrace_instant_for_track(ATRACE_TAG, trackName, name)
+static inline void atrace_instant_for_track(uint64_t tag, const char* trackName, const char* name) {
+    if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+        void atrace_instant_for_track_body(const char*, const char*);
+        atrace_instant_for_track_body(trackName, name);
+    }
+}
+
+/**
  * Traces an integer counter value.  name is used to identify the counter.
  * This can be used to track how a value changes over time.
  */
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 596a8c3..23d1415 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -159,6 +159,7 @@
 #define AID_READPROC 3009     /* Allow /proc read access */
 #define AID_WAKELOCK 3010     /* Allow system wakelock read/write access */
 #define AID_UHID 3011         /* Allow read/write to /dev/uhid node */
+#define AID_READTRACEFS 3012  /* Allow tracefs read */
 
 /* The range 5000-5999 is also reserved for vendor partition. */
 #define AID_OEM_RESERVED_2_START 5000
diff --git a/libcutils/trace-container.cpp b/libcutils/trace-container.cpp
index f7eed48..ef7c72d 100644
--- a/libcutils/trace-container.cpp
+++ b/libcutils/trace-container.cpp
@@ -217,6 +217,28 @@
     WRITE_MSG("F|%d|", "|%" PRId32, name, cookie);
 }
 
+void atrace_instant_body(const char* name) {
+    if (CC_LIKELY(atrace_use_container_sock)) {
+        WRITE_MSG_IN_CONTAINER("I", "|", "%s", name, "");
+        return;
+    }
+
+    if (atrace_marker_fd < 0) return;
+
+    WRITE_MSG("I|%d|", "%s", name, "");
+}
+
+void atrace_instant_for_track_body(const char* trackName, const char* name) {
+    if (CC_LIKELY(atrace_use_container_sock)) {
+        WRITE_MSG_IN_CONTAINER("N", "|", "|%s", trackName, name);
+        return;
+    }
+
+    if (atrace_marker_fd < 0) return;
+
+    WRITE_MSG("N|%d|", "|%s", name, trackName);
+}
+
 void atrace_int_body(const char* name, int32_t value)
 {
     if (CC_LIKELY(atrace_use_container_sock)) {
diff --git a/libcutils/trace-dev.cpp b/libcutils/trace-dev.cpp
index 1ab63dc..25c86f4 100644
--- a/libcutils/trace-dev.cpp
+++ b/libcutils/trace-dev.cpp
@@ -89,6 +89,14 @@
     WRITE_MSG("F|%d|", "|%" PRId32, name, cookie);
 }
 
+void atrace_instant_body(const char* name) {
+    WRITE_MSG("I|%d|", "%s", name, "");
+}
+
+void atrace_instant_for_track_body(const char* trackName, const char* name) {
+    WRITE_MSG("N|%d|", "|%s", trackName, name);
+}
+
 void atrace_int_body(const char* name, int32_t value)
 {
     WRITE_MSG("C|%d|", "|%" PRId32, name, value);
diff --git a/libcutils/trace-dev_test.cpp b/libcutils/trace-dev_test.cpp
index 832b36a..ff6d202 100644
--- a/libcutils/trace-dev_test.cpp
+++ b/libcutils/trace-dev_test.cpp
@@ -195,6 +195,106 @@
   ASSERT_STREQ(expected.c_str(), actual.c_str());
 }
 
+TEST_F(TraceDevTest, atrace_instant_body_normal) {
+    atrace_instant_body("fake_name");
+
+    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("I|%d|fake_name", getpid());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_body_exact) {
+    std::string expected = android::base::StringPrintf("I|%d|", getpid());
+    std::string name = MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1);
+    atrace_instant_body(name.c_str());
+
+    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 += name;
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+    // Add a single character and verify we get the exact same value as before.
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    name += '*';
+    atrace_instant_body(name.c_str());
+    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_instant_body_truncated) {
+    std::string expected = android::base::StringPrintf("I|%d|", getpid());
+    std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_instant_body(name.c_str());
+
+    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;
+    expected += android::base::StringPrintf("%.*s", expected_len, name.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_for_track_body_normal) {
+    atrace_instant_for_track_body("fake_track", "fake_name");
+
+    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("N|%d|fake_track|fake_name", getpid());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_for_track_body_exact) {
+    const int nameSize = 5;
+    std::string expected = android::base::StringPrintf("N|%d|", getpid());
+    std::string trackName = MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - nameSize);
+    atrace_instant_for_track_body(trackName.c_str(), "name");
+
+    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 += trackName + "|name";
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+    // Add a single character and verify we get the exact same value as before.
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    trackName += '*';
+    atrace_instant_for_track_body(trackName.c_str(), "name");
+    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_instant_for_track_body_truncated) {
+    const int nameSize = 5;
+    std::string expected = android::base::StringPrintf("N|%d|", getpid());
+    std::string trackName = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_instant_for_track_body(trackName.c_str(), "name");
+
+    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 - nameSize;
+    expected += android::base::StringPrintf("%.*s|name", expected_len, trackName.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
 TEST_F(TraceDevTest, atrace_int_body_normal) {
   atrace_int_body("fake_name", 12345);
 
diff --git a/libcutils/trace-host.cpp b/libcutils/trace-host.cpp
index 9781ad3..b01a0ec 100644
--- a/libcutils/trace-host.cpp
+++ b/libcutils/trace-host.cpp
@@ -28,6 +28,9 @@
 void atrace_end_body() { }
 void atrace_async_begin_body(const char* /*name*/, int32_t /*cookie*/) {}
 void atrace_async_end_body(const char* /*name*/, int32_t /*cookie*/) {}
+void atrace_instant_body(const char* /*name*/) {}
+void atrace_instant_for_track_body(const char* /*trackName*/,
+                                   const char* /*name*/) {}
 void atrace_int_body(const char* /*name*/, int32_t /*value*/) {}
 void atrace_int64_body(const char* /*name*/, int64_t /*value*/) {}
 void atrace_init() {}
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 0734f25..352847a 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -34,6 +34,7 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <cgroup_map.h>
 #include <json/reader.h>
@@ -41,6 +42,7 @@
 #include <processgroup/processgroup.h>
 
 using android::base::GetBoolProperty;
+using android::base::StartsWith;
 using android::base::StringPrintf;
 using android::base::unique_fd;
 using android::base::WriteStringToFile;
@@ -204,6 +206,24 @@
     return CgroupController(nullptr);
 }
 
+CgroupController CgroupMap::FindControllerByPath(const std::string& path) const {
+    if (!loaded_) {
+        LOG(ERROR) << "CgroupMap::FindControllerByPath called for [" << getpid()
+                   << "] failed, RC file was not initialized properly";
+        return CgroupController(nullptr);
+    }
+
+    auto controller_count = ACgroupFile_getControllerCount();
+    for (uint32_t i = 0; i < controller_count; ++i) {
+        const ACgroupController* controller = ACgroupFile_getController(i);
+        if (StartsWith(path, ACgroupController_getPath(controller))) {
+            return CgroupController(controller);
+        }
+    }
+
+    return CgroupController(nullptr);
+}
+
 int CgroupMap::ActivateControllers(const std::string& path) const {
     if (__builtin_available(android 30, *)) {
         auto controller_count = ACgroupFile_getControllerCount();
diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h
index 22d717b..5cdf8b2 100644
--- a/libprocessgroup/cgroup_map.h
+++ b/libprocessgroup/cgroup_map.h
@@ -62,6 +62,7 @@
 
     static CgroupMap& GetInstance();
     CgroupController FindController(const std::string& name) const;
+    CgroupController FindControllerByPath(const std::string& path) const;
     int ActivateControllers(const std::string& path) const;
 
   private:
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index fa2642d..be34f95 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -26,6 +26,7 @@
 static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
 
 bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
+bool CgroupGetControllerFromPath(const std::string& path, std::string* cgroup_name);
 bool CgroupGetAttributePath(const std::string& attr_name, std::string* path);
 bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
 
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index faf945c..0320b02 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -69,6 +69,20 @@
     return true;
 }
 
+bool CgroupGetControllerFromPath(const std::string& path, std::string* cgroup_name) {
+    auto controller = CgroupMap::GetInstance().FindControllerByPath(path);
+
+    if (!controller.HasValue()) {
+        return false;
+    }
+
+    if (cgroup_name) {
+        *cgroup_name = controller.name();
+    }
+
+    return true;
+}
+
 bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) {
     const TaskProfiles& tp = TaskProfiles::GetInstance();
     const ProfileAttribute* attr = tp.GetAttribute(attr_name);
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 45d3c7c..b668dcb 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -651,6 +651,10 @@
     {
       "Name": "Dex2OatBootComplete",
       "Profiles": [ "Dex2oatPerformance", "LowIoPriority", "TimerSlackHigh" ]
+    },
+    {
+      "Name": "OtaProfiles",
+      "Profiles": [ "ServiceCapacityLow", "LowIoPriority", "HighEnergySaving" ]
     }
   ]
 }
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index e935f99..3834f91 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -144,30 +144,13 @@
     return true;
 }
 
-bool SetCgroupAction::IsAppDependentPath(const std::string& path) {
-    return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
-}
-
-SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
-    : controller_(c), path_(p) {
-    // file descriptors for app-dependent paths can't be cached
-    if (IsAppDependentPath(path_)) {
-        // file descriptor is not cached
-        fd_.reset(FDS_APP_DEPENDENT);
-        return;
-    }
-
-    // file descriptor can be cached later on request
-    fd_.reset(FDS_NOT_CACHED);
-}
-
-void SetCgroupAction::EnableResourceCaching() {
+void CachedFdProfileAction::EnableResourceCaching() {
     std::lock_guard<std::mutex> lock(fd_mutex_);
     if (fd_ != FDS_NOT_CACHED) {
         return;
     }
 
-    std::string tasks_path = controller_.GetTasksFilePath(path_);
+    std::string tasks_path = GetPath();
 
     if (access(tasks_path.c_str(), W_OK) != 0) {
         // file is not accessible
@@ -185,7 +168,7 @@
     fd_ = std::move(fd);
 }
 
-void SetCgroupAction::DropResourceCaching() {
+void CachedFdProfileAction::DropResourceCaching() {
     std::lock_guard<std::mutex> lock(fd_mutex_);
     if (fd_ == FDS_NOT_CACHED) {
         return;
@@ -194,6 +177,26 @@
     fd_.reset(FDS_NOT_CACHED);
 }
 
+bool CachedFdProfileAction::IsAppDependentPath(const std::string& path) {
+    return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
+}
+
+void CachedFdProfileAction::InitFd(const std::string& path) {
+    // file descriptors for app-dependent paths can't be cached
+    if (IsAppDependentPath(path)) {
+        // file descriptor is not cached
+        fd_.reset(FDS_APP_DEPENDENT);
+        return;
+    }
+    // file descriptor can be cached later on request
+    fd_.reset(FDS_NOT_CACHED);
+}
+
+SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
+    : controller_(c), path_(p) {
+    InitFd(controller_.GetTasksFilePath(path_));
+}
+
 bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
     if (tid <= 0) {
         return true;
@@ -270,7 +273,7 @@
     std::string tasks_path = controller()->GetTasksFilePath(path_);
     unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
     if (tmp_fd < 0) {
-        PLOG(WARNING) << "Failed to open " << tasks_path << ": " << strerror(errno);
+        PLOG(WARNING) << "Failed to open " << tasks_path;
         return false;
     }
     if (!AddTidToCgroup(tid, tmp_fd, controller()->name())) {
@@ -281,37 +284,73 @@
     return true;
 }
 
-bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
-    std::string filepath(filepath_), value(value_);
+WriteFileAction::WriteFileAction(const std::string& path, const std::string& value,
+                                 bool logfailures)
+    : path_(path), value_(value), logfailures_(logfailures) {
+    InitFd(path_);
+}
 
-    filepath = StringReplace(filepath, "<uid>", std::to_string(uid), true);
-    filepath = StringReplace(filepath, "<pid>", std::to_string(pid), true);
-    value = StringReplace(value, "<uid>", std::to_string(uid), true);
-    value = StringReplace(value, "<pid>", std::to_string(pid), true);
+bool WriteFileAction::WriteValueToFile(const std::string& value, const std::string& path,
+                                       bool logfailures) {
+    // Use WriteStringToFd instead of WriteStringToFile because the latter will open file with
+    // O_TRUNC which causes kernfs_mutex contention
+    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_WRONLY | O_CLOEXEC)));
 
-    if (!WriteStringToFile(value, filepath)) {
-        if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+    if (tmp_fd < 0) {
+        if (logfailures) PLOG(WARNING) << "Failed to open " << path;
+        return false;
+    }
+
+    if (!WriteStringToFd(value, tmp_fd)) {
+        if (logfailures) PLOG(ERROR) << "Failed to write '" << value << "' to " << path;
         return false;
     }
 
     return true;
 }
 
+bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    std::string value(value_);
+    std::string path(path_);
+
+    value = StringReplace(value, "<uid>", std::to_string(uid), true);
+    value = StringReplace(value, "<pid>", std::to_string(pid), true);
+    path = StringReplace(path, "<uid>", std::to_string(uid), true);
+    path = StringReplace(path, "<pid>", std::to_string(pid), true);
+
+    return WriteValueToFile(value, path, logfailures_);
+}
+
 bool WriteFileAction::ExecuteForTask(int tid) const {
-    std::string filepath(filepath_), value(value_);
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    std::string value(value_);
     int uid = getuid();
 
-    filepath = StringReplace(filepath, "<uid>", std::to_string(uid), true);
-    filepath = StringReplace(filepath, "<pid>", std::to_string(tid), true);
     value = StringReplace(value, "<uid>", std::to_string(uid), true);
     value = StringReplace(value, "<pid>", std::to_string(tid), true);
 
-    if (!WriteStringToFile(value, filepath)) {
-        if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+    if (IsFdValid()) {
+        // fd is cached, reuse it
+        if (!WriteStringToFd(value, fd_)) {
+            if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << path_;
+            return false;
+        }
+        return true;
+    }
+
+    if (fd_ == FDS_INACCESSIBLE) {
+        // no permissions to access the file, ignore
+        return true;
+    }
+
+    if (fd_ == FDS_APP_DEPENDENT) {
+        // application-dependent path can't be used with tid
+        PLOG(ERROR) << "Application profile can't be applied to a thread";
         return false;
     }
 
-    return true;
+    return WriteValueToFile(value, path_, logfailures_);
 }
 
 bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 97c38f4..278892d 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -108,50 +108,67 @@
     std::string value_;
 };
 
-// Set cgroup profile element
-class SetCgroupAction : public ProfileAction {
+// Abstract profile element for cached fd
+class CachedFdProfileAction : public ProfileAction {
   public:
-    SetCgroupAction(const CgroupController& c, const std::string& p);
-
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
     virtual void EnableResourceCaching();
     virtual void DropResourceCaching();
 
-    const CgroupController* controller() const { return &controller_; }
-    std::string path() const { return path_; }
-
-  private:
+  protected:
     enum FdState {
         FDS_INACCESSIBLE = -1,
         FDS_APP_DEPENDENT = -2,
         FDS_NOT_CACHED = -3,
     };
 
-    CgroupController controller_;
-    std::string path_;
     android::base::unique_fd fd_;
     mutable std::mutex fd_mutex_;
 
     static bool IsAppDependentPath(const std::string& path);
-    static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
 
+    void InitFd(const std::string& path);
     bool IsFdValid() const { return fd_ > FDS_INACCESSIBLE; }
+
+    virtual const std::string GetPath() const = 0;
 };
 
-// Write to file action
-class WriteFileAction : public ProfileAction {
+// Set cgroup profile element
+class SetCgroupAction : public CachedFdProfileAction {
   public:
-    WriteFileAction(const std::string& filepath, const std::string& value,
-                    bool logfailures) noexcept
-        : filepath_(filepath), value_(value), logfailures_(logfailures) {}
+    SetCgroupAction(const CgroupController& c, const std::string& p);
 
     virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     virtual bool ExecuteForTask(int tid) const;
 
+    const CgroupController* controller() const { return &controller_; }
+
+  protected:
+    const std::string GetPath() const override { return controller_.GetTasksFilePath(path_); }
+
   private:
-    std::string filepath_, value_;
+    CgroupController controller_;
+    std::string path_;
+
+    static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
+};
+
+// Write to file action
+class WriteFileAction : public CachedFdProfileAction {
+  public:
+    WriteFileAction(const std::string& path, const std::string& value, bool logfailures);
+
+    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
+    virtual bool ExecuteForTask(int tid) const;
+
+  protected:
+    const std::string GetPath() const override { return path_; }
+
+  private:
+    std::string path_, value_;
     bool logfailures_;
+
+    static bool WriteValueToFile(const std::string& value, const std::string& path,
+                                 bool logfailures);
 };
 
 class TaskProfile {
diff --git a/libprocessgroup/tools/Android.bp b/libprocessgroup/tools/Android.bp
new file mode 100644
index 0000000..91418e1
--- /dev/null
+++ b/libprocessgroup/tools/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "settaskprofile",
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    srcs: ["settaskprofile.cpp"],
+    shared_libs: [
+        "libprocessgroup",
+    ],
+}
diff --git a/libprocessgroup/tools/settaskprofile.cpp b/libprocessgroup/tools/settaskprofile.cpp
new file mode 100644
index 0000000..f83944a
--- /dev/null
+++ b/libprocessgroup/tools/settaskprofile.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <iostream>
+
+#include <processgroup/processgroup.h>
+
+[[noreturn]] static void usage(int exit_status) {
+    std::cerr << "Usage: " << getprogname() << " <tid> <profile> [... profileN]" << std::endl
+              << "    tid      Thread ID to apply the profiles to." << std::endl
+              << "    profile  Name of the profile to apply." << std::endl
+              << "Applies listed profiles to the thread with specified ID." << std::endl
+              << "Profiles are applied in the order specified in the command line." << std::endl
+              << "If applying a profile fails, remaining profiles are ignored." << std::endl;
+    exit(exit_status);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 3) {
+        usage(EXIT_FAILURE);
+    }
+
+    int tid = atoi(argv[1]);
+    if (tid == 0) {
+        std::cerr << "Invalid thread id" << std::endl;
+        exit(EXIT_FAILURE);
+    }
+
+    for (int i = 2; i < argc; i++) {
+        if (!SetTaskProfiles(tid, {argv[i]})) {
+            std::cerr << "Failed to apply " << argv[i] << " profile" << std::endl;
+            exit(EXIT_FAILURE);
+        }
+        std::cout << "Profile " << argv[i] << " is applied successfully!" << std::endl;
+    }
+
+    return 0;
+}
diff --git a/libstats/bootstrap/Android.bp b/libstats/bootstrap/Android.bp
new file mode 100644
index 0000000..332d9c8
--- /dev/null
+++ b/libstats/bootstrap/Android.bp
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// =========================================================================
+// Native library that provide a client to StatsBootstrapAtomService.
+// This library should only be used by processes that start in the bootstrap namespace.
+// All other clients should use libstatssocket, provided by the statsd apex.
+// =========================================================================
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+    name: "libstatsbootstrap_defaults",
+    srcs: [
+        "BootstrapClientInternal.cpp",
+        "StatsBootstrapAtomClient.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libutils",
+        "android.os.statsbootstrap_aidl-cpp",
+    ],
+}
+
+cc_library {
+    name: "libstatsbootstrap",
+    defaults: ["libstatsbootstrap_defaults"],
+    export_include_dirs: ["include"],
+}
+
+
diff --git a/libstats/bootstrap/BootstrapClientInternal.cpp b/libstats/bootstrap/BootstrapClientInternal.cpp
new file mode 100644
index 0000000..b02e116
--- /dev/null
+++ b/libstats/bootstrap/BootstrapClientInternal.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BootstrapClientInternal.h"
+
+#include <binder/IServiceManager.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+sp<BootstrapClientInternal> BootstrapClientInternal::getInstance() {
+    static sp<BootstrapClientInternal> client = new BootstrapClientInternal();
+    return client;
+}
+
+sp<IStatsBootstrapAtomService> BootstrapClientInternal::getServiceNonBlocking() {
+    std::lock_guard<std::mutex> lock(mLock);
+    if (mService != nullptr) {
+        return mService;
+    }
+    connectNonBlockingLocked();
+    return mService;
+}
+
+void BootstrapClientInternal::binderDied(const wp<IBinder>&) {
+    std::lock_guard<std::mutex> lock(mLock);
+    mService = nullptr;
+    connectNonBlockingLocked();
+}
+
+void BootstrapClientInternal::connectNonBlockingLocked() {
+    const String16 name("statsbootstrap");
+    mService =
+            interface_cast<IStatsBootstrapAtomService>(defaultServiceManager()->checkService(name));
+    if (mService != nullptr) {
+        // Set up binder death.
+        IInterface::asBinder(mService)->linkToDeath(this);
+    }
+}
+
+}  // namespace stats
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/libstats/bootstrap/BootstrapClientInternal.h b/libstats/bootstrap/BootstrapClientInternal.h
new file mode 100644
index 0000000..96238da
--- /dev/null
+++ b/libstats/bootstrap/BootstrapClientInternal.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/os/IStatsBootstrapAtomService.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+class BootstrapClientInternal : public IBinder::DeathRecipient {
+  public:
+    static sp<BootstrapClientInternal> getInstance();
+    void binderDied(const wp<IBinder>& who) override;
+    sp<IStatsBootstrapAtomService> getServiceNonBlocking();
+
+  private:
+    BootstrapClientInternal() {}
+    void connectNonBlockingLocked();
+
+    mutable std::mutex mLock;
+    sp<IStatsBootstrapAtomService> mService;
+};
+
+}  // namespace stats
+}  // namespace os
+}  // namespace android
diff --git a/libstats/bootstrap/StatsBootstrapAtomClient.cpp b/libstats/bootstrap/StatsBootstrapAtomClient.cpp
new file mode 100644
index 0000000..348b7fa
--- /dev/null
+++ b/libstats/bootstrap/StatsBootstrapAtomClient.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "include/StatsBootstrapAtomClient.h"
+
+#include <android/os/IStatsBootstrapAtomService.h>
+
+#include "BootstrapClientInternal.h"
+
+namespace android {
+namespace os {
+namespace stats {
+
+bool StatsBootstrapAtomClient::reportBootstrapAtom(const StatsBootstrapAtom& atom) {
+    sp<IStatsBootstrapAtomService> service =
+            BootstrapClientInternal::getInstance()->getServiceNonBlocking();
+    if (service == nullptr) {
+        return false;
+    }
+    return service->reportBootstrapAtom(atom).isOk();
+}
+
+}  // namespace stats
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/libstats/bootstrap/include/StatsBootstrapAtomClient.h b/libstats/bootstrap/include/StatsBootstrapAtomClient.h
new file mode 100644
index 0000000..87930fd
--- /dev/null
+++ b/libstats/bootstrap/include/StatsBootstrapAtomClient.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/os/StatsBootstrapAtom.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+class StatsBootstrapAtomClient {
+  public:
+    static bool reportBootstrapAtom(const StatsBootstrapAtom& atom);
+};
+
+}  // namespace stats
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 13e4c02..e6d9a4c 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -99,7 +99,6 @@
 
             shared_libs: [
                 "libprocessgroup",
-                "libdl",
                 "libvndksupport",
             ],
 
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 6e293c7..3bf5779 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -317,10 +317,7 @@
     if (pri >= ANDROID_PRIORITY_BACKGROUND) {
         rc = SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
     } else if (curr_pri >= ANDROID_PRIORITY_BACKGROUND) {
-        SchedPolicy policy = SP_FOREGROUND;
-        // Change to the sched policy group of the process.
-        get_sched_policy(getpid(), &policy);
-        rc = SetTaskProfiles(tid, {get_sched_policy_profile_name(policy)}, true) ? 0 : -1;
+        rc = SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true) ? 0 : -1;
     }
 
     if (rc) {
diff --git a/libvndksupport/linker.cpp b/libvndksupport/linker.cpp
index 30b9c2e..ad4fb31 100644
--- a/libvndksupport/linker.cpp
+++ b/libvndksupport/linker.cpp
@@ -39,7 +39,7 @@
 
 static VendorNamespace get_vendor_namespace() {
     static VendorNamespace result = ([] {
-        for (const char* name : {"sphal", "default"}) {
+        for (const char* name : {"sphal", "vendor", "default"}) {
             if (android_namespace_t* ns = android_get_exported_namespace(name)) {
                 return VendorNamespace{ns, name};
             }
diff --git a/mini_keyctl/Android.bp b/mini_keyctl/Android.bp
index 417ddac..0325c5b 100644
--- a/mini_keyctl/Android.bp
+++ b/mini_keyctl/Android.bp
@@ -13,6 +13,7 @@
     ],
     cflags: ["-Werror", "-Wall", "-Wextra"],
     export_include_dirs: ["."],
+    recovery_available: true,
 }
 
 cc_binary {
diff --git a/mini_keyctl/OWNERS b/mini_keyctl/OWNERS
new file mode 100644
index 0000000..f9e7b25
--- /dev/null
+++ b/mini_keyctl/OWNERS
@@ -0,0 +1,5 @@
+alanstokes@google.com
+ebiggers@google.com
+jeffv@google.com
+jiyong@google.com
+victorhsieh@google.com
diff --git a/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp b/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
index 3907413..77cbdd4 100644
--- a/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
+++ b/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
@@ -490,7 +490,6 @@
       {"media.recorder.show_manufacturer_and_model", "u:object_r:default_prop:s0"},
       {"net.bt.name", "u:object_r:system_prop:s0"},
       {"net.lte.ims.data.enabled", "u:object_r:net_radio_prop:s0"},
-      {"net.qtaguid_enabled", "u:object_r:system_prop:s0"},
       {"net.tcp.default_init_rwnd", "u:object_r:system_prop:s0"},
       {"nfc.initialized", "u:object_r:nfc_prop:s0"},
       {"persist.audio.fluence.speaker", "u:object_r:audio_prop:s0"},
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 27fa059..42545d9 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -78,8 +78,8 @@
     mkdir /dev/boringssl 0755 root root
     mkdir /dev/boringssl/selftest 0755 root root
 
-    # Mount tracefs
-    mount tracefs tracefs /sys/kernel/tracing
+    # Mount tracefs (with GID=AID_READTRACEFS)
+    mount tracefs tracefs /sys/kernel/tracing gid=3012
 
     # create sys dirctory
     mkdir /dev/sys 0755 system system
@@ -460,11 +460,6 @@
     class_stop charger
     trigger late-init
 
-on load_persist_props_action
-    load_persist_props
-    start logd
-    start logd-reinit
-
 # Indicate to fw loaders that the relevant mounts are up.
 on firmware_mounts_complete
     rm /dev/.booting
@@ -491,9 +486,6 @@
     # /data, which in turn can only be loaded when system properties are present.
     trigger post-fs-data
 
-    # Load persist properties and override properties (if enabled) from /data.
-    trigger load_persist_props_action
-
     # Should be before netd, but after apex, properties and logging is available.
     trigger load_bpf_programs
 
@@ -583,6 +575,7 @@
     restorecon_recursive /metadata/apex
 
     mkdir /metadata/staged-install 0770 root system
+    mkdir /metadata/sepolicy 0700 root root
 on late-fs
     # Ensure that tracefs has the correct permissions.
     # This does not work correctly if it is called in post-fs.
@@ -676,6 +669,18 @@
     # use of MAX_BOOT_LEVEL keys.
     exec - system system -- /system/bin/vdc keymaster earlyBootEnded
 
+    # Multi-installed APEXes are selected using persist props.
+    # Load persist properties and override properties (if enabled) from /data,
+    # before starting apexd.
+    load_persist_props
+    start logd
+    start logd-reinit
+    # Some existing vendor rc files use 'on load_persist_props_action' to know
+    # when persist props are ready. These are difficult to change due to GRF,
+    # so continue triggering this action here even though props are already loaded
+    # by the 'load_persist_props' call above.
+    trigger load_persist_props_action
+
     # /data/apex is now available. Start apexd to scan and activate APEXes.
     #
     # To handle userspace reboots as well as devices that use FDE, make sure
@@ -1119,37 +1124,6 @@
 on charger
     class_start charger
 
-on property:vold.decrypt=trigger_load_persist_props
-    load_persist_props
-    start logd
-    start logd-reinit
-
-on property:vold.decrypt=trigger_post_fs_data
-    trigger post-fs-data
-    trigger zygote-start
-
-on property:vold.decrypt=trigger_restart_min_framework
-    # A/B update verifier that marks a successful boot.
-    exec_start update_verifier
-    class_start main
-
-on property:vold.decrypt=trigger_restart_framework
-    # A/B update verifier that marks a successful boot.
-    exec_start update_verifier
-    class_start_post_data hal
-    class_start_post_data core
-    class_start main
-    class_start late_start
-    setprop service.bootanim.exit 0
-    setprop service.bootanim.progress 0
-    start bootanim
-
-on property:vold.decrypt=trigger_shutdown_framework
-    class_reset late_start
-    class_reset main
-    class_reset_post_data core
-    class_reset_post_data hal
-
 on property:sys.boot_completed=1
     bootchart stop
     # Setup per_boot directory so other .rc could start to use it on boot_completed
diff --git a/rootdir/init.zygote32.rc b/rootdir/init.zygote32.rc
index 0090841..63b09c0 100644
--- a/rootdir/init.zygote32.rc
+++ b/rootdir/init.zygote32.rc
@@ -10,6 +10,7 @@
     onrestart restart audioserver
     onrestart restart cameraserver
     onrestart restart media
+    onrestart restart media.tuner
     onrestart restart netd
     onrestart restart wificond
     task_profiles ProcessCapacityHigh
diff --git a/rootdir/init.zygote64.rc b/rootdir/init.zygote64.rc
index 63772bd..5bde5f4 100644
--- a/rootdir/init.zygote64.rc
+++ b/rootdir/init.zygote64.rc
@@ -10,6 +10,7 @@
     onrestart restart audioserver
     onrestart restart cameraserver
     onrestart restart media
+    onrestart restart media.tuner
     onrestart restart netd
     onrestart restart wificond
     task_profiles ProcessCapacityHigh
diff --git a/rootdir/init.zygote64_32.rc b/rootdir/init.zygote64_32.rc
index 3eee180..efb30d6 100644
--- a/rootdir/init.zygote64_32.rc
+++ b/rootdir/init.zygote64_32.rc
@@ -10,6 +10,7 @@
     onrestart restart audioserver
     onrestart restart cameraserver
     onrestart restart media
+    onrestart restart media.tuner
     onrestart restart netd
     onrestart restart wificond
     task_profiles ProcessCapacityHigh MaxPerformance
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index 97e8d8e..d85f6ed 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -26,6 +26,7 @@
         "mkshrc",
         "newfs_msdos",
         "reboot",
+        "settaskprofile",
         "sh",
         "simpleperf",
         "simpleperf_app_runner",
diff --git a/storaged/Android.bp b/storaged/Android.bp
index b557dee..7960af3 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -24,8 +24,10 @@
     shared_libs: [
         "android.hardware.health@1.0",
         "android.hardware.health@2.0",
+        "android.hardware.health-V1-ndk",
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "libhidlbase",
         "liblog",
@@ -35,6 +37,12 @@
         "packagemanager_aidl-cpp",
     ],
 
+    static_libs: [
+        "android.hardware.health-translate-ndk",
+        "libhealthhalutils",
+        "libhealthshim",
+    ],
+
     cflags: [
         "-Wall",
         "-Werror",
@@ -67,7 +75,6 @@
         ":storaged_aidl_private",
     ],
 
-    static_libs: ["libhealthhalutils"],
     header_libs: ["libbatteryservice_headers"],
 
     logtags: ["EventLogTags.logtags"],
@@ -90,7 +97,6 @@
     srcs: ["main.cpp"],
 
     static_libs: [
-        "libhealthhalutils",
         "libstoraged",
     ],
 }
@@ -107,9 +113,11 @@
     srcs: ["tests/storaged_test.cpp"],
 
     static_libs: [
-        "libhealthhalutils",
         "libstoraged",
     ],
+    test_suites: [
+        "general-tests",
+    ],
 }
 
 // AIDL interface between storaged and framework.jar
diff --git a/storaged/include/storaged.h b/storaged/include/storaged.h
index 79b5d41..e120271 100644
--- a/storaged/include/storaged.h
+++ b/storaged/include/storaged.h
@@ -28,6 +28,7 @@
 
 #include <utils/Mutex.h>
 
+#include <aidl/android/hardware/health/IHealth.h>
 #include <android/hardware/health/2.0/IHealth.h>
 
 #define FRIEND_TEST(test_case_name, test_name) \
@@ -67,6 +68,8 @@
 // UID IO threshold in bytes
 #define DEFAULT_PERIODIC_CHORES_UID_IO_THRESHOLD ( 1024 * 1024 * 1024ULL )
 
+class storaged_t;
+
 struct storaged_config {
     int periodic_chores_interval_unit;
     int periodic_chores_interval_disk_stats_publish;
@@ -75,15 +78,33 @@
     int event_time_check_usec;  // check how much cputime spent in event loop
 };
 
-class storaged_t : public android::hardware::health::V2_0::IHealthInfoCallback,
-                   public android::hardware::hidl_death_recipient {
+struct HealthServicePair {
+    std::shared_ptr<aidl::android::hardware::health::IHealth> aidl_health;
+    android::sp<android::hardware::health::V2_0::IHealth> hidl_health;
+    static HealthServicePair get();
+};
+
+class hidl_health_death_recipient : public android::hardware::hidl_death_recipient {
+  public:
+    hidl_health_death_recipient(const android::sp<android::hardware::health::V2_0::IHealth>& health)
+        : mHealth(health) {}
+    void serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who);
+
+  private:
+    android::sp<android::hardware::health::V2_0::IHealth> mHealth;
+};
+
+class storaged_t : public RefBase {
   private:
     time_t mTimer;
     storaged_config mConfig;
     unique_ptr<disk_stats_monitor> mDsm;
     uid_monitor mUidm;
     time_t mStarttime;
-    sp<android::hardware::health::V2_0::IHealth> health;
+    std::shared_ptr<aidl::android::hardware::health::IHealth> health;
+    sp<android::hardware::hidl_death_recipient> hidl_death_recp;
+    ndk::ScopedAIBinder_DeathRecipient aidl_death_recp;
+    shared_ptr<aidl::android::hardware::health::IHealthInfoCallback> aidl_health_callback;
     unique_ptr<storage_info_t> storage_info;
     static const uint32_t current_version;
     Mutex proto_lock;
@@ -135,10 +156,6 @@
     void add_user_ce(userid_t user_id);
     void remove_user_ce(userid_t user_id);
 
-    virtual ::android::hardware::Return<void> healthInfoChanged(
-        const ::android::hardware::health::V2_0::HealthInfo& info);
-    void serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who);
-
     void report_storage_info();
 
     void flush_protos(unordered_map<int, StoragedProto>* protos);
diff --git a/storaged/include/storaged_diskstats.h b/storaged/include/storaged_diskstats.h
index 0b93ba6..3996ef6 100644
--- a/storaged/include/storaged_diskstats.h
+++ b/storaged/include/storaged_diskstats.h
@@ -19,7 +19,7 @@
 
 #include <stdint.h>
 
-#include <android/hardware/health/2.0/IHealth.h>
+#include <aidl/android/hardware/health/IHealth.h>
 
 // number of attributes diskstats has
 #define DISK_STATS_SIZE ( 11 )
@@ -162,7 +162,7 @@
     const double mSigma;
     struct disk_perf mMean;
     struct disk_perf mStd;
-    android::sp<android::hardware::health::V2_0::IHealth> mHealth;
+    std::shared_ptr<aidl::android::hardware::health::IHealth> mHealth;
 
     void update_mean();
     void update_std();
@@ -173,14 +173,15 @@
     void update(struct disk_stats* stats);
 
 public:
-  disk_stats_monitor(const android::sp<android::hardware::health::V2_0::IHealth>& healthService,
+  disk_stats_monitor(const std::shared_ptr<aidl::android::hardware::health::IHealth>& healthService,
                      uint32_t window_size = 5, double sigma = 1.0)
       : DISK_STATS_PATH(
-            healthService != nullptr
-                ? nullptr
-                : (access(MMC_DISK_STATS_PATH, R_OK) == 0
-                       ? MMC_DISK_STATS_PATH
-                       : (access(SDA_DISK_STATS_PATH, R_OK) == 0 ? SDA_DISK_STATS_PATH : nullptr))),
+                healthService != nullptr
+                        ? nullptr
+                        : (access(MMC_DISK_STATS_PATH, R_OK) == 0
+                                   ? MMC_DISK_STATS_PATH
+                                   : (access(SDA_DISK_STATS_PATH, R_OK) == 0 ? SDA_DISK_STATS_PATH
+                                                                             : nullptr))),
         mPrevious(),
         mAccumulate(),
         mAccumulate_pub(),
diff --git a/storaged/include/storaged_info.h b/storaged/include/storaged_info.h
index 9c3d0e7..83c97ad 100644
--- a/storaged/include/storaged_info.h
+++ b/storaged/include/storaged_info.h
@@ -21,7 +21,7 @@
 
 #include <chrono>
 
-#include <android/hardware/health/2.0/IHealth.h>
+#include <aidl/android/hardware/health/IHealth.h>
 #include <utils/Mutex.h>
 
 #include "storaged.h"
@@ -71,8 +71,8 @@
 
   public:
     static storage_info_t* get_storage_info(
-        const sp<android::hardware::health::V2_0::IHealth>& healthService);
-    virtual ~storage_info_t() {};
+            const shared_ptr<aidl::android::hardware::health::IHealth>& healthService);
+    virtual ~storage_info_t(){};
     virtual void report() {};
     void load_perf_history_proto(const IOPerfHistory& perf_history);
     void refresh(IOPerfHistory* perf_history);
@@ -105,14 +105,14 @@
 
 class health_storage_info_t : public storage_info_t {
   private:
-    using IHealth = hardware::health::V2_0::IHealth;
-    using StorageInfo = hardware::health::V2_0::StorageInfo;
+    using IHealth = aidl::android::hardware::health::IHealth;
+    using StorageInfo = aidl::android::hardware::health::StorageInfo;
 
-    sp<IHealth> mHealth;
+    shared_ptr<IHealth> mHealth;
     void set_values_from_hal_storage_info(const StorageInfo& halInfo);
 
   public:
-    health_storage_info_t(const sp<IHealth>& service) : mHealth(service){};
+    health_storage_info_t(const shared_ptr<IHealth>& service) : mHealth(service){};
     virtual ~health_storage_info_t() {}
     virtual void report();
 };
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index b7aa89f..fb855f7 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -28,12 +28,16 @@
 #include <sstream>
 #include <string>
 
+#include <aidl/android/hardware/health/BnHealthInfoCallback.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
 #include <batteryservice/BatteryServiceConstants.h>
 #include <cutils/properties.h>
+#include <health-shim/shim.h>
 #include <healthhalutils/HealthHalUtils.h>
 #include <hidl/HidlTransportSupport.h>
 #include <hwbinder/IPCThreadState.h>
@@ -64,26 +68,59 @@
 
 const uint32_t storaged_t::current_version = 4;
 
+using aidl::android::hardware::health::BatteryStatus;
+using aidl::android::hardware::health::BnHealthInfoCallback;
+using aidl::android::hardware::health::HealthInfo;
+using aidl::android::hardware::health::IHealth;
+using aidl::android::hardware::health::IHealthInfoCallback;
 using android::hardware::interfacesEqual;
-using android::hardware::Return;
-using android::hardware::health::V1_0::BatteryStatus;
-using android::hardware::health::V1_0::toString;
 using android::hardware::health::V2_0::get_health_service;
-using android::hardware::health::V2_0::HealthInfo;
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
 using android::hidl::manager::V1_0::IServiceManager;
+using HidlHealth = android::hardware::health::V2_0::IHealth;
+using aidl::android::hardware::health::HealthShim;
+using ndk::ScopedAIBinder_DeathRecipient;
+using ndk::ScopedAStatus;
 
+HealthServicePair HealthServicePair::get() {
+    HealthServicePair ret;
+    auto service_name = IHealth::descriptor + "/default"s;
+    if (AServiceManager_isDeclared(service_name.c_str())) {
+        ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+        ret.aidl_health = IHealth::fromBinder(binder);
+        if (ret.aidl_health == nullptr) {
+            LOG(WARNING) << "AIDL health service is declared, but it cannot be retrieved.";
+        }
+    }
+    if (ret.aidl_health == nullptr) {
+        LOG(INFO) << "Unable to get AIDL health service, trying HIDL...";
+        ret.hidl_health = get_health_service();
+        if (ret.hidl_health != nullptr) {
+            ret.aidl_health = ndk::SharedRefBase::make<HealthShim>(ret.hidl_health);
+        }
+    }
+    if (ret.aidl_health == nullptr) {
+        LOG(WARNING) << "health: failed to find IHealth service";
+        return {};
+    }
+    return ret;
+}
 
 inline charger_stat_t is_charger_on(BatteryStatus prop) {
     return (prop == BatteryStatus::CHARGING || prop == BatteryStatus::FULL) ?
         CHARGER_ON : CHARGER_OFF;
 }
 
-Return<void> storaged_t::healthInfoChanged(const HealthInfo& props) {
-    mUidm.set_charger_state(is_charger_on(props.legacy.batteryStatus));
-    return android::hardware::Void();
-}
+class HealthInfoCallback : public BnHealthInfoCallback {
+  public:
+    HealthInfoCallback(uid_monitor* uidm) : mUidm(uidm) {}
+    ScopedAStatus healthInfoChanged(const HealthInfo& info) override {
+        mUidm->set_charger_state(is_charger_on(info.batteryStatus));
+        return ScopedAStatus::ok();
+    }
+
+  private:
+    uid_monitor* mUidm;
+};
 
 void storaged_t::init() {
     init_health_service();
@@ -91,42 +128,59 @@
     storage_info.reset(storage_info_t::get_storage_info(health));
 }
 
+static void onHealthBinderDied(void*) {
+    LOG(ERROR) << "health service died, exiting";
+    android::hardware::IPCThreadState::self()->stopProcess();
+    exit(1);
+}
+
 void storaged_t::init_health_service() {
     if (!mUidm.enabled())
         return;
 
-    health = get_health_service();
-    if (health == NULL) {
-        LOG(WARNING) << "health: failed to find IHealth service";
-        return;
-    }
+    auto [aidlHealth, hidlHealth] = HealthServicePair::get();
+    health = aidlHealth;
+    if (health == nullptr) return;
 
     BatteryStatus status = BatteryStatus::UNKNOWN;
-    auto ret = health->getChargeStatus([&](Result r, BatteryStatus v) {
-        if (r != Result::SUCCESS) {
-            LOG(WARNING) << "health: cannot get battery status " << toString(r);
-            return;
-        }
-        if (v == BatteryStatus::UNKNOWN) {
-            LOG(WARNING) << "health: invalid battery status";
-        }
-        status = v;
-    });
+    auto ret = health->getChargeStatus(&status);
     if (!ret.isOk()) {
-        LOG(WARNING) << "health: get charge status transaction error " << ret.description();
+        LOG(WARNING) << "health: cannot get battery status: " << ret.getDescription();
+    }
+    if (status == BatteryStatus::UNKNOWN) {
+        LOG(WARNING) << "health: invalid battery status";
     }
 
     mUidm.init(is_charger_on(status));
     // register listener after init uid_monitor
-    health->registerCallback(this);
-    health->linkToDeath(this, 0 /* cookie */);
+    aidl_health_callback = std::make_shared<HealthInfoCallback>(&mUidm);
+    ret = health->registerCallback(aidl_health_callback);
+    if (!ret.isOk()) {
+        LOG(WARNING) << "health: failed to register callback: " << ret.getDescription();
+    }
+
+    if (hidlHealth != nullptr) {
+        hidl_death_recp = new hidl_health_death_recipient(hidlHealth);
+        auto ret = hidlHealth->linkToDeath(hidl_death_recp, 0 /* cookie */);
+        if (!ret.isOk()) {
+            LOG(WARNING) << "Failed to link to death (HIDL): " << ret.description();
+        }
+    } else {
+        aidl_death_recp =
+                ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new(onHealthBinderDied));
+        auto ret = AIBinder_linkToDeath(health->asBinder().get(), aidl_death_recp.get(),
+                                        nullptr /* cookie */);
+        if (ret != STATUS_OK) {
+            LOG(WARNING) << "Failed to link to death (AIDL): "
+                         << ScopedAStatus(AStatus_fromStatus(ret)).getDescription();
+        }
+    }
 }
 
-void storaged_t::serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who) {
-    if (health != NULL && interfacesEqual(health, who.promote())) {
-        LOG(ERROR) << "health service died, exiting";
-        android::hardware::IPCThreadState::self()->stopProcess();
-        exit(1);
+void hidl_health_death_recipient::serviceDied(uint64_t cookie,
+                                              const wp<::android::hidl::base::V1_0::IBase>& who) {
+    if (mHealth != nullptr && interfacesEqual(mHealth, who.promote())) {
+        onHealthBinderDied(reinterpret_cast<void*>(cookie));
     } else {
         LOG(ERROR) << "unknown service died";
     }
diff --git a/storaged/storaged_diskstats.cpp b/storaged/storaged_diskstats.cpp
index 52bd4e0..1eae5a1 100644
--- a/storaged/storaged_diskstats.cpp
+++ b/storaged/storaged_diskstats.cpp
@@ -30,11 +30,8 @@
 
 namespace {
 
-using android::sp;
-using android::hardware::health::V2_0::DiskStats;
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_0::toString;
+using aidl::android::hardware::health::DiskStats;
+using aidl::android::hardware::health::IHealth;
 
 #ifdef DEBUG
 void log_debug_disk_perf(struct disk_perf* perf, const char* type) {
@@ -121,39 +118,30 @@
     dst->io_in_queue = src.ioInQueue;
 }
 
-bool get_disk_stats_from_health_hal(const sp<IHealth>& service, struct disk_stats* stats) {
+bool get_disk_stats_from_health_hal(const std::shared_ptr<IHealth>& service,
+                                    struct disk_stats* stats) {
     struct timespec ts;
     if (!get_time(&ts)) {
         return false;
     }
 
-    bool success = false;
-    auto ret = service->getDiskStats([&success, stats](auto result, const auto& halStats) {
-        if (result == Result::NOT_SUPPORTED) {
-            LOG(DEBUG) << "getDiskStats is not supported on health HAL.";
-            return;
+    std::vector<DiskStats> halStats;
+    auto ret = service->getDiskStats(&halStats);
+    if (ret.isOk()) {
+        if (halStats.size() > 0) {
+            convert_hal_disk_stats(stats, halStats[0]);
+            init_disk_stats_other(ts, stats);
+            return true;
         }
-        if (result != Result::SUCCESS || halStats.size() == 0) {
-            LOG(ERROR) << "getDiskStats failed with result " << toString(result) << " and size "
-                       << halStats.size();
-            return;
-        }
-
-        convert_hal_disk_stats(stats, halStats[0]);
-        success = true;
-    });
-
-    if (!ret.isOk()) {
-        LOG(ERROR) << "getDiskStats failed with " << ret.description();
+        LOG(ERROR) << "getDiskStats succeeded but size is 0";
         return false;
     }
-
-    if (!success) {
+    if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+        LOG(DEBUG) << "getDiskStats is not supported on health HAL.";
         return false;
     }
-
-    init_disk_stats_other(ts, stats);
-    return true;
+    LOG(ERROR) << "getDiskStats failed with " << ret.getDescription();
+    return false;
 }
 
 struct disk_perf get_disk_perf(struct disk_stats* stats)
diff --git a/storaged/storaged_info.cpp b/storaged/storaged_info.cpp
index bb21829..3e646e0 100644
--- a/storaged/storaged_info.cpp
+++ b/storaged/storaged_info.cpp
@@ -36,9 +36,8 @@
 using namespace android::base;
 using namespace storaged_proto;
 
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_0::StorageInfo;
+using aidl::android::hardware::health::IHealth;
+using aidl::android::hardware::health::StorageInfo;
 
 const string emmc_info_t::emmc_sysfs = "/sys/bus/mmc/devices/mmc0:0001/";
 const char* emmc_info_t::emmc_ver_str[9] = {
@@ -57,7 +56,7 @@
 
 } // namespace
 
-storage_info_t* storage_info_t::get_storage_info(const sp<IHealth>& healthService) {
+storage_info_t* storage_info_t::get_storage_info(const shared_ptr<IHealth>& healthService) {
     if (healthService != nullptr) {
         return new health_storage_info_t(healthService);
     }
@@ -326,23 +325,22 @@
 }
 
 void health_storage_info_t::report() {
-    auto ret = mHealth->getStorageInfo([this](auto result, const auto& halInfos) {
-        if (result == Result::NOT_SUPPORTED) {
-            LOG(DEBUG) << "getStorageInfo is not supported on health HAL.";
+    vector<StorageInfo> halInfos;
+    auto ret = mHealth->getStorageInfo(&halInfos);
+    if (ret.isOk()) {
+        if (halInfos.size() != 0) {
+            set_values_from_hal_storage_info(halInfos[0]);
+            publish();
             return;
         }
-        if (result != Result::SUCCESS || halInfos.size() == 0) {
-            LOG(ERROR) << "getStorageInfo failed with result " << toString(result) << " and size "
-                       << halInfos.size();
-            return;
-        }
-        set_values_from_hal_storage_info(halInfos[0]);
-        publish();
-    });
-
-    if (!ret.isOk()) {
-        LOG(ERROR) << "getStorageInfo failed with " << ret.description();
+        LOG(ERROR) << "getStorageInfo succeeded but size is 0";
+        return;
     }
+    if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+        LOG(DEBUG) << "getStorageInfo is not supported on health HAL.";
+        return;
+    }
+    LOG(ERROR) << "getStorageInfo failed with " << ret.getDescription();
 }
 
 void health_storage_info_t::set_values_from_hal_storage_info(const StorageInfo& halInfo) {
diff --git a/storaged/tests/storaged_test.cpp b/storaged/tests/storaged_test.cpp
index 64009c2..bb71bf3 100644
--- a/storaged/tests/storaged_test.cpp
+++ b/storaged/tests/storaged_test.cpp
@@ -25,6 +25,7 @@
 
 #include <gtest/gtest.h>
 
+#include <aidl/android/hardware/health/IHealth.h>
 #include <healthhalutils/HealthHalUtils.h>
 #include <storaged.h>               // data structures
 #include <storaged_utils.h>         // functions to test
@@ -64,20 +65,23 @@
 } // namespace
 
 // the return values of the tested functions should be the expected ones
-const char* DISK_STATS_PATH;
+const char* get_disk_stats_path() {
+    if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
+        return MMC_DISK_STATS_PATH;
+    } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) {
+        return SDA_DISK_STATS_PATH;
+    } else {
+        return nullptr;
+    }
+}
 TEST(storaged_test, retvals) {
     struct disk_stats stats;
     memset(&stats, 0, sizeof(struct disk_stats));
 
-    if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
-        DISK_STATS_PATH = MMC_DISK_STATS_PATH;
-    } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) {
-        DISK_STATS_PATH = SDA_DISK_STATS_PATH;
-    } else {
-        return;
-    }
+    auto disk_stats_path = get_disk_stats_path();
+    if (disk_stats_path == nullptr) GTEST_SKIP();
 
-    EXPECT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats));
+    EXPECT_TRUE(parse_disk_stats(disk_stats_path, &stats));
 
     struct disk_stats old_stats;
     memset(&old_stats, 0, sizeof(struct disk_stats));
@@ -92,7 +96,9 @@
 
 TEST(storaged_test, disk_stats) {
     struct disk_stats stats = {};
-    ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats));
+    auto disk_stats_path = get_disk_stats_path();
+    if (disk_stats_path == nullptr) GTEST_SKIP();
+    ASSERT_TRUE(parse_disk_stats(disk_stats_path, &stats));
 
     // every entry of stats (except io_in_flight) should all be greater than 0
     for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
@@ -103,7 +109,7 @@
     // accumulation of the increments should be the same with the overall increment
     struct disk_stats base = {}, tmp = {}, curr, acc = {}, inc[5];
     for (uint i = 0; i < 5; ++i) {
-        ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &curr));
+        ASSERT_TRUE(parse_disk_stats(disk_stats_path, &curr));
         if (i == 0) {
             base = curr;
             tmp = curr;
@@ -235,9 +241,7 @@
 }
 
 TEST(storaged_test, disk_stats_monitor) {
-    using android::hardware::health::V2_0::get_health_service;
-
-    auto healthService = get_health_service();
+    auto [healthService, hidlHealth] = HealthServicePair::get();
 
     // asserting that there is one file for diskstats
     ASSERT_TRUE(healthService != nullptr || access(MMC_DISK_STATS_PATH, R_OK) >= 0 ||
@@ -246,6 +250,13 @@
     // testing if detect() will return the right value
     disk_stats_monitor dsm_detect{healthService};
     ASSERT_TRUE(dsm_detect.enabled());
+
+    // Even if enabled(), healthService may not support disk stats. Check if it is supported.
+    std::vector<aidl::android::hardware::health::DiskStats> halStats;
+    if (healthService->getDiskStats(&halStats).getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+        GTEST_SKIP();
+    }
+
     // feed monitor with constant perf data for io perf baseline
     // using constant perf is reasonable since the functionality of stream_stats
     // has already been tested
diff --git a/trusty/Android.bp b/trusty/Android.bp
index 38c6204..e733839 100644
--- a/trusty/Android.bp
+++ b/trusty/Android.bp
@@ -14,29 +14,5 @@
 // limitations under the License.
 
 package {
-    default_applicable_licenses: ["system_core_trusty_license"],
-}
-
-// Added automatically by a large-scale-change that took the approach of
-// 'apply every license found to every target'. While this makes sure we respect
-// every license restriction, it may not be entirely correct.
-//
-// e.g. GPL in an MIT project might only apply to the contrib/ directory.
-//
-// Please consider splitting the single license below into multiple licenses,
-// taking care not to lose any license_kind information, and overriding the
-// default license using the 'licenses: [...]' property on targets as needed.
-//
-// For unused files, consider creating a 'fileGroup' with "//visibility:private"
-// to attach the license to, and including a comment whether the files may be
-// used in the current project.
-// See: http://go/android-license-faq
-license {
-    name: "system_core_trusty_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-        "SPDX-license-identifier-MIT",
-    ],
-    // large-scale-change unable to identify any license_text files
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index 99d9e56..0e916ef 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -105,8 +105,10 @@
         "keymint/TrustySharedSecret.cpp",
         "keymint/service.cpp",
     ],
+    defaults: [
+        "keymint_use_latest_hal_aidl_ndk_shared",
+    ],
     shared_libs: [
-        "android.hardware.security.keymint-V1-ndk",
         "android.hardware.security.secureclock-V1-ndk",
         "android.hardware.security.sharedsecret-V1-ndk",
         "lib_android_keymaster_keymint_utils",
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index 21ea7ae..d40a59e 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -31,3 +31,6 @@
 	ro.hardware.keystore_desede=true \
 	ro.hardware.keystore=trusty \
 	ro.hardware.gatekeeper=trusty
+
+PRODUCT_COPY_FILES += \
+	frameworks/native/data/etc/android.hardware.keystore.app_attest_key.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.keystore.app_attest_key.xml
diff --git a/trusty/utils/rpmb_dev/Android.bp b/trusty/utils/rpmb_dev/Android.bp
index c5853ef..a270087 100644
--- a/trusty/utils/rpmb_dev/Android.bp
+++ b/trusty/utils/rpmb_dev/Android.bp
@@ -12,13 +12,7 @@
 // limitations under the License.
 
 package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_core_trusty_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    default_applicable_licenses: ["system_core_trusty_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 cc_binary {
diff --git a/trusty/utils/rpmb_dev/rpmb_dev.c b/trusty/utils/rpmb_dev/rpmb_dev.c
index 2025621..0a9e6a1 100644
--- a/trusty/utils/rpmb_dev/rpmb_dev.c
+++ b/trusty/utils/rpmb_dev/rpmb_dev.c
@@ -1,25 +1,17 @@
 /*
  * Copyright (C) 2018 The Android Open Source Project
  *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
+ * 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
  *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #define LOG_TAG "rpmb_mock"