Merge "Configure Trusty KeyMint devices to use attest_keys."
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/UnwinderMock.h b/debuggerd/libdebuggerd/test/UnwinderMock.h
index 8f84346..1e3c559 100644
--- a/debuggerd/libdebuggerd/test/UnwinderMock.h
+++ b/debuggerd/libdebuggerd/test/UnwinderMock.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <memory>
+
 #include <unwindstack/MapInfo.h>
 #include <unwindstack/Maps.h>
 #include <unwindstack/Unwinder.h>
@@ -31,7 +33,7 @@
   }
 
   void MockSetBuildID(uint64_t offset, const std::string& build_id) {
-    unwindstack::MapInfo* map_info = GetMaps()->Find(offset);
+    std::shared_ptr<unwindstack::MapInfo> map_info = GetMaps()->Find(offset);
     if (map_info != nullptr) {
       map_info->SetBuildID(std::string(build_id));
     }
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 534d7be..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.
-    unwindstack::MapInfo* 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);
-    unwindstack::MapInfo* 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) {
-      unwindstack::MapInfo* 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.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 6c380a1..b1c4ef3 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -103,7 +103,7 @@
     // 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.
-    unwindstack::MapInfo* map_info = maps->Find(sp);
+    std::shared_ptr<unwindstack::MapInfo> 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)) {
@@ -226,7 +226,7 @@
       cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
     }
   } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
-    unwindstack::MapInfo* map_info = maps->Find(fault_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 {
@@ -342,8 +342,8 @@
 
   f->set_file_map_offset(frame.map_elf_start_offset);
 
-  unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
-  if (map_info) {
+  auto map_info = maps->Find(frame.map_start);
+  if (map_info.get() != nullptr) {
     f->set_build_id(map_info->GetPrintableBuildID());
   }
 }
@@ -370,7 +370,7 @@
           MemoryDump dump;
 
           dump.set_register_name(name);
-          unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
+          std::shared_ptr<unwindstack::MapInfo> map_info = maps->Find(untag_address(value));
           if (map_info) {
             dump.set_mapping_name(map_info->name());
           }
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/fastboot/Android.bp b/fastboot/Android.bp
index 2c70778..339f392 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -408,3 +408,9 @@
         ":fastboot_test_vendor_boot_v4_with_frag"
     ],
 }
+
+cc_library_headers {
+    name: "fastboot_headers",
+    host_supported: true,
+    export_include_dirs: ["."],
+}
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index 0e918a3..322fe5c 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -18,10 +18,10 @@
 # Package fastboot-related executables.
 #
 
-my_dist_files := $(SOONG_HOST_OUT_EXECUTABLES)/mke2fs
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/e2fsdroid
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/make_f2fs
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/make_f2fs_casefold
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/sload_f2fs
+my_dist_files := $(HOST_OUT_EXECUTABLES)/mke2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/e2fsdroid
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold
+my_dist_files += $(HOST_OUT_EXECUTABLES)/sload_f2fs
 $(call dist-for-goals,dist_files sdk win_sdk,$(my_dist_files))
 my_dist_files :=
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 0a72812..4042531 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -725,7 +725,7 @@
             return false;
         }
 
-        if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) {
+        if (!OpenPartition(device_, partition_name_, &handle_, O_RDONLY)) {
             ret_ = device_->WriteFail(
                     android::base::StringPrintf("Cannot open %s", partition_name_.c_str()));
             return false;
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 9b5d2cd..3f9bcdc 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -16,6 +16,7 @@
 #include "flashing.h"
 
 #include <fcntl.h>
+#include <string.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -77,9 +78,20 @@
 
 int FlashRawDataChunk(int fd, const char* data, size_t len) {
     size_t ret = 0;
+    const size_t max_write_size = 1048576;
+    void* aligned_buffer;
+
+    if (posix_memalign(&aligned_buffer, 4096, max_write_size)) {
+        PLOG(ERROR) << "Failed to allocate write buffer";
+        return -ENOMEM;
+    }
+
+    auto aligned_buffer_unique_ptr = std::unique_ptr<void, decltype(&free)>{aligned_buffer, free};
+
     while (ret < len) {
-        int this_len = std::min(static_cast<size_t>(1048576UL * 8), len - ret);
-        int this_ret = write(fd, data, this_len);
+        int this_len = std::min(max_write_size, len - ret);
+        memcpy(aligned_buffer_unique_ptr.get(), data, this_len);
+        int this_ret = write(fd, aligned_buffer_unique_ptr.get(), this_len);
         if (this_ret < 0) {
             PLOG(ERROR) << "Failed to flash data of len " << len;
             return -1;
@@ -147,7 +159,7 @@
 
 int Flash(FastbootDevice* device, const std::string& partition_name) {
     PartitionHandle handle;
-    if (!OpenPartition(device, partition_name, &handle)) {
+    if (!OpenPartition(device, partition_name, &handle, O_WRONLY | O_DIRECT)) {
         return -ENOENT;
     }
 
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 07ad902..97b5ad4 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -78,7 +78,7 @@
 }  // namespace
 
 bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
-                   bool read) {
+                   int flags) {
     // We prioritize logical partitions over physical ones, and do this
     // consistently for other partition operations (like getvar:partition-size).
     if (LogicalPartitionExists(device, name)) {
@@ -90,7 +90,6 @@
         return false;
     }
 
-    int flags = (read ? O_RDONLY : O_WRONLY);
     flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
     unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
     if (fd < 0) {
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index c2646d7..1d81b7a 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -76,9 +76,11 @@
 bool LogicalPartitionExists(FastbootDevice* device, const std::string& name,
                             bool* is_zero_length = nullptr);
 
-// If read, partition is readonly. Else it is write only.
+// Partition is O_WRONLY by default, caller should pass O_RDONLY for reading.
+// Caller may pass additional flags if needed. (O_EXCL | O_CLOEXEC | O_BINARY)
+// will be logically ORed internally.
 bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
-                   bool read = false);
+                   int flags = O_WRONLY);
 
 bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
 std::vector<std::string> ListPartitions(FastbootDevice* device);
diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp
new file mode 100644
index 0000000..fcd3bd6
--- /dev/null
+++ b/fastboot/fuzzer/Android.bp
@@ -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.
+ *
+ */
+
+cc_fuzz {
+    name: "fastboot_fuzzer",
+    host_supported: true,
+    device_supported: false,
+    srcs: [
+        "fastboot_fuzzer.cpp",
+        "socket_mock_fuzz.cpp",
+    ],
+    header_libs: [
+        "bootimg_headers",
+        "fastboot_headers",
+    ],
+    static_libs: [
+        "libext4_utils",
+        "libcrypto",
+        "libfastboot",
+        "libbuildversion",
+        "libbase",
+        "libziparchive",
+        "libsparse",
+        "libutils",
+        "liblog",
+        "libz",
+        "libdiagnose_usb",
+        "libbase",
+        "libcutils",
+        "libgtest",
+        "libgtest_main",
+        "libbase",
+        "libadb_host",
+        "liblp",
+        "liblog",
+    ],
+    fuzz_config: {
+        cc: [
+            "android-media-fuzzing-reports@google.com",
+        ],
+        componentid: 533764,
+    },
+}
diff --git a/fastboot/fuzzer/README.md b/fastboot/fuzzer/README.md
new file mode 100644
index 0000000..10b06ea
--- /dev/null
+++ b/fastboot/fuzzer/README.md
@@ -0,0 +1,51 @@
+# Fuzzer for libfastboot
+
+## Plugin Design Considerations
+The fuzzer plugin for libfastboot is designed based on the understanding of the
+source code and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+libfastboot supports the following parameters:
+1. Year (parameter name: `year`)
+2. Month (parameter name: `month`)
+3. Day (parameter name: `day`)
+4. Version (parameter name: `version`)
+5. Fs Option (parameter name: `fsOption`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `year` | `2000` to `2127` | Value obtained from FuzzedDataProvider|
+| `month` | `1` to `12` | Value obtained from FuzzedDataProvider|
+| `day` | `1` to `31` | Value obtained from FuzzedDataProvider|
+| `version` | `0` to `127` | Value obtained from FuzzedDataProvider|
+| `fsOption` | 0. `casefold` 1. `projid` 2. `compress` | Value obtained from FuzzedDataProvider|
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the module.
+This ensures that the plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesnt `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build fastboot_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+  $ mm -j$(nproc) fastboot_fuzzer_fuzzer
+```
+#### Steps to run
+To run on host
+```
+  $ $ANDROID_HOST_OUT/fuzz/${TARGET_ARCH}/fastboot_fuzzer/fastboot_fuzzer CORPUS_DIR
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/fastboot/fuzzer/fastboot_fuzzer.cpp b/fastboot/fuzzer/fastboot_fuzzer.cpp
new file mode 100644
index 0000000..60940fe
--- /dev/null
+++ b/fastboot/fuzzer/fastboot_fuzzer.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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 <android-base/file.h>
+#include "fastboot.h"
+#include "socket.h"
+#include "socket_mock_fuzz.h"
+#include "tcp.h"
+#include "udp.h"
+#include "vendor_boot_img_utils.h"
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+using namespace std;
+
+const size_t kYearMin = 2000;
+const size_t kYearMax = 2127;
+const size_t kMonthMin = 1;
+const size_t kMonthMax = 12;
+const size_t kDayMin = 1;
+const size_t kDayMax = 31;
+const size_t kVersionMin = 0;
+const size_t kVersionMax = 127;
+const size_t kMaxStringSize = 100;
+const size_t kMinTimeout = 10;
+const size_t kMaxTimeout = 3000;
+const uint16_t kValidUdpPacketSize = 512;
+const uint16_t kMinUdpPackets = 1;
+const uint16_t kMaxUdpPackets = 10;
+
+const string kValidTcpHandshakeString = "FB01";
+const string kInvalidTcpHandshakeString = "FB00";
+const string kValidRamdiskName = "default";
+const string kVendorBootFile = "/tmp/vendorBootFile";
+const string kRamdiskFile = "/tmp/ramdiskFile";
+const char* kFsOptionsArray[] = {"casefold", "projid", "compress"};
+
+class FastbootFuzzer {
+  public:
+    void Process(const uint8_t* data, size_t size);
+
+  private:
+    void InvokeParseApi();
+    void InvokeSocket();
+    void InvokeTcp();
+    void InvokeUdp();
+    void InvokeVendorBootImgUtils(const uint8_t* data, size_t size);
+    bool MakeConnectedSockets(Socket::Protocol protocol, unique_ptr<Socket>* server,
+                              unique_ptr<Socket>* client, const string& hostname);
+    unique_ptr<FuzzedDataProvider> fdp_ = nullptr;
+};
+
+void FastbootFuzzer::InvokeParseApi() {
+    boot_img_hdr_v1 hdr = {};
+    FastBootTool fastBoot;
+
+    int32_t year = fdp_->ConsumeIntegralInRange<int32_t>(kYearMin, kYearMax);
+    int32_t month = fdp_->ConsumeIntegralInRange<int32_t>(kMonthMin, kMonthMax);
+    int32_t day = fdp_->ConsumeIntegralInRange<int32_t>(kDayMin, kDayMax);
+    string date = to_string(year) + "-" + to_string(month) + "-" + to_string(day);
+    fastBoot.ParseOsPatchLevel(&hdr, date.c_str());
+
+    int32_t major = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+    int32_t minor = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+    int32_t patch = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+    string version = to_string(major) + "." + to_string(minor) + "." + to_string(patch);
+    fastBoot.ParseOsVersion(&hdr, version.c_str());
+
+    fastBoot.ParseFsOption(fdp_->PickValueInArray(kFsOptionsArray));
+}
+
+bool FastbootFuzzer::MakeConnectedSockets(Socket::Protocol protocol, unique_ptr<Socket>* server,
+                                          unique_ptr<Socket>* client,
+                                          const string& hostname = "localhost") {
+    *server = Socket::NewServer(protocol, 0);
+    if (*server == nullptr) {
+        return false;
+    }
+    *client = Socket::NewClient(protocol, hostname, (*server)->GetLocalPort(), nullptr);
+    if (*client == nullptr) {
+        return false;
+    }
+    if (protocol == Socket::Protocol::kTcp) {
+        *server = (*server)->Accept();
+        if (*server == nullptr) {
+            return false;
+        }
+    }
+    return true;
+}
+
+void FastbootFuzzer::InvokeSocket() {
+    unique_ptr<Socket> server, client;
+
+    for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) {
+        if (MakeConnectedSockets(protocol, &server, &client)) {
+            string message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+            client->Send(message.c_str(), message.length());
+            string received(message.length(), '\0');
+            if (fdp_->ConsumeBool()) {
+                client->Close();
+            }
+            if (fdp_->ConsumeBool()) {
+                server->Close();
+            }
+            server->ReceiveAll(&received[0], received.length(),
+                               /* timeout_ms */
+                               fdp_->ConsumeIntegralInRange<size_t>(kMinTimeout, kMaxTimeout));
+            server->Close();
+            client->Close();
+        }
+    }
+}
+
+void FastbootFuzzer::InvokeTcp() {
+    /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */
+    SocketMockFuzz* tcp_mock = new SocketMockFuzz;
+    tcp_mock->ExpectSend(fdp_->ConsumeBool() ? kValidTcpHandshakeString
+                                             : kInvalidTcpHandshakeString);
+    tcp_mock->AddReceive(fdp_->ConsumeBool() ? kValidTcpHandshakeString
+                                             : kInvalidTcpHandshakeString);
+
+    string error;
+    unique_ptr<Transport> transport = tcp::internal::Connect(unique_ptr<Socket>(tcp_mock), &error);
+
+    if (transport.get()) {
+        string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+        if (fdp_->ConsumeBool()) {
+            tcp_mock->ExpectSend(write_message);
+        } else {
+            tcp_mock->ExpectSendFailure(write_message);
+        }
+        string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+        if (fdp_->ConsumeBool()) {
+            tcp_mock->AddReceive(read_message);
+        } else {
+            tcp_mock->AddReceiveFailure();
+        }
+
+        transport->Write(write_message.data(), write_message.length());
+
+        string buffer(read_message.length(), '\0');
+        transport->Read(&buffer[0], buffer.length());
+
+        transport->Close();
+    }
+}
+
+static string PacketValue(uint16_t value) {
+    return string{static_cast<char>(value >> 8), static_cast<char>(value)};
+}
+
+static string ErrorPacket(uint16_t sequence, const string& message = "",
+                          char flags = udp::internal::kFlagNone) {
+    return string{udp::internal::kIdError, flags} + PacketValue(sequence) + message;
+}
+
+static string InitPacket(uint16_t sequence, uint16_t version, uint16_t max_packet_size) {
+    return string{udp::internal::kIdInitialization, udp::internal::kFlagNone} +
+           PacketValue(sequence) + PacketValue(version) + PacketValue(max_packet_size);
+}
+
+static string QueryPacket(uint16_t sequence, uint16_t new_sequence) {
+    return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence) +
+           PacketValue(new_sequence);
+}
+
+static string QueryPacket(uint16_t sequence) {
+    return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence);
+}
+
+static string FastbootPacket(uint16_t sequence, const string& data = "",
+                             char flags = udp::internal::kFlagNone) {
+    return string{udp::internal::kIdFastboot, flags} + PacketValue(sequence) + data;
+}
+
+void FastbootFuzzer::InvokeUdp() {
+    /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */
+    SocketMockFuzz* udp_mock = new SocketMockFuzz;
+    uint16_t starting_sequence = fdp_->ConsumeIntegral<uint16_t>();
+    int32_t device_max_packet_size = fdp_->ConsumeBool() ? kValidUdpPacketSize
+                                                         : fdp_->ConsumeIntegralInRange<uint16_t>(
+                                                                   0, kValidUdpPacketSize - 1);
+    udp_mock->ExpectSend(QueryPacket(0));
+    udp_mock->AddReceive(QueryPacket(0, starting_sequence));
+    udp_mock->ExpectSend(InitPacket(starting_sequence, udp::internal::kProtocolVersion,
+                                    udp::internal::kHostMaxPacketSize));
+    udp_mock->AddReceive(
+            InitPacket(starting_sequence, udp::internal::kProtocolVersion, device_max_packet_size));
+
+    string error;
+    unique_ptr<Transport> transport = udp::internal::Connect(unique_ptr<Socket>(udp_mock), &error);
+    bool is_transport_initialized = transport != nullptr && error.empty();
+
+    if (is_transport_initialized) {
+        uint16_t num_packets =
+                fdp_->ConsumeIntegralInRange<uint16_t>(kMinUdpPackets, kMaxUdpPackets);
+
+        for (uint16_t i = 0; i < num_packets; ++i) {
+            string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+            string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+            if (fdp_->ConsumeBool()) {
+                udp_mock->ExpectSend(FastbootPacket(i, write_message));
+            } else {
+                udp_mock->ExpectSend(ErrorPacket(i, write_message));
+            }
+
+            if (fdp_->ConsumeBool()) {
+                udp_mock->AddReceive(FastbootPacket(i, read_message));
+            } else {
+                udp_mock->AddReceive(ErrorPacket(i, read_message));
+            }
+            transport->Write(write_message.data(), write_message.length());
+            string buffer(read_message.length(), '\0');
+            transport->Read(&buffer[0], buffer.length());
+        }
+        transport->Close();
+    }
+}
+
+void FastbootFuzzer::InvokeVendorBootImgUtils(const uint8_t* data, size_t size) {
+    int32_t vendor_boot_fd = open(kVendorBootFile.c_str(), O_CREAT | O_RDWR, 0644);
+    if (vendor_boot_fd < 0) {
+        return;
+    }
+    int32_t ramdisk_fd = open(kRamdiskFile.c_str(), O_CREAT | O_RDWR, 0644);
+    if (ramdisk_fd < 0) {
+        return;
+    }
+    write(vendor_boot_fd, data, size);
+    write(ramdisk_fd, data, size);
+    string ramdisk_name = fdp_->ConsumeBool() ? kValidRamdiskName
+                                              : fdp_->ConsumeRandomLengthString(kMaxStringSize);
+    string content_vendor_boot_fd = {};
+    string content_ramdisk_fd = {};
+    lseek(vendor_boot_fd, 0, SEEK_SET);
+    lseek(ramdisk_fd, 0, SEEK_SET);
+    android::base::ReadFdToString(vendor_boot_fd, &content_vendor_boot_fd);
+    android::base::ReadFdToString(ramdisk_fd, &content_ramdisk_fd);
+    uint64_t vendor_boot_size =
+            fdp_->ConsumeBool() ? content_vendor_boot_fd.size() : fdp_->ConsumeIntegral<uint64_t>();
+    uint64_t ramdisk_size =
+            fdp_->ConsumeBool() ? content_ramdisk_fd.size() : fdp_->ConsumeIntegral<uint64_t>();
+    (void)replace_vendor_ramdisk(vendor_boot_fd, vendor_boot_size, ramdisk_name, ramdisk_fd,
+                                 ramdisk_size);
+    close(vendor_boot_fd);
+    close(ramdisk_fd);
+}
+
+void FastbootFuzzer::Process(const uint8_t* data, size_t size) {
+    fdp_ = make_unique<FuzzedDataProvider>(data, size);
+    InvokeParseApi();
+    InvokeSocket();
+    InvokeTcp();
+    InvokeUdp();
+    InvokeVendorBootImgUtils(data, size);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FastbootFuzzer fastbootFuzzer;
+    fastbootFuzzer.Process(data, size);
+    return 0;
+}
diff --git a/fastboot/fuzzer/socket_mock_fuzz.cpp b/fastboot/fuzzer/socket_mock_fuzz.cpp
new file mode 100644
index 0000000..df96eb0
--- /dev/null
+++ b/fastboot/fuzzer/socket_mock_fuzz.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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 "socket_mock_fuzz.h"
+
+SocketMockFuzz::SocketMockFuzz() : Socket(INVALID_SOCKET) {}
+
+SocketMockFuzz::~SocketMockFuzz() {}
+
+bool SocketMockFuzz::Send(const void* data, size_t length) {
+    if (events_.empty()) {
+        return false;
+    }
+
+    if (events_.front().type != EventType::kSend) {
+        return false;
+    }
+
+    std::string message(reinterpret_cast<const char*>(data), length);
+    if (events_.front().message != message) {
+        return false;
+    }
+
+    bool return_value = events_.front().status;
+    events_.pop();
+    return return_value;
+}
+
+// Mock out multi-buffer send to be one large send, since that's what it should looks like from
+// the user's perspective.
+bool SocketMockFuzz::Send(std::vector<cutils_socket_buffer_t> buffers) {
+    std::string data;
+    for (const auto& buffer : buffers) {
+        data.append(reinterpret_cast<const char*>(buffer.data), buffer.length);
+    }
+    return Send(data.data(), data.size());
+}
+
+ssize_t SocketMockFuzz::Receive(void* data, size_t length, int /*timeout_ms*/) {
+    if (events_.empty()) {
+        return -1;
+    }
+
+    const Event& event = events_.front();
+    if (event.type != EventType::kReceive) {
+        return -1;
+    }
+
+    const std::string& message = event.message;
+    if (message.length() > length) {
+        return -1;
+    }
+
+    receive_timed_out_ = event.status;
+    ssize_t return_value = message.length();
+
+    // Empty message indicates failure.
+    if (message.empty()) {
+        return_value = -1;
+    } else {
+        memcpy(data, message.data(), message.length());
+    }
+
+    events_.pop();
+    return return_value;
+}
+
+int SocketMockFuzz::Close() {
+    return 0;
+}
+
+std::unique_ptr<Socket> SocketMockFuzz::Accept() {
+    if (events_.empty()) {
+        return nullptr;
+    }
+
+    if (events_.front().type != EventType::kAccept) {
+        return nullptr;
+    }
+
+    std::unique_ptr<Socket> sock = std::move(events_.front().sock);
+    events_.pop();
+    return sock;
+}
+
+void SocketMockFuzz::ExpectSend(std::string message) {
+    events_.push(Event(EventType::kSend, std::move(message), true, nullptr));
+}
+
+void SocketMockFuzz::ExpectSendFailure(std::string message) {
+    events_.push(Event(EventType::kSend, std::move(message), false, nullptr));
+}
+
+void SocketMockFuzz::AddReceive(std::string message) {
+    events_.push(Event(EventType::kReceive, std::move(message), false, nullptr));
+}
+
+void SocketMockFuzz::AddReceiveTimeout() {
+    events_.push(Event(EventType::kReceive, "", true, nullptr));
+}
+
+void SocketMockFuzz::AddReceiveFailure() {
+    events_.push(Event(EventType::kReceive, "", false, nullptr));
+}
+
+void SocketMockFuzz::AddAccept(std::unique_ptr<Socket> sock) {
+    events_.push(Event(EventType::kAccept, "", false, std::move(sock)));
+}
+
+SocketMockFuzz::Event::Event(EventType _type, std::string _message, ssize_t _status,
+                             std::unique_ptr<Socket> _sock)
+    : type(_type), message(_message), status(_status), sock(std::move(_sock)) {}
diff --git a/fastboot/fuzzer/socket_mock_fuzz.h b/fastboot/fuzzer/socket_mock_fuzz.h
new file mode 100644
index 0000000..67bd0d6
--- /dev/null
+++ b/fastboot/fuzzer/socket_mock_fuzz.h
@@ -0,0 +1,73 @@
+/*
+ * 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 <memory>
+#include <queue>
+#include <string>
+
+#include <android-base/macros.h>
+
+#include "socket.h"
+
+class SocketMockFuzz : public Socket {
+  public:
+    SocketMockFuzz();
+    ~SocketMockFuzz() override;
+
+    bool Send(const void* data, size_t length) override;
+    bool Send(std::vector<cutils_socket_buffer_t> buffers) override;
+    ssize_t Receive(void* data, size_t length, int timeout_ms) override;
+    int Close() override;
+    virtual std::unique_ptr<Socket> Accept();
+
+    // Adds an expectation for Send().
+    void ExpectSend(std::string message);
+
+    // Adds an expectation for Send() that returns false.
+    void ExpectSendFailure(std::string message);
+
+    // Adds data to provide for Receive().
+    void AddReceive(std::string message);
+
+    // Adds a Receive() timeout after which ReceiveTimedOut() will return true.
+    void AddReceiveTimeout();
+
+    // Adds a Receive() failure after which ReceiveTimedOut() will return false.
+    void AddReceiveFailure();
+
+    // Adds a Socket to return from Accept().
+    void AddAccept(std::unique_ptr<Socket> sock);
+
+  private:
+    enum class EventType { kSend, kReceive, kAccept };
+
+    struct Event {
+        Event(EventType _type, std::string _message, ssize_t _status,
+              std::unique_ptr<Socket> _sock);
+
+        EventType type;
+        std::string message;
+        bool status;  // Return value for Send() or timeout status for Receive().
+        std::unique_ptr<Socket> sock;
+    };
+
+    std::queue<Event> events_;
+
+    DISALLOW_COPY_AND_ASSIGN(SocketMockFuzz);
+};
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index cb74ae0..5872dda 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -246,28 +246,12 @@
                 "-UALLOW_ADBD_DISABLE_VERITY",
                 "-DALLOW_ADBD_DISABLE_VERITY=1",
             ],
-        },
-    },
-    required: [
-        "clean_scratch_files",
-    ],
-}
-
-cc_binary {
-    name: "clean_scratch_files",
-    defaults: ["fs_mgr_defaults"],
-    shared_libs: [
-        "libbase",
-        "libfs_mgr_binder",
-    ],
-    srcs: [
-        "clean_scratch_files.cpp",
-    ],
-    product_variables: {
-        debuggable: {
             init_rc: [
                 "clean_scratch_files.rc",
             ],
         },
     },
+    symlinks: [
+        "clean_scratch_files",
+    ],
 }
diff --git a/fs_mgr/clean_scratch_files.cpp b/fs_mgr/clean_scratch_files.cpp
deleted file mode 100644
index 42fe35a..0000000
--- a/fs_mgr/clean_scratch_files.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <fs_mgr_overlayfs.h>
-
-int main() {
-    android::fs_mgr::CleanupOldScratchFiles();
-    return 0;
-}
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 002b302..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)) {
@@ -2119,6 +2052,9 @@
         PERROR << "Cannot open " << loop_device;
         return false;
     }
+    if (!LoopControl::SetAutoClearStatus(loop_fd.get())) {
+        PERROR << "Failed set LO_FLAGS_AUTOCLEAR for " << loop_device;
+    }
     if (!LoopControl::EnableDirectIo(loop_fd.get())) {
         return false;
     }
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 9b6c3dd..d68b7e8 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -130,13 +130,15 @@
             if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) {
                 const auto arg = flag.substr(equal_sign + 1);
                 if (entry->fs_type == "f2fs" && StartsWith(flag, "reserve_root=")) {
-                    if (!ParseInt(arg, &entry->reserved_size)) {
+                    off64_t size_in_4k_blocks;
+                    if (!ParseInt(arg, &size_in_4k_blocks, static_cast<off64_t>(0),
+                                  std::numeric_limits<off64_t>::max() >> 12)) {
                         LWARNING << "Warning: reserve_root= flag malformed: " << arg;
                     } else {
-                        entry->reserved_size <<= 12;
+                        entry->reserved_size = size_in_4k_blocks << 12;
                     }
                 } else if (StartsWith(flag, "lowerdir=")) {
-                    entry->lowerdir = std::move(arg);
+                    entry->lowerdir = arg;
                 }
             }
         }
@@ -144,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;
@@ -187,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
@@ -233,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;
@@ -304,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() {
@@ -442,92 +456,6 @@
     return "";
 }
 
-bool ReadFstabFile(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;
-    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';
-        }
-
-        /* Skip any leading whitespace */
-        p = line;
-        while (isspace(*p)) {
-            p++;
-        }
-        /* ignore comments or empty lines */
-        if (*p == '#' || *p == '\0')
-            continue;
-
-        FstabEntry entry;
-
-        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);
-
-        // 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;
-        }
-
-        ParseFsMgrFlags(p, &entry);
-
-        if (entry.fs_mgr_flags.logical) {
-            entry.logical_partition_name = entry.blk_device;
-        }
-
-        fstab.emplace_back(std::move(entry));
-    }
-
-    if (fstab.empty()) {
-        LERROR << "No entries found in fstab";
-        goto err;
-    }
-
-    /* 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;
-    }
-    free(line);
-    *fstab_out = std::move(fstab);
-    return true;
-
-err:
-    free(line);
-    return false;
-}
-
 /* Extracts <device>s from the by-name symlinks specified in a fstab:
  *   /dev/block/<type>/<device>/by-name/<partition>
  *
@@ -602,6 +530,61 @@
 
 }  // namespace
 
+bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out) {
+    const int expected_fields = proc_mounts ? 4 : 5;
+
+    Fstab fstab;
+
+    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;
+        }
+
+        if (fields.size() < expected_fields) {
+            LERROR << "Error parsing fstab: expected " << expected_fields << " fields, got "
+                   << fields.size();
+            return false;
+        }
+
+        FstabEntry entry;
+        auto it = fields.begin();
+
+        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 && !ParseFsMgrFlags(std::move(*it++), &entry)) {
+            LERROR << "Error parsing fs_mgr_flags";
+            return false;
+        }
+
+        if (entry.fs_mgr_flags.logical) {
+            entry.logical_partition_name = entry.blk_device;
+        }
+
+        fstab.emplace_back(std::move(entry));
+    }
+
+    if (fstab.empty()) {
+        LERROR << "No entries found in fstab";
+        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";
+        return false;
+    }
+
+    *fstab_out = std::move(fstab);
+    return true;
+}
+
 void TransformFstabForDsu(Fstab* fstab, const std::string& dsu_slot,
                           const std::vector<std::string>& dsu_partitions) {
     static constexpr char kDsuKeysDir[] = "/avb";
@@ -698,16 +681,16 @@
 }
 
 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 (!ReadFstabFile(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;
     }
@@ -754,15 +737,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 (!ReadFstabFile(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_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 5411aca..deaf5f7 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -42,6 +42,8 @@
 #include <libavb_user/libavb_user.h>
 #include <libgsi/libgsid.h>
 
+using namespace std::literals;
+
 namespace {
 
 [[noreturn]] void usage(int exit_status) {
@@ -142,6 +144,7 @@
     BINDER_ERROR,
     CHECKPOINTING,
     GSID_ERROR,
+    CLEAN_SCRATCH_FILES,
 };
 
 static int do_remount(int argc, char* argv[]) {
@@ -163,6 +166,7 @@
             {"help", no_argument, nullptr, 'h'},
             {"reboot", no_argument, nullptr, 'R'},
             {"verbose", no_argument, nullptr, 'v'},
+            {"clean_scratch_files", no_argument, nullptr, 'C'},
             {0, 0, nullptr, 0},
     };
     for (int opt; (opt = ::getopt_long(argc, argv, "hRT:v", longopts, nullptr)) != -1;) {
@@ -183,6 +187,8 @@
             case 'v':
                 verbose = true;
                 break;
+            case 'C':
+                return CLEAN_SCRATCH_FILES;
             default:
                 LOG(ERROR) << "Bad Argument -" << char(opt);
                 usage(BADARG);
@@ -476,13 +482,24 @@
     return retval;
 }
 
+static int do_clean_scratch_files() {
+    android::fs_mgr::CleanupOldScratchFiles();
+    return 0;
+}
+
 int main(int argc, char* argv[]) {
     android::base::InitLogging(argv, MyLogger);
+    if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) {
+        return do_clean_scratch_files();
+    }
     int result = do_remount(argc, argv);
     if (result == MUST_REBOOT) {
         LOG(INFO) << "Now reboot your device for settings to take effect";
+        result = 0;
     } else if (result == REMOUNT_SUCCESS) {
         printf("remount succeeded\n");
+    } else if (result == CLEAN_SCRATCH_FILES) {
+        return do_clean_scratch_files();
     } else {
         printf("remount failed\n");
     }
diff --git a/fs_mgr/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/Android.bp b/fs_mgr/fuzz/Android.bp
new file mode 100644
index 0000000..f0afd28
--- /dev/null
+++ b/fs_mgr/fuzz/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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.
+//
+
+cc_fuzz {
+  name: "libfstab_fuzzer",
+  srcs: [
+    "fs_mgr_fstab_fuzzer.cpp",
+  ],
+  static_libs: [
+    "libfstab",
+  ],
+  shared_libs: [
+    "libbase",
+  ],
+
+  dictionary: "fstab.dict",
+  fuzz_config: {
+    cc: [
+      "yochiang@google.com",
+    ],
+  },
+}
diff --git a/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
new file mode 100644
index 0000000..6a8a191
--- /dev/null
+++ b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
@@ -0,0 +1,26 @@
+//
+// 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 <cstdio>
+
+#include <fstab/fstab.h>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    std::string make_fstab_str(reinterpret_cast<const char*>(data), size);
+    android::fs_mgr::Fstab fstab;
+    android::fs_mgr::ParseFstabFromString(make_fstab_str, /* proc_mounts = */ false, &fstab);
+    return 0;
+}
diff --git a/fs_mgr/fuzz/fstab.dict b/fs_mgr/fuzz/fstab.dict
new file mode 100644
index 0000000..84dddf7
--- /dev/null
+++ b/fs_mgr/fuzz/fstab.dict
@@ -0,0 +1,70 @@
+"#"
+"="
+","
+"f2fs"
+
+# mount flags
+"noatime"
+"noexec"
+"nosuid"
+"nodev"
+"nodiratime"
+"ro"
+"rw"
+"sync"
+"remount"
+"bind"
+"rec"
+"unbindable"
+"private"
+"slave"
+"shared"
+"defaults"
+
+# fs_mgr flags
+"wait"
+"check"
+"nonremovable"
+"recoveryonly"
+"noemulatedsd"
+"notrim"
+"verify"
+"formattable"
+"slotselect"
+"latemount"
+"nofail"
+"verifyatboot"
+"quota"
+"avb"
+"logical"
+"checkpoint=block"
+"checkpoint=fs"
+"first_stage_mount"
+"slotselect_other"
+"fsverity"
+"metadata_csum"
+"fscompress"
+"overlayfs_remove_missing_lowerdir"
+
+# fs_mgr flags that expect an argument
+"reserve_root="
+"lowerdir="
+"encryptable="
+"voldmanaged="
+"length="
+"swapprio="
+"zramsize="
+"forceencrypt="
+"fileencryption="
+"forcefdeorfbe="
+"max_comp_streams="
+"reservedsize="
+"readahead_size_kb="
+"eraseblk="
+"logicalblk="
+"avb_keys="
+"avb="
+"keydirectory="
+"metadata_encryption="
+"sysfs_path="
+"zram_backingdev_size="
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 9a4ed46..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,6 +94,9 @@
 // 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 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);
 bool ReadDefaultFstab(Fstab* fstab);
diff --git a/fs_mgr/libdm/include/libdm/loop_control.h b/fs_mgr/libdm/include/libdm/loop_control.h
index ad53c11..f519054 100644
--- a/fs_mgr/libdm/include/libdm/loop_control.h
+++ b/fs_mgr/libdm/include/libdm/loop_control.h
@@ -46,6 +46,9 @@
     // Enable Direct I/O on a loop device. This requires kernel 4.9+.
     static bool EnableDirectIo(int fd);
 
+    // Set LO_FLAGS_AUTOCLEAR on a loop device.
+    static bool SetAutoClearStatus(int fd);
+
     LoopControl(const LoopControl&) = delete;
     LoopControl& operator=(const LoopControl&) = delete;
     LoopControl& operator=(LoopControl&&) = default;
diff --git a/fs_mgr/libdm/loop_control.cpp b/fs_mgr/libdm/loop_control.cpp
index 2e40a18..32d5f38 100644
--- a/fs_mgr/libdm/loop_control.cpp
+++ b/fs_mgr/libdm/loop_control.cpp
@@ -133,6 +133,16 @@
     return true;
 }
 
+bool LoopControl::SetAutoClearStatus(int fd) {
+    struct loop_info64 info = {};
+
+    info.lo_flags |= LO_FLAGS_AUTOCLEAR;
+    if (ioctl(fd, LOOP_SET_STATUS64, &info)) {
+        return false;
+    }
+    return true;
+}
+
 LoopDevice::LoopDevice(android::base::borrowed_fd fd, const std::chrono::milliseconds& timeout_ms,
                        bool auto_close)
     : fd_(fd), owned_fd_(-1) {
diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp
index 8acb885..275388e 100644
--- a/fs_mgr/libfiemap/fiemap_writer.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -498,24 +498,6 @@
     return IsFilePinned(fd, file_path, sfs.f_type);
 }
 
-static bool CountFiemapExtents(int file_fd, const std::string& file_path, uint32_t* num_extents) {
-    struct fiemap fiemap = {};
-    fiemap.fm_start = 0;
-    fiemap.fm_length = UINT64_MAX;
-    fiemap.fm_flags = FIEMAP_FLAG_SYNC;
-    fiemap.fm_extent_count = 0;
-
-    if (ioctl(file_fd, FS_IOC_FIEMAP, &fiemap)) {
-        PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
-        return false;
-    }
-
-    if (num_extents) {
-        *num_extents = fiemap.fm_mapped_extents;
-    }
-    return true;
-}
-
 static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) {
     if (extent->fe_flags & kUnsupportedExtentFlags) {
         LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path
@@ -530,12 +512,16 @@
 }
 
 static bool FiemapToExtents(struct fiemap* fiemap, std::vector<struct fiemap_extent>* extents,
-                            uint32_t num_extents, std::string_view file_path) {
-    if (num_extents == 0) return false;
-
+                            std::string_view file_path) {
+    uint32_t num_extents = fiemap->fm_mapped_extents;
+    if (num_extents == 0) {
+        LOG(ERROR) << "File " << file_path << " has zero extent";
+        return false;
+    }
     const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1];
     if (!IsLastExtent(last_extent)) {
-        LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path;
+        LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path
+                   << " num_extents=" << num_extents << " max_extents=" << kMaxExtents;
         return false;
     }
 
@@ -577,21 +563,7 @@
 
 static bool ReadFiemap(int file_fd, const std::string& file_path,
                        std::vector<struct fiemap_extent>* extents) {
-    uint32_t num_extents;
-    if (!CountFiemapExtents(file_fd, file_path, &num_extents)) {
-        return false;
-    }
-    if (num_extents == 0) {
-        LOG(ERROR) << "File " << file_path << " has zero extents";
-        return false;
-    }
-    if (num_extents > kMaxExtents) {
-        LOG(ERROR) << "File has " << num_extents << ", maximum is " << kMaxExtents << ": "
-                   << file_path;
-        return false;
-    }
-
-    uint64_t fiemap_size = sizeof(struct fiemap) + num_extents * sizeof(struct fiemap_extent);
+    uint64_t fiemap_size = sizeof(struct fiemap) + kMaxExtents * sizeof(struct fiemap_extent);
     auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
     if (buffer == nullptr) {
         LOG(ERROR) << "Failed to allocate memory for fiemap";
@@ -603,19 +575,13 @@
     fiemap->fm_length = UINT64_MAX;
     // make sure file is synced to disk before we read the fiemap
     fiemap->fm_flags = FIEMAP_FLAG_SYNC;
-    fiemap->fm_extent_count = num_extents;
+    fiemap->fm_extent_count = kMaxExtents;
 
     if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
         PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
         return false;
     }
-    if (fiemap->fm_mapped_extents != num_extents) {
-        LOG(ERROR) << "FIEMAP returned unexpected extent count (" << num_extents
-                   << " expected, got " << fiemap->fm_mapped_extents << ") for file: " << file_path;
-        return false;
-    }
-
-    return FiemapToExtents(fiemap, extents, num_extents, file_path);
+    return FiemapToExtents(fiemap, extents, file_path);
 }
 
 static bool ReadFibmap(int file_fd, const std::string& file_path,
diff --git a/fs_mgr/libfiemap/fiemap_writer_test.cpp b/fs_mgr/libfiemap/fiemap_writer_test.cpp
index b31c78d..c65481b 100644
--- a/fs_mgr/libfiemap/fiemap_writer_test.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer_test.cpp
@@ -258,6 +258,13 @@
     EXPECT_EQ(memcmp(actual.data(), data.data(), data.size()), 0);
 }
 
+TEST_F(FiemapWriterTest, CheckEmptyFile) {
+    // Can't get any fiemap_extent out of a zero-sized file.
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 0);
+    EXPECT_EQ(fptr, nullptr);
+    EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+}
+
 TEST_F(SplitFiemapTest, Create) {
     auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
     ASSERT_NE(ptr, nullptr);
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index dcbbc54..2c14c8a 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -696,7 +696,12 @@
     bool ok = true;
     for (const auto& partition : metadata->partitions) {
         if (partition.attributes & LP_PARTITION_ATTR_DISABLED) {
-            ok &= DeleteBackingImage(GetPartitionName(partition));
+            const auto name = GetPartitionName(partition);
+            if (!DeleteBackingImage(name)) {
+                ok = false;
+            } else {
+                LOG(INFO) << "Removed disabled partition image: " << name;
+            }
         }
     }
     return ok;
diff --git a/fs_mgr/libsnapshot/OWNERS b/fs_mgr/libsnapshot/OWNERS
index 801c446..37319fe 100644
--- a/fs_mgr/libsnapshot/OWNERS
+++ b/fs_mgr/libsnapshot/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 30545
 balsini@google.com
 dvander@google.com
 elsk@google.com
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 5306b28..20030b9 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -34,11 +34,12 @@
 namespace android {
 namespace snapshot {
 
-CowReader::CowReader()
+CowReader::CowReader(ReaderFlags reader_flag)
     : fd_(-1),
       header_(),
       fd_size_(0),
-      merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()) {}
+      merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()),
+      reader_flag_(reader_flag) {}
 
 static void SHA256(const void*, size_t, uint8_t[]) {
 #if 0
@@ -415,7 +416,7 @@
 //==============================================================
 bool CowReader::PrepMergeOps() {
     auto merge_op_blocks = std::make_shared<std::vector<uint32_t>>();
-    std::set<int, std::greater<int>> other_ops;
+    std::vector<int> other_ops;
     auto seq_ops_set = std::unordered_set<uint32_t>();
     auto block_map = std::make_shared<std::unordered_map<uint32_t, int>>();
     size_t num_seqs = 0;
@@ -446,7 +447,7 @@
         if (!has_seq_ops_ && IsOrderedOp(current_op)) {
             merge_op_blocks->emplace_back(current_op.new_block);
         } else if (seq_ops_set.count(current_op.new_block) == 0) {
-            other_ops.insert(current_op.new_block);
+            other_ops.push_back(current_op.new_block);
         }
         block_map->insert({current_op.new_block, i});
     }
@@ -462,6 +463,18 @@
     } else {
         num_ordered_ops_to_merge_ = 0;
     }
+
+    // Sort the vector in increasing order if merging in user-space as
+    // we can batch merge them when iterating from forward.
+    //
+    // dm-snapshot-merge requires decreasing order as we iterate the blocks
+    // in reverse order.
+    if (reader_flag_ == ReaderFlags::USERSPACE_MERGE) {
+        std::sort(other_ops.begin(), other_ops.end());
+    } else {
+        std::sort(other_ops.begin(), other_ops.end(), std::greater<int>());
+    }
+
     merge_op_blocks->reserve(merge_op_blocks->size() + other_ops.size());
     for (auto block : other_ops) {
         merge_op_blocks->emplace_back(block);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index c15682a..9f4ddbb 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -26,8 +26,8 @@
 
 static constexpr uint32_t kCowVersionManifest = 2;
 
-static constexpr uint32_t BLOCK_SZ = 4096;
-static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+static constexpr size_t BLOCK_SZ = 4096;
+static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
 
 // This header appears as the first sequence of bytes in the COW. All fields
 // in the layout are little-endian encoded. The on-disk layout is:
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 143f73c..d5b4335 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -104,7 +104,12 @@
 
 class CowReader final : public ICowReader {
   public:
-    CowReader();
+    enum class ReaderFlags {
+        DEFAULT = 0,
+        USERSPACE_MERGE = 1,
+    };
+
+    CowReader(ReaderFlags reader_flag = ReaderFlags::DEFAULT);
     ~CowReader() { owned_fd_ = {}; }
 
     // Parse the COW, optionally, up to the given label. If no label is
@@ -145,6 +150,8 @@
     // Creates a clone of the current CowReader without the file handlers
     std::unique_ptr<CowReader> CloneCowReader();
 
+    void UpdateMergeOpsCompleted(int num_merge_ops) { header_.num_merge_ops += num_merge_ops; }
+
   private:
     bool ParseOps(std::optional<uint64_t> label);
     bool PrepMergeOps();
@@ -164,6 +171,7 @@
     uint64_t num_ordered_ops_to_merge_;
     bool has_seq_ops_;
     std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+    ReaderFlags reader_flag_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 55f4ed7..a49b026 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -408,6 +408,7 @@
     FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
     FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
+    FRIEND_TEST(SnapshotUpdateTest, QueryStatusError);
     FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
     FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate);
     friend class SnapshotTest;
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 1f57bbc..c3b40dc 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -100,6 +100,9 @@
         return IDeviceInfo::OpenImageManager("ota/test");
     }
     android::dm::IDeviceMapper& GetDeviceMapper() override {
+        if (dm_) {
+            return *dm_;
+        }
         return android::dm::DeviceMapper::Instance();
     }
 
@@ -111,6 +114,8 @@
     }
     void set_recovery(bool value) { recovery_ = value; }
     void set_first_stage_init(bool value) { first_stage_init_ = value; }
+    void set_dm(android::dm::IDeviceMapper* dm) { dm_ = dm; }
+
     MergeStatus merge_status() const { return merge_status_; }
 
   private:
@@ -120,6 +125,45 @@
     bool recovery_ = false;
     bool first_stage_init_ = false;
     std::unordered_set<uint32_t> unbootable_slots_;
+    android::dm::IDeviceMapper* dm_ = nullptr;
+};
+
+class DeviceMapperWrapper : public android::dm::IDeviceMapper {
+    using DmDeviceState = android::dm::DmDeviceState;
+    using DmTable = android::dm::DmTable;
+
+  public:
+    DeviceMapperWrapper() : impl_(android::dm::DeviceMapper::Instance()) {}
+    explicit DeviceMapperWrapper(android::dm::IDeviceMapper& impl) : impl_(impl) {}
+
+    virtual bool CreateDevice(const std::string& name, const DmTable& table, std::string* path,
+                              const std::chrono::milliseconds& timeout_ms) override {
+        return impl_.CreateDevice(name, table, path, timeout_ms);
+    }
+    virtual DmDeviceState GetState(const std::string& name) const override {
+        return impl_.GetState(name);
+    }
+    virtual bool LoadTableAndActivate(const std::string& name, const DmTable& table) {
+        return impl_.LoadTableAndActivate(name, table);
+    }
+    virtual bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) {
+        return impl_.GetTableInfo(name, table);
+    }
+    virtual bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) {
+        return impl_.GetTableStatus(name, table);
+    }
+    virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) {
+        return impl_.GetDmDevicePathByName(name, path);
+    }
+    virtual bool GetDeviceString(const std::string& name, std::string* dev) {
+        return impl_.GetDeviceString(name, dev);
+    }
+    virtual bool DeleteDeviceIfExists(const std::string& name) {
+        return impl_.DeleteDeviceIfExists(name);
+    }
+
+  private:
+    android::dm::IDeviceMapper& impl_;
 };
 
 class SnapshotTestPropertyFetcher : public android::fs_mgr::testing::MockPropertyFetcher {
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_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 43c7fe2..d78ba0a 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -56,6 +56,7 @@
 using android::base::unique_fd;
 using android::dm::DeviceMapper;
 using android::dm::DmDeviceState;
+using android::dm::IDeviceMapper;
 using android::fiemap::FiemapStatus;
 using android::fiemap::IImageManager;
 using android::fs_mgr::BlockDeviceInfo;
@@ -911,6 +912,11 @@
             ASSERT_TRUE(hash.has_value());
             hashes_[name] = *hash;
         }
+
+        // OTA client blindly unmaps all partitions that are possibly mapped.
+        for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+            ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+        }
     }
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
@@ -925,6 +931,14 @@
         MountMetadata();
         for (const auto& suffix : {"_a", "_b"}) {
             test_device->set_slot_suffix(suffix);
+
+            // Cheat our way out of merge failed states.
+            if (sm->ProcessUpdateState() == UpdateState::MergeFailed) {
+                ASSERT_TRUE(AcquireLock());
+                ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None));
+                lock_ = {};
+            }
+
             EXPECT_TRUE(sm->CancelUpdate()) << suffix;
         }
         EXPECT_TRUE(UnmapAll());
@@ -1097,11 +1111,6 @@
 // Also test UnmapUpdateSnapshot unmaps everything.
 // Also test first stage mount and merge after this.
 TEST_F(SnapshotUpdateTest, FullUpdateFlow) {
-    // OTA client blindly unmaps all partitions that are possibly mapped.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
-    }
-
     // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
     // fit in super, but not |prd|.
     constexpr uint64_t partition_size = 3788_KiB;
@@ -1189,11 +1198,6 @@
         GTEST_SKIP() << "Compression-only test";
     }
 
-    // OTA client blindly unmaps all partitions that are possibly mapped.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
-    }
-
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
@@ -1239,11 +1243,6 @@
         GTEST_SKIP() << "Skipping Virtual A/B Compression test";
     }
 
-    // OTA client blindly unmaps all partitions that are possibly mapped.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
-    }
-
     auto old_sys_size = GetSize(sys_);
     auto old_prd_size = GetSize(prd_);
 
@@ -1630,11 +1629,6 @@
     ASSERT_NE(nullptr, metadata);
     ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
 
-    // OTA client blindly unmaps all partitions that are possibly mapped.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
-    }
-
     // Add operations for sys. The whole device is written.
     AddOperation(sys_);
 
@@ -2074,11 +2068,6 @@
 }
 
 TEST_F(SnapshotUpdateTest, AddPartition) {
-    // OTA client blindly unmaps all partitions that are possibly mapped.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
-    }
-
     group_->add_partition_names("dlkm");
 
     auto dlkm = manifest_.add_partitions();
@@ -2249,6 +2238,60 @@
     ASSERT_TRUE(sm->BeginUpdate());
 }
 
+TEST_F(SnapshotUpdateTest, QueryStatusError) {
+    // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
+    // fit in super, but not |prd|.
+    constexpr uint64_t partition_size = 3788_KiB;
+    SetSize(sys_, partition_size);
+
+    AddOperationForPartitions({sys_});
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+    ASSERT_TRUE(UnmapAll());
+
+    class DmStatusFailure final : public DeviceMapperWrapper {
+      public:
+        bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) override {
+            if (!DeviceMapperWrapper::GetTableStatus(name, table)) {
+                return false;
+            }
+            if (name == "sys_b" && !table->empty()) {
+                auto& info = table->at(0);
+                if (DeviceMapper::GetTargetType(info.spec) == "snapshot-merge") {
+                    info.data = "Merge failed";
+                }
+            }
+            return true;
+        }
+    };
+    DmStatusFailure wrapper;
+
+    // After reboot, init does first stage mount.
+    auto info = new TestDeviceInfo(fake_super, "_b");
+    info->set_dm(&wrapper);
+
+    auto init = NewManagerForFirstStageMount(info);
+    ASSERT_NE(init, nullptr);
+
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    // Initiate the merge and wait for it to be completed.
+    ASSERT_TRUE(init->InitiateMerge());
+    ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState());
+
+    // Simulate a reboot that tries the merge again, with the non-failing dm.
+    ASSERT_TRUE(UnmapAll());
+    init = NewManagerForFirstStageMount("_b");
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+}
+
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
                              public WithParamInterface<std::tuple<uint32_t, bool>> {
   public:
@@ -2265,11 +2308,6 @@
 };
 
 TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) {
-    // OTA client blindly unmaps all partitions that are possibly mapped.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
-    }
-
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 6bd5323..c9b0512 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -56,11 +56,18 @@
         "fs_mgr_defaults",
     ],
     srcs: [
-        "snapuserd_server.cpp",
-        "snapuserd.cpp",
+        "dm-snapshot-merge/snapuserd_server.cpp",
+        "dm-snapshot-merge/snapuserd.cpp",
+        "dm-snapshot-merge/snapuserd_worker.cpp",
+        "dm-snapshot-merge/snapuserd_readahead.cpp",
         "snapuserd_daemon.cpp",
-        "snapuserd_worker.cpp",
-        "snapuserd_readahead.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",
     ],
 
     cflags: [
@@ -101,9 +108,10 @@
         "fs_mgr_defaults",
     ],
     srcs: [
-        "cow_snapuserd_test.cpp",
-        "snapuserd.cpp",
-        "snapuserd_worker.cpp",
+        "dm-snapshot-merge/cow_snapuserd_test.cpp",
+        "dm-snapshot-merge/snapuserd.cpp",
+        "dm-snapshot-merge/snapuserd_worker.cpp",
+	"snapuserd_buffer.cpp",
     ],
     cflags: [
         "-Wall",
diff --git a/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
similarity index 99%
rename from fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index bff0a50..b86a802 100644
--- a/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
@@ -33,6 +33,7 @@
 #include <libdm/dm.h>
 #include <libdm/loop_control.h>
 #include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
 #include <snapuserd/snapuserd_client.h>
 #include <storage_literals/storage_literals.h>
 
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
similarity index 100%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
similarity index 91%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd.h
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
index 6388a83..47b9b22 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
@@ -42,6 +42,7 @@
 #include <libdm/dm.h>
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
 #include <snapuserd/snapuserd_kernel.h>
 
 namespace android {
@@ -89,39 +90,6 @@
     READ_AHEAD_FAILURE,
 };
 
-class BufferSink : public IByteSink {
-  public:
-    void Initialize(size_t size);
-    void* GetBufPtr() { return buffer_.get(); }
-    void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
-    void* GetPayloadBuffer(size_t size);
-    void* GetBuffer(size_t requested, size_t* actual) override;
-    void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
-    struct dm_user_header* GetHeaderPtr();
-    bool ReturnData(void*, size_t) override { return true; }
-    void ResetBufferOffset() { buffer_offset_ = 0; }
-    void* GetPayloadBufPtr();
-
-  private:
-    std::unique_ptr<uint8_t[]> buffer_;
-    loff_t buffer_offset_;
-    size_t buffer_size_;
-};
-
-class XorSink : public IByteSink {
-  public:
-    void Initialize(BufferSink* sink, size_t size);
-    void Reset();
-    void* GetBuffer(size_t requested, size_t* actual) override;
-    bool ReturnData(void* buffer, size_t len) override;
-
-  private:
-    BufferSink* bufsink_;
-    std::unique_ptr<uint8_t[]> buffer_;
-    size_t buffer_size_;
-    size_t returned_;
-};
-
 class Snapuserd;
 
 class ReadAheadThread {
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
similarity index 100%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
similarity index 99%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
index 2f87557..9ddc963 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
@@ -31,7 +31,7 @@
 #include <android-base/scopeguard.h>
 #include <fs_mgr/file_wait.h>
 #include <snapuserd/snapuserd_client.h>
-#include "snapuserd.h"
+
 #include "snapuserd_server.h"
 
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h
similarity index 100%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_server.h
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
similarity index 93%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 5d184ad..0e9f0f1 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -32,78 +32,6 @@
 #define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
 #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
 
-void BufferSink::Initialize(size_t size) {
-    buffer_size_ = size;
-    buffer_offset_ = 0;
-    buffer_ = std::make_unique<uint8_t[]>(size);
-}
-
-void* BufferSink::GetPayloadBuffer(size_t size) {
-    if ((buffer_size_ - buffer_offset_) < size) return nullptr;
-
-    char* buffer = reinterpret_cast<char*>(GetBufPtr());
-    struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
-    return (char*)msg->payload.buf + buffer_offset_;
-}
-
-void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
-    void* buf = GetPayloadBuffer(requested);
-    if (!buf) {
-        *actual = 0;
-        return nullptr;
-    }
-    *actual = requested;
-    return buf;
-}
-
-struct dm_user_header* BufferSink::GetHeaderPtr() {
-    if (!(sizeof(struct dm_user_header) <= buffer_size_)) {
-        return nullptr;
-    }
-    char* buf = reinterpret_cast<char*>(GetBufPtr());
-    struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
-    return header;
-}
-
-void* BufferSink::GetPayloadBufPtr() {
-    char* buffer = reinterpret_cast<char*>(GetBufPtr());
-    struct dm_user_message* msg = reinterpret_cast<struct dm_user_message*>(&(buffer[0]));
-    return msg->payload.buf;
-}
-
-void XorSink::Initialize(BufferSink* sink, size_t size) {
-    bufsink_ = sink;
-    buffer_size_ = size;
-    returned_ = 0;
-    buffer_ = std::make_unique<uint8_t[]>(size);
-}
-
-void XorSink::Reset() {
-    returned_ = 0;
-}
-
-void* XorSink::GetBuffer(size_t requested, size_t* actual) {
-    if (requested > buffer_size_) {
-        *actual = buffer_size_;
-    } else {
-        *actual = requested;
-    }
-    return buffer_.get();
-}
-
-bool XorSink::ReturnData(void* buffer, size_t len) {
-    uint8_t* xor_data = reinterpret_cast<uint8_t*>(buffer);
-    uint8_t* buff = reinterpret_cast<uint8_t*>(bufsink_->GetPayloadBuffer(len + returned_));
-    if (buff == nullptr) {
-        return false;
-    }
-    for (size_t i = 0; i < len; i++) {
-        buff[returned_ + i] ^= xor_data[i];
-    }
-    returned_ += len;
-    return true;
-}
-
 WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device,
                            const std::string& control_device, const std::string& misc_name,
                            std::shared_ptr<Snapuserd> snapuserd) {
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
new file mode 100644
index 0000000..2e4cac6
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
@@ -0,0 +1,62 @@
+// 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 <linux/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <iostream>
+
+#include <libsnapshot/cow_reader.h>
+
+namespace android {
+namespace snapshot {
+
+class BufferSink : public IByteSink {
+  public:
+    void Initialize(size_t size);
+    void* GetBufPtr() { return buffer_.get(); }
+    void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
+    void* GetPayloadBuffer(size_t size);
+    void* GetBuffer(size_t requested, size_t* actual) override;
+    void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
+    struct dm_user_header* GetHeaderPtr();
+    bool ReturnData(void*, size_t) override { return true; }
+    void ResetBufferOffset() { buffer_offset_ = 0; }
+    void* GetPayloadBufPtr();
+
+  private:
+    std::unique_ptr<uint8_t[]> buffer_;
+    loff_t buffer_offset_;
+    size_t buffer_size_;
+};
+
+class XorSink : public IByteSink {
+  public:
+    void Initialize(BufferSink* sink, size_t size);
+    void Reset();
+    void* GetBuffer(size_t requested, size_t* actual) override;
+    bool ReturnData(void* buffer, size_t len) override;
+
+  private:
+    BufferSink* bufsink_;
+    std::unique_ptr<uint8_t[]> buffer_;
+    size_t buffer_size_;
+    size_t returned_;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index aeecf41..6ed55af 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -79,6 +79,15 @@
 
     // Returns true if the snapuserd instance supports bridging a socket to second-stage init.
     bool SupportsSecondStageSocketHandoff();
+
+    // Returns true if the merge is started(or resumed from crash).
+    bool InitiateMerge(const std::string& misc_name);
+
+    // Returns Merge completion percentage
+    double GetMergePercent();
+
+    // Return the status of the snapshot
+    std::string QuerySnapshotStatus(const std::string& misc_name);
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp
new file mode 100644
index 0000000..ab763ab
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <snapuserd/snapuserd_buffer.h>
+#include <snapuserd/snapuserd_kernel.h>
+
+namespace android {
+namespace snapshot {
+
+void BufferSink::Initialize(size_t size) {
+    buffer_size_ = size;
+    buffer_offset_ = 0;
+    buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void* BufferSink::GetPayloadBuffer(size_t size) {
+    if ((buffer_size_ - buffer_offset_) < size) return nullptr;
+
+    char* buffer = reinterpret_cast<char*>(GetBufPtr());
+    struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+    return (char*)msg->payload.buf + buffer_offset_;
+}
+
+void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
+    void* buf = GetPayloadBuffer(requested);
+    if (!buf) {
+        *actual = 0;
+        return nullptr;
+    }
+    *actual = requested;
+    return buf;
+}
+
+struct dm_user_header* BufferSink::GetHeaderPtr() {
+    if (!(sizeof(struct dm_user_header) <= buffer_size_)) {
+        return nullptr;
+    }
+    char* buf = reinterpret_cast<char*>(GetBufPtr());
+    struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
+    return header;
+}
+
+void* BufferSink::GetPayloadBufPtr() {
+    char* buffer = reinterpret_cast<char*>(GetBufPtr());
+    struct dm_user_message* msg = reinterpret_cast<struct dm_user_message*>(&(buffer[0]));
+    return msg->payload.buf;
+}
+
+void XorSink::Initialize(BufferSink* sink, size_t size) {
+    bufsink_ = sink;
+    buffer_size_ = size;
+    returned_ = 0;
+    buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void XorSink::Reset() {
+    returned_ = 0;
+}
+
+void* XorSink::GetBuffer(size_t requested, size_t* actual) {
+    if (requested > buffer_size_) {
+        *actual = buffer_size_;
+    } else {
+        *actual = requested;
+    }
+    return buffer_.get();
+}
+
+bool XorSink::ReturnData(void* buffer, size_t len) {
+    uint8_t* xor_data = reinterpret_cast<uint8_t*>(buffer);
+    uint8_t* buff = reinterpret_cast<uint8_t*>(bufsink_->GetPayloadBuffer(len + returned_));
+    if (buff == nullptr) {
+        return false;
+    }
+    for (size_t i = 0; i < len; i++) {
+        buff[returned_ + i] ^= xor_data[i];
+    }
+    returned_ += len;
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 1ea05a3..e345269 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -231,5 +231,35 @@
     return true;
 }
 
+bool SnapuserdClient::InitiateMerge(const std::string& misc_name) {
+    std::string msg = "initiate_merge," + misc_name;
+    if (!Sendmsg(msg)) {
+        LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+        return false;
+    }
+    std::string response = Receivemsg();
+    return response == "success";
+}
+
+double SnapuserdClient::GetMergePercent() {
+    std::string msg = "merge_percent";
+    if (!Sendmsg(msg)) {
+        LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+        return false;
+    }
+    std::string response = Receivemsg();
+
+    return std::stod(response);
+}
+
+std::string SnapuserdClient::QuerySnapshotStatus(const std::string& misc_name) {
+    std::string msg = "getstatus," + misc_name;
+    if (!Sendmsg(msg)) {
+        LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+        return "snapshot-merge-failed";
+    }
+    return Receivemsg();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index e05822e..912884f 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -21,8 +21,6 @@
 #include <gflags/gflags.h>
 #include <snapuserd/snapuserd_client.h>
 
-#include "snapuserd_server.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.");
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
index b660ba2..fbf57d9 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
@@ -19,7 +19,7 @@
 #include <string>
 #include <vector>
 
-#include "snapuserd_server.h"
+#include "dm-snapshot-merge/snapuserd_server.h"
 
 namespace android {
 namespace snapshot {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
new file mode 100644
index 0000000..57e47e7
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+#include <android-base/strings.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
+                                 std::string backing_device, std::string base_path_merge) {
+    misc_name_ = std::move(misc_name);
+    cow_device_ = std::move(cow_device);
+    backing_store_device_ = std::move(backing_device);
+    control_device_ = "/dev/dm-user/" + misc_name_;
+    base_path_merge_ = std::move(base_path_merge);
+}
+
+bool SnapshotHandler::InitializeWorkers() {
+    for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
+        std::unique_ptr<Worker> wt =
+                std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
+                                         misc_name_, base_path_merge_, GetSharedPtr());
+        if (!wt->Init()) {
+            SNAP_LOG(ERROR) << "Thread initialization failed";
+            return false;
+        }
+
+        worker_threads_.push_back(std::move(wt));
+    }
+
+    merge_thread_ = std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
+                                             misc_name_, base_path_merge_, GetSharedPtr());
+
+    read_ahead_thread_ = std::make_unique<ReadAhead>(cow_device_, backing_store_device_, misc_name_,
+                                                     GetSharedPtr());
+    return true;
+}
+
+std::unique_ptr<CowReader> SnapshotHandler::CloneReaderForWorker() {
+    return reader_->CloneCowReader();
+}
+
+void SnapshotHandler::UpdateMergeCompletionPercentage() {
+    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+    merge_completion_percentage_ = (ch->num_merge_ops * 100.0) / reader_->get_num_total_data_ops();
+
+    SNAP_LOG(DEBUG) << "Merge-complete %: " << merge_completion_percentage_
+                    << " num_merge_ops: " << ch->num_merge_ops
+                    << " total-ops: " << reader_->get_num_total_data_ops();
+}
+
+bool SnapshotHandler::CommitMerge(int num_merge_ops) {
+    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+    ch->num_merge_ops += num_merge_ops;
+
+    if (scratch_space_) {
+        if (ra_thread_) {
+            struct BufferState* ra_state = GetBufferState();
+            ra_state->read_ahead_state = kCowReadAheadInProgress;
+        }
+
+        int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+        if (ret < 0) {
+            SNAP_PLOG(ERROR) << "msync header failed: " << ret;
+            return false;
+        }
+    } else {
+        reader_->UpdateMergeOpsCompleted(num_merge_ops);
+        CowHeader header;
+        reader_->GetHeader(&header);
+
+        if (lseek(cow_fd_.get(), 0, SEEK_SET) < 0) {
+            SNAP_PLOG(ERROR) << "lseek failed";
+            return false;
+        }
+
+        if (!android::base::WriteFully(cow_fd_, &header, sizeof(CowHeader))) {
+            SNAP_PLOG(ERROR) << "Write to header failed";
+            return false;
+        }
+
+        if (fsync(cow_fd_.get()) < 0) {
+            SNAP_PLOG(ERROR) << "fsync failed";
+            return false;
+        }
+    }
+
+    // Update the merge completion - this is used by update engine
+    // to track the completion. No need to take a lock. It is ok
+    // even if there is a miss on reading a latest updated value.
+    // Subsequent polling will eventually converge to completion.
+    UpdateMergeCompletionPercentage();
+
+    return true;
+}
+
+void SnapshotHandler::PrepareReadAhead() {
+    struct BufferState* ra_state = GetBufferState();
+    // Check if the data has to be re-constructed from COW device
+    if (ra_state->read_ahead_state == kCowReadAheadDone) {
+        populate_data_from_cow_ = true;
+    } else {
+        populate_data_from_cow_ = false;
+    }
+
+    NotifyRAForMergeReady();
+}
+
+void SnapshotHandler::CheckMergeCompletionStatus() {
+    if (!merge_initiated_) {
+        SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: "
+                       << reader_->get_num_total_data_ops();
+        return;
+    }
+
+    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+
+    SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops
+                   << " Total-data-ops: " << reader_->get_num_total_data_ops();
+}
+
+bool SnapshotHandler::ReadMetadata() {
+    reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE);
+    CowHeader header;
+    CowOptions options;
+
+    SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
+
+    if (!reader_->Parse(cow_fd_)) {
+        SNAP_LOG(ERROR) << "Failed to parse";
+        return false;
+    }
+
+    if (!reader_->GetHeader(&header)) {
+        SNAP_LOG(ERROR) << "Failed to get header";
+        return false;
+    }
+
+    if (!(header.block_size == BLOCK_SZ)) {
+        SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "Merge-ops: " << header.num_merge_ops;
+
+    if (!MmapMetadata()) {
+        SNAP_LOG(ERROR) << "mmap failed";
+        return false;
+    }
+
+    UpdateMergeCompletionPercentage();
+
+    // Initialize the iterator for reading metadata
+    std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+
+    int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
+    int ra_index = 0;
+
+    size_t copy_ops = 0, replace_ops = 0, zero_ops = 0, xor_ops = 0;
+
+    while (!cowop_iter->Done()) {
+        const CowOperation* cow_op = &cowop_iter->Get();
+
+        if (cow_op->type == kCowCopyOp) {
+            copy_ops += 1;
+        } else if (cow_op->type == kCowReplaceOp) {
+            replace_ops += 1;
+        } else if (cow_op->type == kCowZeroOp) {
+            zero_ops += 1;
+        } else if (cow_op->type == kCowXorOp) {
+            xor_ops += 1;
+        }
+
+        chunk_vec_.push_back(std::make_pair(ChunkToSector(cow_op->new_block), cow_op));
+
+        if (IsOrderedOp(*cow_op)) {
+            ra_thread_ = true;
+            block_to_ra_index_[cow_op->new_block] = ra_index;
+            num_ra_ops_per_iter -= 1;
+
+            if ((ra_index + 1) - merge_blk_state_.size() == 1) {
+                std::unique_ptr<MergeGroupState> blk_state = std::make_unique<MergeGroupState>(
+                        MERGE_GROUP_STATE::GROUP_MERGE_PENDING, 0);
+
+                merge_blk_state_.push_back(std::move(blk_state));
+            }
+
+            // Move to next RA block
+            if (num_ra_ops_per_iter == 0) {
+                num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
+                ra_index += 1;
+            }
+        }
+        cowop_iter->Next();
+    }
+
+    chunk_vec_.shrink_to_fit();
+
+    // Sort the vector based on sectors as we need this during un-aligned access
+    std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare);
+
+    PrepareReadAhead();
+
+    SNAP_LOG(INFO) << "Merged-ops: " << header.num_merge_ops
+                   << " Total-data-ops: " << reader_->get_num_total_data_ops()
+                   << " Unmerged-ops: " << chunk_vec_.size() << " Copy-ops: " << copy_ops
+                   << " Zero-ops: " << zero_ops << " Replace-ops: " << replace_ops
+                   << " Xor-ops: " << xor_ops;
+
+    return true;
+}
+
+bool SnapshotHandler::MmapMetadata() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+
+    if (header.major_version >= 2 && header.buffer_size > 0) {
+        scratch_space_ = true;
+    }
+
+    if (scratch_space_) {
+        mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED,
+                            cow_fd_.get(), 0);
+    } else {
+        mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE,
+                            MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+        struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+        ch->num_merge_ops = header.num_merge_ops;
+    }
+
+    if (mapped_addr_ == MAP_FAILED) {
+        SNAP_LOG(ERROR) << "mmap metadata failed";
+        return false;
+    }
+
+    return true;
+}
+
+void SnapshotHandler::UnmapBufferRegion() {
+    int ret = munmap(mapped_addr_, total_mapped_addr_length_);
+    if (ret < 0) {
+        SNAP_PLOG(ERROR) << "munmap failed";
+    }
+}
+
+bool SnapshotHandler::InitCowDevice() {
+    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+    if (cow_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+        return false;
+    }
+
+    unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge_.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (fd < 0) {
+        SNAP_LOG(ERROR) << "Cannot open block device";
+        return false;
+    }
+
+    uint64_t dev_sz = get_block_device_size(fd.get());
+    if (!dev_sz) {
+        SNAP_LOG(ERROR) << "Failed to find block device size: " << base_path_merge_;
+        return false;
+    }
+
+    num_sectors_ = dev_sz >> SECTOR_SHIFT;
+
+    return ReadMetadata();
+}
+
+void SnapshotHandler::ReadBlocksToCache(const std::string& dm_block_device,
+                                        const std::string& partition_name, off_t offset,
+                                        size_t size) {
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
+    if (fd.get() == -1) {
+        SNAP_PLOG(ERROR) << "Error reading " << dm_block_device
+                         << " partition-name: " << partition_name;
+        return;
+    }
+
+    size_t remain = size;
+    off_t file_offset = offset;
+    // We pick 4M I/O size based on the fact that the current
+    // update_verifier has a similar I/O size.
+    size_t read_sz = 1024 * BLOCK_SZ;
+    std::vector<uint8_t> buf(read_sz);
+
+    while (remain > 0) {
+        size_t to_read = std::min(remain, read_sz);
+
+        if (!android::base::ReadFullyAtOffset(fd.get(), buf.data(), to_read, file_offset)) {
+            SNAP_PLOG(ERROR) << "Failed to read block from block device: " << dm_block_device
+                             << " at offset: " << file_offset
+                             << " partition-name: " << partition_name << " total-size: " << size
+                             << " remain_size: " << remain;
+            return;
+        }
+
+        file_offset += to_read;
+        remain -= to_read;
+    }
+
+    SNAP_LOG(INFO) << "Finished reading block-device: " << dm_block_device
+                   << " partition: " << partition_name << " size: " << size
+                   << " offset: " << offset;
+}
+
+void SnapshotHandler::ReadBlocks(const std::string partition_name,
+                                 const std::string& dm_block_device) {
+    SNAP_LOG(DEBUG) << "Reading partition: " << partition_name
+                    << " Block-Device: " << dm_block_device;
+
+    uint64_t dev_sz = 0;
+
+    unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (fd < 0) {
+        SNAP_LOG(ERROR) << "Cannot open block device";
+        return;
+    }
+
+    dev_sz = get_block_device_size(fd.get());
+    if (!dev_sz) {
+        SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
+        return;
+    }
+
+    int num_threads = 2;
+    size_t num_blocks = dev_sz >> BLOCK_SHIFT;
+    size_t num_blocks_per_thread = num_blocks / num_threads;
+    size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
+    off_t offset = 0;
+
+    for (int i = 0; i < num_threads; i++) {
+        std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this, dm_block_device,
+                   partition_name, offset, read_sz_per_thread);
+
+        offset += read_sz_per_thread;
+    }
+}
+
+/*
+ * Entry point to launch threads
+ */
+bool SnapshotHandler::Start() {
+    std::vector<std::future<bool>> threads;
+    std::future<bool> ra_thread_status;
+
+    if (ra_thread_) {
+        ra_thread_status =
+                std::async(std::launch::async, &ReadAhead::RunThread, read_ahead_thread_.get());
+
+        SNAP_LOG(INFO) << "Read-ahead thread started...";
+    }
+
+    // Launch worker threads
+    for (int i = 0; i < worker_threads_.size(); i++) {
+        threads.emplace_back(
+                std::async(std::launch::async, &Worker::RunThread, worker_threads_[i].get()));
+    }
+
+    bool second_stage_init = true;
+
+    // We don't want to read the blocks during first stage init.
+    if (android::base::EndsWith(misc_name_, "-init") || is_socket_present_) {
+        second_stage_init = false;
+    }
+
+    if (second_stage_init) {
+        SNAP_LOG(INFO) << "Reading blocks to cache....";
+        auto& dm = DeviceMapper::Instance();
+        auto dm_block_devices = dm.FindDmPartitions();
+        if (dm_block_devices.empty()) {
+            SNAP_LOG(ERROR) << "No dm-enabled block device is found.";
+        } else {
+            auto parts = android::base::Split(misc_name_, "-");
+            std::string partition_name = parts[0];
+
+            const char* suffix_b = "_b";
+            const char* suffix_a = "_a";
+
+            partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1);
+            partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1);
+
+            if (dm_block_devices.find(partition_name) == dm_block_devices.end()) {
+                SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name;
+            } else {
+                ReadBlocks(partition_name, dm_block_devices.at(partition_name));
+            }
+        }
+    } else {
+        SNAP_LOG(INFO) << "Not reading block device into cache";
+    }
+
+    std::future<bool> merge_thread =
+            std::async(std::launch::async, &Worker::RunMergeThread, merge_thread_.get());
+
+    bool ret = true;
+    for (auto& t : threads) {
+        ret = t.get() && ret;
+    }
+
+    // Worker threads are terminated by this point - this can only happen:
+    //
+    // 1: If dm-user device is destroyed
+    // 2: We had an I/O failure when reading root partitions
+    //
+    // In case (1), this would be a graceful shutdown. In this case, merge
+    // thread and RA thread should have already terminated by this point. We will be
+    // destroying the dm-user device only _after_ merge is completed.
+    //
+    // In case (2), if merge thread had started, then it will be
+    // continuing to merge; however, since we had an I/O failure and the
+    // I/O on root partitions are no longer served, we will terminate the
+    // merge
+
+    NotifyIOTerminated();
+
+    bool read_ahead_retval = false;
+
+    SNAP_LOG(INFO) << "Snapshot I/O terminated. Waiting for merge thread....";
+    bool merge_thread_status = merge_thread.get();
+
+    if (ra_thread_) {
+        read_ahead_retval = ra_thread_status.get();
+    }
+
+    SNAP_LOG(INFO) << "Worker threads terminated with ret: " << ret
+                   << " Merge-thread with ret: " << merge_thread_status
+                   << " RA-thread with ret: " << read_ahead_retval;
+    return ret;
+}
+
+uint64_t SnapshotHandler::GetBufferMetadataOffset() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    return (header.header_size + sizeof(BufferState));
+}
+
+/*
+ * Metadata for read-ahead is 16 bytes. For a 2 MB region, we will
+ * end up with 8k (2 PAGE) worth of metadata. Thus, a 2MB buffer
+ * region is split into:
+ *
+ * 1: 8k metadata
+ * 2: Scratch space
+ *
+ */
+size_t SnapshotHandler::GetBufferMetadataSize() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+    size_t buffer_size = header.buffer_size;
+
+    // If there is no scratch space, then just use the
+    // anonymous memory
+    if (buffer_size == 0) {
+        buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+    }
+
+    return ((buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ);
+}
+
+size_t SnapshotHandler::GetBufferDataOffset() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    return (header.header_size + GetBufferMetadataSize());
+}
+
+/*
+ * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
+ */
+size_t SnapshotHandler::GetBufferDataSize() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+    size_t buffer_size = header.buffer_size;
+
+    // If there is no scratch space, then just use the
+    // anonymous memory
+    if (buffer_size == 0) {
+        buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+    }
+
+    return (buffer_size - GetBufferMetadataSize());
+}
+
+struct BufferState* SnapshotHandler::GetBufferState() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    struct BufferState* ra_state =
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+    return ra_state;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
new file mode 100644
index 0000000..13b56fa
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -0,0 +1,358 @@
+// 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 <linux/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#include <condition_variable>
+#include <cstring>
+#include <future>
+#include <iostream>
+#include <limits>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <libdm/dm.h>
+#include <libsnapshot/cow_reader.h>
+#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
+#include <snapuserd/snapuserd_kernel.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::unique_fd;
+using namespace std::chrono_literals;
+
+static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
+static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
+
+static constexpr int NUM_THREADS_PER_PARTITION = 1;
+
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
+
+enum class MERGE_IO_TRANSITION {
+    MERGE_READY,
+    MERGE_BEGIN,
+    MERGE_FAILED,
+    MERGE_COMPLETE,
+    IO_TERMINATED,
+    READ_AHEAD_FAILURE,
+};
+
+class SnapshotHandler;
+
+enum class MERGE_GROUP_STATE {
+    GROUP_MERGE_PENDING,
+    GROUP_MERGE_RA_READY,
+    GROUP_MERGE_IN_PROGRESS,
+    GROUP_MERGE_COMPLETED,
+    GROUP_MERGE_FAILED,
+    GROUP_INVALID,
+};
+
+struct MergeGroupState {
+    MERGE_GROUP_STATE merge_state_;
+    // Ref count I/O when group state
+    // is in "GROUP_MERGE_PENDING"
+    size_t num_ios_in_progress;
+    std::mutex m_lock;
+    std::condition_variable m_cv;
+
+    MergeGroupState(MERGE_GROUP_STATE state, size_t n_ios)
+        : merge_state_(state), num_ios_in_progress(n_ios) {}
+};
+
+class ReadAhead {
+  public:
+    ReadAhead(const std::string& cow_device, const std::string& backing_device,
+              const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd);
+    bool RunThread();
+
+  private:
+    void InitializeRAIter();
+    bool RAIterDone();
+    void RAIterNext();
+    const CowOperation* GetRAOpIter();
+
+    void InitializeBuffer();
+    bool InitReader();
+    bool InitializeFds();
+
+    void CloseFds() { backing_store_fd_ = {}; }
+
+    bool ReadAheadIOStart();
+    int PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops,
+                             std::vector<uint64_t>& blocks,
+                             std::vector<const CowOperation*>& xor_op_vec);
+    bool ReconstructDataFromCow();
+    void CheckOverlap(const CowOperation* cow_op);
+
+    void* read_ahead_buffer_;
+    void* metadata_buffer_;
+
+    std::unique_ptr<ICowOpIter> cowop_iter_;
+
+    std::string cow_device_;
+    std::string backing_store_device_;
+    std::string misc_name_;
+
+    unique_fd cow_fd_;
+    unique_fd backing_store_fd_;
+
+    std::shared_ptr<SnapshotHandler> snapuserd_;
+    std::unique_ptr<CowReader> reader_;
+
+    std::unordered_set<uint64_t> dest_blocks_;
+    std::unordered_set<uint64_t> source_blocks_;
+    bool overlap_;
+    BufferSink bufsink_;
+};
+
+class Worker {
+  public:
+    Worker(const std::string& cow_device, const std::string& backing_device,
+           const std::string& control_device, const std::string& misc_name,
+           const std::string& base_path_merge, std::shared_ptr<SnapshotHandler> snapuserd);
+    bool RunThread();
+    bool RunMergeThread();
+    bool Init();
+
+  private:
+    // Initialization
+    void InitializeBufsink();
+    bool InitializeFds();
+    bool InitReader();
+    void CloseFds() {
+        ctrl_fd_ = {};
+        backing_store_fd_ = {};
+        base_path_merge_fd_ = {};
+    }
+
+    // Functions interacting with dm-user
+    bool ReadDmUserHeader();
+    bool WriteDmUserPayload(size_t size, bool header_response);
+    bool DmuserReadRequest();
+
+    // IO Path
+    bool ProcessIORequest();
+    bool IsBlockAligned(size_t size) { return ((size & (BLOCK_SZ - 1)) == 0); }
+
+    bool ReadDataFromBaseDevice(sector_t sector, size_t read_size);
+    bool ReadFromSourceDevice(const CowOperation* cow_op);
+
+    bool ReadAlignedSector(sector_t sector, size_t sz, bool header_response);
+    bool ReadUnalignedSector(sector_t sector, size_t size);
+    int ReadUnalignedSector(sector_t sector, size_t size,
+                            std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
+    bool RespondIOError(bool header_response);
+
+    // Processing COW operations
+    bool ProcessCowOp(const CowOperation* cow_op);
+    bool ProcessReplaceOp(const CowOperation* cow_op);
+    bool ProcessZeroOp();
+
+    // Handles Copy and Xor
+    bool ProcessCopyOp(const CowOperation* cow_op);
+    bool ProcessXorOp(const CowOperation* cow_op);
+    bool ProcessOrderedOp(const CowOperation* cow_op);
+
+    // Merge related ops
+    bool Merge();
+    bool MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+    bool MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+    int PrepareMerge(uint64_t* source_offset, int* pending_ops,
+                     const std::unique_ptr<ICowOpIter>& cowop_iter,
+                     std::vector<const CowOperation*>* replace_zero_vec = nullptr);
+
+    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+
+    std::unique_ptr<CowReader> reader_;
+    BufferSink bufsink_;
+    XorSink xorsink_;
+
+    std::string cow_device_;
+    std::string backing_store_device_;
+    std::string control_device_;
+    std::string misc_name_;
+    std::string base_path_merge_;
+
+    unique_fd cow_fd_;
+    unique_fd backing_store_fd_;
+    unique_fd base_path_merge_fd_;
+    unique_fd ctrl_fd_;
+
+    std::shared_ptr<SnapshotHandler> snapuserd_;
+};
+
+class SnapshotHandler : public std::enable_shared_from_this<SnapshotHandler> {
+  public:
+    SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device,
+                    std::string base_path_merge);
+    bool InitCowDevice();
+    bool Start();
+
+    const std::string& GetControlDevicePath() { return control_device_; }
+    const std::string& GetMiscName() { return misc_name_; }
+    const uint64_t& GetNumSectors() { return num_sectors_; }
+    const bool& IsAttached() const { return attached_; }
+    void AttachControlDevice() { attached_ = true; }
+
+    void CheckMergeCompletionStatus();
+    bool CommitMerge(int num_merge_ops);
+
+    void CloseFds() { cow_fd_ = {}; }
+    void FreeResources() {
+        worker_threads_.clear();
+        read_ahead_thread_ = nullptr;
+        merge_thread_ = nullptr;
+    }
+
+    bool InitializeWorkers();
+    std::unique_ptr<CowReader> CloneReaderForWorker();
+    std::shared_ptr<SnapshotHandler> GetSharedPtr() { return shared_from_this(); }
+
+    std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; }
+
+    static bool compare(std::pair<sector_t, const CowOperation*> p1,
+                        std::pair<sector_t, const CowOperation*> p2) {
+        return p1.first < p2.first;
+    }
+
+    void UnmapBufferRegion();
+    bool MmapMetadata();
+
+    // Read-ahead related functions
+    void* GetMappedAddr() { return mapped_addr_; }
+    void PrepareReadAhead();
+    std::unordered_map<uint64_t, void*>& GetReadAheadMap() { return read_ahead_buffer_map_; }
+
+    // State transitions for merge
+    void InitiateMerge();
+    void WaitForMergeComplete();
+    bool WaitForMergeBegin();
+    void NotifyRAForMergeReady();
+    bool WaitForMergeReady();
+    void MergeFailed();
+    bool IsIOTerminated();
+    void MergeCompleted();
+    void NotifyIOTerminated();
+    bool ReadAheadIOCompleted(bool sync);
+    void ReadAheadIOFailed();
+
+    bool ShouldReconstructDataFromCow() { return populate_data_from_cow_; }
+    void FinishReconstructDataFromCow() { populate_data_from_cow_ = false; }
+    // Return the snapshot status
+    std::string GetMergeStatus();
+
+    // RA related functions
+    uint64_t GetBufferMetadataOffset();
+    size_t GetBufferMetadataSize();
+    size_t GetBufferDataOffset();
+    size_t GetBufferDataSize();
+
+    // Total number of blocks to be merged in a given read-ahead buffer region
+    void SetMergedBlockCountForNextCommit(int x) { total_ra_blocks_merged_ = x; }
+    int GetTotalBlocksToMerge() { return total_ra_blocks_merged_; }
+    void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
+    bool MergeInitiated() { return merge_initiated_; }
+    double GetMergePercentage() { return merge_completion_percentage_; }
+
+    // Merge Block State Transitions
+    void SetMergeCompleted(size_t block_index);
+    void SetMergeInProgress(size_t block_index);
+    void SetMergeFailed(size_t block_index);
+    void NotifyIOCompletion(uint64_t new_block);
+    bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
+    MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer);
+
+  private:
+    bool ReadMetadata();
+    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+    bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
+    struct BufferState* GetBufferState();
+    void UpdateMergeCompletionPercentage();
+
+    void ReadBlocks(const std::string partition_name, const std::string& dm_block_device);
+    void ReadBlocksToCache(const std::string& dm_block_device, const std::string& partition_name,
+                           off_t offset, size_t size);
+
+    // COW device
+    std::string cow_device_;
+    // Source device
+    std::string backing_store_device_;
+    // dm-user control device
+    std::string control_device_;
+    std::string misc_name_;
+    // Base device for merging
+    std::string base_path_merge_;
+
+    unique_fd cow_fd_;
+
+    uint64_t num_sectors_;
+
+    std::unique_ptr<CowReader> reader_;
+
+    // chunk_vec stores the pseudo mapping of sector
+    // to COW operations.
+    std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
+
+    std::mutex lock_;
+    std::condition_variable cv;
+
+    void* mapped_addr_;
+    size_t total_mapped_addr_length_;
+
+    std::vector<std::unique_ptr<Worker>> worker_threads_;
+    // Read-ahead related
+    bool populate_data_from_cow_ = false;
+    bool ra_thread_ = false;
+    int total_ra_blocks_merged_ = 0;
+    MERGE_IO_TRANSITION io_state_;
+    std::unique_ptr<ReadAhead> read_ahead_thread_;
+    std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
+
+    // user-space-merging
+    std::unordered_map<uint64_t, int> block_to_ra_index_;
+
+    // Merge Block state
+    std::vector<std::unique_ptr<MergeGroupState>> merge_blk_state_;
+
+    std::unique_ptr<Worker> merge_thread_;
+    double merge_completion_percentage_;
+
+    bool merge_initiated_ = false;
+    bool attached_ = false;
+    bool is_socket_present_;
+    bool scratch_space_ = false;
+};
+
+}  // namespace snapshot
+}  // namespace android
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
new file mode 100644
index 0000000..bfbacf9
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+Worker::Worker(const std::string& cow_device, const std::string& backing_device,
+               const std::string& control_device, const std::string& misc_name,
+               const std::string& base_path_merge, std::shared_ptr<SnapshotHandler> snapuserd) {
+    cow_device_ = cow_device;
+    backing_store_device_ = backing_device;
+    control_device_ = control_device;
+    misc_name_ = misc_name;
+    base_path_merge_ = base_path_merge;
+    snapuserd_ = snapuserd;
+}
+
+bool Worker::InitializeFds() {
+    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+    if (backing_store_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+        return false;
+    }
+
+    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+    if (cow_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+        return false;
+    }
+
+    ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
+    if (ctrl_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
+        return false;
+    }
+
+    // Base device used by merge thread
+    base_path_merge_fd_.reset(open(base_path_merge_.c_str(), O_RDWR));
+    if (base_path_merge_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << base_path_merge_;
+        return false;
+    }
+
+    return true;
+}
+
+bool Worker::InitReader() {
+    reader_ = snapuserd_->CloneReaderForWorker();
+
+    if (!reader_->InitForMerge(std::move(cow_fd_))) {
+        return false;
+    }
+    return true;
+}
+
+// Start the replace operation. This will read the
+// internal COW format and if the block is compressed,
+// it will be de-compressed.
+bool Worker::ProcessReplaceOp(const CowOperation* cow_op) {
+    if (!reader_->ReadData(*cow_op, &bufsink_)) {
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+        return false;
+    }
+
+    return true;
+}
+
+bool Worker::ReadFromSourceDevice(const CowOperation* cow_op) {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+        return false;
+    }
+    SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
+                    << " Source: " << cow_op->source;
+    uint64_t offset = cow_op->source;
+    if (cow_op->type == kCowCopyOp) {
+        offset *= BLOCK_SZ;
+    }
+    if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
+        std::string op;
+        if (cow_op->type == kCowCopyOp)
+            op = "Copy-op";
+        else {
+            op = "Xor-op";
+        }
+        SNAP_PLOG(ERROR) << op << " failed. Read from backing store: " << backing_store_device_
+                         << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ;
+        return false;
+    }
+
+    return true;
+}
+
+// Start the copy operation. This will read the backing
+// block device which is represented by cow_op->source.
+bool Worker::ProcessCopyOp(const CowOperation* cow_op) {
+    if (!ReadFromSourceDevice(cow_op)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool Worker::ProcessXorOp(const CowOperation* cow_op) {
+    if (!ReadFromSourceDevice(cow_op)) {
+        return false;
+    }
+    xorsink_.Reset();
+    if (!reader_->ReadData(*cow_op, &xorsink_)) {
+        SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block;
+        return false;
+    }
+
+    return true;
+}
+
+bool Worker::ProcessZeroOp() {
+    // Zero out the entire block
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ProcessZeroOp: Failed to get payload buffer";
+        return false;
+    }
+
+    memset(buffer, 0, BLOCK_SZ);
+    return true;
+}
+
+bool Worker::ProcessOrderedOp(const CowOperation* cow_op) {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ProcessOrderedOp: Failed to get payload buffer";
+        return false;
+    }
+
+    MERGE_GROUP_STATE state = snapuserd_->ProcessMergingBlock(cow_op->new_block, buffer);
+
+    switch (state) {
+        case MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED: {
+            // Merge is completed for this COW op; just read directly from
+            // the base device
+            SNAP_LOG(DEBUG) << "Merge-completed: Reading from base device sector: "
+                            << (cow_op->new_block >> SECTOR_SHIFT)
+                            << " Block-number: " << cow_op->new_block;
+            if (!ReadDataFromBaseDevice(ChunkToSector(cow_op->new_block), BLOCK_SZ)) {
+                SNAP_LOG(ERROR) << "ReadDataFromBaseDevice at sector: "
+                                << (cow_op->new_block >> SECTOR_SHIFT) << " after merge-complete.";
+                return false;
+            }
+            return true;
+        }
+        case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
+            bool ret;
+            if (cow_op->type == kCowCopyOp) {
+                ret = ProcessCopyOp(cow_op);
+            } else {
+                ret = ProcessXorOp(cow_op);
+            }
+
+            // I/O is complete - decrement the refcount irrespective of the return
+            // status
+            snapuserd_->NotifyIOCompletion(cow_op->new_block);
+            return ret;
+        }
+        // We already have the data in the buffer retrieved from RA thread.
+        // Nothing to process further.
+        case MERGE_GROUP_STATE::GROUP_MERGE_RA_READY: {
+            [[fallthrough]];
+        }
+        case MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS: {
+            return true;
+        }
+        default: {
+            // All other states, fail the I/O viz (GROUP_MERGE_FAILED and GROUP_INVALID)
+            return false;
+        }
+    }
+
+    return false;
+}
+
+bool Worker::ProcessCowOp(const CowOperation* cow_op) {
+    if (cow_op == nullptr) {
+        SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op";
+        return false;
+    }
+
+    switch (cow_op->type) {
+        case kCowReplaceOp: {
+            return ProcessReplaceOp(cow_op);
+        }
+
+        case kCowZeroOp: {
+            return ProcessZeroOp();
+        }
+
+        case kCowCopyOp:
+            [[fallthrough]];
+        case kCowXorOp: {
+            return ProcessOrderedOp(cow_op);
+        }
+
+        default: {
+            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+        }
+    }
+    return false;
+}
+
+void Worker::InitializeBufsink() {
+    // Allocate the buffer which is used to communicate between
+    // daemon and dm-user. The buffer comprises of header and a fixed payload.
+    // If the dm-user requests a big IO, the IO will be broken into chunks
+    // of PAYLOAD_SIZE.
+    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
+    bufsink_.Initialize(buf_size);
+}
+
+bool Worker::Init() {
+    InitializeBufsink();
+    xorsink_.Initialize(&bufsink_, BLOCK_SZ);
+
+    if (!InitializeFds()) {
+        return false;
+    }
+
+    if (!InitReader()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool Worker::RunThread() {
+    SNAP_LOG(INFO) << "Processing snapshot I/O requests....";
+    // Start serving IO
+    while (true) {
+        if (!ProcessIORequest()) {
+            break;
+        }
+    }
+
+    CloseFds();
+    reader_->CloseCowFd();
+
+    return true;
+}
+
+// Read Header from dm-user misc device. This gives
+// us the sector number for which IO is issued by dm-snapshot device
+bool Worker::ReadDmUserHeader() {
+    if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
+        if (errno != ENOTBLK) {
+            SNAP_PLOG(ERROR) << "Control-read failed";
+        }
+
+        SNAP_PLOG(DEBUG) << "ReadDmUserHeader failed....";
+        return false;
+    }
+
+    return true;
+}
+
+// Send the payload/data back to dm-user misc device.
+bool Worker::WriteDmUserPayload(size_t size, bool header_response) {
+    size_t payload_size = size;
+    void* buf = bufsink_.GetPayloadBufPtr();
+    if (header_response) {
+        payload_size += sizeof(struct dm_user_header);
+        buf = bufsink_.GetBufPtr();
+    }
+
+    if (!android::base::WriteFully(ctrl_fd_, buf, payload_size)) {
+        SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << payload_size;
+        return false;
+    }
+
+    return true;
+}
+
+bool Worker::ReadDataFromBaseDevice(sector_t sector, size_t read_size) {
+    CHECK(read_size <= BLOCK_SZ);
+
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+        return false;
+    }
+
+    loff_t offset = sector << SECTOR_SHIFT;
+    if (!android::base::ReadFullyAtOffset(base_path_merge_fd_, buffer, read_size, offset)) {
+        SNAP_PLOG(ERROR) << "ReadDataFromBaseDevice failed. fd: " << base_path_merge_fd_
+                         << "at sector :" << sector << " size: " << read_size;
+        return false;
+    }
+
+    return true;
+}
+
+bool Worker::ReadAlignedSector(sector_t sector, size_t sz, bool header_response) {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+    size_t remaining_size = sz;
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+    bool io_error = false;
+    int ret = 0;
+
+    do {
+        // Process 1MB payload at a time
+        size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+
+        header->type = DM_USER_RESP_SUCCESS;
+        size_t total_bytes_read = 0;
+        io_error = false;
+        bufsink_.ResetBufferOffset();
+
+        while (read_size) {
+            // We need to check every 4k block to verify if it is
+            // present in the mapping.
+            size_t size = std::min(BLOCK_SZ, read_size);
+
+            auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+                                       std::make_pair(sector, nullptr), SnapshotHandler::compare);
+            bool not_found = (it == chunk_vec.end() || it->first != sector);
+
+            if (not_found) {
+                // Block not found in map - which means this block was not
+                // changed as per the OTA. Just route the I/O to the base
+                // device.
+                if (!ReadDataFromBaseDevice(sector, size)) {
+                    SNAP_LOG(ERROR) << "ReadDataFromBaseDevice failed";
+                    header->type = DM_USER_RESP_ERROR;
+                }
+
+                ret = size;
+            } else {
+                // We found the sector in mapping. Check the type of COW OP and
+                // process it.
+                if (!ProcessCowOp(it->second)) {
+                    SNAP_LOG(ERROR) << "ProcessCowOp failed";
+                    header->type = DM_USER_RESP_ERROR;
+                }
+
+                ret = BLOCK_SZ;
+            }
+
+            // Just return the header if it is an error
+            if (header->type == DM_USER_RESP_ERROR) {
+                if (!RespondIOError(header_response)) {
+                    return false;
+                }
+
+                io_error = true;
+                break;
+            }
+
+            read_size -= ret;
+            total_bytes_read += ret;
+            sector += (ret >> SECTOR_SHIFT);
+            bufsink_.UpdateBufferOffset(ret);
+        }
+
+        if (!io_error) {
+            if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+                return false;
+            }
+
+            SNAP_LOG(DEBUG) << "WriteDmUserPayload success total_bytes_read: " << total_bytes_read
+                            << " header-response: " << header_response
+                            << " remaining_size: " << remaining_size;
+            header_response = false;
+            remaining_size -= total_bytes_read;
+        }
+    } while (remaining_size > 0 && !io_error);
+
+    return true;
+}
+
+int Worker::ReadUnalignedSector(
+        sector_t sector, size_t size,
+        std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) {
+    size_t skip_sector_size = 0;
+
+    SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
+                    << " Aligned sector: " << it->first;
+
+    if (!ProcessCowOp(it->second)) {
+        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size
+                        << " Aligned sector: " << it->first;
+        return -1;
+    }
+
+    int num_sectors_skip = sector - it->first;
+
+    if (num_sectors_skip > 0) {
+        skip_sector_size = num_sectors_skip << SECTOR_SHIFT;
+        char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr());
+        struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+
+        if (skip_sector_size == BLOCK_SZ) {
+            SNAP_LOG(ERROR) << "Invalid un-aligned IO request at sector: " << sector
+                            << " Base-sector: " << it->first;
+            return -1;
+        }
+
+        memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
+                (BLOCK_SZ - skip_sector_size));
+    }
+
+    bufsink_.ResetBufferOffset();
+    return std::min(size, (BLOCK_SZ - skip_sector_size));
+}
+
+bool Worker::ReadUnalignedSector(sector_t sector, size_t size) {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+    header->type = DM_USER_RESP_SUCCESS;
+    bufsink_.ResetBufferOffset();
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+
+    auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
+                               SnapshotHandler::compare);
+
+    // |-------|-------|-------|
+    // 0       1       2       3
+    //
+    // Block 0 - op 1
+    // Block 1 - op 2
+    // Block 2 - op 3
+    //
+    // chunk_vec will have block 0, 1, 2 which maps to relavant COW ops.
+    //
+    // Each block is 4k bytes. Thus, the last block will span 8 sectors
+    // ranging till block 3 (However, block 3 won't be in chunk_vec as
+    // it doesn't have any mapping to COW ops. Now, if we get an I/O request for a sector
+    // spanning between block 2 and block 3, we need to step back
+    // and get hold of the last element.
+    //
+    // Additionally, we need to make sure that the requested sector is
+    // indeed within the range of the final sector. It is perfectly valid
+    // to get an I/O request for block 3 and beyond which are not mapped
+    // to any COW ops. In that case, we just need to read from the base
+    // device.
+    bool merge_complete = false;
+    bool header_response = true;
+    if (it == chunk_vec.end()) {
+        if (chunk_vec.size() > 0) {
+            // I/O request beyond the last mapped sector
+            it = std::prev(chunk_vec.end());
+        } else {
+            // This can happen when a partition merge is complete but snapshot
+            // state in /metadata is not yet deleted; during this window if the
+            // device is rebooted, subsequent attempt will mount the snapshot.
+            // However, since the merge was completed we wouldn't have any
+            // mapping to COW ops thus chunk_vec will be empty. In that case,
+            // mark this as merge_complete and route the I/O to the base device.
+            merge_complete = true;
+        }
+    } else if (it->first != sector) {
+        if (it != chunk_vec.begin()) {
+            --it;
+        }
+    } else {
+        return ReadAlignedSector(sector, size, header_response);
+    }
+
+    loff_t requested_offset = sector << SECTOR_SHIFT;
+
+    loff_t final_offset = 0;
+    if (!merge_complete) {
+        final_offset = it->first << SECTOR_SHIFT;
+    }
+
+    // Since a COW op span 4k block size, we need to make sure that the requested
+    // offset is within the 4k region. Consider the following case:
+    //
+    // |-------|-------|-------|
+    // 0       1       2       3
+    //
+    // Block 0 - op 1
+    // Block 1 - op 2
+    //
+    // We have an I/O request for a sector between block 2 and block 3. However,
+    // we have mapping to COW ops only for block 0 and block 1. Thus, the
+    // requested offset in this case is beyond the last mapped COW op size (which
+    // is block 1 in this case).
+
+    size_t total_bytes_read = 0;
+    size_t remaining_size = size;
+    int ret = 0;
+    if (!merge_complete && (requested_offset >= final_offset) &&
+        (requested_offset - final_offset) < BLOCK_SZ) {
+        // Read the partial un-aligned data
+        ret = ReadUnalignedSector(sector, remaining_size, it);
+        if (ret < 0) {
+            SNAP_LOG(ERROR) << "ReadUnalignedSector failed for sector: " << sector
+                            << " size: " << size << " it->sector: " << it->first;
+            return RespondIOError(header_response);
+        }
+
+        remaining_size -= ret;
+        total_bytes_read += ret;
+        sector += (ret >> SECTOR_SHIFT);
+
+        // Send the data back
+        if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+            return false;
+        }
+
+        header_response = false;
+        // If we still have pending data to be processed, this will be aligned I/O
+        if (remaining_size) {
+            return ReadAlignedSector(sector, remaining_size, header_response);
+        }
+    } else {
+        // This is all about handling I/O request to be routed to base device
+        // as the I/O is not mapped to any of the COW ops.
+        loff_t aligned_offset = requested_offset;
+        // Align to nearest 4k
+        aligned_offset += BLOCK_SZ - 1;
+        aligned_offset &= ~(BLOCK_SZ - 1);
+        // Find the diff of the aligned offset
+        size_t diff_size = aligned_offset - requested_offset;
+        CHECK(diff_size <= BLOCK_SZ);
+        if (remaining_size < diff_size) {
+            if (!ReadDataFromBaseDevice(sector, remaining_size)) {
+                return RespondIOError(header_response);
+            }
+            total_bytes_read += remaining_size;
+
+            if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+                return false;
+            }
+        } else {
+            if (!ReadDataFromBaseDevice(sector, diff_size)) {
+                return RespondIOError(header_response);
+            }
+
+            total_bytes_read += diff_size;
+
+            if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+                return false;
+            }
+
+            remaining_size -= diff_size;
+            size_t num_sectors_read = (diff_size >> SECTOR_SHIFT);
+            sector += num_sectors_read;
+            CHECK(IsBlockAligned(sector << SECTOR_SHIFT));
+            header_response = false;
+
+            // If we still have pending data to be processed, this will be aligned I/O
+            return ReadAlignedSector(sector, remaining_size, header_response);
+        }
+    }
+
+    return true;
+}
+
+bool Worker::RespondIOError(bool header_response) {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+    header->type = DM_USER_RESP_ERROR;
+    // This is an issue with the dm-user interface. There
+    // is no way to propagate the I/O error back to dm-user
+    // if we have already communicated the header back. Header
+    // is responded once at the beginning; however I/O can
+    // be processed in chunks. If we encounter an I/O error
+    // somewhere in the middle of the processing, we can't communicate
+    // this back to dm-user.
+    //
+    // TODO: Fix the interface
+    CHECK(header_response);
+
+    if (!WriteDmUserPayload(0, header_response)) {
+        return false;
+    }
+
+    // There is no need to process further as we have already seen
+    // an I/O error
+    return true;
+}
+
+bool Worker::DmuserReadRequest() {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+    // Unaligned I/O request
+    if (!IsBlockAligned(header->sector << SECTOR_SHIFT)) {
+        return ReadUnalignedSector(header->sector, header->len);
+    }
+
+    return ReadAlignedSector(header->sector, header->len, true);
+}
+
+bool Worker::ProcessIORequest() {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+    if (!ReadDmUserHeader()) {
+        return false;
+    }
+
+    SNAP_LOG(DEBUG) << "Daemon: msg->seq: " << std::dec << header->seq;
+    SNAP_LOG(DEBUG) << "Daemon: msg->len: " << std::dec << header->len;
+    SNAP_LOG(DEBUG) << "Daemon: msg->sector: " << std::dec << header->sector;
+    SNAP_LOG(DEBUG) << "Daemon: msg->type: " << std::dec << header->type;
+    SNAP_LOG(DEBUG) << "Daemon: msg->flags: " << std::dec << header->flags;
+
+    switch (header->type) {
+        case DM_USER_REQ_MAP_READ: {
+            if (!DmuserReadRequest()) {
+                return false;
+            }
+            break;
+        }
+
+        case DM_USER_REQ_MAP_WRITE: {
+            // TODO: We should not get any write request
+            // to dm-user as we mount all partitions
+            // as read-only. Need to verify how are TRIM commands
+            // handled during mount.
+            return false;
+        }
+    }
+
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
new file mode 100644
index 0000000..47fc7db
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+int Worker::PrepareMerge(uint64_t* source_offset, int* pending_ops,
+                         const std::unique_ptr<ICowOpIter>& cowop_iter,
+                         std::vector<const CowOperation*>* replace_zero_vec) {
+    int num_ops = *pending_ops;
+    int nr_consecutive = 0;
+    bool checkOrderedOp = (replace_zero_vec == nullptr);
+
+    do {
+        if (!cowop_iter->Done() && num_ops) {
+            const CowOperation* cow_op = &cowop_iter->Get();
+            if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
+                break;
+            }
+
+            *source_offset = cow_op->new_block * BLOCK_SZ;
+            if (!checkOrderedOp) {
+                replace_zero_vec->push_back(cow_op);
+            }
+
+            cowop_iter->Next();
+            num_ops -= 1;
+            nr_consecutive = 1;
+
+            while (!cowop_iter->Done() && num_ops) {
+                const CowOperation* op = &cowop_iter->Get();
+                if (checkOrderedOp && !IsOrderedOp(*op)) {
+                    break;
+                }
+
+                uint64_t next_offset = op->new_block * BLOCK_SZ;
+                if (next_offset != (*source_offset + nr_consecutive * BLOCK_SZ)) {
+                    break;
+                }
+
+                if (!checkOrderedOp) {
+                    replace_zero_vec->push_back(op);
+                }
+
+                nr_consecutive += 1;
+                num_ops -= 1;
+                cowop_iter->Next();
+            }
+        }
+    } while (0);
+
+    return nr_consecutive;
+}
+
+bool Worker::MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+    // Flush every 2048 ops. Since all ops are independent and there is no
+    // dependency between COW ops, we will flush the data and the number
+    // of ops merged in COW file for every 2048 ops. If there is a crash,
+    // we will end up replaying some of the COW ops which were already merged.
+    // That is ok.
+    //
+    // 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 num_ops_merged = 0;
+
+    while (!cowop_iter->Done()) {
+        int num_ops = PAYLOAD_SIZE / BLOCK_SZ;
+        std::vector<const CowOperation*> replace_zero_vec;
+        uint64_t source_offset;
+
+        int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter, &replace_zero_vec);
+        if (linear_blocks == 0) {
+            // Merge complete
+            CHECK(cowop_iter->Done());
+            break;
+        }
+
+        for (size_t i = 0; i < replace_zero_vec.size(); i++) {
+            const CowOperation* cow_op = replace_zero_vec[i];
+            if (cow_op->type == kCowReplaceOp) {
+                if (!ProcessReplaceOp(cow_op)) {
+                    SNAP_LOG(ERROR) << "Merge - ReplaceOp failed for block: " << cow_op->new_block;
+                    return false;
+                }
+            } else {
+                CHECK(cow_op->type == kCowZeroOp);
+                if (!ProcessZeroOp()) {
+                    SNAP_LOG(ERROR) << "Merge ZeroOp failed.";
+                    return false;
+                }
+            }
+
+            bufsink_.UpdateBufferOffset(BLOCK_SZ);
+        }
+
+        size_t io_size = linear_blocks * BLOCK_SZ;
+
+        // Merge - Write the contents back to base device
+        int ret = pwrite(base_path_merge_fd_.get(), bufsink_.GetPayloadBufPtr(), io_size,
+                         source_offset);
+        if (ret < 0 || ret != io_size) {
+            SNAP_LOG(ERROR)
+                    << "Merge: ReplaceZeroOps: Failed to write to backing device while merging "
+                    << " at offset: " << source_offset << " io_size: " << io_size;
+            return false;
+        }
+
+        num_ops_merged += linear_blocks;
+
+        if (num_ops_merged == total_ops_merged_per_commit) {
+            // Flush the data
+            if (fsync(base_path_merge_fd_.get()) < 0) {
+                SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data";
+                return false;
+            }
+
+            // Track the merge completion
+            if (!snapuserd_->CommitMerge(num_ops_merged)) {
+                SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+                return false;
+            }
+
+            num_ops_merged = 0;
+        }
+
+        bufsink_.ResetBufferOffset();
+
+        if (snapuserd_->IsIOTerminated()) {
+            SNAP_LOG(ERROR)
+                    << "MergeReplaceZeroOps: Worker threads terminated - shutting down merge";
+            return false;
+        }
+    }
+
+    // Any left over ops not flushed yet.
+    if (num_ops_merged) {
+        // Flush the data
+        if (fsync(base_path_merge_fd_.get()) < 0) {
+            SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data";
+            return false;
+        }
+
+        if (!snapuserd_->CommitMerge(num_ops_merged)) {
+            SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+            return false;
+        }
+
+        num_ops_merged = 0;
+    }
+
+    return true;
+}
+
+bool Worker::MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+    void* mapped_addr = snapuserd_->GetMappedAddr();
+    void* read_ahead_buffer =
+            static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+    size_t block_index = 0;
+
+    SNAP_LOG(INFO) << "MergeOrderedOps started....";
+
+    while (!cowop_iter->Done()) {
+        const CowOperation* cow_op = &cowop_iter->Get();
+        if (!IsOrderedOp(*cow_op)) {
+            break;
+        }
+
+        SNAP_LOG(DEBUG) << "Waiting for merge begin...";
+        // Wait for RA thread to notify that the merge window
+        // is ready for merging.
+        if (!snapuserd_->WaitForMergeBegin()) {
+            snapuserd_->SetMergeFailed(block_index);
+            return false;
+        }
+
+        snapuserd_->SetMergeInProgress(block_index);
+
+        loff_t offset = 0;
+        int num_ops = snapuserd_->GetTotalBlocksToMerge();
+        SNAP_LOG(DEBUG) << "Merging copy-ops of size: " << num_ops;
+        while (num_ops) {
+            uint64_t source_offset;
+
+            int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter);
+            if (linear_blocks == 0) {
+                break;
+            }
+
+            size_t io_size = (linear_blocks * BLOCK_SZ);
+            // Write to the base device. Data is already in the RA buffer. Note
+            // that XOR ops is already handled by the RA thread. We just write
+            // the contents out.
+            int ret = pwrite(base_path_merge_fd_.get(), (char*)read_ahead_buffer + offset, io_size,
+                             source_offset);
+            if (ret < 0 || ret != io_size) {
+                SNAP_LOG(ERROR) << "Failed to write to backing device while merging "
+                                << " at offset: " << source_offset << " io_size: " << io_size;
+                snapuserd_->SetMergeFailed(block_index);
+                return false;
+            }
+
+            offset += io_size;
+            num_ops -= linear_blocks;
+        }
+
+        // Verify all ops are merged
+        CHECK(num_ops == 0);
+
+        // Flush the data
+        if (fsync(base_path_merge_fd_.get()) < 0) {
+            SNAP_LOG(ERROR) << " Failed to fsync merged data";
+            snapuserd_->SetMergeFailed(block_index);
+            return false;
+        }
+
+        // Merge is done and data is on disk. Update the COW Header about
+        // the merge completion
+        if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) {
+            SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+            snapuserd_->SetMergeFailed(block_index);
+            return false;
+        }
+
+        SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge();
+        // Mark the block as merge complete
+        snapuserd_->SetMergeCompleted(block_index);
+
+        // Notify RA thread that the merge thread is ready to merge the next
+        // window
+        snapuserd_->NotifyRAForMergeReady();
+
+        // Get the next block
+        block_index += 1;
+    }
+
+    return true;
+}
+
+bool Worker::Merge() {
+    std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+
+    // Start with Copy and Xor ops
+    if (!MergeOrderedOps(cowop_iter)) {
+        SNAP_LOG(ERROR) << "Merge failed for ordered ops";
+        snapuserd_->MergeFailed();
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "MergeOrderedOps completed...";
+
+    // Replace and Zero ops
+    if (!MergeReplaceZeroOps(cowop_iter)) {
+        SNAP_LOG(ERROR) << "Merge failed for replace/zero ops";
+        snapuserd_->MergeFailed();
+        return false;
+    }
+
+    snapuserd_->MergeCompleted();
+
+    return true;
+}
+
+bool Worker::RunMergeThread() {
+    SNAP_LOG(DEBUG) << "Waiting for merge begin...";
+    if (!snapuserd_->WaitForMergeBegin()) {
+        SNAP_LOG(ERROR) << "Merge terminated early...";
+        return true;
+    }
+
+    SNAP_LOG(INFO) << "Merge starting..";
+
+    if (!Init()) {
+        SNAP_LOG(ERROR) << "Merge thread initialization failed...";
+        return false;
+    }
+
+    if (!Merge()) {
+        return false;
+    }
+
+    CloseFds();
+    reader_->CloseCowFd();
+
+    SNAP_LOG(INFO) << "Merge finish";
+
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
new file mode 100644
index 0000000..0bcf26e
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+ReadAhead::ReadAhead(const std::string& cow_device, const std::string& backing_device,
+                     const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd) {
+    cow_device_ = cow_device;
+    backing_store_device_ = backing_device;
+    misc_name_ = misc_name;
+    snapuserd_ = snapuserd;
+}
+
+void ReadAhead::CheckOverlap(const CowOperation* cow_op) {
+    uint64_t source_block = cow_op->source;
+    uint64_t source_offset = 0;
+    if (cow_op->type == kCowXorOp) {
+        source_block /= BLOCK_SZ;
+        source_offset = cow_op->source % BLOCK_SZ;
+    }
+    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
+        (source_offset > 0 && source_blocks_.count(source_block + 1))) {
+        overlap_ = true;
+    }
+
+    dest_blocks_.insert(source_block);
+    if (source_offset > 0) {
+        dest_blocks_.insert(source_block + 1);
+    }
+    source_blocks_.insert(cow_op->new_block);
+}
+
+int ReadAhead::PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops,
+                                    std::vector<uint64_t>& blocks,
+                                    std::vector<const CowOperation*>& xor_op_vec) {
+    int num_ops = *pending_ops;
+    int nr_consecutive = 0;
+
+    bool is_ops_present = (!RAIterDone() && num_ops);
+
+    if (!is_ops_present) {
+        return nr_consecutive;
+    }
+
+    // Get the first block with offset
+    const CowOperation* cow_op = GetRAOpIter();
+    *source_offset = cow_op->source;
+
+    if (cow_op->type == kCowCopyOp) {
+        *source_offset *= BLOCK_SZ;
+    } else if (cow_op->type == kCowXorOp) {
+        xor_op_vec.push_back(cow_op);
+    }
+
+    RAIterNext();
+    num_ops -= 1;
+    nr_consecutive = 1;
+    blocks.push_back(cow_op->new_block);
+
+    if (!overlap_) {
+        CheckOverlap(cow_op);
+    }
+
+    /*
+     * Find number of consecutive blocks
+     */
+    while (!RAIterDone() && num_ops) {
+        const CowOperation* op = GetRAOpIter();
+        uint64_t next_offset = op->source;
+
+        if (cow_op->type == kCowCopyOp) {
+            next_offset *= BLOCK_SZ;
+        }
+
+        // Check for consecutive blocks
+        if (next_offset != (*source_offset + nr_consecutive * BLOCK_SZ)) {
+            break;
+        }
+
+        if (op->type == kCowXorOp) {
+            xor_op_vec.push_back(op);
+        }
+
+        nr_consecutive += 1;
+        num_ops -= 1;
+        blocks.push_back(op->new_block);
+        RAIterNext();
+
+        if (!overlap_) {
+            CheckOverlap(op);
+        }
+    }
+
+    return nr_consecutive;
+}
+
+bool ReadAhead::ReconstructDataFromCow() {
+    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+    loff_t metadata_offset = 0;
+    loff_t start_data_offset = snapuserd_->GetBufferDataOffset();
+    int num_ops = 0;
+    int total_blocks_merged = 0;
+
+    while (true) {
+        struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+                (char*)metadata_buffer_ + metadata_offset);
+
+        // Done reading metadata
+        if (bm->new_block == 0 && bm->file_offset == 0) {
+            break;
+        }
+
+        loff_t buffer_offset = bm->file_offset - start_data_offset;
+        void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + buffer_offset);
+        read_ahead_buffer_map[bm->new_block] = bufptr;
+        num_ops += 1;
+        total_blocks_merged += 1;
+
+        metadata_offset += sizeof(struct ScratchMetadata);
+    }
+
+    // We are done re-constructing the mapping; however, we need to make sure
+    // all the COW operations to-be merged are present in the re-constructed
+    // mapping.
+    while (!RAIterDone()) {
+        const CowOperation* op = GetRAOpIter();
+        if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
+            num_ops -= 1;
+            RAIterNext();
+            continue;
+        }
+
+        // Verify that we have covered all the ops which were re-constructed
+        // from COW device - These are the ops which are being
+        // re-constructed after crash.
+        if (!(num_ops == 0)) {
+            SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
+                            << " Pending ops: " << num_ops;
+            snapuserd_->ReadAheadIOFailed();
+            return false;
+        }
+
+        break;
+    }
+
+    snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged);
+
+    snapuserd_->FinishReconstructDataFromCow();
+
+    if (!snapuserd_->ReadAheadIOCompleted(true)) {
+        SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+        snapuserd_->ReadAheadIOFailed();
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+    return true;
+}
+
+bool ReadAhead::ReadAheadIOStart() {
+    // Check if the data has to be constructed from the COW file.
+    // This will be true only once during boot up after a crash
+    // during merge.
+    if (snapuserd_->ShouldReconstructDataFromCow()) {
+        return ReconstructDataFromCow();
+    }
+
+    std::vector<uint64_t> blocks;
+
+    int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
+    loff_t buffer_offset = 0;
+    int total_blocks_merged = 0;
+    overlap_ = false;
+    dest_blocks_.clear();
+    source_blocks_.clear();
+    std::vector<const CowOperation*> xor_op_vec;
+
+    auto ra_temp_buffer = std::make_unique<uint8_t[]>(snapuserd_->GetBufferDataSize());
+
+    // Number of ops to be merged in this window. This is a fixed size
+    // except for the last window wherein the number of ops can be less
+    // than the size of the RA window.
+    while (num_ops) {
+        uint64_t source_offset;
+
+        int linear_blocks = PrepareNextReadAhead(&source_offset, &num_ops, blocks, xor_op_vec);
+        if (linear_blocks == 0) {
+            // No more blocks to read
+            SNAP_LOG(DEBUG) << " Read-ahead completed....";
+            break;
+        }
+
+        size_t io_size = (linear_blocks * BLOCK_SZ);
+
+        // Read from the base device consecutive set of blocks in one shot
+        if (!android::base::ReadFullyAtOffset(backing_store_fd_,
+                                              (char*)ra_temp_buffer.get() + buffer_offset, io_size,
+                                              source_offset)) {
+            SNAP_PLOG(ERROR) << "Ordered-op failed. Read from backing store: "
+                             << backing_store_device_ << "at block :" << source_offset / BLOCK_SZ
+                             << " offset :" << source_offset % BLOCK_SZ
+                             << " buffer_offset : " << buffer_offset << " io_size : " << io_size
+                             << " buf-addr : " << read_ahead_buffer_;
+
+            snapuserd_->ReadAheadIOFailed();
+            return false;
+        }
+
+        buffer_offset += io_size;
+        total_blocks_merged += linear_blocks;
+        num_ops -= linear_blocks;
+    }
+
+    // Done with merging ordered ops
+    if (RAIterDone() && total_blocks_merged == 0) {
+        return true;
+    }
+
+    loff_t metadata_offset = 0;
+
+    auto ra_temp_meta_buffer = std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+
+    struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+            (char*)ra_temp_meta_buffer.get() + metadata_offset);
+
+    bm->new_block = 0;
+    bm->file_offset = 0;
+
+    loff_t file_offset = snapuserd_->GetBufferDataOffset();
+
+    loff_t offset = 0;
+    CHECK(blocks.size() == total_blocks_merged);
+
+    size_t xor_index = 0;
+    for (size_t block_index = 0; block_index < blocks.size(); block_index++) {
+        void* bufptr = static_cast<void*>((char*)ra_temp_buffer.get() + offset);
+        uint64_t new_block = blocks[block_index];
+
+        if (xor_index < xor_op_vec.size()) {
+            const CowOperation* xor_op = xor_op_vec[xor_index];
+
+            // Check if this block is an XOR op
+            if (xor_op->new_block == new_block) {
+                // Read the xor'ed data from COW
+                if (!reader_->ReadData(*xor_op, &bufsink_)) {
+                    SNAP_LOG(ERROR)
+                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
+                    snapuserd_->ReadAheadIOFailed();
+                    return false;
+                }
+
+                // Pointer to the data read from base device
+                uint8_t* buffer = reinterpret_cast<uint8_t*>(bufptr);
+                // Get the xor'ed data read from COW device
+                uint8_t* xor_data = reinterpret_cast<uint8_t*>(bufsink_.GetPayloadBufPtr());
+
+                // Retrieve the original data
+                for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) {
+                    buffer[byte_offset] ^= xor_data[byte_offset];
+                }
+
+                // Move to next XOR op
+                xor_index += 1;
+            }
+        }
+
+        offset += BLOCK_SZ;
+        // Track the metadata blocks which are stored in scratch space
+        bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer.get() +
+                                                       metadata_offset);
+
+        bm->new_block = new_block;
+        bm->file_offset = file_offset;
+
+        metadata_offset += sizeof(struct ScratchMetadata);
+        file_offset += BLOCK_SZ;
+    }
+
+    // Verify if all the xor blocks were scanned to retrieve the original data
+    CHECK(xor_index == xor_op_vec.size());
+
+    // This is important - explicitly set the contents to zero. This is used
+    // when re-constructing the data after crash. This indicates end of
+    // reading metadata contents when re-constructing the data
+    bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer.get() +
+                                                   metadata_offset);
+    bm->new_block = 0;
+    bm->file_offset = 0;
+
+    // Wait for the merge to finish for the previous RA window. We shouldn't
+    // be touching the scratch space until merge is complete of previous RA
+    // window. If there is a crash during this time frame, merge should resume
+    // based on the contents of the scratch space.
+    if (!snapuserd_->WaitForMergeReady()) {
+        return false;
+    }
+
+    // Copy the data to scratch space
+    memcpy(metadata_buffer_, ra_temp_meta_buffer.get(), snapuserd_->GetBufferMetadataSize());
+    memcpy(read_ahead_buffer_, ra_temp_buffer.get(), total_blocks_merged * BLOCK_SZ);
+
+    offset = 0;
+    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+    read_ahead_buffer_map.clear();
+
+    for (size_t block_index = 0; block_index < blocks.size(); block_index++) {
+        void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
+        uint64_t new_block = blocks[block_index];
+
+        read_ahead_buffer_map[new_block] = bufptr;
+        offset += BLOCK_SZ;
+    }
+
+    snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged);
+
+    // Flush the data only if we have a overlapping blocks in the region
+    // Notify the Merge thread to resume merging this window
+    if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
+        SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+        snapuserd_->ReadAheadIOFailed();
+        return false;
+    }
+
+    return true;
+}
+
+bool ReadAhead::RunThread() {
+    if (!InitializeFds()) {
+        return false;
+    }
+
+    InitializeBuffer();
+
+    if (!InitReader()) {
+        return false;
+    }
+
+    InitializeRAIter();
+
+    while (!RAIterDone()) {
+        if (!ReadAheadIOStart()) {
+            break;
+        }
+    }
+
+    CloseFds();
+    reader_->CloseCowFd();
+    SNAP_LOG(INFO) << " ReadAhead thread terminating....";
+    return true;
+}
+
+// Initialization
+bool ReadAhead::InitializeFds() {
+    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+    if (backing_store_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+        return false;
+    }
+
+    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+    if (cow_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+        return false;
+    }
+
+    return true;
+}
+
+bool ReadAhead::InitReader() {
+    reader_ = snapuserd_->CloneReaderForWorker();
+
+    if (!reader_->InitForMerge(std::move(cow_fd_))) {
+        return false;
+    }
+    return true;
+}
+
+void ReadAhead::InitializeRAIter() {
+    cowop_iter_ = reader_->GetMergeOpIter();
+}
+
+bool ReadAhead::RAIterDone() {
+    if (cowop_iter_->Done()) {
+        return true;
+    }
+
+    const CowOperation* cow_op = GetRAOpIter();
+
+    if (!IsOrderedOp(*cow_op)) {
+        return true;
+    }
+
+    return false;
+}
+
+void ReadAhead::RAIterNext() {
+    cowop_iter_->Next();
+}
+
+const CowOperation* ReadAhead::GetRAOpIter() {
+    const CowOperation* cow_op = &cowop_iter_->Get();
+    return cow_op;
+}
+
+void ReadAhead::InitializeBuffer() {
+    void* mapped_addr = snapuserd_->GetMappedAddr();
+    // Map the scratch space region into memory
+    metadata_buffer_ =
+            static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
+    read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+    // For xor ops
+    bufsink_.Initialize(PAYLOAD_SIZE);
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
similarity index 66%
copy from fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
copy to fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index 2f87557..a4fd5a0 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -31,7 +31,6 @@
 #include <android-base/scopeguard.h>
 #include <fs_mgr/file_wait.h>
 #include <snapuserd/snapuserd_client.h>
-#include "snapuserd.h"
 #include "snapuserd_server.h"
 
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
@@ -45,26 +44,29 @@
 using android::base::borrowed_fd;
 using android::base::unique_fd;
 
-DaemonOperations SnapuserdServer::Resolveop(std::string& input) {
-    if (input == "init") return DaemonOperations::INIT;
-    if (input == "start") return DaemonOperations::START;
-    if (input == "stop") return DaemonOperations::STOP;
-    if (input == "query") return DaemonOperations::QUERY;
-    if (input == "delete") return DaemonOperations::DELETE;
-    if (input == "detach") return DaemonOperations::DETACH;
-    if (input == "supports") return DaemonOperations::SUPPORTS;
+DaemonOps SnapuserServer::Resolveop(std::string& input) {
+    if (input == "init") return DaemonOps::INIT;
+    if (input == "start") return DaemonOps::START;
+    if (input == "stop") return DaemonOps::STOP;
+    if (input == "query") return DaemonOps::QUERY;
+    if (input == "delete") return DaemonOps::DELETE;
+    if (input == "detach") return DaemonOps::DETACH;
+    if (input == "supports") return DaemonOps::SUPPORTS;
+    if (input == "initiate_merge") return DaemonOps::INITIATE;
+    if (input == "merge_percent") return DaemonOps::PERCENTAGE;
+    if (input == "getstatus") return DaemonOps::GETSTATUS;
 
-    return DaemonOperations::INVALID;
+    return DaemonOps::INVALID;
 }
 
-SnapuserdServer::~SnapuserdServer() {
+SnapuserServer::~SnapuserServer() {
     // Close any client sockets that were added via AcceptClient().
     for (size_t i = 1; i < watched_fds_.size(); i++) {
         close(watched_fds_[i].fd);
     }
 }
 
-std::string SnapuserdServer::GetDaemonStatus() {
+std::string SnapuserServer::GetDaemonStatus() {
     std::string msg = "";
 
     if (IsTerminating())
@@ -75,8 +77,8 @@
     return msg;
 }
 
-void SnapuserdServer::Parsemsg(std::string const& msg, const char delim,
-                               std::vector<std::string>& out) {
+void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
+                              std::vector<std::string>& out) {
     std::stringstream ss(msg);
     std::string s;
 
@@ -85,15 +87,15 @@
     }
 }
 
-void SnapuserdServer::ShutdownThreads() {
-    StopThreads();
+void SnapuserServer::ShutdownThreads() {
+    terminating_ = true;
     JoinAllThreads();
 }
 
-DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd)
+DmUserHandler::DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
     : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
 
-bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
+bool SnapuserServer::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";
@@ -107,7 +109,7 @@
     return true;
 }
 
-bool SnapuserdServer::Recv(android::base::borrowed_fd fd, std::string* data) {
+bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
     char msg[MAX_PACKET_SIZE];
     ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
     if (rv < 0) {
@@ -118,25 +120,25 @@
     return true;
 }
 
-bool SnapuserdServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
+bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
     const char delim = ',';
 
     std::vector<std::string> out;
     Parsemsg(str, delim, out);
-    DaemonOperations op = Resolveop(out[0]);
+    DaemonOps op = Resolveop(out[0]);
 
     switch (op) {
-        case DaemonOperations::INIT: {
+        case DaemonOps::INIT: {
             // Message format:
-            // init,<misc_name>,<cow_device_path>,<backing_device>
+            // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
             //
             // Reads the metadata and send the number of sectors
-            if (out.size() != 4) {
+            if (out.size() != 5) {
                 LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
                 return Sendmsg(fd, "fail");
             }
 
-            auto handler = AddHandler(out[1], out[2], out[3]);
+            auto handler = AddHandler(out[1], out[2], out[3], out[4]);
             if (!handler) {
                 return Sendmsg(fd, "fail");
             }
@@ -144,7 +146,7 @@
             auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
             return Sendmsg(fd, retval);
         }
-        case DaemonOperations::START: {
+        case DaemonOps::START: {
             // Message format:
             // start,<misc_name>
             //
@@ -169,7 +171,7 @@
             }
             return Sendmsg(fd, "success");
         }
-        case DaemonOperations::STOP: {
+        case DaemonOps::STOP: {
             // Message format: stop
             //
             // Stop all the threads gracefully and then shutdown the
@@ -178,7 +180,7 @@
             ShutdownThreads();
             return true;
         }
-        case DaemonOperations::QUERY: {
+        case DaemonOps::QUERY: {
             // Message format: query
             //
             // As part of transition, Second stage daemon will be
@@ -190,23 +192,41 @@
             // be ready to receive control message.
             return Sendmsg(fd, GetDaemonStatus());
         }
-        case DaemonOperations::DELETE: {
+        case DaemonOps::DELETE: {
             // Message format:
             // delete,<misc_name>
             if (out.size() != 2) {
                 LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
                 return Sendmsg(fd, "fail");
             }
+            {
+                std::lock_guard<std::mutex> lock(lock_);
+                auto iter = FindHandler(&lock, out[1]);
+                if (iter == dm_users_.end()) {
+                    // After merge is completed, we swap dm-user table with
+                    // the underlying dm-linear base device. Hence, worker
+                    // threads would have terminted and was removed from
+                    // the list.
+                    LOG(DEBUG) << "Could not find handler: " << out[1];
+                    return Sendmsg(fd, "success");
+                }
+
+                if (!(*iter)->ThreadTerminated()) {
+                    (*iter)->snapuserd()->NotifyIOTerminated();
+                }
+            }
             if (!RemoveAndJoinHandler(out[1])) {
                 return Sendmsg(fd, "fail");
             }
             return Sendmsg(fd, "success");
         }
-        case DaemonOperations::DETACH: {
+        case DaemonOps::DETACH: {
+            std::lock_guard<std::mutex> lock(lock_);
+            TerminateMergeThreads(&lock);
             terminating_ = true;
             return true;
         }
-        case DaemonOperations::SUPPORTS: {
+        case DaemonOps::SUPPORTS: {
             if (out.size() != 2) {
                 LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
                 return Sendmsg(fd, "fail");
@@ -216,6 +236,52 @@
             }
             return Sendmsg(fd, "fail");
         }
+        case DaemonOps::INITIATE: {
+            if (out.size() != 2) {
+                LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
+                return Sendmsg(fd, "fail");
+            }
+            if (out[0] == "initiate_merge") {
+                std::lock_guard<std::mutex> lock(lock_);
+                auto iter = FindHandler(&lock, out[1]);
+                if (iter == dm_users_.end()) {
+                    LOG(ERROR) << "Could not find handler: " << out[1];
+                    return Sendmsg(fd, "fail");
+                }
+
+                if (!StartMerge(*iter)) {
+                    return Sendmsg(fd, "fail");
+                }
+
+                return Sendmsg(fd, "success");
+            }
+            return Sendmsg(fd, "fail");
+        }
+        case DaemonOps::PERCENTAGE: {
+            std::lock_guard<std::mutex> lock(lock_);
+            double percentage = GetMergePercentage(&lock);
+
+            return Sendmsg(fd, std::to_string(percentage));
+        }
+        case DaemonOps::GETSTATUS: {
+            // Message format:
+            // getstatus,<misc_name>
+            if (out.size() != 2) {
+                LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+                return Sendmsg(fd, "snapshot-merge-failed");
+            }
+            {
+                std::lock_guard<std::mutex> lock(lock_);
+                auto iter = FindHandler(&lock, out[1]);
+                if (iter == dm_users_.end()) {
+                    LOG(ERROR) << "Could not find handler: " << out[1];
+                    return Sendmsg(fd, "snapshot-merge-failed");
+                }
+
+                std::string merge_status = GetMergeStatus(*iter);
+                return Sendmsg(fd, merge_status);
+            }
+        }
         default: {
             LOG(ERROR) << "Received unknown message type from client";
             Sendmsg(fd, "fail");
@@ -224,7 +290,7 @@
     }
 }
 
-void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
+void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
     LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
     handler->snapuserd()->SetSocketPresent(is_socket_present_);
@@ -241,6 +307,8 @@
 
     {
         std::lock_guard<std::mutex> lock(lock_);
+        num_partitions_merge_complete_ += 1;
+        handler->SetThreadTerminated();
         auto iter = FindHandler(&lock, handler->misc_name());
         if (iter == dm_users_.end()) {
             // RemoveAndJoinHandler() already removed us from the list, and is
@@ -265,10 +333,11 @@
         // WaitForDelete() is called, the handler is either in the list, or
         // it's not and its resources are guaranteed to be freed.
         handler->FreeResources();
+        dm_users_.erase(iter);
     }
 }
 
-bool SnapuserdServer::Start(const std::string& socketname) {
+bool SnapuserServer::Start(const std::string& socketname) {
     bool start_listening = true;
 
     sockfd_.reset(android_get_control_socket(socketname.c_str()));
@@ -284,7 +353,7 @@
     return StartWithSocket(start_listening);
 }
 
-bool SnapuserdServer::StartWithSocket(bool start_listening) {
+bool SnapuserServer::StartWithSocket(bool start_listening) {
     if (start_listening && listen(sockfd_.get(), 4) < 0) {
         PLOG(ERROR) << "listen socket failed";
         return false;
@@ -305,7 +374,7 @@
     return true;
 }
 
-bool SnapuserdServer::Run() {
+bool SnapuserServer::Run() {
     LOG(INFO) << "Now listening on snapuserd socket";
 
     while (!IsTerminating()) {
@@ -337,7 +406,7 @@
     return true;
 }
 
-void SnapuserdServer::JoinAllThreads() {
+void SnapuserServer::JoinAllThreads() {
     // Acquire the thread list within the lock.
     std::vector<std::shared_ptr<DmUserHandler>> dm_users;
     {
@@ -352,14 +421,14 @@
     }
 }
 
-void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
+void SnapuserServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
     struct pollfd p = {};
     p.fd = fd.get();
     p.events = events;
     watched_fds_.emplace_back(std::move(p));
 }
 
-void SnapuserdServer::AcceptClient() {
+void SnapuserServer::AcceptClient() {
     int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC));
     if (fd < 0) {
         PLOG(ERROR) << "accept4 failed";
@@ -369,7 +438,7 @@
     AddWatchedFd(fd, POLLIN);
 }
 
-bool SnapuserdServer::HandleClient(android::base::borrowed_fd fd, int revents) {
+bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
     if (revents & POLLHUP) {
         LOG(DEBUG) << "Snapuserd client disconnected";
         return false;
@@ -386,16 +455,18 @@
     return true;
 }
 
-void SnapuserdServer::Interrupt() {
+void SnapuserServer::Interrupt() {
     // Force close the socket so poll() fails.
     sockfd_ = {};
     SetTerminating();
 }
 
-std::shared_ptr<DmUserHandler> SnapuserdServer::AddHandler(const std::string& misc_name,
-                                                           const std::string& cow_device_path,
-                                                           const std::string& backing_device) {
-    auto snapuserd = std::make_shared<Snapuserd>(misc_name, cow_device_path, backing_device);
+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) {
+    auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
+                                                       base_path_merge);
     if (!snapuserd->InitCowDevice()) {
         LOG(ERROR) << "Failed to initialize Snapuserd";
         return nullptr;
@@ -418,7 +489,7 @@
     return handler;
 }
 
-bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
+bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
     if (handler->snapuserd()->IsAttached()) {
         LOG(ERROR) << "Handler already attached";
         return false;
@@ -426,12 +497,22 @@
 
     handler->snapuserd()->AttachControlDevice();
 
-    handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler));
+    handler->thread() = std::thread(std::bind(&SnapuserServer::RunThread, this, handler));
     return true;
 }
 
-auto SnapuserdServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                  const std::string& misc_name) -> HandlerList::iterator {
+bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
+    if (!handler->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
+        return false;
+    }
+
+    handler->snapuserd()->InitiateMerge();
+    return true;
+}
+
+auto SnapuserServer::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++) {
@@ -442,7 +523,51 @@
     return dm_users_.end();
 }
 
-bool SnapuserdServer::RemoveAndJoinHandler(const std::string& misc_name) {
+void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
+    CHECK(proof_of_lock);
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        if (!(*iter)->ThreadTerminated()) {
+            (*iter)->snapuserd()->NotifyIOTerminated();
+        }
+    }
+}
+
+std::string SnapuserServer::GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler) {
+    return handler->snapuserd()->GetMergeStatus();
+}
+
+double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
+    CHECK(proof_of_lock);
+    double percentage = 0.0;
+    int n = 0;
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        auto& th = (*iter)->thread();
+        if (th.joinable()) {
+            // Merge percentage by individual partitions wherein merge is still
+            // in-progress
+            percentage += (*iter)->snapuserd()->GetMergePercentage();
+            n += 1;
+        }
+    }
+
+    // Calculate final merge including those partitions where merge was already
+    // completed - num_partitions_merge_complete_ will track them when each
+    // thread exists in RunThread.
+    int total_partitions = n + num_partitions_merge_complete_;
+
+    if (total_partitions) {
+        percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
+    }
+
+    LOG(DEBUG) << "Merge %: " << percentage
+               << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
+               << " total_partitions: " << total_partitions << " n: " << n;
+    return percentage;
+}
+
+bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
     std::shared_ptr<DmUserHandler> handler;
     {
         std::lock_guard<std::mutex> lock(lock_);
@@ -463,7 +588,7 @@
     return true;
 }
 
-bool SnapuserdServer::WaitForSocket() {
+bool SnapuserServer::WaitForSocket() {
     auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
 
     auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
@@ -517,7 +642,7 @@
     return Run();
 }
 
-bool SnapuserdServer::RunForSocketHandoff() {
+bool SnapuserServer::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
new file mode 100644
index 0000000..e93621c
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <poll.h>
+
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <future>
+#include <iostream>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+static constexpr uint32_t MAX_PACKET_SIZE = 512;
+
+enum class DaemonOps {
+    INIT,
+    START,
+    QUERY,
+    STOP,
+    DELETE,
+    DETACH,
+    SUPPORTS,
+    INITIATE,
+    PERCENTAGE,
+    GETSTATUS,
+    INVALID,
+};
+
+class DmUserHandler {
+  public:
+    explicit DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
+
+    void FreeResources() {
+        // Each worker thread holds a reference to snapuserd.
+        // Clear them so that all the resources
+        // held by snapuserd is released
+        if (snapuserd_) {
+            snapuserd_->FreeResources();
+            snapuserd_ = nullptr;
+        }
+    }
+    const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
+    std::thread& thread() { return thread_; }
+
+    const std::string& misc_name() const { return misc_name_; }
+    bool ThreadTerminated() { return thread_terminated_; }
+    void SetThreadTerminated() { thread_terminated_ = true; }
+
+  private:
+    std::thread thread_;
+    std::shared_ptr<SnapshotHandler> snapuserd_;
+    std::string misc_name_;
+    bool thread_terminated_ = false;
+};
+
+class SnapuserServer {
+  private:
+    android::base::unique_fd sockfd_;
+    bool terminating_;
+    volatile bool received_socket_signal_ = false;
+    std::vector<struct pollfd> watched_fds_;
+    bool is_socket_present_ = false;
+    int num_partitions_merge_complete_ = 0;
+
+    std::mutex lock_;
+
+    using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
+    HandlerList dm_users_;
+
+    void AddWatchedFd(android::base::borrowed_fd fd, int events);
+    void AcceptClient();
+    bool HandleClient(android::base::borrowed_fd fd, int revents);
+    bool Recv(android::base::borrowed_fd fd, std::string* data);
+    bool Sendmsg(android::base::borrowed_fd fd, const std::string& msg);
+    bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
+
+    void ShutdownThreads();
+    bool RemoveAndJoinHandler(const std::string& control_device);
+    DaemonOps Resolveop(std::string& input);
+    std::string GetDaemonStatus();
+    void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
+
+    bool IsTerminating() { return terminating_; }
+
+    void RunThread(std::shared_ptr<DmUserHandler> handler);
+    void JoinAllThreads();
+    bool StartWithSocket(bool start_listening);
+
+    // Find a DmUserHandler within a lock.
+    HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                      const std::string& misc_name);
+
+    double GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock);
+    void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
+
+  public:
+    SnapuserServer() { terminating_ = false; }
+    ~SnapuserServer();
+
+    bool Start(const std::string& socketname);
+    bool Run();
+    void Interrupt();
+    bool RunForSocketHandoff();
+    bool WaitForSocket();
+
+    std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
+                                              const std::string& cow_device_path,
+                                              const std::string& backing_device,
+                                              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);
+
+    void SetTerminating() { terminating_ = true; }
+    void ReceivedSocketSignal() { received_socket_signal_ = true; }
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
new file mode 100644
index 0000000..6c91fde
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+/*
+ * Readahead is used to optimize the merge of COPY and XOR Ops.
+ *
+ * We create a scratch space of 2MB to store the read-ahead data in the COW
+ * device.
+ *
+ *      +-----------------------+
+ *      |     Header (fixed)    |
+ *      +-----------------------+
+ *      |    Scratch space      |  <-- 2MB
+ *      +-----------------------+
+ *
+ *      Scratch space is as follows:
+ *
+ *      +-----------------------+
+ *      |       Metadata        | <- 4k page
+ *      +-----------------------+
+ *      |       Metadata        | <- 4k page
+ *      +-----------------------+
+ *      |                       |
+ *      |    Read-ahead data    |
+ *      |                       |
+ *      +-----------------------+
+ *
+ *
+ * * ===================================================================
+ *
+ * Example:
+ *
+ * We have 6 copy operations to be executed in OTA. Update-engine
+ * will write to COW file as follows:
+ *
+ * Op-1: 20 -> 23
+ * Op-2: 19 -> 22
+ * Op-3: 18 -> 21
+ * Op-4: 17 -> 20
+ * Op-5: 16 -> 19
+ * Op-6: 15 -> 18
+ *
+ * Read-ahead thread will read all the 6 source blocks and store the data in the
+ * scratch space. Metadata will contain the destination block numbers. Thus,
+ * scratch space will look something like this:
+ *
+ * +--------------+
+ * | Block   23   |
+ * | offset - 1   |
+ * +--------------+
+ * | Block   22   |
+ * | offset - 2   |
+ * +--------------+
+ * | Block   21   |
+ * | offset - 3   |
+ * +--------------+
+ *    ...
+ *    ...
+ * +--------------+
+ * | Data-Block 20| <-- offset - 1
+ * +--------------+
+ * | Data-Block 19| <-- offset - 2
+ * +--------------+
+ * | Data-Block 18| <-- offset - 3
+ * +--------------+
+ *     ...
+ *     ...
+ *
+ * ====================================================================
+ *
+ *
+ *  Read-ahead thread will process the COW Ops in fixed set. Consider
+ *  the following example:
+ *
+ *  +--------------------------+
+ *  |op-1|op-2|op-3|....|op-510|
+ *  +--------------------------+
+ *
+ *  <------ One RA Block ------>
+ *
+ *  RA thread will read 510 ordered COW ops at a time and will store
+ *  the data in the scratch space.
+ *
+ *  RA thread and Merge thread will go lock-step wherein RA thread
+ *  will make sure that 510 COW operation data are read upfront
+ *  and is in memory. Thus, when merge thread will pick up the data
+ *  directly from memory and write it back to base device.
+ *
+ *
+ *  +--------------------------+------------------------------------+
+ *  |op-1|op-2|op-3|....|op-510|op-511|op-512|op-513........|op-1020|
+ *  +--------------------------+------------------------------------+
+ *
+ *  <------Merge 510 Blocks----><-Prepare 510 blocks for merge by RA->
+ *           ^                                  ^
+ *           |                                  |
+ *      Merge thread                        RA thread
+ *
+ * Both Merge and RA thread will strive to work in parallel.
+ *
+ * ===========================================================================
+ *
+ * State transitions and communication between RA thread and Merge thread:
+ *
+ *  Merge Thread                                      RA Thread
+ *  ----------------------------------------------------------------------------
+ *
+ *          |                                         |
+ *    WAIT for RA Block N                     READ one RA Block (N)
+ *        for merge                                   |
+ *          |                                         |
+ *          |                                         |
+ *          <--------------MERGE BEGIN--------READ Block N done(copy to scratch)
+ *          |                                         |
+ *          |                                         |
+ *    Merge Begin Block N                     READ one RA BLock (N+1)
+ *          |                                         |
+ *          |                                         |
+ *          |                                  READ done. Wait for merge complete
+ *          |                                         |
+ *          |                                        WAIT
+ *          |                                         |
+ *    Merge done Block N                              |
+ *          ----------------MERGE READY-------------->|
+ *    WAIT for RA Block N+1                     Copy RA Block (N+1)
+ *        for merge                              to scratch space
+ *          |                                         |
+ *          <---------------MERGE BEGIN---------BLOCK N+1 Done
+ *          |                                         |
+ *          |                                         |
+ *    Merge Begin Block N+1                   READ one RA BLock (N+2)
+ *          |                                         |
+ *          |                                         |
+ *          |                                  READ done. Wait for merge complete
+ *          |                                         |
+ *          |                                        WAIT
+ *          |                                         |
+ *    Merge done Block N+1                            |
+ *          ----------------MERGE READY-------------->|
+ *    WAIT for RA Block N+2                     Copy RA Block (N+2)
+ *        for merge                              to scratch space
+ *          |                                         |
+ *          <---------------MERGE BEGIN---------BLOCK N+2 Done
+ */
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+// This is invoked once primarily by update-engine to initiate
+// the merge
+void SnapshotHandler::InitiateMerge() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        merge_initiated_ = true;
+
+        // If there are only REPLACE ops to be merged, then we need
+        // to explicitly set the state to MERGE_BEGIN as there
+        // is no read-ahead thread
+        if (!ra_thread_) {
+            io_state_ = MERGE_IO_TRANSITION::MERGE_BEGIN;
+        }
+    }
+    cv.notify_all();
+}
+
+// Invoked by Merge thread - Waits on RA thread to resume merging. Will
+// be waken up RA thread.
+bool SnapshotHandler::WaitForMergeBegin() {
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!MergeInitiated()) {
+            cv.wait(lock);
+
+            if (io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+                io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+                return false;
+            }
+        }
+
+        while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_BEGIN ||
+                 io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+                 io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+            cv.wait(lock);
+        }
+
+        if (io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+            io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+            return false;
+        }
+
+        return true;
+    }
+}
+
+// Invoked by RA thread - Flushes the RA block to scratch space if necessary
+// and then notifies the merge thread to resume merging
+bool SnapshotHandler::ReadAheadIOCompleted(bool sync) {
+    if (sync) {
+        // Flush the entire buffer region
+        int ret = msync(mapped_addr_, total_mapped_addr_length_, MS_SYNC);
+        if (ret < 0) {
+            PLOG(ERROR) << "msync failed after ReadAheadIOCompleted: " << ret;
+            return false;
+        }
+
+        // Metadata and data are synced. Now, update the state.
+        // We need to update the state after flushing data; if there is a crash
+        // when read-ahead IO is in progress, the state of data in the COW file
+        // is unknown. kCowReadAheadDone acts as a checkpoint wherein the data
+        // in the scratch space is good and during next reboot, read-ahead thread
+        // can safely re-construct the data.
+        struct BufferState* ra_state = GetBufferState();
+        ra_state->read_ahead_state = kCowReadAheadDone;
+
+        ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+        if (ret < 0) {
+            PLOG(ERROR) << "msync failed to flush Readahead completion state...";
+            return false;
+        }
+    }
+
+    // Notify the merge thread to resume merging
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        if (io_state_ != MERGE_IO_TRANSITION::IO_TERMINATED &&
+            io_state_ != MERGE_IO_TRANSITION::MERGE_FAILED) {
+            io_state_ = MERGE_IO_TRANSITION::MERGE_BEGIN;
+        }
+    }
+
+    cv.notify_all();
+    return true;
+}
+
+// Invoked by RA thread - Waits for merge thread to finish merging
+// RA Block N - RA thread would be ready will with Block N+1 but
+// will wait to merge thread to finish Block N. Once Block N
+// is merged, RA thread will be woken up by Merge thread and will
+// flush the data of Block N+1 to scratch space
+bool SnapshotHandler::WaitForMergeReady() {
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_READY ||
+                 io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+                 io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+                 io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+            cv.wait(lock);
+        }
+
+        // Check if merge failed
+        if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+            io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+            io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+            return false;
+        }
+        return true;
+    }
+}
+
+// Invoked by Merge thread - Notify RA thread about Merge completion
+// for Block N and wake up
+void SnapshotHandler::NotifyRAForMergeReady() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        if (io_state_ != MERGE_IO_TRANSITION::IO_TERMINATED &&
+            io_state_ != MERGE_IO_TRANSITION::READ_AHEAD_FAILURE) {
+            io_state_ = MERGE_IO_TRANSITION::MERGE_READY;
+        }
+    }
+
+    cv.notify_all();
+}
+
+// The following transitions are mostly in the failure paths
+void SnapshotHandler::MergeFailed() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = MERGE_IO_TRANSITION::MERGE_FAILED;
+    }
+
+    cv.notify_all();
+}
+
+void SnapshotHandler::MergeCompleted() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = MERGE_IO_TRANSITION::MERGE_COMPLETE;
+    }
+
+    cv.notify_all();
+}
+
+// This is invoked by worker threads.
+//
+// Worker threads are terminated either by two scenarios:
+//
+// 1: If dm-user device is destroyed
+// 2: We had an I/O failure when reading root partitions
+//
+// In case (1), this would be a graceful shutdown. In this case, merge
+// thread and RA thread should have _already_ terminated by this point. We will be
+// destroying the dm-user device only _after_ merge is completed.
+//
+// In case (2), if merge thread had started, then it will be
+// continuing to merge; however, since we had an I/O failure and the
+// I/O on root partitions are no longer served, we will terminate the
+// merge.
+//
+// This functions is about handling case (2)
+void SnapshotHandler::NotifyIOTerminated() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = MERGE_IO_TRANSITION::IO_TERMINATED;
+    }
+
+    cv.notify_all();
+}
+
+bool SnapshotHandler::IsIOTerminated() {
+    std::lock_guard<std::mutex> lock(lock_);
+    return (io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED);
+}
+
+// Invoked by RA thread
+void SnapshotHandler::ReadAheadIOFailed() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = MERGE_IO_TRANSITION::READ_AHEAD_FAILURE;
+    }
+
+    cv.notify_all();
+}
+
+void SnapshotHandler::WaitForMergeComplete() {
+    std::unique_lock<std::mutex> lock(lock_);
+    while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+             io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+             io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+        cv.wait(lock);
+    }
+}
+
+std::string SnapshotHandler::GetMergeStatus() {
+    bool merge_not_initiated = false;
+    bool merge_failed = false;
+
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        if (!MergeInitiated()) {
+            merge_not_initiated = true;
+        }
+
+        if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED) {
+            merge_failed = true;
+        }
+    }
+
+    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+    bool merge_complete = (ch->num_merge_ops == reader_->get_num_total_data_ops());
+
+    if (merge_not_initiated) {
+        // Merge was not initiated yet; however, we have merge completion
+        // recorded in the COW Header. This can happen if the device was
+        // rebooted during merge. During next reboot, libsnapshot will
+        // query the status and if the merge is completed, then snapshot-status
+        // file will be deleted
+        if (merge_complete) {
+            return "snapshot-merge-complete";
+        }
+
+        // Return the state as "snapshot". If the device was rebooted during
+        // merge, we will return the status as "snapshot". This is ok, as
+        // libsnapshot will explicitly resume the merge. This is slightly
+        // different from kernel snapshot wherein once the snapshot was switched
+        // to merge target, during next boot, we immediately switch to merge
+        // target. We don't do that here because, during first stage init, we
+        // don't want to initiate the merge. The problem is that we have daemon
+        // transition between first and second stage init. If the merge was
+        // started, then we will have to quiesce the merge before switching
+        // the dm tables. Instead, we just wait until second stage daemon is up
+        // before resuming the merge.
+        return "snapshot";
+    }
+
+    if (merge_failed) {
+        return "snapshot-merge-failed";
+    }
+
+    // Merge complete
+    if (merge_complete) {
+        return "snapshot-merge-complete";
+    }
+
+    // Merge is in-progress
+    return "snapshot-merge";
+}
+
+//========== End of Read-ahead state transition functions ====================
+
+/*
+ * Root partitions are mounted off dm-user and the I/O's are served
+ * by snapuserd worker threads.
+ *
+ * When there is an I/O request to be served by worker threads, we check
+ * if the corresponding sector is "changed" due to OTA by doing a lookup.
+ * If the lookup succeeds then the sector has been changed and that can
+ * either fall into 4 COW operations viz: COPY, XOR, REPLACE and ZERO.
+ *
+ * For the case of REPLACE and ZERO ops, there is not much of a concern
+ * as there is no dependency between blocks. Hence all the I/O request
+ * mapped to these two COW operations will be served by reading the COW device.
+ *
+ * However, COPY and XOR ops are tricky. Since the merge operations are
+ * in-progress, we cannot just go and read from the source device. We need
+ * to be in sync with the state of the merge thread before serving the I/O.
+ *
+ * Given that we know merge thread processes a set of COW ops called as RA
+ * Blocks - These set of COW ops are fixed size wherein each Block comprises
+ * of 510 COW ops.
+ *
+ *  +--------------------------+
+ *  |op-1|op-2|op-3|....|op-510|
+ *  +--------------------------+
+ *
+ *  <------ Merge Group Block N ------>
+ *
+ * Thus, a Merge Group Block N, will fall into one of these states and will
+ * transition the states in the following order:
+ *
+ * 1: GROUP_MERGE_PENDING
+ * 2: GROUP_MERGE_RA_READY
+ * 2: GROUP_MERGE_IN_PROGRESS
+ * 3: GROUP_MERGE_COMPLETED
+ * 4: GROUP_MERGE_FAILED
+ *
+ * Let's say that we have the I/O request from dm-user whose sector gets mapped
+ * to a COPY operation with op-10 in the above "Merge Group Block N".
+ *
+ * 1: If the Group is in "GROUP_MERGE_PENDING" state:
+ *
+ *    Just read the data from source block based on COW op->source field. Note,
+ *    that we will take a ref count on "Block N". This ref count will prevent
+ *    merge thread to begin merging if there are any pending I/Os. Once the I/O
+ *    is completed, ref count on "Group N" is decremented. Merge thread will
+ *    resume merging "Group N" if there are no pending I/Os.
+ *
+ * 2: If the Group is in "GROUP_MERGE_IN_PROGRESS" or "GROUP_MERGE_RA_READY" state:
+ *
+ *    When the merge thread is ready to process a "Group", it will first move
+ *    the state to GROUP_MERGE_PENDING -> GROUP_MERGE_RA_READY. From this point
+ *    onwards, I/O will be served from Read-ahead buffer. However, merge thread
+ *    cannot start merging this "Group" immediately. If there were any in-flight
+ *    I/O requests, merge thread should wait and allow those I/O's to drain.
+ *    Once all the in-flight I/O's are completed, merge thread will move the
+ *    state from "GROUP_MERGE_RA_READY" -> "GROUP_MERGE_IN_PROGRESS". I/O will
+ *    be continued to serve from Read-ahead buffer during the entire duration
+ *    of the merge.
+ *
+ *    See SetMergeInProgress().
+ *
+ * 3: If the Group is in "GROUP_MERGE_COMPLETED" state:
+ *
+ *    This is straightforward. We just read the data directly from "Base"
+ *    device. We should not be reading the COW op->source field.
+ *
+ * 4: If the Block is in "GROUP_MERGE_FAILED" state:
+ *
+ *    Terminate the I/O with an I/O error as we don't know which "op" in the
+ *    "Group" failed.
+ *
+ *    Transition ensures that the I/O from root partitions are never made to
+ *    wait and are processed immediately. Thus the state transition for any
+ *    "Group" is:
+ *
+ *    GROUP_MERGE_PENDING
+ *          |
+ *          |
+ *          v
+ *    GROUP_MERGE_RA_READY
+ *          |
+ *          |
+ *          v
+ *    GROUP_MERGE_IN_PROGRESS
+ *          |
+ *          |----------------------------(on failure)
+ *          |                           |
+ *          v                           v
+ *    GROUP_MERGE_COMPLETED           GROUP_MERGE_FAILED
+ *
+ */
+
+// Invoked by Merge thread
+void SnapshotHandler::SetMergeCompleted(size_t ra_index) {
+    MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+    {
+        std::lock_guard<std::mutex> lock(blk_state->m_lock);
+
+        CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS);
+        CHECK(blk_state->num_ios_in_progress == 0);
+
+        // Merge is complete - All I/O henceforth should be read directly
+        // from base device
+        blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED;
+    }
+}
+
+// Invoked by Merge thread. This is called just before the beginning
+// of merging a given Block of 510 ops. If there are any in-flight I/O's
+// from dm-user then wait for them to complete.
+void SnapshotHandler::SetMergeInProgress(size_t ra_index) {
+    MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+    {
+        std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+        CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
+
+        // First set the state to RA_READY so that in-flight I/O will drain
+        // and any new I/O will start reading from RA buffer
+        blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_RA_READY;
+
+        // Wait if there are any in-flight I/O's - we cannot merge at this point
+        while (!(blk_state->num_ios_in_progress == 0)) {
+            blk_state->m_cv.wait(lock);
+        }
+
+        blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS;
+    }
+}
+
+// Invoked by Merge thread on failure
+void SnapshotHandler::SetMergeFailed(size_t ra_index) {
+    MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+    {
+        std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+        blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_FAILED;
+    }
+}
+
+// Invoked by worker threads when I/O is complete on a "MERGE_PENDING"
+// Block. If there are no more in-flight I/Os, wake up merge thread
+// to resume merging.
+void SnapshotHandler::NotifyIOCompletion(uint64_t new_block) {
+    auto it = block_to_ra_index_.find(new_block);
+    CHECK(it != block_to_ra_index_.end()) << " invalid block: " << new_block;
+
+    bool pending_ios = true;
+
+    int ra_index = it->second;
+    MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+    {
+        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;
+        }
+    }
+
+    // Give a chance to merge-thread to resume merge
+    // as there are no pending I/O.
+    if (!pending_ios) {
+        blk_state->m_cv.notify_all();
+    }
+}
+
+bool SnapshotHandler::GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block,
+                                  void* buffer) {
+    if (!lock->owns_lock()) {
+        SNAP_LOG(ERROR) << "GetRABuffer - Lock not held";
+        return false;
+    }
+    std::unordered_map<uint64_t, void*>::iterator it = read_ahead_buffer_map_.find(block);
+
+    if (it == read_ahead_buffer_map_.end()) {
+        SNAP_LOG(ERROR) << "Block: " << block << " not found in RA buffer";
+        return false;
+    }
+
+    memcpy(buffer, it->second, BLOCK_SZ);
+    return true;
+}
+
+// Invoked by worker threads in the I/O path. This is called when a sector
+// is mapped to a COPY/XOR COW op.
+MERGE_GROUP_STATE SnapshotHandler::ProcessMergingBlock(uint64_t new_block, void* buffer) {
+    auto it = block_to_ra_index_.find(new_block);
+    if (it == block_to_ra_index_.end()) {
+        return MERGE_GROUP_STATE::GROUP_INVALID;
+    }
+
+    int ra_index = it->second;
+    MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+    {
+        std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+        MERGE_GROUP_STATE state = blk_state->merge_state_;
+        switch (state) {
+            case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
+                blk_state->num_ios_in_progress += 1;  // ref count
+                [[fallthrough]];
+            }
+            case MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED: {
+                [[fallthrough]];
+            }
+            case MERGE_GROUP_STATE::GROUP_MERGE_FAILED: {
+                return state;
+            }
+            // Fetch the data from RA buffer.
+            case MERGE_GROUP_STATE::GROUP_MERGE_RA_READY: {
+                [[fallthrough]];
+            }
+            case MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS: {
+                if (!GetRABuffer(&lock, new_block, buffer)) {
+                    return MERGE_GROUP_STATE::GROUP_INVALID;
+                }
+                return state;
+            }
+            default: {
+                return MERGE_GROUP_STATE::GROUP_INVALID;
+            }
+        }
+    }
+}
+
+}  // 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/healthd/Android.bp b/healthd/Android.bp
index ec47f68..eaa8e5b 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -106,6 +106,7 @@
 
 cc_library_static {
     name: "libhealthd_draw",
+    vendor_available: true,
     export_include_dirs: ["."],
     static_libs: [
         "libcharger_sysprop",
@@ -117,24 +118,34 @@
     header_libs: ["libbatteryservice_headers"],
 
     srcs: ["healthd_draw.cpp"],
+
+    target: {
+        vendor: {
+            exclude_static_libs: [
+                "libcharger_sysprop",
+            ],
+        },
+    },
 }
 
 cc_library_static {
-    name: "libhealthd_charger",
-    local_include_dirs: ["include"],
-    export_include_dirs: [".", "include"],
+    name: "libhealthd_charger_ui",
+    vendor_available: true,
+    export_include_dirs: [
+        "include",
+        "include_charger",
+    ],
 
     static_libs: [
-        "android.hardware.health@1.0-convert",
+        "android.hardware.health-V1-ndk",
+        "android.hardware.health-translate-ndk",
         "libcharger_sysprop",
         "libhealthd_draw",
         "libhealthloop",
-        "libhealth2impl",
         "libminui",
     ],
 
     shared_libs: [
-        "android.hardware.health@2.1",
         "libbase",
         "libcutils",
         "liblog",
@@ -143,14 +154,60 @@
         "libutils",
     ],
 
+    header_libs: [
+        "libhealthd_headers",
+    ],
+
+    export_static_lib_headers: [
+        "android.hardware.health-V1-ndk",
+    ],
+
     srcs: [
         "healthd_mode_charger.cpp",
         "AnimationParser.cpp",
     ],
+
+    target: {
+        vendor: {
+            exclude_static_libs: [
+                "libcharger_sysprop",
+            ],
+        },
+    },
+}
+
+cc_library_static {
+    name: "libhealthd_charger",
+    export_include_dirs: [
+        "include",
+        "include_charger",
+    ],
+
+    static_libs: [
+        "android.hardware.health@1.0-convert",
+        "libcharger_sysprop",
+        "libhealth2impl",
+        "libhealthd_charger_ui",
+    ],
+
+    shared_libs: [
+        "android.hardware.health@2.1",
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+
+    srcs: [
+        "healthd_mode_charger_hidl.cpp",
+    ],
 }
 
 cc_defaults {
     name: "charger_defaults",
+    local_include_dirs: [
+        "include_charger",
+    ],
 
     cflags: [
         "-Wall",
@@ -174,6 +231,7 @@
     static_libs: [
         // common
         "android.hardware.health@1.0-convert",
+        "android.hardware.health-V1-ndk",
         "libbatterymonitor",
         "libcharger_sysprop",
         "libhealthd_charger_nops",
@@ -183,6 +241,7 @@
         // system charger only
         "libhealthd_draw",
         "libhealthd_charger",
+        "libhealthd_charger_ui",
         "libminui",
         "libsuspend",
     ],
@@ -209,6 +268,7 @@
             exclude_static_libs: [
                 "libhealthd_draw",
                 "libhealthd_charger",
+                "libhealthd_charger_ui",
                 "libminui",
                 "libsuspend",
             ],
@@ -265,3 +325,29 @@
         "system_core_charger_res_images_battery_scale.png",
     ],
 }
+
+// /vendor/etc/res/images/charger/battery_fail.png
+prebuilt_etc {
+    name: "system_core_charger_res_images_battery_fail.png_default_vendor",
+    src: "images/battery_fail.png",
+    relative_install_path: "res/images/charger/default",
+    vendor: true,
+    filename: "battery_fail.png",
+}
+
+// /vendor/etc/res/images/charger/battery_scale.png
+prebuilt_etc {
+    name: "system_core_charger_res_images_battery_scale.png_default_vendor",
+    src: "images/battery_scale.png",
+    relative_install_path: "res/images/charger/default",
+    vendor: true,
+    filename: "battery_scale.png",
+}
+
+phony {
+    name: "charger_res_images_vendor",
+    required: [
+        "system_core_charger_res_images_battery_fail.png_default_vendor",
+        "system_core_charger_res_images_battery_scale.png_default_vendor",
+    ],
+}
diff --git a/healthd/charger.cpp b/healthd/charger.cpp
index d03978d..73e04fe 100644
--- a/healthd/charger.cpp
+++ b/healthd/charger.cpp
@@ -15,9 +15,9 @@
  */
 
 #include <android-base/logging.h>
+#include <charger.sysprop.h>
 
-#include "charger.sysprop.h"
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
 #include "healthd_mode_charger_nops.h"
 
 #ifndef CHARGER_FORCE_NO_UI
diff --git a/healthd/charger_test.cpp b/healthd/charger_test.cpp
index e0bde68..dc5c459 100644
--- a/healthd/charger_test.cpp
+++ b/healthd/charger_test.cpp
@@ -31,7 +31,7 @@
 #include <health/utils.h>
 #include <health2impl/Health.h>
 
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
 
 using android::hardware::health::InitHealthdConfig;
 using android::hardware::health::V2_1::HealthInfo;
@@ -153,7 +153,7 @@
     sp<IHealth> passthrough = new TestHealth(std::move(config));
 
     std::thread bgThread([=] {
-        android::Charger charger(passthrough);
+        android::ChargerHidl charger(passthrough);
         charger.StartLoop();
     });
 
diff --git a/healthd/healthd_draw.cpp b/healthd/healthd_draw.cpp
index 9a47f6b..4484fa6 100644
--- a/healthd/healthd_draw.cpp
+++ b/healthd/healthd_draw.cpp
@@ -18,19 +18,30 @@
 #include <batteryservice/BatteryService.h>
 #include <cutils/klog.h>
 
-#include "charger.sysprop.h"
 #include "healthd_draw.h"
 
+#if !defined(__ANDROID_VNDK__)
+#include "charger.sysprop.h"
+#endif
+
 #define LOGE(x...) KLOG_ERROR("charger", x);
 #define LOGW(x...) KLOG_WARNING("charger", x);
 #define LOGV(x...) KLOG_DEBUG("charger", x);
 
 static bool get_split_screen() {
+#if !defined(__ANDROID_VNDK__)
     return android::sysprop::ChargerProperties::draw_split_screen().value_or(false);
+#else
+    return false;
+#endif
 }
 
 static int get_split_offset() {
+#if !defined(__ANDROID_VNDK__)
     int64_t value = android::sysprop::ChargerProperties::draw_split_offset().value_or(0);
+#else
+    int64_t value = 0;
+#endif
     if (value < static_cast<int64_t>(std::numeric_limits<int>::min())) {
         LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value,
              std::numeric_limits<int>::min());
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index 3ea90b0..0f9779c 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "healthd_mode_charger.h"
+#include <charger/healthd_mode_charger.h>
 
 #include <dirent.h>
 #include <errno.h>
@@ -50,27 +50,20 @@
 #include <suspend/autosuspend.h>
 
 #include "AnimationParser.h"
-#include "charger.sysprop.h"
-#include "charger_utils.h"
 #include "healthd_draw.h"
 
-#include <android/hardware/health/2.0/IHealthInfoCallback.h>
-#include <health/utils.h>
-#include <health2impl/HalHealthLoop.h>
-#include <health2impl/Health.h>
+#include <aidl/android/hardware/health/BatteryStatus.h>
+#include <health/HealthLoop.h>
 #include <healthd/healthd.h>
 
+#if !defined(__ANDROID_VNDK__)
+#include "charger.sysprop.h"
+#endif
+
 using std::string_literals::operator""s;
 using namespace android;
-using android::hardware::Return;
-using android::hardware::health::GetHealthServiceOrDefault;
+using aidl::android::hardware::health::BatteryStatus;
 using android::hardware::health::HealthLoop;
-using android::hardware::health::V1_0::BatteryStatus;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_1::IHealth;
-using IHealth_2_0 = android::hardware::health::V2_0::IHealth;
-using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
-using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
 
 // main healthd loop
 extern int healthd_main(void);
@@ -106,6 +99,13 @@
 
 namespace android {
 
+#if defined(__ANDROID_VNDK__)
+static constexpr const char* vendor_animation_desc_path =
+        "/vendor/etc/res/values/charger/animation.txt";
+static constexpr const char* vendor_animation_root = "/vendor/etc/res/images/";
+static constexpr const char* vendor_default_animation_root = "/vendor/etc/res/images/default/";
+#else
+
 // Legacy animation resources are loaded from this directory.
 static constexpr const char* legacy_animation_root = "/res/images/";
 
@@ -119,6 +119,7 @@
         "/product/etc/res/values/charger/animation.txt";
 static constexpr const char* product_animation_root = "/product/etc/res/images/";
 static constexpr const char* animation_desc_path = "/res/values/charger/animation.txt";
+#endif
 
 static const animation BASE_ANIMATION = {
     .text_clock =
@@ -199,8 +200,8 @@
     };
 }
 
-Charger::Charger(const sp<IHealth>& service)
-    : HalHealthLoop("charger", service), batt_anim_(BASE_ANIMATION) {}
+Charger::Charger(ChargerConfigurationInterface* configuration)
+    : batt_anim_(BASE_ANIMATION), configuration_(configuration) {}
 
 Charger::~Charger() {}
 
@@ -264,15 +265,18 @@
     LOGW("************* END LAST KMSG *************\n");
 }
 
-static int request_suspend(bool enable) {
-    if (!android::sysprop::ChargerProperties::enable_suspend().value_or(false)) {
+int Charger::RequestEnableSuspend() {
+    if (!configuration_->ChargerEnableSuspend()) {
         return 0;
     }
+    return autosuspend_enable();
+}
 
-    if (enable)
-        return autosuspend_enable();
-    else
-        return autosuspend_disable();
+int Charger::RequestDisableSuspend() {
+    if (!configuration_->ChargerEnableSuspend()) {
+        return 0;
+    }
+    return autosuspend_disable();
 }
 
 static void kick_animation(animation* anim) {
@@ -291,7 +295,7 @@
     if (!batt_anim_.run || now < next_screen_transition_) return;
 
     // If battery level is not ready, keep checking in the defined time
-    if (health_info_.batteryLevel == 0 && health_info_.batteryStatus == BatteryStatus::UNKNOWN) {
+    if (health_info_.battery_level == 0 && health_info_.battery_status == BatteryStatus::UNKNOWN) {
         if (wait_batt_level_timestamp_ == 0) {
             // Set max delay time and skip drawing screen
             wait_batt_level_timestamp_ = now + MAX_BATT_LEVEL_WAIT_TIME;
@@ -305,18 +309,15 @@
     }
 
     if (healthd_draw_ == nullptr) {
-        std::optional<bool> out_screen_on;
-        service()->shouldKeepScreenOn([&](Result res, bool screen_on) {
-            if (res == Result::SUCCESS) {
-                *out_screen_on = screen_on;
-            }
-        });
+        std::optional<bool> out_screen_on = configuration_->ChargerShouldKeepScreenOn();
         if (out_screen_on.has_value()) {
             if (!*out_screen_on) {
                 LOGV("[%" PRId64 "] leave screen off\n", now);
                 batt_anim_.run = false;
                 next_screen_transition_ = -1;
-                if (charger_online()) request_suspend(true);
+                if (configuration_->ChargerIsOnline()) {
+                    RequestEnableSuspend();
+                }
                 return;
             }
         }
@@ -324,10 +325,12 @@
         healthd_draw_ = HealthdDraw::Create(&batt_anim_);
         if (healthd_draw_ == nullptr) return;
 
+#if !defined(__ANDROID_VNDK__)
         if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
             healthd_draw_->blank_screen(true);
             screen_blanked_ = true;
         }
+#endif
     }
 
     /* animation is over, blank screen and leave */
@@ -337,7 +340,9 @@
         healthd_draw_->blank_screen(true);
         screen_blanked_ = true;
         LOGV("[%" PRId64 "] animation done\n", now);
-        if (charger_online()) request_suspend(true);
+        if (configuration_->ChargerIsOnline()) {
+            RequestEnableSuspend();
+        }
         return;
     }
 
@@ -351,9 +356,9 @@
     /* animation starting, set up the animation */
     if (batt_anim_.cur_frame == 0) {
         LOGV("[%" PRId64 "] animation starting\n", now);
-        batt_anim_.cur_level = health_info_.batteryLevel;
-        batt_anim_.cur_status = (int)health_info_.batteryStatus;
-        if (health_info_.batteryLevel >= 0 && batt_anim_.num_frames != 0) {
+        batt_anim_.cur_level = health_info_.battery_level;
+        batt_anim_.cur_status = (int)health_info_.battery_status;
+        if (health_info_.battery_level >= 0 && batt_anim_.num_frames != 0) {
             /* find first frame given current battery level */
             for (int i = 0; i < batt_anim_.num_frames; i++) {
                 if (batt_anim_.cur_level >= batt_anim_.frames[i].min_level &&
@@ -363,7 +368,7 @@
                 }
             }
 
-            if (charger_online()) {
+            if (configuration_->ChargerIsOnline()) {
                 // repeat the first frame first_frame_repeats times
                 disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time *
                             batt_anim_.first_frame_repeats;
@@ -394,7 +399,7 @@
     /* advance frame cntr to the next valid frame only if we are charging
      * if necessary, advance cycle cntr, and reset frame cntr
      */
-    if (charger_online()) {
+    if (configuration_->ChargerIsOnline()) {
         batt_anim_.cur_frame++;
 
         while (batt_anim_.cur_frame < batt_anim_.num_frames &&
@@ -492,13 +497,13 @@
                  * rather than on key release
                  */
                 kick_animation(&batt_anim_);
-                request_suspend(false);
+                RequestDisableSuspend();
             }
         } else {
             /* if the power key got released, force screen state cycle */
             if (key->pending) {
                 kick_animation(&batt_anim_);
-                request_suspend(false);
+                RequestDisableSuspend();
             }
         }
     }
@@ -516,8 +521,8 @@
     int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME;
     if (!have_battery_state_) return;
 
-    if (!charger_online()) {
-        request_suspend(false);
+    if (!configuration_->ChargerIsOnline()) {
+        RequestDisableSuspend();
         if (next_pwr_check_ == -1) {
             /* Last cycle would have stopped at the extreme top of battery-icon
              * Need to show the correct level corresponding to capacity.
@@ -547,7 +552,7 @@
              * Reset & kick animation to show complete animation cycles
              * when charger connected again.
              */
-            request_suspend(false);
+            RequestDisableSuspend();
             next_screen_transition_ = now - 1;
             reset_animation(&batt_anim_);
             kick_animation(&batt_anim_);
@@ -557,7 +562,7 @@
     }
 }
 
-void Charger::Heartbeat() {
+void Charger::OnHeartbeat() {
     // charger* charger = &charger_state;
     int64_t now = curr_time_ms();
 
@@ -570,22 +575,18 @@
     UpdateScreenState(now);
 }
 
-void Charger::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {
-    set_charger_online(health_info);
-
+void Charger::OnHealthInfoChanged(const ChargerHealthInfo& health_info) {
     if (!have_battery_state_) {
         have_battery_state_ = true;
         next_screen_transition_ = curr_time_ms() - 1;
-        request_suspend(false);
+        RequestDisableSuspend();
         reset_animation(&batt_anim_);
         kick_animation(&batt_anim_);
     }
-    health_info_ = health_info.legacy.legacy;
-
-    AdjustWakealarmPeriods(charger_online());
+    health_info_ = health_info;
 }
 
-int Charger::PrepareToWait(void) {
+int Charger::OnPrepareToWait(void) {
     int64_t now = curr_time_ms();
     int64_t next_event = INT64_MAX;
     int64_t timeout;
@@ -626,6 +627,16 @@
     bool parse_success;
 
     std::string content;
+
+#if defined(__ANDROID_VNDK__)
+    if (base::ReadFileToString(vendor_animation_desc_path, &content)) {
+        parse_success = parse_animation_desc(content, &batt_anim_);
+        batt_anim_.set_resource_root(vendor_animation_root);
+    } else {
+        LOGW("Could not open animation description at %s\n", vendor_animation_desc_path);
+        parse_success = false;
+    }
+#else
     if (base::ReadFileToString(product_animation_desc_path, &content)) {
         parse_success = parse_animation_desc(content, &batt_anim_);
         batt_anim_.set_resource_root(product_animation_root);
@@ -641,17 +652,26 @@
         LOGW("Could not open animation description at %s\n", animation_desc_path);
         parse_success = false;
     }
+#endif
+
+#if defined(__ANDROID_VNDK__)
+    auto default_animation_root = vendor_default_animation_root;
+#else
+    auto default_animation_root = system_animation_root;
+#endif
 
     if (!parse_success) {
-        LOGW("Could not parse animation description. Using default animation.\n");
+        LOGW("Could not parse animation description. "
+             "Using default animation with resources at %s\n",
+             default_animation_root);
         batt_anim_ = BASE_ANIMATION;
-        batt_anim_.animation_file.assign(system_animation_root + "charger/battery_scale.png"s);
+        batt_anim_.animation_file.assign(default_animation_root + "charger/battery_scale.png"s);
         InitDefaultAnimationFrames();
         batt_anim_.frames = owned_frames_.data();
         batt_anim_.num_frames = owned_frames_.size();
     }
     if (batt_anim_.fail_file.empty()) {
-        batt_anim_.fail_file.assign(system_animation_root + "charger/battery_fail.png"s);
+        batt_anim_.fail_file.assign(default_animation_root + "charger/battery_fail.png"s);
     }
 
     LOGV("Animation Description:\n");
@@ -672,7 +692,7 @@
     }
 }
 
-void Charger::Init(struct healthd_config* config) {
+void Charger::OnInit(struct healthd_config* config) {
     int ret;
     int i;
     int epollfd;
@@ -685,16 +705,18 @@
             std::bind(&Charger::InputCallback, this, std::placeholders::_1, std::placeholders::_2));
     if (!ret) {
         epollfd = ev_get_epollfd();
-        RegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD);
+        configuration_->ChargerRegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD);
     }
 
     InitAnimation();
 
     ret = CreateDisplaySurface(batt_anim_.fail_file, &surf_unknown_);
     if (ret < 0) {
+#if !defined(__ANDROID_VNDK__)
         LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret);
         ret = CreateDisplaySurface((system_animation_root + "charger/battery_fail.png"s).c_str(),
                                    &surf_unknown_);
+#endif
         if (ret < 0) {
             LOGE("Cannot load built in battery_fail image\n");
             surf_unknown_ = NULL;
@@ -730,7 +752,7 @@
     wait_batt_level_timestamp_ = 0;
 
     // Retrieve healthd_config from the existing health HAL.
-    HalHealthLoop::Init(config);
+    configuration_->ChargerInitConfig(config);
 
     boot_min_cap_ = config->boot_min_cap;
 }
@@ -773,25 +795,3 @@
 }
 
 }  // namespace android
-
-int healthd_charger_main(int argc, char** argv) {
-    int ch;
-
-    while ((ch = getopt(argc, argv, "cr")) != -1) {
-        switch (ch) {
-            case 'c':
-                // -c is now a noop
-                break;
-            case 'r':
-                // -r is now a noop
-                break;
-            case '?':
-            default:
-                LOGE("Unrecognized charger option: %c\n", optopt);
-                exit(1);
-        }
-    }
-
-    Charger charger(GetHealthServiceOrDefault());
-    return charger.StartLoop();
-}
diff --git a/healthd/healthd_mode_charger.h b/healthd/healthd_mode_charger.h
deleted file mode 100644
index 6f9ae8c..0000000
--- a/healthd/healthd_mode_charger.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <linux/input.h>
-
-#include <memory>
-#include <vector>
-
-#include <android/hardware/health/2.0/IHealthInfoCallback.h>
-#include <android/hardware/health/2.1/IHealth.h>
-#include <health2impl/HalHealthLoop.h>
-
-#include "animation.h"
-
-class GRSurface;
-class HealthdDraw;
-
-namespace android {
-struct key_state {
-    bool pending;
-    bool down;
-    int64_t timestamp;
-};
-
-class Charger : public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
-  public:
-    using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
-    using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
-
-    Charger(const sp<android::hardware::health::V2_1::IHealth>& service);
-    ~Charger();
-
-  protected:
-    // HealthLoop overrides.
-    void Heartbeat() override;
-    int PrepareToWait() override;
-    void Init(struct healthd_config* config) override;
-    // HalHealthLoop overrides
-    void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;
-
-    // Allowed to be mocked for testing.
-    virtual int CreateDisplaySurface(const std::string& name, GRSurface** surface);
-    virtual int CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps,
-                                          GRSurface*** surface);
-
-  private:
-    void InitDefaultAnimationFrames();
-    void UpdateScreenState(int64_t now);
-    int SetKeyCallback(int code, int value);
-    void UpdateInputState(input_event* ev);
-    void SetNextKeyCheck(key_state* key, int64_t timeout);
-    void ProcessKey(int code, int64_t now);
-    void HandleInputState(int64_t now);
-    void HandlePowerSupplyState(int64_t now);
-    int InputCallback(int fd, unsigned int epevents);
-    void InitAnimation();
-
-    bool have_battery_state_ = false;
-    bool screen_blanked_ = false;
-    int64_t next_screen_transition_ = 0;
-    int64_t next_key_check_ = 0;
-    int64_t next_pwr_check_ = 0;
-    int64_t wait_batt_level_timestamp_ = 0;
-
-    key_state keys_[KEY_MAX + 1] = {};
-
-    animation batt_anim_;
-    GRSurface* surf_unknown_ = nullptr;
-    int boot_min_cap_ = 0;
-
-    HealthInfo_1_0 health_info_ = {};
-    std::unique_ptr<HealthdDraw> healthd_draw_;
-    std::vector<animation::frame> owned_frames_;
-};
-}  // namespace android
-
-int healthd_charger_main(int argc, char** argv);
diff --git a/healthd/healthd_mode_charger_hidl.cpp b/healthd/healthd_mode_charger_hidl.cpp
new file mode 100644
index 0000000..3a33c02
--- /dev/null
+++ b/healthd/healthd_mode_charger_hidl.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 "healthd_mode_charger_hidl.h"
+
+#include <android/hardware/health/2.0/types.h>
+#include <charger.sysprop.h>
+#include <cutils/klog.h>
+
+#include "charger_utils.h"
+
+using android::hardware::health::GetHealthServiceOrDefault;
+using android::hardware::health::V2_0::Result;
+
+namespace android {
+
+ChargerHidl::ChargerHidl(const sp<android::hardware::health::V2_1::IHealth>& service)
+    : HalHealthLoop("charger", service), charger_(std::make_unique<Charger>(this)) {}
+
+void ChargerHidl::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {
+    set_charger_online(health_info);
+
+    charger_->OnHealthInfoChanged(ChargerHealthInfo{
+            .battery_level = health_info.legacy.legacy.batteryLevel,
+            .battery_status = static_cast<::aidl::android::hardware::health::BatteryStatus>(
+                    health_info.legacy.legacy.batteryStatus),
+    });
+
+    AdjustWakealarmPeriods(charger_online());
+}
+
+std::optional<bool> ChargerHidl::ChargerShouldKeepScreenOn() {
+    std::optional<bool> out_screen_on;
+    service()->shouldKeepScreenOn([&](Result res, bool screen_on) {
+        if (res == Result::SUCCESS) {
+            *out_screen_on = screen_on;
+        }
+    });
+    return out_screen_on;
+}
+
+bool ChargerHidl::ChargerEnableSuspend() {
+    return android::sysprop::ChargerProperties::enable_suspend().value_or(false);
+}
+
+}  // namespace android
+
+int healthd_charger_main(int argc, char** argv) {
+    int ch;
+
+    while ((ch = getopt(argc, argv, "cr")) != -1) {
+        switch (ch) {
+            case 'c':
+                // -c is now a noop
+                break;
+            case 'r':
+                // -r is now a noop
+                break;
+            case '?':
+            default:
+                KLOG_ERROR("charger", "Unrecognized charger option: %c\n", optopt);
+                exit(1);
+        }
+    }
+
+    android::ChargerHidl charger(GetHealthServiceOrDefault());
+    return charger.StartLoop();
+}
diff --git a/healthd/healthd_mode_charger_hidl.h b/healthd/healthd_mode_charger_hidl.h
new file mode 100644
index 0000000..0149d07
--- /dev/null
+++ b/healthd/healthd_mode_charger_hidl.h
@@ -0,0 +1,55 @@
+/*
+ * 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 <health2impl/HalHealthLoop.h>
+
+#include <charger/healthd_mode_charger.h>
+
+namespace android {
+
+// An implementation of Charger backed by HIDL implementation. Uses HIDL health
+// HAL's HalHealthLoop.
+class ChargerHidl : public ::android::ChargerConfigurationInterface,
+                    public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
+    using HalHealthLoop = ::android::hardware::health::V2_1::implementation::HalHealthLoop;
+    using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
+
+  public:
+    explicit ChargerHidl(const sp<android::hardware::health::V2_1::IHealth>& service);
+    std::optional<bool> ChargerShouldKeepScreenOn() override;
+    bool ChargerIsOnline() override { return HalHealthLoop::charger_online(); }
+    void ChargerInitConfig(healthd_config* config) override { return HalHealthLoop::Init(config); }
+    int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) override {
+        return HalHealthLoop::RegisterEvent(fd, func, wakeup);
+    }
+    bool ChargerEnableSuspend() override;
+    // HealthLoop overrides
+    void Heartbeat() override { charger_->OnHeartbeat(); }
+    int PrepareToWait() override { return charger_->OnPrepareToWait(); }
+    void Init(struct healthd_config* config) override { charger_->OnInit(config); }
+    // HalHealthLoop overrides
+    void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;
+
+  private:
+    sp<android::hardware::health::V2_1::IHealth> service_;
+    std::unique_ptr<Charger> charger_;
+};
+
+}  // namespace android
+
+int healthd_charger_main(int argc, char** argv);
diff --git a/healthd/healthd_mode_charger_test.cpp b/healthd/healthd_mode_charger_test.cpp
index f444f66..b7aace3 100644
--- a/healthd/healthd_mode_charger_test.cpp
+++ b/healthd/healthd_mode_charger_test.cpp
@@ -23,11 +23,12 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/strings.h>
+#include <android/hardware/health/2.1/IHealth.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <health/utils.h>
 
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
 
 using android::hardware::Return;
 using android::hardware::health::InitHealthdConfig;
@@ -102,12 +103,12 @@
     MOCK_METHOD(Return<void>, shouldKeepScreenOn, (shouldKeepScreenOn_cb _hidl_cb));
 };
 
-class TestCharger : public Charger {
+class TestCharger : public ChargerHidl {
   public:
     // Inherit constructor.
-    using Charger::Charger;
+    using ChargerHidl::ChargerHidl;
     // Expose protected functions to be used in tests.
-    void Init(struct healthd_config* config) override { Charger::Init(config); }
+    void Init(struct healthd_config* config) override { ChargerHidl::Init(config); }
     MOCK_METHOD(int, CreateDisplaySurface, (const std::string& name, GRSurface** surface));
     MOCK_METHOD(int, CreateMultiDisplaySurface,
                 (const std::string& name, int* frames, int* fps, GRSurface*** surface));
diff --git a/healthd/include_charger/charger/healthd_mode_charger.h b/healthd/include_charger/charger/healthd_mode_charger.h
new file mode 100644
index 0000000..216e5ad
--- /dev/null
+++ b/healthd/include_charger/charger/healthd_mode_charger.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/input.h>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include <aidl/android/hardware/health/BatteryStatus.h>
+#include <health/HealthLoop.h>
+#include <healthd/healthd.h>
+
+#include "animation.h"
+
+class GRSurface;
+class HealthdDraw;
+
+namespace android {
+struct key_state {
+    bool pending;
+    bool down;
+    int64_t timestamp;
+};
+
+// Health info that interests charger
+struct ChargerHealthInfo {
+    int32_t battery_level;
+    aidl::android::hardware::health::BatteryStatus battery_status;
+};
+
+// Configuration interface for charger. This includes:
+// - HalHealthLoop APIs that interests charger.
+// - configuration values that used to be provided by sysprops
+class ChargerConfigurationInterface {
+  public:
+    virtual ~ChargerConfigurationInterface() = default;
+    // HalHealthLoop related APIs
+    virtual std::optional<bool> ChargerShouldKeepScreenOn() = 0;
+    virtual bool ChargerIsOnline() = 0;
+    virtual void ChargerInitConfig(healthd_config* config) = 0;
+    using BoundFunction =
+            std::function<void(android::hardware::health::HealthLoop*, uint32_t /* epevents */)>;
+    virtual int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) = 0;
+
+    // Other configuration values
+    virtual bool ChargerEnableSuspend() = 0;
+};
+
+// charger UI
+class Charger {
+  public:
+    explicit Charger(ChargerConfigurationInterface* configuration);
+    virtual ~Charger();
+
+    // Hooks for ChargerConfigurationInterface
+    void OnHeartbeat();
+    int OnPrepareToWait();
+    // |cookie| is passed to ChargerConfigurationInterface::ChargerInitConfig
+    void OnInit(struct healthd_config* config);
+    void OnHealthInfoChanged(const ChargerHealthInfo& health_info);
+
+  protected:
+    // Allowed to be mocked for testing.
+    virtual int CreateDisplaySurface(const std::string& name, GRSurface** surface);
+    virtual int CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps,
+                                          GRSurface*** surface);
+
+  private:
+    void InitDefaultAnimationFrames();
+    void UpdateScreenState(int64_t now);
+    int SetKeyCallback(int code, int value);
+    void UpdateInputState(input_event* ev);
+    void SetNextKeyCheck(key_state* key, int64_t timeout);
+    void ProcessKey(int code, int64_t now);
+    void HandleInputState(int64_t now);
+    void HandlePowerSupplyState(int64_t now);
+    int InputCallback(int fd, unsigned int epevents);
+    void InitAnimation();
+    int RequestEnableSuspend();
+    int RequestDisableSuspend();
+
+    bool have_battery_state_ = false;
+    bool screen_blanked_ = false;
+    int64_t next_screen_transition_ = 0;
+    int64_t next_key_check_ = 0;
+    int64_t next_pwr_check_ = 0;
+    int64_t wait_batt_level_timestamp_ = 0;
+
+    key_state keys_[KEY_MAX + 1] = {};
+
+    animation batt_anim_;
+    GRSurface* surf_unknown_ = nullptr;
+    int boot_min_cap_ = 0;
+
+    ChargerHealthInfo health_info_ = {};
+    std::unique_ptr<HealthdDraw> healthd_draw_;
+    std::vector<animation::frame> owned_frames_;
+
+    ChargerConfigurationInterface* configuration_;
+};
+}  // namespace android
diff --git a/init/Android.bp b/init/Android.bp
index 9b02c38..66427dc 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -448,6 +448,7 @@
 
     srcs: [
         "devices_test.cpp",
+        "epoll_test.cpp",
         "firmware_handler_test.cpp",
         "init_test.cpp",
         "keychords_test.cpp",
diff --git a/init/README.md b/init/README.md
index 58a8d6b..ebab073 100644
--- a/init/README.md
+++ b/init/README.md
@@ -77,6 +77,43 @@
 conflict resolution when multiple services are added to the system, as
 each one will go into a separate file.
 
+Versioned RC files within APEXs
+-------------------------------
+
+With the arrival of mainline on Android Q, the individual mainline
+modules carry their own init.rc files within their boundaries. Init
+processes these files according to the naming pattern `/apex/*/etc/*rc`.
+
+Because APEX modules must run on more than one release of Android,
+they may require different parameters as part of the services they
+define. This is achieved, starting in Android T, by incorporating
+the SDK version information in the name of the init file.  The suffix
+is changed from `.rc` to `.#rc` where # is the first SDK where that
+RC file is accepted. An init file specific to SDK=31 might be named
+`init.31rc`. With this scheme, an APEX may include multiple init files. An
+example is appropriate.
+
+For an APEX module with the following files in /apex/sample-module/apex/etc/:
+
+   1. init.rc
+   2. init.32rc
+   4. init.35rc
+
+The selection rule chooses the highest `.#rc` value that does not
+exceed the SDK of the currently running system. The unadorned `.rc`
+is interpreted as sdk=0.
+
+When this APEX is installed on a device with SDK <=31, the system will
+process init.rc.  When installed on a device running SDK 32, 33, or 34,
+it will use init.32rc.  When installed on a device running SDKs >= 35,
+it will choose init.35rc
+
+This versioning scheme is used only for the init files within APEX
+modules; it does not apply to the init files stored in /system/etc/init,
+/vendor/etc/init, or other directories.
+
+This naming scheme is available after Android S.
+
 Actions
 -------
 Actions are named sequences of commands.  Actions have a trigger which
@@ -450,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.
@@ -464,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
@@ -570,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
@@ -696,7 +724,10 @@
 `verity_update_state`
 > Internal implementation detail used to update dm-verity state and
   set the partition._mount-point_.verified properties used by adb remount
-  because fs\_mgr can't set them directly itself.
+  because fs\_mgr can't set them directly itself. This is required since
+  Android 12, because CtsNativeVerifiedBootTestCases will read property
+  "partition.${partition}.verified.hash_alg" to check that sha1 is not used.
+  See https://r.android.com/1546980 for more details.
 
 `wait <path> [ <timeout> ]`
 > Poll for the existence of the given file and return when found,
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 994eed9..98831e1 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -28,6 +28,7 @@
 #include <net/if.h>
 #include <sched.h>
 #include <signal.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -42,9 +43,9 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <map>
 #include <memory>
 
-#include <ApexProperties.sysprop.h>
 #include <InitProperties.sysprop.h>
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
@@ -175,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 {};
@@ -207,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 {};
 }
 
@@ -584,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 {};
@@ -1117,17 +1082,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");
@@ -1313,7 +1267,7 @@
 
 static Result<void> parse_apex_configs() {
     glob_t glob_result;
-    static constexpr char glob_pattern[] = "/apex/*/etc/*.rc";
+    static constexpr char glob_pattern[] = "/apex/*/etc/*rc";
     const int ret = glob(glob_pattern, GLOB_MARK, nullptr, &glob_result);
     if (ret != 0 && ret != GLOB_NOMATCH) {
         globfree(&glob_result);
@@ -1330,17 +1284,66 @@
         if (paths.size() >= 3 && paths[2].find('@') != std::string::npos) {
             continue;
         }
+        // Filter directories
+        if (path.back() == '/') {
+            continue;
+        }
         configs.push_back(path);
     }
     globfree(&glob_result);
 
-    bool success = true;
+    // Compare all files /apex/path.#rc and /apex/path.rc with the same "/apex/path" prefix,
+    // choosing the one with the highest # that doesn't exceed the system's SDK.
+    // (.rc == .0rc for ranking purposes)
+    //
+    int active_sdk = android::base::GetIntProperty("ro.build.version.sdk", INT_MAX);
+
+    std::map<std::string, std::pair<std::string, int>> script_map;
+
     for (const auto& c : configs) {
-        if (c.back() == '/') {
-            // skip if directory
+        int sdk = 0;
+        const std::vector<std::string> parts = android::base::Split(c, ".");
+        std::string base;
+        if (parts.size() < 2) {
             continue;
         }
-        success &= parser.ParseConfigFile(c);
+
+        // parts[size()-1], aka the suffix, should be "rc" or "#rc"
+        // any other pattern gets discarded
+
+        const auto& suffix = parts[parts.size() - 1];
+        if (suffix == "rc") {
+            sdk = 0;
+        } else {
+            char trailer[9] = {0};
+            int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer);
+            if (r != 2) {
+                continue;
+            }
+            if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) {
+                continue;
+            }
+        }
+
+        if (sdk < 0 || sdk > active_sdk) {
+            continue;
+        }
+
+        base = parts[0];
+        for (unsigned int i = 1; i < parts.size() - 1; i++) {
+            base = base + "." + parts[i];
+        }
+
+        // is this preferred over what we already have
+        auto it = script_map.find(base);
+        if (it == script_map.end() || it->second.second < sdk) {
+            script_map[base] = std::make_pair(c, sdk);
+        }
+    }
+
+    bool success = true;
+    for (const auto& m : script_map) {
+        success &= parser.ParseConfigFile(m.second.first);
     }
     ServiceList::GetInstance().MarkServicesUpdate();
     if (success) {
@@ -1413,10 +1416,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}}},
diff --git a/init/epoll.cpp b/init/epoll.cpp
index 17d63fa..74d8aac 100644
--- a/init/epoll.cpp
+++ b/init/epoll.cpp
@@ -38,11 +38,12 @@
     return {};
 }
 
-Result<void> Epoll::RegisterHandler(int fd, std::function<void()> handler, uint32_t events) {
+Result<void> Epoll::RegisterHandler(int fd, Handler handler, uint32_t events) {
     if (!events) {
         return Error() << "Must specify events";
     }
-    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(handler));
+    auto sp = std::make_shared<decltype(handler)>(std::move(handler));
+    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(sp));
     if (!inserted) {
         return Error() << "Cannot specify two epoll handlers for a given FD";
     }
@@ -69,7 +70,7 @@
     return {};
 }
 
-Result<std::vector<std::function<void()>*>> Epoll::Wait(
+Result<std::vector<std::shared_ptr<Epoll::Handler>>> Epoll::Wait(
         std::optional<std::chrono::milliseconds> timeout) {
     int timeout_ms = -1;
     if (timeout && timeout->count() < INT_MAX) {
@@ -81,9 +82,10 @@
     if (num_events == -1) {
         return ErrnoError() << "epoll_wait failed";
     }
-    std::vector<std::function<void()>*> pending_functions;
+    std::vector<std::shared_ptr<Handler>> pending_functions;
     for (int i = 0; i < num_events; ++i) {
-        pending_functions.emplace_back(reinterpret_cast<std::function<void()>*>(ev[i].data.ptr));
+        auto sp = *reinterpret_cast<std::shared_ptr<Handler>*>(ev[i].data.ptr);
+        pending_functions.emplace_back(std::move(sp));
     }
 
     return pending_functions;
diff --git a/init/epoll.h b/init/epoll.h
index c32a661..0df5289 100644
--- a/init/epoll.h
+++ b/init/epoll.h
@@ -22,6 +22,7 @@
 #include <chrono>
 #include <functional>
 #include <map>
+#include <memory>
 #include <optional>
 #include <vector>
 
@@ -36,15 +37,17 @@
   public:
     Epoll();
 
+    typedef std::function<void()> Handler;
+
     Result<void> Open();
-    Result<void> RegisterHandler(int fd, std::function<void()> handler, uint32_t events = EPOLLIN);
+    Result<void> RegisterHandler(int fd, Handler handler, uint32_t events = EPOLLIN);
     Result<void> UnregisterHandler(int fd);
-    Result<std::vector<std::function<void()>*>> Wait(
+    Result<std::vector<std::shared_ptr<Handler>>> Wait(
             std::optional<std::chrono::milliseconds> timeout);
 
   private:
     android::base::unique_fd epoll_fd_;
-    std::map<int, std::function<void()>> epoll_handlers_;
+    std::map<int, std::shared_ptr<Handler>> epoll_handlers_;
 };
 
 }  // namespace init
diff --git a/init/epoll_test.cpp b/init/epoll_test.cpp
new file mode 100644
index 0000000..9236cd5
--- /dev/null
+++ b/init/epoll_test.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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 "epoll.h"
+
+#include <sys/unistd.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace init {
+
+std::unordered_set<void*> sValidObjects;
+
+class CatchDtor final {
+  public:
+    CatchDtor() { sValidObjects.emplace(this); }
+    CatchDtor(const CatchDtor&) { sValidObjects.emplace(this); }
+    ~CatchDtor() {
+        auto iter = sValidObjects.find(this);
+        if (iter != sValidObjects.end()) {
+            sValidObjects.erase(iter);
+        }
+    }
+};
+
+TEST(epoll, UnregisterHandler) {
+    Epoll epoll;
+    ASSERT_RESULT_OK(epoll.Open());
+
+    int fds[2];
+    ASSERT_EQ(pipe(fds), 0);
+
+    CatchDtor catch_dtor;
+    bool handler_invoked;
+    auto handler = [&, catch_dtor]() -> void {
+        auto result = epoll.UnregisterHandler(fds[0]);
+        ASSERT_EQ(result.ok(), !handler_invoked);
+        handler_invoked = true;
+        ASSERT_NE(sValidObjects.find((void*)&catch_dtor), sValidObjects.end());
+    };
+
+    epoll.RegisterHandler(fds[0], std::move(handler));
+
+    uint8_t byte = 0xee;
+    ASSERT_TRUE(android::base::WriteFully(fds[1], &byte, sizeof(byte)));
+
+    auto results = epoll.Wait({});
+    ASSERT_RESULT_OK(results);
+    ASSERT_EQ(results->size(), size_t(1));
+
+    for (const auto& function : *results) {
+        (*function)();
+        (*function)();
+    }
+    ASSERT_TRUE(handler_invoked);
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 84ed58e..70e26ec 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -100,6 +100,7 @@
 constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
 constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
 constexpr auto DIGEST_SIZE_USED = 8;
+constexpr auto API_LEVEL_CURRENT = 10000;
 
 static bool persistent_properties_loaded = false;
 
@@ -1017,29 +1018,37 @@
     }
 }
 
+static int read_api_level_props(const std::vector<std::string>& api_level_props) {
+    int api_level = API_LEVEL_CURRENT;
+    for (const auto& api_level_prop : api_level_props) {
+        api_level = android::base::GetIntProperty(api_level_prop, API_LEVEL_CURRENT);
+        if (api_level != API_LEVEL_CURRENT) {
+            break;
+        }
+    }
+    return api_level;
+}
+
 static void property_initialize_ro_vendor_api_level() {
     // ro.vendor.api_level shows the api_level that the vendor images (vendor, odm, ...) are
     // required to support.
     constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
-    // Candidate api levels. The order of the properties must be kept.
-    const char* VENDOR_API_LEVEL_PROPS[] = {
-            "ro.board.api_level", "ro.board.first_api_level",    "ro.product.first_api_level",
-            "ro.vndk.version",    "ro.vendor.build.version.sdk", "ro.build.version.sdk"};
 
-    for (const auto& api_level_prop : VENDOR_API_LEVEL_PROPS) {
-        int api_level = android::base::GetIntProperty(api_level_prop, 0);
-        if (api_level != 0) {
-            std::string error;
-            uint32_t res = PropertySet(VENDOR_API_LEVEL_PROP, std::to_string(api_level), &error);
-            if (res != PROP_SUCCESS) {
-                LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << api_level
-                           << ": " << error;
-            }
-            return;
-        }
+    // Api level properties of the board. The order of the properties must be kept.
+    std::vector<std::string> BOARD_API_LEVEL_PROPS = {
+            "ro.board.api_level", "ro.board.first_api_level", "ro.vendor.build.version.sdk"};
+    // Api level properties of the device. The order of the properties must be kept.
+    std::vector<std::string> DEVICE_API_LEVEL_PROPS = {"ro.product.first_api_level",
+                                                       "ro.build.version.sdk"};
+
+    int api_level = std::min(read_api_level_props(BOARD_API_LEVEL_PROPS),
+                             read_api_level_props(DEVICE_API_LEVEL_PROPS));
+    std::string error;
+    uint32_t res = PropertySet(VENDOR_API_LEVEL_PROP, std::to_string(api_level), &error);
+    if (res != PROP_SUCCESS) {
+        LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << api_level << ": "
+                   << error << "(" << res << ")";
     }
-    // If no api integers are found from the vendor api level properties, ro.vendor.api_level
-    // will not be set.
 }
 
 void PropertyLoadBootDefaults() {
@@ -1166,10 +1175,8 @@
             LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
                                      &property_infos);
         }
-        if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
-                                      &property_infos)) {
-            // Fallback to nonplat_* if vendor_* doesn't exist.
-            LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
+        if (access("/vendor/etc/selinux/vendor_property_contexts", R_OK) != -1) {
+            LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                      &property_infos);
         }
         if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
@@ -1184,10 +1191,7 @@
             return;
         }
         LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos);
-        if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
-            // Fallback to nonplat_* if vendor_* doesn't exist.
-            LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
-        }
+        LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos);
         LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
         LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
     }
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index ac6b7b2..5f34cc4 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -99,7 +99,7 @@
 
     std::string vbmeta_digest = GetProperty("ro.boot.vbmeta.digest", "");
     ASSERT_GE(vbmeta_digest.size(), 8u);
-    std::string build_id = GetProperty("ro.boot.build.id", "");
+    std::string build_id = GetProperty("ro.build.id", "");
     // Check that the build id is constructed with the prefix of vbmeta digest
     std::string expected_build_id = legacy_build_id + "." + vbmeta_digest.substr(0, 8);
     ASSERT_EQ(expected_build_id, build_id);
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 29c0ff3..28cd012 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -27,7 +27,7 @@
 // file located at /sepolicy and is directly loaded into the kernel SELinux subsystem.
 
 // The split policy is for supporting treble devices.  It splits the SEPolicy across files on
-// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'nonplat'
+// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'vendor'
 // portion of the policy).  This is necessary to allow the system image to be updated independently
 // of the vendor image, while maintaining contributions from both partitions in the SEPolicy.  This
 // is especially important for VTS testing, where the SEPolicy on the Google System Image may not be
@@ -320,12 +320,12 @@
 };
 
 bool OpenSplitPolicy(PolicyFile* policy_file) {
-    // IMPLEMENTATION NOTE: Split policy consists of three CIL files:
+    // IMPLEMENTATION NOTE: Split policy consists of three or more CIL files:
     // * platform -- policy needed due to logic contained in the system image,
-    // * non-platform -- policy needed due to logic contained in the vendor image,
+    // * vendor -- policy needed due to logic contained in the vendor image,
     // * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
     //   with newer versions of platform policy.
-    //
+    // * (optional) policy needed due to logic on product, system_ext, or odm images.
     // secilc is invoked to compile the above three policy files into a single monolithic policy
     // file. This file is then loaded into the kernel.
 
@@ -404,17 +404,14 @@
         product_mapping_file.clear();
     }
 
-    // vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace
-    // nonplat_sepolicy.cil.
-    std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
     std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
-
     if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
-        // For backward compatibility.
-        // TODO: remove this after no device is using nonplat_sepolicy.cil.
-        vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";
-        plat_pub_versioned_cil_file.clear();
-    } else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
+        LOG(ERROR) << "Missing " << vendor_policy_cil_file;
+        return false;
+    }
+
+    std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
+    if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
         LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
         return false;
     }
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/libcutils/include/cutils/list.h b/libcutils/include/cutils/list.h
index dfdc53b..7eb8725 100644
--- a/libcutils/include/cutils/list.h
+++ b/libcutils/include/cutils/list.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2013 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef _CUTILS_LIST_H_
-#define _CUTILS_LIST_H_
+#pragma once
 
 #include <stddef.h>
 
@@ -38,9 +37,6 @@
         .prev = &(name), \
     }
 
-#define list_for_each(node, list) \
-    for ((node) = (list)->next; (node) != (list); (node) = (node)->next)
-
 #define list_for_each_reverse(node, list) \
     for ((node) = (list)->prev; (node) != (list); (node) = (node)->prev)
 
@@ -49,6 +45,10 @@
          (node) != (list); \
          (node) = (n), (n) = (node)->next)
 
+#define list_for_each(node, list)                                                \
+    for (struct listnode* __n = ((node) = (list)->next)->next; (node) != (list); \
+         (node) = __n, __n = (node)->next)
+
 static inline void list_init(struct listnode *node)
 {
     node->next = node;
@@ -84,5 +84,3 @@
 #ifdef __cplusplus
 };
 #endif /* __cplusplus */
-
-#endif
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index c471fa0..e65fe92 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -130,6 +130,7 @@
 #define AID_VIRTUALIZATIONSERVICE 1081 /* VirtualizationService daemon */
 #define AID_ARTD 1082             /* ART Service daemon */
 #define AID_UWB 1083              /* UWB subsystem */
+#define AID_THREAD_NETWORK 1084   /* Thread Network subsystem */
 /* Changes to this file must be made in AOSP, *not* in internal branches. */
 
 #define AID_SHELL 2000 /* adb and debug shell user */
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 449a505..45d3c7c 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -183,7 +183,19 @@
         }
       ]
     },
-
+    {
+      "Name": "Dex2oatPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpu",
+            "Path": "dex2oat"
+          }
+        }
+      ]
+    },
     {
       "Name": "CpuPolicySpread",
       "Actions": [
@@ -638,7 +650,7 @@
     },
     {
       "Name": "Dex2OatBootComplete",
-      "Profiles": [ "SCHED_SP_BACKGROUND" ]
+      "Profiles": [ "Dex2oatPerformance", "LowIoPriority", "TimerSlackHigh" ]
     }
   ]
 }
diff --git a/libprocessgroup/profiles/task_profiles_28.json b/libprocessgroup/profiles/task_profiles_28.json
index 9f83785..e7be548 100644
--- a/libprocessgroup/profiles/task_profiles_28.json
+++ b/libprocessgroup/profiles/task_profiles_28.json
@@ -40,6 +40,19 @@
       ]
     },
     {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
       "Name": "HighPerformance",
       "Actions": [
         {
@@ -104,7 +117,19 @@
         }
       ]
     },
-
+    {
+      "Name": "Dex2oatPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
     {
       "Name": "CpuPolicySpread",
       "Actions": [
diff --git a/libprocessgroup/profiles/task_profiles_29.json b/libprocessgroup/profiles/task_profiles_29.json
index 9f83785..6174c8d 100644
--- a/libprocessgroup/profiles/task_profiles_29.json
+++ b/libprocessgroup/profiles/task_profiles_29.json
@@ -53,6 +53,19 @@
       ]
     },
     {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
       "Name": "MaxPerformance",
       "Actions": [
         {
@@ -104,7 +117,19 @@
         }
       ]
     },
-
+    {
+      "Name": "Dex2oatPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
     {
       "Name": "CpuPolicySpread",
       "Actions": [
diff --git a/libprocessgroup/profiles/task_profiles_30.json b/libprocessgroup/profiles/task_profiles_30.json
index 9f83785..e7be548 100644
--- a/libprocessgroup/profiles/task_profiles_30.json
+++ b/libprocessgroup/profiles/task_profiles_30.json
@@ -40,6 +40,19 @@
       ]
     },
     {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
       "Name": "HighPerformance",
       "Actions": [
         {
@@ -104,7 +117,19 @@
         }
       ]
     },
-
+    {
+      "Name": "Dex2oatPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
     {
       "Name": "CpuPolicySpread",
       "Actions": [
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index cf74e65..e935f99 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -194,22 +194,39 @@
     fd_.reset(FDS_NOT_CACHED);
 }
 
-bool SetCgroupAction::AddTidToCgroup(int tid, int fd) {
+bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
     if (tid <= 0) {
         return true;
     }
 
     std::string value = std::to_string(tid);
 
-    if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) < 0) {
-        // If the thread is in the process of exiting, don't flag an error
-        if (errno != ESRCH) {
-            PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
-            return false;
-        }
+    if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) == value.length()) {
+        return true;
     }
 
-    return true;
+    // If the thread is in the process of exiting, don't flag an error
+    if (errno == ESRCH) {
+        return true;
+    }
+
+    // ENOSPC is returned when cpuset cgroup that we are joining has no online cpus
+    if (errno == ENOSPC && !strcmp(controller_name, "cpuset")) {
+        // This is an abnormal case happening only in testing, so report it only once
+        static bool empty_cpuset_reported = false;
+
+        if (empty_cpuset_reported) {
+            return true;
+        }
+
+        LOG(ERROR) << "Failed to add task '" << value
+                   << "' into cpuset because all cpus in that cpuset are offline";
+        empty_cpuset_reported = true;
+    } else {
+        PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
+    }
+
+    return false;
 }
 
 bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
@@ -219,7 +236,7 @@
         PLOG(WARNING) << "Failed to open " << procs_path;
         return false;
     }
-    if (!AddTidToCgroup(pid, tmp_fd)) {
+    if (!AddTidToCgroup(pid, tmp_fd, controller()->name())) {
         LOG(ERROR) << "Failed to add task into cgroup";
         return false;
     }
@@ -231,7 +248,7 @@
     std::lock_guard<std::mutex> lock(fd_mutex_);
     if (IsFdValid()) {
         // fd is cached, reuse it
-        if (!AddTidToCgroup(tid, fd_)) {
+        if (!AddTidToCgroup(tid, fd_, controller()->name())) {
             LOG(ERROR) << "Failed to add task into cgroup";
             return false;
         }
@@ -256,7 +273,7 @@
         PLOG(WARNING) << "Failed to open " << tasks_path << ": " << strerror(errno);
         return false;
     }
-    if (!AddTidToCgroup(tid, tmp_fd)) {
+    if (!AddTidToCgroup(tid, tmp_fd, controller()->name())) {
         LOG(ERROR) << "Failed to add task into cgroup";
         return false;
     }
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 25a84b0..97c38f4 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -134,7 +134,7 @@
     mutable std::mutex fd_mutex_;
 
     static bool IsAppDependentPath(const std::string& path);
-    static bool AddTidToCgroup(int tid, int fd);
+    static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
 
     bool IsFdValid() const { return fd_ > FDS_INACCESSIBLE; }
 };
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/libsuspend/Android.bp b/libsuspend/Android.bp
index 671de4d..144b4b6 100644
--- a/libsuspend/Android.bp
+++ b/libsuspend/Android.bp
@@ -6,6 +6,7 @@
 
 cc_library {
     name: "libsuspend",
+    vendor_available: true,
     srcs: [
         "autosuspend.c",
         "autosuspend_wakeup_count.cpp",
diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 3b6cfd8..515cc10 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -31,14 +31,41 @@
 #include <netinet/in.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/personality.h>
 #include <sys/socket.h>
 #include <sys/types.h>
+#include <sys/utsname.h>
+
+#include <android-base/parseint.h>
+#include <log/log.h>
+#include <sysutils/NetlinkEvent.h>
+
+using android::base::ParseInt;
 
 /* From kernel's net/netfilter/xt_quota2.c */
 const int LOCAL_QLOG_NL_EVENT = 112;
 const int LOCAL_NFLOG_PACKET = NFNL_SUBSYS_ULOG << 8 | NFULNL_MSG_PACKET;
 
-/* From deprecated ipt_ULOG.h to parse QLOG_NL_EVENT. */
+/******************************************************************************
+ * WARNING: HERE BE DRAGONS!                                                  *
+ *                                                                            *
+ * This is here to provide for compatibility with both 32 and 64-bit kernels  *
+ * from 32-bit userspace.                                                     *
+ *                                                                            *
+ * The kernel definition of this struct uses types (like long) that are not   *
+ * the same across 32-bit and 64-bit builds, and there is no compatibility    *
+ * layer to fix it up before it reaches userspace.                            *
+ * As such we need to detect the bit-ness of the kernel and deal with it.     *
+ *                                                                            *
+ ******************************************************************************/
+
+/*
+ * This is the verbatim kernel declaration from net/netfilter/xt_quota2.c,
+ * it is *NOT* of a well defined layout and is included here for compile
+ * time assertions only.
+ *
+ * It got there from deprecated ipt_ULOG.h to parse QLOG_NL_EVENT.
+ */
 #define ULOG_MAC_LEN 80
 #define ULOG_PREFIX_LEN 32
 typedef struct ulog_packet_msg {
@@ -55,11 +82,117 @@
     unsigned char payload[0];
 } ulog_packet_msg_t;
 
-#include <android-base/parseint.h>
-#include <log/log.h>
-#include <sysutils/NetlinkEvent.h>
+// On Linux int is always 32 bits, while sizeof(long) == sizeof(void*),
+// thus long on a 32-bit Linux kernel is 32-bits, like int always is
+typedef int long32;
+typedef unsigned int ulong32;
+static_assert(sizeof(long32) == 4);
+static_assert(sizeof(ulong32) == 4);
 
-using android::base::ParseInt;
+// Here's the same structure definition with the assumption the kernel
+// is compiled for 32-bits.
+typedef struct {
+    ulong32 mark;
+    long32 timestamp_sec;
+    long32 timestamp_usec;
+    unsigned int hook;
+    char indev_name[IFNAMSIZ];
+    char outdev_name[IFNAMSIZ];
+    ulong32 data_len;
+    char prefix[ULOG_PREFIX_LEN];
+    unsigned char mac_len;
+    unsigned char mac[ULOG_MAC_LEN];
+    unsigned char payload[0];
+} ulog_packet_msg32_t;
+
+// long on a 64-bit kernel is 64-bits with 64-bit alignment,
+// while long long is 64-bit but may have 32-bit aligment.
+typedef long long __attribute__((__aligned__(8))) long64;
+typedef unsigned long long __attribute__((__aligned__(8))) ulong64;
+static_assert(sizeof(long64) == 8);
+static_assert(sizeof(ulong64) == 8);
+
+// Here's the same structure definition with the assumption the kernel
+// is compiled for 64-bits.
+typedef struct {
+    ulong64 mark;
+    long64 timestamp_sec;
+    long64 timestamp_usec;
+    unsigned int hook;
+    char indev_name[IFNAMSIZ];
+    char outdev_name[IFNAMSIZ];
+    ulong64 data_len;
+    char prefix[ULOG_PREFIX_LEN];
+    unsigned char mac_len;
+    unsigned char mac[ULOG_MAC_LEN];
+    unsigned char payload[0];
+} ulog_packet_msg64_t;
+
+// One expects the 32-bit version to be smaller than the 64-bit version.
+static_assert(sizeof(ulog_packet_msg32_t) < sizeof(ulog_packet_msg64_t));
+// And either way the 'native' version should match either the 32 or 64 bit one.
+static_assert(sizeof(ulog_packet_msg_t) == sizeof(ulog_packet_msg32_t) ||
+              sizeof(ulog_packet_msg_t) == sizeof(ulog_packet_msg64_t));
+
+// In practice these sizes are always simply (for both x86 and arm):
+static_assert(sizeof(ulog_packet_msg32_t) == 168);
+static_assert(sizeof(ulog_packet_msg64_t) == 192);
+
+// Figure out the bitness of userspace.
+// Trivial and known at compile time.
+static bool isUserspace64bit(void) {
+    return sizeof(long) == 8;
+}
+
+// Figure out the bitness of the kernel.
+static bool isKernel64Bit(void) {
+    // a 64-bit userspace requires a 64-bit kernel
+    if (isUserspace64bit()) return true;
+
+    static bool init = false;
+    static bool cache = false;
+    if (init) return cache;
+
+    // Retrieve current personality - on Linux this system call *cannot* fail.
+    int p = personality(0xffffffff);
+    // But if it does just assume kernel and userspace (which is 32-bit) match...
+    if (p == -1) return false;
+
+    // This will effectively mask out the bottom 8 bits, and switch to 'native'
+    // personality, and then return the previous personality of this thread
+    // (likely PER_LINUX or PER_LINUX32) with any extra options unmodified.
+    int q = personality((p & ~PER_MASK) | PER_LINUX);
+    // Per man page this theoretically could error out with EINVAL,
+    // but kernel code analysis suggests setting PER_LINUX cannot fail.
+    // Either way, assume kernel and userspace (which is 32-bit) match...
+    if (q != p) return false;
+
+    struct utsname u;
+    (void)uname(&u);  // only possible failure is EFAULT, but u is on stack.
+
+    // Switch back to previous personality.
+    // Theoretically could fail with EINVAL on arm64 with no 32-bit support,
+    // but then we wouldn't have fetched 'p' from the kernel in the first place.
+    // Either way there's nothing meaningul we can do in case of error.
+    // Since PER_LINUX32 vs PER_LINUX only affects uname.machine it doesn't
+    // really hurt us either.  We're really just switching back to be 'clean'.
+    (void)personality(p);
+
+    // Possible values of utsname.machine observed on x86_64 desktop (arm via qemu):
+    //   x86_64 i686 aarch64 armv7l
+    // additionally observed on arm device:
+    //   armv8l
+    // presumably also might just be possible:
+    //   i386 i486 i586
+    // and there might be other weird arm32 cases.
+    // We note that the 64 is present in both 64-bit archs,
+    // and in general is likely to be present in only 64-bit archs.
+    cache = !!strstr(u.machine, "64");
+    init = true;
+    return cache;
+}
+
+/******************************************************************************/
 
 NetlinkEvent::NetlinkEvent() {
     mAction = Action::kUnknown;
@@ -280,13 +413,22 @@
  * Parse a QLOG_NL_EVENT message.
  */
 bool NetlinkEvent::parseUlogPacketMessage(const struct nlmsghdr *nh) {
-    const char *devname;
-    ulog_packet_msg_t *pm = (ulog_packet_msg_t *) NLMSG_DATA(nh);
-    if (!checkRtNetlinkLength(nh, sizeof(*pm)))
-        return false;
+    const char* alert;
+    const char* devname;
 
-    devname = pm->indev_name[0] ? pm->indev_name : pm->outdev_name;
-    asprintf(&mParams[0], "ALERT_NAME=%s", pm->prefix);
+    if (isKernel64Bit()) {
+        ulog_packet_msg64_t* pm64 = (ulog_packet_msg64_t*)NLMSG_DATA(nh);
+        if (!checkRtNetlinkLength(nh, sizeof(*pm64))) return false;
+        alert = pm64->prefix;
+        devname = pm64->indev_name[0] ? pm64->indev_name : pm64->outdev_name;
+    } else {
+        ulog_packet_msg32_t* pm32 = (ulog_packet_msg32_t*)NLMSG_DATA(nh);
+        if (!checkRtNetlinkLength(nh, sizeof(*pm32))) return false;
+        alert = pm32->prefix;
+        devname = pm32->indev_name[0] ? pm32->indev_name : pm32->outdev_name;
+    }
+
+    asprintf(&mParams[0], "ALERT_NAME=%s", alert);
     asprintf(&mParams[1], "INTERFACE=%s", devname);
     mSubsystem = strdup("qlog");
     mAction = Action::kChange;
diff --git a/libutils/String16.cpp b/libutils/String16.cpp
index c42cada..68642d8 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -199,99 +199,59 @@
     return NO_MEMORY;
 }
 
-status_t String16::append(const String16& other)
-{
-    const size_t myLen = size();
-    const size_t otherLen = other.size();
-    if (myLen == 0) {
-        setTo(other);
-        return OK;
-    } else if (otherLen == 0) {
-        return OK;
-    }
-
-    if (myLen >= SIZE_MAX / sizeof(char16_t) - otherLen) {
-        android_errorWriteLog(0x534e4554, "73826242");
-        abort();
-    }
-
-    SharedBuffer* buf =
-            static_cast<SharedBuffer*>(editResize((myLen + otherLen + 1) * sizeof(char16_t)));
-    if (buf) {
-        char16_t* str = (char16_t*)buf->data();
-        memcpy(str+myLen, other, (otherLen+1)*sizeof(char16_t));
-        mString = str;
-        return OK;
-    }
-    return NO_MEMORY;
+status_t String16::append(const String16& other) {
+    return append(other.string(), other.size());
 }
 
-status_t String16::append(const char16_t* chrs, size_t otherLen)
-{
+status_t String16::append(const char16_t* chrs, size_t otherLen) {
     const size_t myLen = size();
-    if (myLen == 0) {
-        setTo(chrs, otherLen);
-        return OK;
-    } else if (otherLen == 0) {
-        return OK;
-    }
 
-    if (myLen >= SIZE_MAX / sizeof(char16_t) - otherLen) {
-        android_errorWriteLog(0x534e4554, "73826242");
-        abort();
-    }
+    if (myLen == 0) return setTo(chrs, otherLen);
 
-    SharedBuffer* buf =
-            static_cast<SharedBuffer*>(editResize((myLen + otherLen + 1) * sizeof(char16_t)));
-    if (buf) {
-        char16_t* str = (char16_t*)buf->data();
-        memcpy(str+myLen, chrs, otherLen*sizeof(char16_t));
-        str[myLen+otherLen] = 0;
-        mString = str;
-        return OK;
-    }
-    return NO_MEMORY;
+    if (otherLen == 0) return OK;
+
+    size_t size = myLen;
+    if (__builtin_add_overflow(size, otherLen, &size) ||
+        __builtin_add_overflow(size, 1, &size) ||
+        __builtin_mul_overflow(size, sizeof(char16_t), &size)) return NO_MEMORY;
+
+    SharedBuffer* buf = static_cast<SharedBuffer*>(editResize(size));
+    if (!buf) return NO_MEMORY;
+
+    char16_t* str = static_cast<char16_t*>(buf->data());
+    memcpy(str + myLen, chrs, otherLen * sizeof(char16_t));
+    str[myLen + otherLen] = 0;
+    mString = str;
+    return OK;
 }
 
-status_t String16::insert(size_t pos, const char16_t* chrs)
-{
+status_t String16::insert(size_t pos, const char16_t* chrs) {
     return insert(pos, chrs, strlen16(chrs));
 }
 
-status_t String16::insert(size_t pos, const char16_t* chrs, size_t len)
-{
+status_t String16::insert(size_t pos, const char16_t* chrs, size_t otherLen) {
     const size_t myLen = size();
-    if (myLen == 0) {
-        return setTo(chrs, len);
-        return OK;
-    } else if (len == 0) {
-        return OK;
-    }
+
+    if (myLen == 0) return setTo(chrs, otherLen);
+
+    if (otherLen == 0) return OK;
 
     if (pos > myLen) pos = myLen;
 
-    #if 0
-    printf("Insert in to %s: pos=%d, len=%d, myLen=%d, chrs=%s\n",
-           String8(*this).string(), pos,
-           len, myLen, String8(chrs, len).string());
-    #endif
+    size_t size = myLen;
+    if (__builtin_add_overflow(size, otherLen, &size) ||
+        __builtin_add_overflow(size, 1, &size) ||
+        __builtin_mul_overflow(size, sizeof(char16_t), &size)) return NO_MEMORY;
 
-    SharedBuffer* buf =
-            static_cast<SharedBuffer*>(editResize((myLen + len + 1) * sizeof(char16_t)));
-    if (buf) {
-        char16_t* str = (char16_t*)buf->data();
-        if (pos < myLen) {
-            memmove(str+pos+len, str+pos, (myLen-pos)*sizeof(char16_t));
-        }
-        memcpy(str+pos, chrs, len*sizeof(char16_t));
-        str[myLen+len] = 0;
-        mString = str;
-        #if 0
-        printf("Result (%d chrs): %s\n", size(), String8(*this).string());
-        #endif
-        return OK;
-    }
-    return NO_MEMORY;
+    SharedBuffer* buf = static_cast<SharedBuffer*>(editResize(size));
+    if (!buf) return NO_MEMORY;
+
+    char16_t* str = static_cast<char16_t*>(buf->data());
+    if (pos < myLen) memmove(str + pos + otherLen, str + pos, (myLen - pos) * sizeof(char16_t));
+    memcpy(str + pos, chrs, otherLen * sizeof(char16_t));
+    str[myLen + otherLen] = 0;
+    mString = str;
+    return OK;
 }
 
 ssize_t String16::findFirst(char16_t c) const
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index 7d7230e..c6e6f74 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -19,7 +19,7 @@
 
 #include <gtest/gtest.h>
 
-namespace android {
+using namespace android;
 
 ::testing::AssertionResult Char16_tStringEquals(const char16_t* a, const char16_t* b) {
     if (strcmp16(a, b) != 0) {
@@ -224,4 +224,36 @@
     EXPECT_STR16EQ(another, u"abcdef");
 }
 
-}  // namespace android
+TEST(String16Test, append) {
+    String16 s;
+    EXPECT_EQ(OK, s.append(String16(u"foo")));
+    EXPECT_STR16EQ(u"foo", s);
+    EXPECT_EQ(OK, s.append(String16(u"bar")));
+    EXPECT_STR16EQ(u"foobar", s);
+    EXPECT_EQ(OK, s.append(u"baz", 0));
+    EXPECT_STR16EQ(u"foobar", s);
+    EXPECT_EQ(NO_MEMORY, s.append(u"baz", SIZE_MAX));
+    EXPECT_STR16EQ(u"foobar", s);
+}
+
+TEST(String16Test, insert) {
+    String16 s;
+
+    // Inserting into the empty string inserts at the start.
+    EXPECT_EQ(OK, s.insert(123, u"foo"));
+    EXPECT_STR16EQ(u"foo", s);
+
+    // Inserting zero characters at any position is okay, but won't expand the string.
+    EXPECT_EQ(OK, s.insert(123, u"foo", 0));
+    EXPECT_STR16EQ(u"foo", s);
+
+    // Inserting past the end of a non-empty string appends.
+    EXPECT_EQ(OK, s.insert(123, u"bar"));
+    EXPECT_STR16EQ(u"foobar", s);
+
+    EXPECT_EQ(OK, s.insert(3, u"!"));
+    EXPECT_STR16EQ(u"foo!bar", s);
+
+    EXPECT_EQ(NO_MEMORY, s.insert(3, u"", SIZE_MAX));
+    EXPECT_STR16EQ(u"foo!bar", s);
+}
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 8511da9..419b2de 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -313,8 +313,8 @@
 
     if (n > 0) {
         size_t oldLength = length();
-        if ((size_t)n > SIZE_MAX - 1 ||
-            oldLength > SIZE_MAX - (size_t)n - 1) {
+        if (n > std::numeric_limits<size_t>::max() - 1 ||
+            oldLength > std::numeric_limits<size_t>::max() - n - 1) {
             return NO_MEMORY;
         }
         char* buf = lockBuffer(oldLength + n);
@@ -327,21 +327,23 @@
     return result;
 }
 
-status_t String8::real_append(const char* other, size_t otherLen)
-{
+status_t String8::real_append(const char* other, size_t otherLen) {
     const size_t myLen = bytes();
 
-    SharedBuffer* buf = SharedBuffer::bufferFromData(mString)
-        ->editResize(myLen+otherLen+1);
-    if (buf) {
-        char* str = (char*)buf->data();
-        mString = str;
-        str += myLen;
-        memcpy(str, other, otherLen);
-        str[otherLen] = '\0';
-        return OK;
+    SharedBuffer* buf;
+    size_t newLen;
+    if (__builtin_add_overflow(myLen, otherLen, &newLen) ||
+        __builtin_add_overflow(newLen, 1, &newLen) ||
+        (buf = SharedBuffer::bufferFromData(mString)->editResize(newLen)) == nullptr) {
+        return NO_MEMORY;
     }
-    return NO_MEMORY;
+
+    char* str = (char*)buf->data();
+    mString = str;
+    str += myLen;
+    memcpy(str, other, otherLen);
+    str[otherLen] = '\0';
+    return OK;
 }
 
 char* String8::lockBuffer(size_t size)
diff --git a/libutils/String8_test.cpp b/libutils/String8_test.cpp
index 9efcc6f..1356cd0 100644
--- a/libutils/String8_test.cpp
+++ b/libutils/String8_test.cpp
@@ -15,13 +15,14 @@
  */
 
 #define LOG_TAG "String8_test"
+
 #include <utils/Log.h>
 #include <utils/String8.h>
 #include <utils/String16.h>
 
 #include <gtest/gtest.h>
 
-namespace android {
+using namespace android;
 
 class String8Test : public testing::Test {
 protected:
@@ -101,4 +102,15 @@
     String8 valid = String8(String16(tmp));
     EXPECT_STREQ(valid, "abcdef");
 }
+
+TEST_F(String8Test, append) {
+    String8 s;
+    EXPECT_EQ(OK, s.append("foo"));
+    EXPECT_STREQ("foo", s);
+    EXPECT_EQ(OK, s.append("bar"));
+    EXPECT_STREQ("foobar", s);
+    EXPECT_EQ(OK, s.append("baz", 0));
+    EXPECT_STREQ("foobar", s);
+    EXPECT_EQ(NO_MEMORY, s.append("baz", SIZE_MAX));
+    EXPECT_STREQ("foobar", s);
 }
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 540dcf4..6e293c7 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -86,8 +86,10 @@
 
         // A new thread will be in its parent's sched group by default,
         // so we just need to handle the background case.
+        // currently set to system_background group which is different
+        // from background group for app.
         if (prio >= ANDROID_PRIORITY_BACKGROUND) {
-            SetTaskProfiles(0, {"SCHED_SP_BACKGROUND"}, true);
+            SetTaskProfiles(0, {"SCHED_SP_SYSTEM"}, true);
         }
 
         if (name) {
@@ -313,7 +315,7 @@
     }
 
     if (pri >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = SetTaskProfiles(tid, {"SCHED_SP_BACKGROUND"}, true) ? 0 : -1;
+        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.
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/llkd/libllkd.cpp b/llkd/libllkd.cpp
index c4c58ee..42602e9 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -1283,8 +1283,7 @@
     llkEnableSysrqT &= !llkLowRam;
     if (debuggable) {
         llkEnableSysrqT |= llkCheckEng(LLK_ENABLE_SYSRQ_T_PROPERTY);
-        if (!LLK_ENABLE_DEFAULT) {  // NB: default is currently true ...
-            llkEnable |= llkCheckEng(LLK_ENABLE_PROPERTY);
+        if (!LLK_ENABLE_DEFAULT) {
             khtEnable |= llkCheckEng(KHT_ENABLE_PROPERTY);
         }
     }
diff --git a/llkd/llkd-debuggable.rc b/llkd/llkd-debuggable.rc
index 8697e9a..8355e9d 100644
--- a/llkd/llkd-debuggable.rc
+++ b/llkd/llkd-debuggable.rc
@@ -1,5 +1,5 @@
 on property:ro.debuggable=1
-    setprop llk.enable ${ro.llk.enable:-1}
+    setprop llk.enable ${ro.llk.enable:-0}
     setprop khungtask.enable ${ro.khungtask.enable:-1}
 
 on property:ro.llk.enable=eng
diff --git a/llkd/tests/llkd_test.cpp b/llkd/tests/llkd_test.cpp
index 475512c..8eb9b00 100644
--- a/llkd/tests/llkd_test.cpp
+++ b/llkd/tests/llkd_test.cpp
@@ -69,13 +69,9 @@
 seconds llkdSleepPeriod(char state) {
     auto default_eng = android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng";
     auto default_enable = LLK_ENABLE_DEFAULT;
-    if (!LLK_ENABLE_DEFAULT && default_eng &&
-        android::base::GetBoolProperty("ro.debuggable", false)) {
-        default_enable = true;
-    }
     default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable);
     if (default_eng) {
-        GTEST_LOG_INFO << LLK_ENABLE_PROPERTY " defaults to \"eng\" thus "
+        GTEST_LOG_INFO << LLK_ENABLE_PROPERTY " defaults to "
                        << (default_enable ? "true" : "false") << "\n";
     }
     // Hail Mary hope is unconfigured.
@@ -108,10 +104,6 @@
         rest();
     }
     default_enable = LLK_ENABLE_DEFAULT;
-    if (!LLK_ENABLE_DEFAULT && (android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng") &&
-        android::base::GetBoolProperty("ro.debuggable", false)) {
-        default_enable = true;
-    }
     default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable);
     if (default_enable) {
         execute("start llkd-1");
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 3831693..b09c2f1 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -155,6 +155,7 @@
     mkdir /dev/cpuctl/rt
     mkdir /dev/cpuctl/system
     mkdir /dev/cpuctl/system-background
+    mkdir /dev/cpuctl/dex2oat
     chown system system /dev/cpuctl
     chown system system /dev/cpuctl/foreground
     chown system system /dev/cpuctl/background
@@ -162,6 +163,7 @@
     chown system system /dev/cpuctl/rt
     chown system system /dev/cpuctl/system
     chown system system /dev/cpuctl/system-background
+    chown system system /dev/cpuctl/dex2oat
     chown system system /dev/cpuctl/tasks
     chown system system /dev/cpuctl/foreground/tasks
     chown system system /dev/cpuctl/background/tasks
@@ -169,6 +171,7 @@
     chown system system /dev/cpuctl/rt/tasks
     chown system system /dev/cpuctl/system/tasks
     chown system system /dev/cpuctl/system-background/tasks
+    chown system system /dev/cpuctl/dex2oat/tasks
     chmod 0664 /dev/cpuctl/tasks
     chmod 0664 /dev/cpuctl/foreground/tasks
     chmod 0664 /dev/cpuctl/background/tasks
@@ -176,6 +179,7 @@
     chmod 0664 /dev/cpuctl/rt/tasks
     chmod 0664 /dev/cpuctl/system/tasks
     chmod 0664 /dev/cpuctl/system-background/tasks
+    chmod 0664 /dev/cpuctl/dex2oat/tasks
 
     # Create a cpu group for NNAPI HAL processes
     mkdir /dev/cpuctl/nnapi-hal
@@ -456,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
@@ -487,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
 
@@ -579,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.
@@ -672,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
@@ -1097,6 +1106,9 @@
     # Define default initial receive window size in segments.
     setprop net.tcp_def_init_rwnd 60
 
+    # Update dm-verity state and set partition.*.verified properties.
+    verity_update_state
+
     # Start standard binderized HAL daemons
     class_start hal
 
@@ -1112,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/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index 7bd1d10..99d9e56 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -117,6 +117,7 @@
         "libkeymint",
         "liblog",
         "libtrusty",
+        "libutils",
     ],
     required: [
         "android.hardware.hardware_keystore.xml",
@@ -142,6 +143,7 @@
         "libtrusty",
         "libhardware",
         "libkeymaster_messages",
+        "libutils",
         "libxml2",
     ],
     export_include_dirs: ["include"],
@@ -169,6 +171,7 @@
         "libtrusty",
         "libhardware",
         "libkeymaster_messages",
+        "libutils",
         "libxml2",
     ],
     cflags: [
diff --git a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
index 2d44009..db1a9f4 100644
--- a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
+++ b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
@@ -19,6 +19,7 @@
 // TODO: make this generic in libtrusty
 
 #include <errno.h>
+#include <poll.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/uio.h>
@@ -33,11 +34,15 @@
 
 #include <trusty_keymaster/ipc/keymaster_ipc.h>
 #include <trusty_keymaster/ipc/trusty_keymaster_ipc.h>
+#include <utils/Timers.h>
 
 #define TRUSTY_DEVICE_NAME "/dev/trusty-ipc-dev0"
 
 static int handle_ = -1;
 
+static const int timeout_ms = 10 * 1000;
+static const int max_timeout_ms = 60 * 1000;
+
 int trusty_keymaster_connect() {
     int rc = tipc_connect(TRUSTY_DEVICE_NAME, KEYMASTER_PORT);
     if (rc < 0) {
@@ -84,7 +89,38 @@
     msg->cmd = cmd;
     memcpy(msg->payload, in, in_size);
 
+    nsecs_t start_time_ns = systemTime(SYSTEM_TIME_MONOTONIC);
+    bool timed_out = false;
+    int poll_timeout_ms = timeout_ms;
+    while (true) {
+        struct pollfd pfd;
+        pfd.fd = handle_;
+        pfd.events = POLLOUT;
+        pfd.revents = 0;
+
+        int p = poll(&pfd, 1, poll_timeout_ms);
+        if (p == 0) {
+            ALOGW("write for cmd %d is taking more than %lld nsecs", cmd,
+                  (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+            timed_out = true;
+            poll_timeout_ms *= 2;
+            if (poll_timeout_ms > max_timeout_ms) {
+                poll_timeout_ms = max_timeout_ms;
+            }
+            continue;
+        } else if (p < 0) {
+            ALOGE("write poll error: %d", errno);
+        } else if (pfd.revents != POLLOUT) {
+            ALOGW("unexpected poll() result: %d", pfd.revents);
+        }
+        break;
+    }
+
     ssize_t rc = write(handle_, msg, msg_size);
+    if (timed_out) {
+        ALOGW("write for cmd %d finished after %lld nsecs", cmd,
+              (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+    }
     free(msg);
 
     if (rc < 0) {
@@ -122,8 +158,37 @@
             return -EOVERFLOW;
         }
         iov[1] = {.iov_base = write_pos, .iov_len = buffer_size};
+        start_time_ns = systemTime(SYSTEM_TIME_MONOTONIC);
+        timed_out = false;
+        poll_timeout_ms = timeout_ms;
+        while (true) {
+            struct pollfd pfd;
+            pfd.fd = handle_;
+            pfd.events = POLLIN;
+            pfd.revents = 0;
 
+            int p = poll(&pfd, 1, poll_timeout_ms);
+            if (p == 0) {
+                ALOGW("readv for cmd %d is taking more than %lld nsecs", cmd,
+                      (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+                timed_out = true;
+                poll_timeout_ms *= 2;
+                if (poll_timeout_ms > max_timeout_ms) {
+                    poll_timeout_ms = max_timeout_ms;
+                }
+                continue;
+            } else if (p < 0) {
+                ALOGE("read poll error: %d", errno);
+            } else if (pfd.revents != POLLIN) {
+                ALOGW("unexpected poll() result: %d", pfd.revents);
+            }
+            break;
+        }
         rc = readv(handle_, iov, 2);
+        if (timed_out) {
+            ALOGW("readv for cmd %d finished after %lld nsecs", cmd,
+                  (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+        }
         if (rc < 0) {
             ALOGE("failed to retrieve response for cmd (%d) to %s: %s\n", cmd, KEYMASTER_PORT,
                   strerror(errno));
diff --git a/trusty/keymaster/keymint/service.cpp b/trusty/keymaster/keymint/service.cpp
index 4060278..d5a77fb 100644
--- a/trusty/keymaster/keymint/service.cpp
+++ b/trusty/keymaster/keymint/service.cpp
@@ -31,7 +31,7 @@
 
 template <typename T, class... Args>
 std::shared_ptr<T> addService(Args&&... args) {
-    std::shared_ptr<T> service = std::make_shared<T>(std::forward<Args>(args)...);
+    std::shared_ptr<T> service = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
     auto instanceName = std::string(T::descriptor) + "/default";
     LOG(ERROR) << "Adding service instance: " << instanceName;
     auto status = AServiceManager_addService(service->asBinder().get(), instanceName.c_str());
diff --git a/trusty/storage/interface/include/trusty/interface/storage.h b/trusty/storage/interface/include/trusty/interface/storage.h
index b196d88..3f1dcb8 100644
--- a/trusty/storage/interface/include/trusty/interface/storage.h
+++ b/trusty/storage/interface/include/trusty/interface/storage.h
@@ -112,26 +112,30 @@
 
 /**
  * enum storage_msg_flag - protocol-level flags in struct storage_msg
- * @STORAGE_MSG_FLAG_BATCH:             if set, command belongs to a batch transaction.
- *                                      No response will be sent by the server until
- *                                      it receives a command with this flag unset, at
- *                                      which point a cummulative result for all messages
- *                                      sent with STORAGE_MSG_FLAG_BATCH will be sent.
- *                                      This is only supported by the non-secure disk proxy
- *                                      server.
- * @STORAGE_MSG_FLAG_PRE_COMMIT:        if set, indicates that server need to commit
- *                                      pending changes before processing this message.
- * @STORAGE_MSG_FLAG_POST_COMMIT:       if set, indicates that server need to commit
- *                                      pending changes after processing this message.
- * @STORAGE_MSG_FLAG_TRANSACT_COMPLETE: if set, indicates that server need to commit
- *                                      current transaction after processing this message.
- *                                      It is an alias for STORAGE_MSG_FLAG_POST_COMMIT.
+ * @STORAGE_MSG_FLAG_BATCH:                 if set, command belongs to a batch transaction.
+ *                                          No response will be sent by the server until
+ *                                          it receives a command with this flag unset, at
+ *                                          which point a cumulative result for all messages
+ *                                          sent with STORAGE_MSG_FLAG_BATCH will be sent.
+ *                                          This is only supported by the non-secure disk proxy
+ *                                          server.
+ * @STORAGE_MSG_FLAG_PRE_COMMIT:            if set, indicates that server need to commit
+ *                                          pending changes before processing this message.
+ * @STORAGE_MSG_FLAG_POST_COMMIT:           if set, indicates that server need to commit
+ *                                          pending changes after processing this message.
+ * @STORAGE_MSG_FLAG_TRANSACT_COMPLETE:     if set, indicates that server need to commit
+ *                                          current transaction after processing this message.
+ *                                          It is an alias for STORAGE_MSG_FLAG_POST_COMMIT.
+ * @STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT: if set, indicates that server needs to ensure
+ *                                          that there is not a pending checkpoint for
+ *                                          userdata before processing this message.
  */
 enum storage_msg_flag {
-	STORAGE_MSG_FLAG_BATCH = 0x1,
-	STORAGE_MSG_FLAG_PRE_COMMIT = 0x2,
-	STORAGE_MSG_FLAG_POST_COMMIT = 0x4,
-	STORAGE_MSG_FLAG_TRANSACT_COMPLETE = STORAGE_MSG_FLAG_POST_COMMIT,
+    STORAGE_MSG_FLAG_BATCH = 0x1,
+    STORAGE_MSG_FLAG_PRE_COMMIT = 0x2,
+    STORAGE_MSG_FLAG_POST_COMMIT = 0x4,
+    STORAGE_MSG_FLAG_TRANSACT_COMPLETE = STORAGE_MSG_FLAG_POST_COMMIT,
+    STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT = 0x8,
 };
 
 /*
diff --git a/trusty/storage/proxy/Android.bp b/trusty/storage/proxy/Android.bp
index d67089f..38d8685 100644
--- a/trusty/storage/proxy/Android.bp
+++ b/trusty/storage/proxy/Android.bp
@@ -23,6 +23,7 @@
     vendor: true,
 
     srcs: [
+        "checkpoint_handling.cpp",
         "ipc.c",
         "rpmb.c",
         "storage.c",
@@ -30,12 +31,14 @@
     ],
 
     shared_libs: [
+        "libbase",
         "liblog",
         "libhardware_legacy",
     ],
     header_libs: ["libcutils_headers"],
 
     static_libs: [
+        "libfstab",
         "libtrustystorageinterface",
         "libtrusty",
     ],
diff --git a/trusty/storage/proxy/checkpoint_handling.cpp b/trusty/storage/proxy/checkpoint_handling.cpp
new file mode 100644
index 0000000..6c2fd36
--- /dev/null
+++ b/trusty/storage/proxy/checkpoint_handling.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 "checkpoint_handling.h"
+#include "log.h"
+
+#include <fstab/fstab.h>
+#include <cstring>
+#include <string>
+
+namespace {
+
+bool checkpointingDoneForever = false;
+
+}  // namespace
+
+int is_data_checkpoint_active(bool* active) {
+    if (!active) {
+        ALOGE("active out parameter is null");
+        return 0;
+    }
+
+    *active = false;
+
+    if (checkpointingDoneForever) {
+        return 0;
+    }
+
+    android::fs_mgr::Fstab procMounts;
+    bool success = android::fs_mgr::ReadFstabFromFile("/proc/mounts", &procMounts);
+    if (!success) {
+        ALOGE("Could not parse /proc/mounts\n");
+        /* Really bad. Tell the caller to abort the write. */
+        return -1;
+    }
+
+    android::fs_mgr::FstabEntry* dataEntry =
+            android::fs_mgr::GetEntryForMountPoint(&procMounts, "/data");
+    if (dataEntry == NULL) {
+        ALOGE("/data is not mounted yet\n");
+        return 0;
+    }
+
+    /* We can't handle e.g., ext4. Nothing we can do about it for now. */
+    if (dataEntry->fs_type != "f2fs") {
+        ALOGW("Checkpoint status not supported for filesystem %s\n", dataEntry->fs_type.c_str());
+        checkpointingDoneForever = true;
+        return 0;
+    }
+
+    /*
+     * The data entry looks like "... blah,checkpoint=disable:0,blah ...".
+     * checkpoint=disable means checkpointing is on (yes, arguably reversed).
+     */
+    size_t checkpointPos = dataEntry->fs_options.find("checkpoint=disable");
+    if (checkpointPos == std::string::npos) {
+        /* Assumption is that once checkpointing turns off, it stays off */
+        checkpointingDoneForever = true;
+    } else {
+        *active = true;
+    }
+
+    return 0;
+}
diff --git a/trusty/storage/proxy/checkpoint_handling.h b/trusty/storage/proxy/checkpoint_handling.h
new file mode 100644
index 0000000..f1bf27c
--- /dev/null
+++ b/trusty/storage/proxy/checkpoint_handling.h
@@ -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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * is_data_checkpoint_active() - Check for an active, uncommitted checkpoint of
+ * /data. If a checkpoint is active, storage should not commit any
+ * rollback-protected writes to /data.
+ * @active: Out parameter that will be set to the result of the check.
+ *
+ * Return: 0 if active was set and is valid, non-zero otherwise.
+ */
+int is_data_checkpoint_active(bool* active);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index e230941..c690a28 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -26,6 +26,7 @@
 
 #include <cutils/android_filesystem_config.h>
 
+#include "checkpoint_handling.h"
 #include "ipc.h"
 #include "log.h"
 #include "rpmb.h"
@@ -130,6 +131,21 @@
         }
     }
 
+    if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT) {
+        bool is_checkpoint_active = false;
+
+        rc = is_data_checkpoint_active(&is_checkpoint_active);
+        if (rc != 0) {
+            ALOGE("is_data_checkpoint_active failed in an unexpected way. Aborting.\n");
+            msg->result = STORAGE_ERR_GENERIC;
+            return ipc_respond(msg, NULL, 0);
+        } else if (is_checkpoint_active) {
+            ALOGE("Checkpoint in progress, dropping write ...\n");
+            msg->result = STORAGE_ERR_GENERIC;
+            return ipc_respond(msg, NULL, 0);
+        }
+    }
+
     switch (msg->cmd) {
         case STORAGE_FILE_DELETE:
             rc = storage_file_delete(msg, req, req_len);