Merge "healthd: remove hridya@ from OWNERS file"
diff --git a/TEST_MAPPING b/TEST_MAPPING
deleted file mode 100644
index da7fca1..0000000
--- a/TEST_MAPPING
+++ /dev/null
@@ -1,66 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "adbd_test"
-    },
-    {
-      "name": "adb_crypto_test"
-    },
-    {
-      "name": "adb_pairing_auth_test"
-    },
-    {
-      "name": "adb_pairing_connection_test"
-    },
-    {
-      "name": "adb_tls_connection_test"
-    },
-    {
-      "name": "CtsFsMgrTestCases"
-    },
-    {
-      "name": "CtsInitTestCases"
-    },
-    {
-      "name": "debuggerd_test"
-    },
-    {
-      "name": "fs_mgr_vendor_overlay_test"
-    },
-    {
-      "name": "init_kill_services_test"
-    },
-    {
-      "name": "libpackagelistparser_test"
-    },
-    {
-      "name": "libcutils_test"
-    },
-    {
-      "name": "libmodprobe_tests"
-    },
-    {
-      "name": "libprocinfo_test"
-    },
-    {
-      "name": "libutils_test"
-    },
-    {
-      "name": "memunreachable_test"
-    },
-    {
-      "name": "memunreachable_unit_test"
-    },
-    {
-      "name": "memunreachable_binder_test"
-    },
-    {
-      "name": "propertyinfoserializer_tests"
-    }
-  ],
-  "imports": [
-    {
-      "path": "frameworks/base/tests/StagedInstallTest"
-    }
-  ]
-}
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index d6ebb0d..2c878f0 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -439,6 +439,30 @@
     {"reboot,forcedsilent", 191},
     {"reboot,forcednonsilent", 192},
     {"reboot,thermal,tj", 193},
+    {"reboot,emergency", 194},
+    {"reboot,factory", 195},
+    {"reboot,fastboot", 196},
+    {"reboot,gsa,hard", 197},
+    {"reboot,gsa,soft", 198},
+    {"reboot,master_dc,fault_n", 199},
+    {"reboot,master_dc,reset", 200},
+    {"reboot,ocp", 201},
+    {"reboot,pin", 202},
+    {"reboot,rom_recovery", 203},
+    {"reboot,uvlo", 204},
+    {"reboot,uvlo,pmic,if", 205},
+    {"reboot,uvlo,pmic,main", 206},
+    {"reboot,uvlo,pmic,sub", 207},
+    {"reboot,warm", 208},
+    {"watchdog,aoc", 209},
+    {"watchdog,apc", 210},
+    {"watchdog,apc,bl,debug,early", 211},
+    {"watchdog,apc,bl,early", 212},
+    {"watchdog,apc,early", 213},
+    {"watchdog,apm", 214},
+    {"watchdog,gsa,hard", 215},
+    {"watchdog,gsa,soft", 216},
+    {"watchdog,pmucal", 217},
 };
 
 // Converts a string value representing the reason the system booted to an
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index f713ff2..edcea44 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -180,7 +180,6 @@
         "libdebuggerd/backtrace.cpp",
         "libdebuggerd/gwp_asan.cpp",
         "libdebuggerd/open_files_list.cpp",
-        "libdebuggerd/scudo.cpp",
         "libdebuggerd/tombstone.cpp",
         "libdebuggerd/tombstone_proto.cpp",
         "libdebuggerd/tombstone_proto_to_text.cpp",
@@ -193,9 +192,6 @@
     include_dirs: [
         // Needed for private/bionic_fdsan.h
         "bionic/libc",
-
-        // Needed for scudo/interface.h
-        "external/scudo/standalone/include",
     ],
     header_libs: [
         "bionic_libc_platform_headers",
@@ -210,11 +206,13 @@
         "libcutils",
         "liblog",
     ],
+    runtime_libs: [
+        "libdexfile",           // libdexfile_support dependency
+    ],
 
     whole_static_libs: [
         "libasync_safe",
         "gwp_asan_crash_handler",
-        "libscudo",
         "libtombstone_proto",
         "libprocinfo",
         "libprotobuf-cpp-lite",
@@ -225,11 +223,17 @@
             exclude_static_libs: [
                 "libdexfile_support",
             ],
+            exclude_runtime_libs: [
+                "libdexfile",
+            ],
         },
         vendor_ramdisk: {
             exclude_static_libs: [
                 "libdexfile_support",
             ],
+            exclude_runtime_libs: [
+                "libdexfile",
+            ],
         },
     },
 
@@ -237,6 +241,13 @@
         debuggable: {
             cflags: ["-DROOT_POSSIBLE"],
         },
+
+        malloc_not_svelte: {
+            cflags: ["-DUSE_SCUDO"],
+            whole_static_libs: ["libscudo"],
+            srcs: ["libdebuggerd/scudo.cpp"],
+            header_libs: ["scudo_headers"],
+        },
     },
 }
 
@@ -272,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: {
@@ -307,10 +319,6 @@
         "gwp_asan_headers",
     ],
 
-    include_dirs: [
-        "external/scudo/standalone/include",
-    ],
-
     local_include_dirs: [
         "libdebuggerd",
     ],
diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING
new file mode 100644
index 0000000..d5327db
--- /dev/null
+++ b/debuggerd/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "debuggerd_test"
+    }
+  ]
+}
diff --git a/debuggerd/crash_test.cpp b/debuggerd/crash_test.cpp
index c15145f..ce0f91f 100644
--- a/debuggerd/crash_test.cpp
+++ b/debuggerd/crash_test.cpp
@@ -16,6 +16,13 @@
 
 #include <stdint.h>
 
-extern "C" void crash() {
+#include "crash_test.h"
+
+extern "C" {
+
+JITDescriptor __dex_debug_descriptor = {.version = 1};
+
+void crash() {
   *reinterpret_cast<volatile char*>(0xdead) = '1';
 }
+}
diff --git a/debuggerd/crash_test.h b/debuggerd/crash_test.h
new file mode 100644
index 0000000..2a8bea3
--- /dev/null
+++ b/debuggerd/crash_test.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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 <stdint.h>
+
+// Only support V1 of these structures.
+// See https://sourceware.org/gdb/onlinedocs/gdb/JIT-Interface.html
+// for information on the JIT Compilation Interface.
+// Also, see libunwindstack/GlobalDebugImpl.h for the full definition of
+// these structures.
+struct JITCodeEntry {
+  uintptr_t next;
+  uintptr_t prev;
+  uintptr_t symfile_addr;
+  uint64_t symfile_size;
+};
+
+struct JITDescriptor {
+  uint32_t version;
+  uint32_t action_flag;
+  uintptr_t relevant_entry;
+  uintptr_t first_entry;
+};
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index f24c4fc..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,9 +56,13 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <unwindstack/Elf.h>
+#include <unwindstack/Memory.h>
+
 #include <libminijail.h>
 #include <scoped_minijail.h>
 
+#include "crash_test.h"
 #include "debuggerd/handler.h"
 #include "libdebuggerd/utility.h"
 #include "protocol.h"
@@ -339,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\))");
   }
 }
 
@@ -369,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
@@ -421,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.
@@ -652,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()) {
@@ -702,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,
@@ -889,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) {
@@ -961,6 +1008,44 @@
   ASSERT_MATCH(result, R"(Abort message: 'x{4045}')");
 }
 
+TEST_F(CrasherTest, abort_message_newline_trimmed) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    android_set_abort_message("Message with a newline.\n");
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(Abort message: 'Message with a newline.')");
+}
+
+TEST_F(CrasherTest, abort_message_multiple_newlines_trimmed) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    android_set_abort_message("Message with multiple newlines.\n\n\n\n\n");
+    abort();
+  });
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGABRT);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(Abort message: 'Message with multiple newlines.')");
+}
+
 TEST_F(CrasherTest, abort_message_backtrace) {
   int intercept_result;
   unique_fd output_fd;
@@ -1851,3 +1936,505 @@
   ConsumeFd(std::move(output_fd), &result);
   ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal");
 }
+
+static std::string format_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", static_cast<uint32_t>(ptr & 0xffffffff));
+#endif
+}
+
+static std::string format_pointer(void* ptr) {
+  return format_pointer(reinterpret_cast<uintptr_t>(ptr));
+}
+
+static std::string format_full_pointer(uintptr_t ptr) {
+#if defined(__LP64__)
+  return android::base::StringPrintf("%016" PRIx64, ptr);
+#else
+  return android::base::StringPrintf("%08x", ptr);
+#endif
+}
+
+static std::string format_full_pointer(void* ptr) {
+  return format_full_pointer(reinterpret_cast<uintptr_t>(ptr));
+}
+
+__attribute__((__noinline__)) int crash_call(uintptr_t ptr) {
+  int* crash_ptr = reinterpret_cast<int*>(ptr);
+  *crash_ptr = 1;
+  return *crash_ptr;
+}
+
+// Verify that a fault address before the first map is properly handled.
+TEST_F(CrasherTest, fault_address_before_first_map) {
+  StartProcess([]() {
+    ASSERT_EQ(0, crash_call(0x1024));
+    _exit(0);
+  });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+1024)");
+
+  ASSERT_MATCH(result, R"(\nmemory map \(.*\):\n)");
+
+  std::string match_str = android::base::StringPrintf(
+      R"(memory map .*:\n--->Fault address falls at %s before any mapped regions\n    )",
+      format_pointer(0x1024).c_str());
+  ASSERT_MATCH(result, match_str);
+}
+
+// Verify that a fault address after the last map is properly handled.
+TEST_F(CrasherTest, fault_address_after_last_map) {
+  uintptr_t crash_uptr = untag_address(UINTPTR_MAX - 15);
+  StartProcess([crash_uptr]() {
+    ASSERT_EQ(0, crash_call(crash_uptr));
+    _exit(0);
+  });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+
+  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 = 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)");
+
+  // Assumes that the open files section comes after the map section.
+  // If that assumption changes, the regex below needs to change.
+  match_str = android::base::StringPrintf(
+      R"(\n--->Fault address falls at %s after any mapped regions\n\nopen files:)",
+      format_pointer(crash_uptr).c_str());
+  ASSERT_MATCH(result, match_str);
+}
+
+// Verify that a fault address between maps is properly handled.
+TEST_F(CrasherTest, fault_address_between_maps) {
+  // Create a map before the fork so it will be present in the child.
+  void* start_ptr =
+      mmap(nullptr, 3 * getpagesize(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  ASSERT_NE(MAP_FAILED, start_ptr);
+  // Unmap the page in the middle.
+  void* middle_ptr =
+      reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(start_ptr) + getpagesize());
+  ASSERT_EQ(0, munmap(middle_ptr, getpagesize()));
+
+  StartProcess([middle_ptr]() {
+    ASSERT_EQ(0, crash_call(reinterpret_cast<uintptr_t>(middle_ptr)));
+    _exit(0);
+  });
+
+  // Unmap the two maps.
+  ASSERT_EQ(0, munmap(start_ptr, getpagesize()));
+  void* end_ptr =
+      reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(start_ptr) + 2 * getpagesize());
+  ASSERT_EQ(0, munmap(end_ptr, getpagesize()));
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+
+  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 = 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)");
+
+  match_str = android::base::StringPrintf(
+      R"(    %s.*\n--->Fault address falls at %s between mapped regions\n    %s)",
+      format_pointer(start_ptr).c_str(), format_pointer(middle_ptr).c_str(),
+      format_pointer(end_ptr).c_str());
+  ASSERT_MATCH(result, match_str);
+}
+
+// Verify that a fault address happens in the correct map.
+TEST_F(CrasherTest, fault_address_in_map) {
+  // Create a map before the fork so it will be present in the child.
+  void* ptr = mmap(nullptr, getpagesize(), 0, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  ASSERT_NE(MAP_FAILED, ptr);
+
+  StartProcess([ptr]() {
+    ASSERT_EQ(0, crash_call(reinterpret_cast<uintptr_t>(ptr)));
+    _exit(0);
+  });
+
+  ASSERT_EQ(0, munmap(ptr, getpagesize()));
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+
+  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 = 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)");
+
+  match_str = android::base::StringPrintf(R"(\n--->%s.*\n)", format_pointer(ptr).c_str());
+  ASSERT_MATCH(result, match_str);
+}
+
+static constexpr uint32_t kDexData[] = {
+    0x0a786564, 0x00383330, 0xc98b3ab8, 0xf3749d94, 0xaecca4d8, 0xffc7b09a, 0xdca9ca7f, 0x5be5deab,
+    0x00000220, 0x00000070, 0x12345678, 0x00000000, 0x00000000, 0x0000018c, 0x00000008, 0x00000070,
+    0x00000004, 0x00000090, 0x00000002, 0x000000a0, 0x00000000, 0x00000000, 0x00000003, 0x000000b8,
+    0x00000001, 0x000000d0, 0x00000130, 0x000000f0, 0x00000122, 0x0000012a, 0x00000132, 0x00000146,
+    0x00000151, 0x00000154, 0x00000158, 0x0000016d, 0x00000001, 0x00000002, 0x00000004, 0x00000006,
+    0x00000004, 0x00000002, 0x00000000, 0x00000005, 0x00000002, 0x0000011c, 0x00000000, 0x00000000,
+    0x00010000, 0x00000007, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000,
+    0x00000003, 0x00000000, 0x0000017e, 0x00000000, 0x00010001, 0x00000001, 0x00000173, 0x00000004,
+    0x00021070, 0x000e0000, 0x00010001, 0x00000000, 0x00000178, 0x00000001, 0x0000000e, 0x00000001,
+    0x3c060003, 0x74696e69, 0x4c06003e, 0x6e69614d, 0x4c12003b, 0x6176616a, 0x6e616c2f, 0x624f2f67,
+    0x7463656a, 0x4d09003b, 0x2e6e6961, 0x6176616a, 0x00560100, 0x004c5602, 0x6a4c5b13, 0x2f617661,
+    0x676e616c, 0x7274532f, 0x3b676e69, 0x616d0400, 0x01006e69, 0x000e0700, 0x07000103, 0x0000000e,
+    0x81000002, 0x01f00480, 0x02880901, 0x0000000c, 0x00000000, 0x00000001, 0x00000000, 0x00000001,
+    0x00000008, 0x00000070, 0x00000002, 0x00000004, 0x00000090, 0x00000003, 0x00000002, 0x000000a0,
+    0x00000005, 0x00000003, 0x000000b8, 0x00000006, 0x00000001, 0x000000d0, 0x00002001, 0x00000002,
+    0x000000f0, 0x00001001, 0x00000001, 0x0000011c, 0x00002002, 0x00000008, 0x00000122, 0x00002003,
+    0x00000002, 0x00000173, 0x00002000, 0x00000001, 0x0000017e, 0x00001000, 0x00000001, 0x0000018c,
+};
+
+TEST_F(CrasherTest, verify_dex_pc_with_function_name) {
+  StartProcess([]() {
+    TemporaryDir td;
+    std::string tmp_so_name;
+    if (!CopySharedLibrary(td.path, &tmp_so_name)) {
+      _exit(1);
+    }
+
+    // In order to cause libunwindstack to look for this __dex_debug_descriptor
+    // move the library to which has a basename of libart.so.
+    std::string art_so_name = android::base::Dirname(tmp_so_name) + "/libart.so";
+    ASSERT_EQ(0, rename(tmp_so_name.c_str(), art_so_name.c_str()));
+    void* handle = dlopen(art_so_name.c_str(), RTLD_NOW | RTLD_LOCAL);
+    if (handle == nullptr) {
+      _exit(1);
+    }
+
+    void* ptr =
+        mmap(nullptr, sizeof(kDexData), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+    ASSERT_TRUE(ptr != MAP_FAILED);
+    memcpy(ptr, kDexData, sizeof(kDexData));
+    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, sizeof(kDexData), "dex");
+
+    JITCodeEntry dex_entry = {.symfile_addr = reinterpret_cast<uintptr_t>(ptr),
+                              .symfile_size = sizeof(kDexData)};
+
+    JITDescriptor* dex_debug =
+        reinterpret_cast<JITDescriptor*>(dlsym(handle, "__dex_debug_descriptor"));
+    ASSERT_TRUE(dex_debug != nullptr);
+    dex_debug->version = 1;
+    dex_debug->action_flag = 0;
+    dex_debug->relevant_entry = 0;
+    dex_debug->first_entry = reinterpret_cast<uintptr_t>(&dex_entry);
+
+    // This sets the magic dex pc value for register 0, using the value
+    // of register 1 + 0x102.
+    asm(".cfi_escape "
+        "0x16 /* DW_CFA_val_expression */, 0, 0x0a /* size */,"
+        "0x0c /* DW_OP_const4u */, 0x44, 0x45, 0x58, 0x31, /* magic = 'DEX1' */"
+        "0x13 /* DW_OP_drop */,"
+        "0x92 /* DW_OP_bregx */, 1, 0x82, 0x02 /* 2-byte SLEB128 */");
+
+    // For each different architecture, set register one to the dex ptr mmap
+    // created above. Then do a nullptr dereference to force a crash.
+#if defined(__arm__)
+    asm volatile(
+        "mov r1, %[base]\n"
+        "mov r2, 0\n"
+        "str r3, [r2]\n"
+        : [base] "+r"(ptr)
+        :
+        : "r1", "r2", "r3", "memory");
+#elif defined(__aarch64__)
+    asm volatile(
+        "mov x1, %[base]\n"
+        "mov x2, 0\n"
+        "str x3, [x2]\n"
+        : [base] "+r"(ptr)
+        :
+        : "x1", "x2", "x3", "memory");
+#elif defined(__i386__)
+    asm volatile(
+        "mov %[base], %%ecx\n"
+        "movl $0, %%edi\n"
+        "movl 0(%%edi), %%edx\n"
+        : [base] "+r"(ptr)
+        :
+        : "edi", "ecx", "edx", "memory");
+#elif defined(__x86_64__)
+    asm volatile(
+        "mov %[base], %%rdx\n"
+        "movq 0, %%rdi\n"
+        "movq 0(%%rdi), %%rcx\n"
+        : [base] "+r"(ptr)
+        :
+        : "rcx", "rdx", "rdi", "memory");
+#else
+#error "Unsupported architecture"
+#endif
+    _exit(0);
+  });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  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*)");
+
+  // 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 9c01f15..f21a203 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -18,547 +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/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/scudo.h"
 #include "libdebuggerd/utility.h"
 #include "util.h"
 
-#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, const siginfo_t* si, unwindstack::Maps* maps,
-                                unwindstack::Regs* regs) {
-  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;
-  }
-
-  _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());
-  }
-
-  std::unique_ptr<GwpAsanCrashData> gwp_asan_crash_data;
-  std::unique_ptr<ScudoCrashData> scudo_crash_data;
-  if (primary_thread) {
-    gwp_asan_crash_data = std::make_unique<GwpAsanCrashData>(unwinder->GetProcessMemory().get(),
-                                                             process_info, thread_info);
-    scudo_crash_data =
-        std::make_unique<ScudoCrashData>(unwinder->GetProcessMemory().get(), process_info);
-  }
-
-  if (primary_thread && gwp_asan_crash_data->CrashIsMine()) {
-    gwp_asan_crash_data->DumpCause(log);
-  } else if (thread_info.siginfo && !(primary_thread && scudo_crash_data->CrashIsMine())) {
-    dump_probable_cause(log, thread_info.siginfo, unwinder->GetMaps(), thread_info.registers.get());
-  }
-
-  if (primary_thread) {
-    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) {
-    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);
-    }
-
-    scudo_crash_data->DumpCause(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();
@@ -627,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 ff12017..b1c4ef3 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -18,7 +18,9 @@
 
 #include "libdebuggerd/tombstone.h"
 #include "libdebuggerd/gwp_asan.h"
+#if defined(USE_SCUDO)
 #include "libdebuggerd/scudo.h"
+#endif
 
 #include <errno.h>
 #include <fcntl.h>
@@ -28,6 +30,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
+#include <sys/sysinfo.h>
 #include <time.h>
 
 #include <memory>
@@ -37,6 +40,7 @@
 #include <async_safe/log.h>
 
 #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>
@@ -99,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)) {
@@ -185,11 +189,13 @@
 
 static void dump_probable_cause(Tombstone* tombstone, 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.AddCauseProtos(tombstone, unwinder);
     return;
   }
+#endif
 
   GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
                                        main_thread);
@@ -220,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 {
@@ -271,6 +277,13 @@
     return;
   }
 
+  // Remove any trailing newlines.
+  size_t index = msg.size();
+  while (index > 0 && (msg[index - 1] == '\0' || msg[index - 1] == '\n')) {
+    --index;
+  }
+  msg.resize(index);
+
   tombstone->set_abort_message(msg);
 }
 
@@ -329,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());
   }
 }
@@ -357,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());
           }
@@ -589,14 +602,6 @@
   }
 }
 
-static std::optional<uint64_t> read_uptime_secs() {
-  std::string uptime;
-  if (!android::base::ReadFileToString("/proc/uptime", &uptime)) {
-    return {};
-  }
-  return strtoll(uptime.c_str(), nullptr, 10);
-}
-
 void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
                              const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
                              const ProcessInfo& process_info, const OpenFilesList* open_files) {
@@ -607,27 +612,25 @@
   result.set_revision(android::base::GetProperty("ro.revision", "unknown"));
   result.set_timestamp(get_timestamp());
 
-  std::optional<uint64_t> system_uptime = read_uptime_secs();
-  if (system_uptime) {
-    android::procinfo::ProcessInfo proc_info;
-    std::string error;
-    if (android::procinfo::GetProcessInfo(target_thread, &proc_info, &error)) {
-      uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK);
-      result.set_process_uptime(*system_uptime - starttime);
-    } else {
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read process info: %s",
-                            error.c_str());
-    }
-  } else {
-    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read /proc/uptime: %s",
-                          strerror(errno));
-  }
-
   const ThreadInfo& main_thread = threads.at(target_thread);
   result.set_pid(main_thread.pid);
   result.set_tid(main_thread.tid);
   result.set_uid(main_thread.uid);
   result.set_selinux_label(main_thread.selinux_label);
+  // The main thread must have a valid siginfo.
+  CHECK(main_thread.siginfo != nullptr);
+
+  struct sysinfo si;
+  sysinfo(&si);
+  android::procinfo::ProcessInfo proc_info;
+  std::string error;
+  if (android::procinfo::GetProcessInfo(main_thread.pid, &proc_info, &error)) {
+    uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK);
+    result.set_process_uptime(si.uptime - starttime);
+  } else {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read process info: %s",
+                          error.c_str());
+  }
 
   auto cmd_line = result.mutable_command_line();
   for (const auto& arg : main_thread.command_line) {
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 053299a..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()) {
@@ -362,9 +364,13 @@
   print_thread_memory_dump(callback, tombstone, thread);
 
   CBS("");
-  CBS("memory map (%d %s):", tombstone.memory_mappings().size(),
-      tombstone.memory_mappings().size() == 1 ? "entry" : "entries");
-  int word_size = pointer_width(tombstone);
+
+  // No memory maps to print.
+  if (tombstone.memory_mappings().empty()) {
+    CBS("No memory maps found");
+    return;
+  }
+
   const auto format_pointer = [word_size](uint64_t ptr) -> std::string {
     if (word_size == 8) {
       uint64_t top = ptr >> 32;
@@ -375,8 +381,41 @@
     return StringPrintf("%0*" PRIx64, word_size * 2, ptr);
   };
 
+  std::string memory_map_header =
+      StringPrintf("memory map (%d %s):", tombstone.memory_mappings().size(),
+                   tombstone.memory_mappings().size() == 1 ? "entry" : "entries");
+
+  bool has_fault_address = signal_info.has_fault_address();
+  uint64_t fault_address = untag_address(signal_info.fault_address());
+  bool preamble_printed = false;
+  bool printed_fault_address_marker = false;
   for (const auto& map : tombstone.memory_mappings()) {
+    if (!preamble_printed) {
+      preamble_printed = true;
+      if (has_fault_address) {
+        if (fault_address < map.begin_address()) {
+          memory_map_header +=
+              StringPrintf("\n--->Fault address falls at %s before any mapped regions",
+                           format_pointer(fault_address).c_str());
+          printed_fault_address_marker = true;
+        } else {
+          memory_map_header += " (fault address prefixed with --->)";
+        }
+      }
+      CBS("%s", memory_map_header.c_str());
+    }
+
     std::string line = "    ";
+    if (has_fault_address && !printed_fault_address_marker) {
+      if (fault_address < map.begin_address()) {
+        printed_fault_address_marker = true;
+        CBS("--->Fault address falls at %s between mapped regions",
+            format_pointer(fault_address).c_str());
+      } else if (fault_address >= map.begin_address() && fault_address < map.end_address()) {
+        printed_fault_address_marker = true;
+        line = "--->";
+      }
+    }
     StringAppendF(&line, "%s-%s", format_pointer(map.begin_address()).c_str(),
                   format_pointer(map.end_address() - 1).c_str());
     StringAppendF(&line, " %s%s%s", map.read() ? "r" : "-", map.write() ? "w" : "-",
@@ -398,6 +437,11 @@
 
     CBS("%s", line.c_str());
   }
+
+  if (has_fault_address && !printed_fault_address_marker) {
+    CBS("--->Fault address falls at %s after any mapped regions",
+        format_pointer(fault_address).c_str());
+  }
 }
 
 void print_logs(CallbackType callback, const Tombstone& tombstone, int tail) {
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/debuggerd/seccomp_policy/crash_dump.arm.policy b/debuggerd/seccomp_policy/crash_dump.arm.policy
index 4eac0e9..8fd03c4 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm.policy
@@ -20,6 +20,7 @@
 faccessat: 1
 recvmsg: 1
 recvfrom: 1
+sysinfo: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index 21887ab..858a338 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -19,6 +19,7 @@
 faccessat: 1
 recvmsg: 1
 recvfrom: 1
+sysinfo: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index 90843fc..152697c 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -25,6 +25,7 @@
 faccessat: 1
 recvmsg: 1
 recvfrom: 1
+sysinfo: 1
 
 process_vm_readv: 1
 
diff --git a/debuggerd/seccomp_policy/crash_dump.x86.policy b/debuggerd/seccomp_policy/crash_dump.x86.policy
index 4eac0e9..8fd03c4 100644
--- a/debuggerd/seccomp_policy/crash_dump.x86.policy
+++ b/debuggerd/seccomp_policy/crash_dump.x86.policy
@@ -20,6 +20,7 @@
 faccessat: 1
 recvmsg: 1
 recvfrom: 1
+sysinfo: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.x86_64.policy b/debuggerd/seccomp_policy/crash_dump.x86_64.policy
index 1585cc6..281e231 100644
--- a/debuggerd/seccomp_policy/crash_dump.x86_64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.x86_64.policy
@@ -19,6 +19,7 @@
 faccessat: 1
 recvmsg: 1
 recvfrom: 1
+sysinfo: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index 05d8050..50558f7 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -449,7 +449,7 @@
   }
 
   if (crash->output.text.fd == -1) {
-    LOG(WARNING) << "missing output fd";
+    LOG(WARNING) << "skipping tombstone file creation due to intercept";
     return;
   }
 
diff --git a/diagnose_usb/Android.bp b/diagnose_usb/Android.bp
index cb79ffe..d7d87b5 100644
--- a/diagnose_usb/Android.bp
+++ b/diagnose_usb/Android.bp
@@ -7,6 +7,7 @@
     cflags: ["-Wall", "-Wextra", "-Werror"],
     host_supported: true,
     recovery_available: true,
+    min_sdk_version: "apex_inherit",
     apex_available: [
         "com.android.adbd",
         // TODO(b/151398197) remove the below
diff --git a/diagnose_usb/diagnose_usb.cpp b/diagnose_usb/diagnose_usb.cpp
index 35edb5e..5716323 100644
--- a/diagnose_usb/diagnose_usb.cpp
+++ b/diagnose_usb/diagnose_usb.cpp
@@ -19,7 +19,9 @@
 #include <errno.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <string>
+#include <vector>
 
 #include <android-base/stringprintf.h>
 
@@ -45,9 +47,25 @@
         return "";
     }
 
-    // getgroups(2) indicates that the GNU group_member(3) may not check the egid so we check it
-    // additionally just to be sure.
-    if (group_member(plugdev_group->gr_gid) || getegid() == plugdev_group->gr_gid) {
+    int ngroups = getgroups(0, nullptr);
+    if (ngroups < 0) {
+        perror("failed to get groups list size");
+        return "";
+    }
+
+    std::vector<gid_t> groups(ngroups);
+    ngroups = getgroups(groups.size(), groups.data());
+    if (ngroups < 0) {
+        perror("failed to get groups list");
+        return "";
+    }
+
+    groups.resize(ngroups);
+
+    // getgroups(2) indicates that the egid may not be included so we check it additionally just
+    // to be sure.
+    if (std::find(groups.begin(), groups.end(), plugdev_group->gr_gid) != groups.end() ||
+        getegid() == plugdev_group->gr_gid) {
         // The user is in plugdev so the problem is likely with the udev rules.
         return "missing udev rules? user is in the plugdev group";
     }
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 2c70778..708a677 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -166,8 +166,10 @@
         "android.hardware.boot@1.1",
         "android.hardware.fastboot@1.1",
         "android.hardware.health@2.0",
+        "android.hardware.health-V1-ndk",
         "libasyncio",
         "libbase",
+        "libbinder_ndk",
         "libbootloader_message",
         "libcutils",
         "libext2_uuid",
@@ -183,8 +185,10 @@
     ],
 
     static_libs: [
+        "android.hardware.health-translate-ndk",
         "libc++fs",
         "libhealthhalutils",
+        "libhealthshim",
         "libsnapshot_cow",
         "libsnapshot_nobinder",
         "update_metadata-protos",
@@ -408,3 +412,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..10bed6d 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
-$(call dist-for-goals,dist_files sdk win_sdk,$(my_dist_files))
+my_dist_files := $(HOST_OUT_EXECUTABLES)/mke2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/e2fsdroid
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold
+my_dist_files += $(HOST_OUT_EXECUTABLES)/sload_f2fs
+$(call dist-for-goals,dist_files sdk,$(my_dist_files))
 my_dist_files :=
diff --git a/fastboot/OWNERS b/fastboot/OWNERS
index 58b2a81..17b3466 100644
--- a/fastboot/OWNERS
+++ b/fastboot/OWNERS
@@ -1,3 +1,3 @@
 dvander@google.com
-hridya@google.com
+elsk@google.com
 enh@google.com
diff --git a/fastboot/README.md b/fastboot/README.md
index c224448..d3b6c1a 100644
--- a/fastboot/README.md
+++ b/fastboot/README.md
@@ -27,16 +27,16 @@
 1. Host sends a command, which is an ascii string in a single
    packet no greater than 64 bytes.
 
-2. Client response with a single packet no greater than 64 bytes.
+2. Client response with a single packet no greater than 256 bytes.
    The first four bytes of the response are "OKAY", "FAIL", "DATA",
    or "INFO".  Additional bytes may contain an (ascii) informative
    message.
 
-   a. INFO -> the remaining 60 bytes are an informative message
+   a. INFO -> the remaining 252 bytes are an informative message
       (providing progress or diagnostic messages).  They should
       be displayed and then step #2 repeats
 
-   b. FAIL -> the requested command failed.  The remaining 60 bytes
+   b. FAIL -> the requested command failed.  The remaining 252 bytes
       of the response (if present) provide a textual failure message
       to present to the user.  Stop.
 
@@ -53,13 +53,13 @@
    until the client has sent or received the number of bytes indicated
    in the "DATA" response above.
 
-4. Client responds with a single packet no greater than 64 bytes.
+4. Client responds with a single packet no greater than 256 bytes.
    The first four bytes of the response are "OKAY", "FAIL", or "INFO".
    Similar to #2:
 
-   a. INFO -> display the remaining 60 bytes and return to #4
+   a. INFO -> display the remaining 252 bytes and return to #4
 
-   b. FAIL -> display the remaining 60 bytes (if present) as a failure
+   b. FAIL -> display the remaining 252 bytes (if present) as a failure
       reason and consider the command failed.  Stop.
 
    c. OKAY -> success.  Go to #5
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 0a72812..b9f6c97 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -268,10 +268,18 @@
     }
 
     // arg[0] is the command name, arg[1] contains size of data to be downloaded
+    // which should always be 8 bytes
+    if (args[1].length() != 8) {
+        return device->WriteStatus(FastbootResult::FAIL,
+                                   "Invalid size (length of size != 8)");
+    }
     unsigned int size;
     if (!android::base::ParseUint("0x" + args[1], &size, kMaxDownloadSizeDefault)) {
         return device->WriteStatus(FastbootResult::FAIL, "Invalid size");
     }
+    if (size == 0) {
+        return device->WriteStatus(FastbootResult::FAIL, "Invalid size (0)");
+    }
     device->download_data().resize(size);
     if (!device->WriteStatus(FastbootResult::DATA, android::base::StringPrintf("%08x", size))) {
         return false;
@@ -725,7 +733,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/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 64a934d..ae225de 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -21,10 +21,12 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
+#include <android/binder_manager.h>
 #include <android/hardware/boot/1.0/IBootControl.h>
 #include <android/hardware/fastboot/1.1/IFastboot.h>
 #include <fs_mgr.h>
 #include <fs_mgr/roots.h>
+#include <health-shim/shim.h>
 #include <healthhalutils/HealthHalUtils.h>
 
 #include "constants.h"
@@ -32,16 +34,36 @@
 #include "tcp_client.h"
 #include "usb_client.h"
 
+using std::string_literals::operator""s;
 using android::fs_mgr::EnsurePathUnmounted;
 using android::fs_mgr::Fstab;
 using ::android::hardware::hidl_string;
 using ::android::hardware::boot::V1_0::IBootControl;
 using ::android::hardware::boot::V1_0::Slot;
 using ::android::hardware::fastboot::V1_1::IFastboot;
-using ::android::hardware::health::V2_0::get_health_service;
 
 namespace sph = std::placeholders;
 
+std::shared_ptr<aidl::android::hardware::health::IHealth> get_health_service() {
+    using aidl::android::hardware::health::IHealth;
+    using HidlHealth = android::hardware::health::V2_0::IHealth;
+    using aidl::android::hardware::health::HealthShim;
+    auto service_name = IHealth::descriptor + "/default"s;
+    if (AServiceManager_isDeclared(service_name.c_str())) {
+        ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+        std::shared_ptr<IHealth> health = IHealth::fromBinder(binder);
+        if (health != nullptr) return health;
+        LOG(WARNING) << "AIDL health service is declared, but it cannot be retrieved.";
+    }
+    LOG(INFO) << "Unable to get AIDL health service, trying HIDL...";
+    android::sp<HidlHealth> hidl_health = android::hardware::health::V2_0::get_health_service();
+    if (hidl_health != nullptr) {
+        return ndk::SharedRefBase::make<HealthShim>(hidl_health);
+    }
+    LOG(WARNING) << "No health implementation is found.";
+    return nullptr;
+}
+
 FastbootDevice::FastbootDevice()
     : kCommandMap({
               {FB_CMD_SET_ACTIVE, SetActiveHandler},
@@ -164,6 +186,11 @@
             PLOG(ERROR) << "Couldn't read command";
             return;
         }
+        if (std::count_if(command, command + bytes_read, iscntrl) != 0) {
+            WriteStatus(FastbootResult::FAIL,
+                        "Command contains control character");
+            continue;
+        }
         command[bytes_read] = '\0';
 
         LOG(INFO) << "Fastboot command: " << command;
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index 3536136..91ffce3 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -22,10 +22,10 @@
 #include <utility>
 #include <vector>
 
+#include <aidl/android/hardware/health/IHealth.h>
 #include <android/hardware/boot/1.0/IBootControl.h>
 #include <android/hardware/boot/1.1/IBootControl.h>
 #include <android/hardware/fastboot/1.1/IFastboot.h>
-#include <android/hardware/health/2.0/IHealth.h>
 
 #include "commands.h"
 #include "transport.h"
@@ -57,7 +57,7 @@
     android::sp<android::hardware::fastboot::V1_1::IFastboot> fastboot_hal() {
         return fastboot_hal_;
     }
-    android::sp<android::hardware::health::V2_0::IHealth> health_hal() { return health_hal_; }
+    std::shared_ptr<aidl::android::hardware::health::IHealth> health_hal() { return health_hal_; }
 
     void set_active_slot(const std::string& active_slot) { active_slot_ = active_slot; }
 
@@ -67,7 +67,7 @@
     std::unique_ptr<Transport> transport_;
     android::sp<android::hardware::boot::V1_0::IBootControl> boot_control_hal_;
     android::sp<android::hardware::boot::V1_1::IBootControl> boot1_1_;
-    android::sp<android::hardware::health::V2_0::IHealth> health_hal_;
+    std::shared_ptr<aidl::android::hardware::health::IHealth> health_hal_;
     android::sp<android::hardware::fastboot::V1_1::IFastboot> fastboot_hal_;
     std::vector<char> download_data_;
     std::string active_slot_;
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 9b5d2cd..7bef72a 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;
     }
 
@@ -160,7 +172,8 @@
         return -EOVERFLOW;
     } else if (data.size() < block_device_size &&
                (partition_name == "boot" || partition_name == "boot_a" ||
-                partition_name == "boot_b")) {
+                partition_name == "boot_b" || partition_name == "init_boot" ||
+                partition_name == "init_boot_a" || partition_name == "init_boot_b")) {
         CopyAVBFooter(&data, block_device_size);
     }
     if (android::base::GetProperty("ro.system.build.type", "") != "user") {
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/device/variables.cpp b/fastboot/device/variables.cpp
index ee1eed8..76e9889 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -26,7 +26,6 @@
 #include <android/hardware/boot/1.1/IBootControl.h>
 #include <ext4_utils/ext4_utils.h>
 #include <fs_mgr.h>
-#include <healthhalutils/HealthHalUtils.h>
 #include <liblp/liblp.h>
 
 #include "fastboot_device.h"
@@ -120,23 +119,17 @@
 }
 
 bool GetBatteryVoltageHelper(FastbootDevice* device, int32_t* battery_voltage) {
-    using android::hardware::health::V2_0::HealthInfo;
-    using android::hardware::health::V2_0::Result;
+    using aidl::android::hardware::health::HealthInfo;
 
     auto health_hal = device->health_hal();
     if (!health_hal) {
         return false;
     }
 
-    Result ret;
-    auto ret_val = health_hal->getHealthInfo([&](Result result, HealthInfo info) {
-        *battery_voltage = info.legacy.batteryVoltage;
-        ret = result;
-    });
-    if (!ret_val.isOk() || (ret != Result::SUCCESS)) {
-        return false;
-    }
-
+    HealthInfo health_info;
+    auto res = health_hal->getHealthInfo(&health_info);
+    if (!res.isOk()) return false;
+    *battery_voltage = health_info.batteryVoltageMillivolts;
     return true;
 }
 
diff --git a/fastboot/fastboot.bash b/fastboot/fastboot.bash
index f5a3384..e9bf9e9 100644
--- a/fastboot/fastboot.bash
+++ b/fastboot/fastboot.bash
@@ -109,7 +109,7 @@
 
     cur="${COMP_WORDS[COMP_CWORD]}"
     if [[ $i -eq $COMP_CWORD ]]; then
-        partitions="boot bootloader dtbo modem odm odm_dlkm oem product pvmfw radio recovery system vbmeta vendor vendor_dlkm"
+        partitions="boot bootloader dtbo init_boot modem odm odm_dlkm oem product pvmfw radio recovery system system_dlkm vbmeta vendor vendor_dlkm"
         COMPREPLY=( $(compgen -W "$partitions" -- $cur) )
     else
         _fastboot_util_complete_local_file "${cur}" '!*.img'
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 532b524..fde1dab 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -141,6 +141,10 @@
 static Image images[] = {
         // clang-format off
     { "boot",     "boot.img",         "boot.sig",     "boot",     false, ImageType::BootCritical },
+    { "init_boot",
+                  "init_boot.img",    "init_boot.sig",
+                                                      "init_boot",
+                                                                  true,  ImageType::BootCritical },
     { nullptr,    "boot_other.img",   "boot.sig",     "boot",     true,  ImageType::Normal },
     { "cache",    "cache.img",        "cache.sig",    "cache",    true,  ImageType::Extra },
     { "dtbo",     "dtbo.img",         "dtbo.sig",     "dtbo",     true,  ImageType::BootCritical },
@@ -152,6 +156,10 @@
     { "recovery", "recovery.img",     "recovery.sig", "recovery", true,  ImageType::BootCritical },
     { "super",    "super.img",        "super.sig",    "super",    true,  ImageType::Extra },
     { "system",   "system.img",       "system.sig",   "system",   false, ImageType::Normal },
+    { "system_dlkm",
+                  "system_dlkm.img",  "system_dlkm.sig",
+                                                      "system_dlkm",
+                                                                  true,  ImageType::Normal },
     { "system_ext",
                   "system_ext.img",   "system_ext.sig",
                                                       "system_ext",
@@ -1017,7 +1025,7 @@
     return partition_size;
 }
 
-static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
+static void copy_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
     if (buf->sz < AVB_FOOTER_SIZE) {
         return;
     }
@@ -1032,9 +1040,9 @@
     // In this case, partition_size will be zero.
     if (partition_size < buf->sz) {
         fprintf(stderr,
-                "Warning: skip copying boot image avb footer"
-                " (boot partition size: %" PRId64 ", boot image size: %" PRId64 ").\n",
-                partition_size, buf->sz);
+                "Warning: skip copying %s image avb footer"
+                " (%s partition size: %" PRId64 ", %s image size: %" PRId64 ").\n",
+                partition.c_str(), partition.c_str(), partition_size, partition.c_str(), buf->sz);
         return;
     }
 
@@ -1042,7 +1050,7 @@
     // Because buf->fd will still be used afterwards.
     std::string data;
     if (!android::base::ReadFdToString(buf->fd, &data)) {
-        die("Failed reading from boot");
+        die("Failed reading from %s", partition.c_str());
     }
 
     uint64_t footer_offset = buf->sz - AVB_FOOTER_SIZE;
@@ -1051,13 +1059,14 @@
         return;
     }
 
-    unique_fd fd(make_temporary_fd("boot rewriting"));
+    const std::string tmp_fd_template = partition + " rewriting";
+    unique_fd fd(make_temporary_fd(tmp_fd_template.c_str()));
     if (!android::base::WriteStringToFd(data, fd)) {
-        die("Failed writing to modified boot");
+        die("Failed writing to modified %s", partition.c_str());
     }
     lseek(fd.get(), partition_size - AVB_FOOTER_SIZE, SEEK_SET);
     if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) {
-        die("Failed copying AVB footer in boot");
+        die("Failed copying AVB footer in %s", partition.c_str());
     }
     buf->fd = std::move(fd);
     buf->sz = partition_size;
@@ -1068,8 +1077,9 @@
 {
     sparse_file** s;
 
-    if (partition == "boot" || partition == "boot_a" || partition == "boot_b") {
-        copy_boot_avb_footer(partition, buf);
+    if (partition == "boot" || partition == "boot_a" || partition == "boot_b" ||
+        partition == "init_boot" || partition == "init_boot_a" || partition == "init_boot_b") {
+        copy_avb_footer(partition, buf);
     }
 
     // Rewrite vbmeta if that's what we're flashing and modification has been requested.
diff --git a/fastboot/fs.cpp b/fastboot/fs.cpp
index 458a7a1..d268a50 100644
--- a/fastboot/fs.cpp
+++ b/fastboot/fs.cpp
@@ -143,6 +143,13 @@
         mke2fs_args.push_back("512");
     }
 
+    if (fsOptions & (1 << FS_OPT_CASEFOLD)) {
+        mke2fs_args.push_back("-O");
+        mke2fs_args.push_back("casefold");
+        mke2fs_args.push_back("-E");
+        mke2fs_args.push_back("encoding=utf8");
+    }
+
     mke2fs_args.push_back(fileName);
 
     std::string size_str = std::to_string(partSize / block_size);
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/fastboot/fuzzy_fastboot/README.md b/fastboot/fuzzy_fastboot/README.md
index 72967c5..a5b64c7 100644
--- a/fastboot/fuzzy_fastboot/README.md
+++ b/fastboot/fuzzy_fastboot/README.md
@@ -293,7 +293,7 @@
 Begin with just the generic tests (i.e. no XML file). In particular, make sure all
 the conformance tests are passing before you move on. All other tests require that
 the basic generic conformance tests all pass for them to be valid. The conformance
-tests can be run with `./fuzzy_fastboot --gtests_filter=Conformance.*`.
+tests can be run with `./fuzzy_fastboot --gtest_filter=Conformance.*`.
 
 #### Understanding and Fixing Failed Tests
 Whenever a test fails, it will print out to the console the reason for failure
diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp
index b6beaf9..8593adc 100644
--- a/fastboot/fuzzy_fastboot/main.cpp
+++ b/fastboot/fuzzy_fastboot/main.cpp
@@ -890,28 +890,51 @@
 
 TEST_F(Fuzz, BadCommandTooLarge) {
     std::string s = RandomString(FB_COMMAND_SZ + 1, rand_legal);
-    EXPECT_EQ(fb->RawCommand(s), DEVICE_FAIL)
+    RetCode ret = fb->RawCommand(s);
+    EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR)
             << "Device did not respond with failure after sending length " << s.size()
             << " string of random ASCII chars";
+    if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed";
     std::string s1 = RandomString(1000, rand_legal);
-    EXPECT_EQ(fb->RawCommand(s1), DEVICE_FAIL)
+    ret = fb->RawCommand(s1);
+    EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR)
             << "Device did not respond with failure after sending length " << s1.size()
             << " string of random ASCII chars";
+    if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed";
     std::string s2 = RandomString(1000, rand_illegal);
-    EXPECT_EQ(fb->RawCommand(s2), DEVICE_FAIL)
-            << "Device did not respond with failure after sending length " << s1.size()
+    ret = fb->RawCommand(s2);
+    EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR)
+            << "Device did not respond with failure after sending length " << s2.size()
             << " string of random non-ASCII chars";
+    if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed";
     std::string s3 = RandomString(1000, rand_char);
-    EXPECT_EQ(fb->RawCommand(s3), DEVICE_FAIL)
-            << "Device did not respond with failure after sending length " << s1.size()
+    ret = fb->RawCommand(s3);
+    EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR)
+            << "Device did not respond with failure after sending length " << s3.size()
             << " string of random chars";
+    if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed";
+
+    std::string s4 = RandomString(10 * 1024 * 1024, rand_legal);
+    ret = fb->RawCommand(s);
+    EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR)
+            << "Device did not respond with failure after sending length " << s4.size()
+            << " string of random ASCII chars ";
+    if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed";
+
+    ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+    std::string resp;
+    EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS)
+                << "Device is unresponsive to getvar command";
 }
 
 TEST_F(Fuzz, CommandTooLarge) {
     for (const std::string& s : CMDS) {
         std::string rs = RandomString(1000, rand_char);
-        EXPECT_EQ(fb->RawCommand(s + rs), DEVICE_FAIL)
-                << "Device did not respond with failure after '" << s + rs << "'";
+        RetCode ret;
+        ret = fb->RawCommand(s + rs);
+        EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR)
+                << "Device did not respond with failure " << ret << "after '" << s + rs << "'";
+        if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed";
         ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
         std::string resp;
         EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS)
diff --git a/fastboot/socket.cpp b/fastboot/socket.cpp
index 5a14b63..3096905 100644
--- a/fastboot/socket.cpp
+++ b/fastboot/socket.cpp
@@ -28,6 +28,10 @@
 
 #include "socket.h"
 
+#ifndef _WIN32
+#include <sys/select.h>
+#endif
+
 #include <android-base/errors.h>
 #include <android-base/stringprintf.h>
 
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index d4fc2b9..49761ac 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -65,10 +65,10 @@
         "-D_FILE_OFFSET_BITS=64",
     ],
     srcs: [
+        "blockdev.cpp",
         "file_wait.cpp",
         "fs_mgr.cpp",
         "fs_mgr_format.cpp",
-        "fs_mgr_verity.cpp",
         "fs_mgr_dm_linear.cpp",
         "fs_mgr_overlayfs.cpp",
         "fs_mgr_roots.cpp",
@@ -126,10 +126,19 @@
     export_header_lib_headers: [
         "libfiemap_headers",
     ],
-    required: [
-        "e2freefrag",
-        "e2fsdroid",
-    ],
+    target: {
+        platform: {
+            required: [
+                "e2freefrag",
+                "e2fsdroid",
+            ],
+        },
+        recovery: {
+            required: [
+                "e2fsdroid.recovery",
+            ],
+        },
+    },
 }
 
 // Two variants of libfs_mgr are provided: libfs_mgr and libfs_mgr_binder.
@@ -205,7 +214,6 @@
     static_libs: [
         "libavb_user",
         "libgsid",
-        "libutils",
         "libvold_binder",
     ],
     shared_libs: [
@@ -220,6 +228,7 @@
         "liblog",
         "liblp",
         "libselinux",
+        "libutils",
     ],
     header_libs: [
         "libcutils_headers",
@@ -236,28 +245,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/OWNERS b/fs_mgr/OWNERS
index cf353a1..c6f9054 100644
--- a/fs_mgr/OWNERS
+++ b/fs_mgr/OWNERS
@@ -1,2 +1,4 @@
+# Bug component: 30545
 bowgotsai@google.com
 dvander@google.com
+elsk@google.com
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index 84709b6..432aa4f 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -1,6 +1,9 @@
 {
   "presubmit": [
     {
+      "name": "CtsFsMgrTestCases"
+    },
+    {
       "name": "libdm_test"
     },
     {
@@ -13,6 +16,9 @@
       "name": "fiemap_writer_test"
     },
     {
+      "name": "fs_mgr_vendor_overlay_test"
+    },
+    {
       "name": "vts_libsnapshot_test"
     },
     {
diff --git a/fs_mgr/blockdev.cpp b/fs_mgr/blockdev.cpp
new file mode 100644
index 0000000..388dadc
--- /dev/null
+++ b/fs_mgr/blockdev.cpp
@@ -0,0 +1,154 @@
+/*
+ * 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 <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include <dirent.h>
+#include <libdm/dm.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include "blockdev.h"
+
+using android::base::Basename;
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::StartsWith;
+using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+
+// Return the parent device of a partition. Converts e.g. "sda26" into "sda".
+static std::string PartitionParent(const std::string& blockdev) {
+    if (blockdev.find('/') != std::string::npos) {
+        LOG(ERROR) << __func__ << ": invalid argument " << blockdev;
+        return blockdev;
+    }
+    auto dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/sys/class/block"), closedir};
+    if (!dir) {
+        return blockdev;
+    }
+    for (struct dirent* ent = readdir(dir.get()); ent; ent = readdir(dir.get())) {
+        if (ent->d_name[0] == '.') {
+            continue;
+        }
+        std::string path = StringPrintf("/sys/class/block/%s/%s", ent->d_name, blockdev.c_str());
+        struct stat statbuf;
+        if (stat(path.c_str(), &statbuf) >= 0) {
+            return ent->d_name;
+        }
+    }
+    return blockdev;
+}
+
+// Convert a major:minor pair into a block device name.
+static std::string BlockdevName(dev_t dev) {
+    auto dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/dev/block"), closedir};
+    if (!dir) {
+        return {};
+    }
+    for (struct dirent* ent = readdir(dir.get()); ent; ent = readdir(dir.get())) {
+        if (ent->d_name[0] == '.') {
+            continue;
+        }
+        const std::string path = std::string("/dev/block/") + ent->d_name;
+        struct stat statbuf;
+        if (stat(path.c_str(), &statbuf) >= 0 && dev == statbuf.st_rdev) {
+            return ent->d_name;
+        }
+    }
+    return {};
+}
+
+// Trim whitespace from the end of a string.
+static void rtrim(std::string& s) {
+    s.erase(s.find_last_not_of('\n') + 1, s.length());
+}
+
+// For file `file_path`, retrieve the block device backing the filesystem on
+// which the file exists and return the queue depth of the block device.
+static Result<uint32_t> BlockDeviceQueueDepth(const std::string& file_path) {
+    struct stat statbuf;
+    int res = stat(file_path.c_str(), &statbuf);
+    if (res < 0) {
+        return ErrnoError() << "stat(" << file_path << ")";
+    }
+    std::string blockdev = "/dev/block/" + BlockdevName(statbuf.st_dev);
+    LOG(DEBUG) << __func__ << ": " << file_path << " -> " << blockdev;
+    if (blockdev.empty()) {
+        return Errorf("Failed to convert {}:{} (path {})", major(statbuf.st_dev),
+                      minor(statbuf.st_dev), file_path.c_str());
+    }
+    auto& dm = DeviceMapper::Instance();
+    for (;;) {
+        std::optional<std::string> child = dm.GetParentBlockDeviceByPath(blockdev);
+        if (!child) {
+            break;
+        }
+        LOG(DEBUG) << __func__ << ": " << blockdev << " -> " << *child;
+        blockdev = *child;
+    }
+    std::optional<std::string> maybe_blockdev = android::dm::ExtractBlockDeviceName(blockdev);
+    if (!maybe_blockdev) {
+        return Errorf("Failed to remove /dev/block/ prefix from {}", blockdev);
+    }
+    blockdev = PartitionParent(*maybe_blockdev);
+    LOG(DEBUG) << __func__ << ": "
+               << "Partition parent: " << blockdev;
+    const std::string nr_tags_path =
+            StringPrintf("/sys/class/block/%s/mq/0/nr_tags", blockdev.c_str());
+    std::string nr_tags;
+    if (!android::base::ReadFileToString(nr_tags_path, &nr_tags)) {
+        return Errorf("Failed to read {}", nr_tags_path);
+    }
+    rtrim(nr_tags);
+    LOG(DEBUG) << __func__ << ": " << file_path << " is backed by /dev/" << blockdev
+               << " and that block device supports queue depth " << nr_tags;
+    return strtol(nr_tags.c_str(), NULL, 0);
+}
+
+// Set 'nr_requests' of `loop_device_path` to the queue depth of the block
+// device backing `file_path`.
+Result<void> ConfigureQueueDepth(const std::string& loop_device_path,
+                                 const std::string& file_path) {
+    if (!StartsWith(loop_device_path, "/dev/")) {
+        return Error() << "Invalid argument " << loop_device_path;
+    }
+
+    const std::string loop_device_name = Basename(loop_device_path);
+
+    const auto qd = BlockDeviceQueueDepth(file_path);
+    if (!qd.ok()) {
+        return qd.error();
+    }
+    const std::string nr_requests = StringPrintf("%u", *qd);
+    const std::string sysfs_path =
+            StringPrintf("/sys/class/block/%s/queue/nr_requests", loop_device_name.c_str());
+    unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
+    if (sysfs_fd == -1) {
+        return ErrnoError() << "Failed to open " << sysfs_path;
+    }
+
+    const int res = write(sysfs_fd.get(), nr_requests.data(), nr_requests.length());
+    if (res < 0) {
+        return ErrnoError() << "Failed to write to " << sysfs_path;
+    }
+    return {};
+}
diff --git a/fs_mgr/clean_scratch_files.cpp b/fs_mgr/blockdev.h
similarity index 67%
rename from fs_mgr/clean_scratch_files.cpp
rename to fs_mgr/blockdev.h
index 42fe35a..2c0d68a 100644
--- a/fs_mgr/clean_scratch_files.cpp
+++ b/fs_mgr/blockdev.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-#include <fs_mgr_overlayfs.h>
+#include <android-base/result.h>
+#include <string>
 
-int main() {
-    android::fs_mgr::CleanupOldScratchFiles();
-    return 0;
-}
+android::base::Result<void> ConfigureQueueDepth(const std::string& loop_device_path,
+                                                const std::string& file_path);
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index ec66144..8ce961b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -72,11 +72,9 @@
 #include <log/log_properties.h>
 #include <logwrap/logwrap.h>
 
+#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"
@@ -172,6 +170,22 @@
             FS_STAT_SET_RESERVED_BLOCKS_FAILED | FS_STAT_ENABLE_ENCRYPTION_FAILED);
 }
 
+static bool umount_retry(const std::string& mount_point) {
+    int retry_count = 5;
+    bool umounted = false;
+
+    while (retry_count-- > 0) {
+        umounted = umount(mount_point.c_str()) == 0;
+        if (umounted) {
+            LINFO << __FUNCTION__ << "(): unmount(" << mount_point << ") succeeded";
+            break;
+        }
+        PERROR << __FUNCTION__ << "(): umount(" << mount_point << ") failed";
+        if (retry_count) sleep(1);
+    }
+    return umounted;
+}
+
 static void check_fs(const std::string& blk_device, const std::string& fs_type,
                      const std::string& target, int* fs_stat) {
     int status;
@@ -211,25 +225,12 @@
                         tmpmnt_opts.c_str());
             PINFO << __FUNCTION__ << "(): mount(" << blk_device << "," << target << "," << fs_type
                   << ")=" << ret;
-            if (!ret) {
-                bool umounted = false;
-                int retry_count = 5;
-                while (retry_count-- > 0) {
-                    umounted = umount(target.c_str()) == 0;
-                    if (umounted) {
-                        LINFO << __FUNCTION__ << "(): unmount(" << target << ") succeeded";
-                        break;
-                    }
-                    PERROR << __FUNCTION__ << "(): umount(" << target << ") failed";
-                    if (retry_count) sleep(1);
-                }
-                if (!umounted) {
-                    // boot may fail but continue and leave it to later stage for now.
-                    PERROR << __FUNCTION__ << "(): umount(" << target << ") timed out";
-                    *fs_stat |= FS_STAT_RO_UNMOUNT_FAILED;
-                }
-            } else {
+            if (ret) {
                 *fs_stat |= FS_STAT_RO_MOUNT_FAILED;
+            } else if (!umount_retry(target)) {
+                // boot may fail but continue and leave it to later stage for now.
+                PERROR << __FUNCTION__ << "(): umount(" << target << ") timed out";
+                *fs_stat |= FS_STAT_RO_UNMOUNT_FAILED;
             }
         }
 
@@ -270,12 +271,12 @@
             LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache "
                   << realpath(blk_device);
             ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_forced_argv), f2fs_fsck_forced_argv,
-                                      &status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
+                                      &status, false, LOG_KLOG | LOG_FILE, false, nullptr);
         } else {
             LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache "
                   << realpath(blk_device);
             ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv, &status, false,
-                                      LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
+                                      LOG_KLOG | LOG_FILE, false, nullptr);
         }
         if (ret < 0) {
             /* No need to check for error in fork, we can't really handle it now */
@@ -906,7 +907,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;
         }
 
@@ -1004,50 +1005,21 @@
     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 (umount(entry.mount_point.c_str()) == 0) {
+    if (should_use_metadata_encryption(entry)) {
+        if (umount_retry(entry.mount_point)) {
             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) {
+        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) {
         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;
     }
@@ -1055,9 +1027,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:
@@ -1476,14 +1445,6 @@
                 // Skips mounting the device.
                 continue;
             }
-        } else if ((current_entry.fs_mgr_flags.verify)) {
-            int rc = fs_mgr_setup_verity(&current_entry, true);
-            if (rc == FS_MGR_SETUP_VERITY_DISABLED || rc == FS_MGR_SETUP_VERITY_SKIPPED) {
-                LINFO << "Verity disabled";
-            } else if (rc != FS_MGR_SETUP_VERITY_SUCCESS) {
-                LERROR << "Could not set up verified partition, skipping!";
-                continue;
-            }
         }
 
         int last_idx_inspected;
@@ -1531,7 +1492,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
@@ -1543,19 +1503,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) &&
@@ -1576,7 +1523,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;
@@ -1589,27 +1536,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)) {
@@ -1681,13 +1609,6 @@
                 ret |= FsMgrUmountStatus::ERROR_VERITY;
                 continue;
             }
-        } else if ((current_entry.fs_mgr_flags.verify)) {
-            if (!fs_mgr_teardown_verity(&current_entry)) {
-                LERROR << "Failed to tear down verified partition on mount point: "
-                       << current_entry.mount_point;
-                ret |= FsMgrUmountStatus::ERROR_VERITY;
-                continue;
-            }
         }
     }
     return ret;
@@ -1888,9 +1809,13 @@
     auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
 
     // Run fsck if needed
-    prepare_fs_for_mount(entry.blk_device, entry, mount_point);
+    int ret = prepare_fs_for_mount(entry.blk_device, entry, mount_point);
+    // Wiped case doesn't require to try __mount below.
+    if (ret & FS_STAT_INVALID_MAGIC) {
+      return FS_MGR_DOMNT_FAILED;
+    }
 
-    int ret = __mount(entry.blk_device, mount_point, entry);
+    ret = __mount(entry.blk_device, mount_point, entry);
     if (ret) {
       ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED;
     }
@@ -1980,14 +1905,6 @@
                 // Skips mounting the device.
                 continue;
             }
-        } else if (fstab_entry.fs_mgr_flags.verify) {
-            int rc = fs_mgr_setup_verity(&fstab_entry, true);
-            if (rc == FS_MGR_SETUP_VERITY_DISABLED || rc == FS_MGR_SETUP_VERITY_SKIPPED) {
-                LINFO << "Verity disabled";
-            } else if (rc != FS_MGR_SETUP_VERITY_SUCCESS) {
-                LERROR << "Could not set up verified partition, skipping!";
-                continue;
-            }
         }
 
         int retry_count = 2;
@@ -2110,12 +2027,19 @@
 
     ConfigureIoScheduler(loop_device);
 
+    if (auto ret = ConfigureQueueDepth(loop_device, "/"); !ret.ok()) {
+        LOG(DEBUG) << "Failed to config queue depth: " << ret.error().message();
+    }
+
     // set block size & direct IO
     unique_fd loop_fd(TEMP_FAILURE_RETRY(open(loop_device.c_str(), O_RDWR | O_CLOEXEC)));
     if (loop_fd.get() == -1) {
         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;
     }
@@ -2199,7 +2123,7 @@
 }
 
 bool fs_mgr_is_verity_enabled(const FstabEntry& entry) {
-    if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
+    if (!entry.fs_mgr_flags.avb) {
         return false;
     }
 
@@ -2210,17 +2134,12 @@
         return false;
     }
 
-    const char* status;
     std::vector<DeviceMapper::TargetInfo> table;
     if (!dm.GetTableStatus(mount_point, &table) || table.empty() || table[0].data.empty()) {
-        if (!entry.fs_mgr_flags.verify_at_boot) {
-            return false;
-        }
-        status = "V";
-    } else {
-        status = table[0].data.c_str();
+        return false;
     }
 
+    auto status = table[0].data.c_str();
     if (*status == 'C' || *status == 'V') {
         return true;
     }
@@ -2228,16 +2147,16 @@
     return false;
 }
 
-std::string fs_mgr_get_hashtree_algorithm(const android::fs_mgr::FstabEntry& entry) {
-    if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
-        return "";
+std::optional<HashtreeInfo> fs_mgr_get_hashtree_info(const android::fs_mgr::FstabEntry& entry) {
+    if (!entry.fs_mgr_flags.avb) {
+        return {};
     }
     DeviceMapper& dm = DeviceMapper::Instance();
     std::string device = GetVerityDeviceName(entry);
 
     std::vector<DeviceMapper::TargetInfo> table;
     if (dm.GetState(device) == DmDeviceState::INVALID || !dm.GetTableInfo(device, &table)) {
-        return "";
+        return {};
     }
     for (const auto& target : table) {
         if (strcmp(target.spec.target_type, "verity") != 0) {
@@ -2253,18 +2172,19 @@
         std::vector<std::string> tokens = android::base::Split(target.data, " \t\r\n");
         if (tokens[0] != "0" && tokens[0] != "1") {
             LOG(WARNING) << "Unrecognized device mapper version in " << target.data;
-            return "";
+            return {};
         }
 
-        // Hashtree algorithm is the 8th token in the output
-        return android::base::Trim(tokens[7]);
+        // Hashtree algorithm & root digest are the 8th & 9th token in the output.
+        return HashtreeInfo{.algorithm = android::base::Trim(tokens[7]),
+                            .root_digest = android::base::Trim(tokens[8])};
     }
 
-    return "";
+    return {};
 }
 
 bool fs_mgr_verity_is_check_at_most_once(const android::fs_mgr::FstabEntry& entry) {
-    if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
+    if (!entry.fs_mgr_flags.avb) {
         return false;
     }
 
@@ -2400,3 +2320,22 @@
     LINFO << report << ret;
     return true;
 }
+
+bool fs_mgr_load_verity_state(int* mode) {
+    // unless otherwise specified, use EIO mode.
+    *mode = VERITY_MODE_EIO;
+
+    // The bootloader communicates verity mode via the kernel commandline
+    std::string verity_mode;
+    if (!fs_mgr_get_boot_config("veritymode", &verity_mode)) {
+        return false;
+    }
+
+    if (verity_mode == "enforcing") {
+        *mode = VERITY_MODE_DEFAULT;
+    } else if (verity_mode == "logging") {
+        *mode = VERITY_MODE_LOGGING;
+    }
+
+    return true;
+}
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..0ca1946 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;
@@ -165,12 +167,10 @@
         CheckFlag("recoveryonly", recovery_only);
         CheckFlag("noemulatedsd", no_emulated_sd);
         CheckFlag("notrim", no_trim);
-        CheckFlag("verify", verify);
         CheckFlag("formattable", formattable);
         CheckFlag("slotselect", slot_select);
         CheckFlag("latemount", late_mount);
         CheckFlag("nofail", no_fail);
-        CheckFlag("verifyatboot", verify_at_boot);
         CheckFlag("quota", quota);
         CheckFlag("avb", avb);
         CheckFlag("logical", logical);
@@ -187,9 +187,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 +242,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 +303,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 +454,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 +528,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";
@@ -683,9 +664,11 @@
 }
 
 void EnableMandatoryFlags(Fstab* fstab) {
-    // Devices launched in R and after should enable fs_verity on userdata. The flag causes tune2fs
-    // to enable the feature. A better alternative would be to enable on mkfs at the beginning.
+    // Devices launched in R and after must support fs_verity. Set flag to cause tune2fs
+    // to enable the feature on userdata and metadata partitions.
     if (android::base::GetIntProperty("ro.product.first_api_level", 0) >= 30) {
+        // Devices launched in R and after should enable fs_verity on userdata.
+        // A better alternative would be to enable on mkfs at the beginning.
         std::vector<FstabEntry*> data_entries = GetEntriesForMountPoint(fstab, "/data");
         for (auto&& entry : data_entries) {
             // Besides ext4, f2fs is also supported. But the image is already created with verity
@@ -694,20 +677,26 @@
                 entry->fs_mgr_flags.fs_verity = true;
             }
         }
+        // Devices shipping with S and earlier likely do not already have fs_verity enabled via
+        // mkfs, so enable it here.
+        std::vector<FstabEntry*> metadata_entries = GetEntriesForMountPoint(fstab, "/metadata");
+        for (auto&& entry : metadata_entries) {
+            entry->fs_mgr_flags.fs_verity = true;
+        }
     }
 }
 
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab_out) {
-    auto fstab_file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
-    if (!fstab_file) {
-        PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'";
+    const bool is_proc_mounts = (path == "/proc/mounts");
+
+    std::string fstab_str;
+    if (!android::base::ReadFileToString(path, &fstab_str, /* follow_symlinks = */ true)) {
+        PERROR << __FUNCTION__ << "(): failed to read file: '" << path << "'";
         return false;
     }
 
-    bool is_proc_mounts = path == "/proc/mounts";
-
     Fstab fstab;
-    if (!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 +743,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..2da5b0f 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;
 }
 
@@ -866,9 +880,14 @@
             errno = save_errno;
         }
         entry.flags &= ~MS_RDONLY;
+        entry.flags |= MS_SYNCHRONOUS;
+        entry.fs_options = "nodiscard";
         fs_mgr_set_blk_ro(device_path, false);
     }
-    entry.fs_mgr_flags.check = true;
+    // check_fs requires apex runtime library
+    if (fs_mgr_overlayfs_already_mounted("/data", false)) {
+        entry.fs_mgr_flags.check = true;
+    }
     auto save_errno = errno;
     if (mounted) mounted = fs_mgr_do_mount_one(entry) == 0;
     if (!mounted) {
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 fdaffbe..2ad8125 100644
--- a/fs_mgr/fs_mgr_roots.cpp
+++ b/fs_mgr/fs_mgr_roots.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "android-base/file.h"
 #include "fs_mgr/roots.h"
 
 #include <sys/mount.h>
@@ -39,18 +40,26 @@
     while (true) {
         auto entry = GetEntryForMountPoint(fstab, str);
         if (entry != nullptr) return entry;
-        if (str == "/") break;
-        auto slash = str.find_last_of('/');
-        if (slash == std::string::npos) break;
-        if (slash == 0) {
-            str = "/";
-        } else {
-            str = str.substr(0, slash);
-        }
+        str = android::base::Dirname(str);
+        if (!str.compare(".") || !str.compare("/")) break;
     }
     return nullptr;
 }
 
+std::vector<FstabEntry*> GetEntriesForPath(Fstab* fstab, const std::string& path) {
+    std::vector<FstabEntry*> entries;
+    if (path.empty()) return entries;
+
+    std::string str(path);
+    while (true) {
+        entries = GetEntriesForMountPoint(fstab, str);
+        if (!entries.empty()) return entries;
+        str = android::base::Dirname(str);
+        if (!str.compare(".") || !str.compare("/")) break;
+    }
+    return entries;
+}
+
 enum class MountState {
     ERROR = -1,
     NOT_MOUNTED = 0,
@@ -71,12 +80,7 @@
     return MountState::NOT_MOUNTED;
 }
 
-bool EnsurePathMounted(Fstab* fstab, const std::string& path, const std::string& mount_pt) {
-    auto rec = GetEntryForPath(fstab, path);
-    if (rec == nullptr) {
-        LERROR << "unknown volume for path [" << path << "]";
-        return false;
-    }
+bool TryPathMount(FstabEntry* rec, const std::string& mount_pt) {
     if (rec->fs_type == "ramdisk") {
         // The ramdisk is always mounted.
         return true;
@@ -121,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;
         }
@@ -136,6 +139,21 @@
     return true;
 }
 
+bool EnsurePathMounted(Fstab* fstab, const std::string& path, const std::string& mount_point) {
+    auto entries = GetEntriesForPath(fstab, path);
+    if (entries.empty()) {
+        LERROR << "unknown volume for path [" << path << "]";
+        return false;
+    }
+
+    for (auto entry : entries) {
+        if (TryPathMount(entry, mount_point)) return true;
+    }
+
+    LERROR << "Failed to mount for path [" << path << "]";
+    return false;
+}
+
 bool EnsurePathUnmounted(Fstab* fstab, const std::string& path) {
     auto rec = GetEntryForPath(fstab, path);
     if (rec == nullptr) {
diff --git a/fs_mgr/fs_mgr_verity.cpp b/fs_mgr/fs_mgr_verity.cpp
deleted file mode 100644
index efa2180..0000000
--- a/fs_mgr/fs_mgr_verity.cpp
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * Copyright (C) 2013 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 <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <libgen.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <android-base/file.h>
-#include <android-base/properties.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <crypto_utils/android_pubkey.h>
-#include <cutils/properties.h>
-#include <fs_mgr/file_wait.h>
-#include <libdm/dm.h>
-#include <logwrap/logwrap.h>
-#include <openssl/obj_mac.h>
-#include <openssl/rsa.h>
-#include <openssl/sha.h>
-
-#include "fec/io.h"
-
-#include "fs_mgr.h"
-#include "fs_mgr_dm_linear.h"
-#include "fs_mgr_priv.h"
-
-// Realistically, this file should be part of the android::fs_mgr namespace;
-using namespace android::fs_mgr;
-
-#define VERITY_TABLE_RSA_KEY "/verity_key"
-#define VERITY_TABLE_HASH_IDX 8
-#define VERITY_TABLE_SALT_IDX 9
-
-#define VERITY_TABLE_OPT_RESTART "restart_on_corruption"
-#define VERITY_TABLE_OPT_LOGGING "ignore_corruption"
-#define VERITY_TABLE_OPT_IGNZERO "ignore_zero_blocks"
-
-#define VERITY_TABLE_OPT_FEC_FORMAT \
-    "use_fec_from_device %s fec_start %" PRIu64 " fec_blocks %" PRIu64 \
-    " fec_roots %u " VERITY_TABLE_OPT_IGNZERO
-#define VERITY_TABLE_OPT_FEC_ARGS 9
-
-#define METADATA_MAGIC 0x01564c54
-#define METADATA_TAG_MAX_LENGTH 63
-#define METADATA_EOD "eod"
-
-#define VERITY_LASTSIG_TAG "verity_lastsig"
-
-#define VERITY_STATE_TAG "verity_state"
-#define VERITY_STATE_HEADER 0x83c0ae9d
-#define VERITY_STATE_VERSION 1
-
-#define VERITY_KMSG_RESTART "dm-verity device corrupted"
-#define VERITY_KMSG_BUFSIZE 1024
-
-#define READ_BUF_SIZE 4096
-
-#define __STRINGIFY(x) #x
-#define STRINGIFY(x) __STRINGIFY(x)
-
-struct verity_state {
-    uint32_t header;
-    uint32_t version;
-    int32_t mode;
-};
-
-extern struct fs_info info;
-
-static RSA *load_key(const char *path)
-{
-    uint8_t key_data[ANDROID_PUBKEY_ENCODED_SIZE];
-
-    auto f = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path, "re"), fclose};
-    if (!f) {
-        LERROR << "Can't open " << path;
-        return nullptr;
-    }
-
-    if (!fread(key_data, sizeof(key_data), 1, f.get())) {
-        LERROR << "Could not read key!";
-        return nullptr;
-    }
-
-    RSA* key = nullptr;
-    if (!android_pubkey_decode(key_data, sizeof(key_data), &key)) {
-        LERROR << "Could not parse key!";
-        return nullptr;
-    }
-
-    return key;
-}
-
-static int verify_table(const uint8_t *signature, size_t signature_size,
-        const char *table, uint32_t table_length)
-{
-    RSA *key;
-    uint8_t hash_buf[SHA256_DIGEST_LENGTH];
-    int retval = -1;
-
-    // Hash the table
-    SHA256((uint8_t*)table, table_length, hash_buf);
-
-    // Now get the public key from the keyfile
-    key = load_key(VERITY_TABLE_RSA_KEY);
-    if (!key) {
-        LERROR << "Couldn't load verity keys";
-        goto out;
-    }
-
-    // verify the result
-    if (!RSA_verify(NID_sha256, hash_buf, sizeof(hash_buf), signature,
-                    signature_size, key)) {
-        LERROR << "Couldn't verify table";
-        goto out;
-    }
-
-    retval = 0;
-
-out:
-    RSA_free(key);
-    return retval;
-}
-
-static int verify_verity_signature(const struct fec_verity_metadata& verity)
-{
-    if (verify_table(verity.signature, sizeof(verity.signature),
-            verity.table, verity.table_length) == 0 ||
-        verify_table(verity.ecc_signature, sizeof(verity.ecc_signature),
-            verity.table, verity.table_length) == 0) {
-        return 0;
-    }
-
-    return -1;
-}
-
-static int invalidate_table(char *table, size_t table_length)
-{
-    size_t n = 0;
-    size_t idx = 0;
-    size_t cleared = 0;
-
-    while (n < table_length) {
-        if (table[n++] == ' ') {
-            ++idx;
-        }
-
-        if (idx != VERITY_TABLE_HASH_IDX && idx != VERITY_TABLE_SALT_IDX) {
-            continue;
-        }
-
-        while (n < table_length && table[n] != ' ') {
-            table[n++] = '0';
-        }
-
-        if (++cleared == 2) {
-            return 0;
-        }
-    }
-
-    return -1;
-}
-
-struct verity_table_params {
-    char *table;
-    int mode;
-    struct fec_ecc_metadata ecc;
-    const char *ecc_dev;
-};
-
-typedef bool (*format_verity_table_func)(char *buf, const size_t bufsize,
-        const struct verity_table_params *params);
-
-static bool format_verity_table(char *buf, const size_t bufsize,
-        const struct verity_table_params *params)
-{
-    const char *mode_flag = NULL;
-    int res = -1;
-
-    if (params->mode == VERITY_MODE_RESTART) {
-        mode_flag = VERITY_TABLE_OPT_RESTART;
-    } else if (params->mode == VERITY_MODE_LOGGING) {
-        mode_flag = VERITY_TABLE_OPT_LOGGING;
-    }
-
-    if (params->ecc.valid) {
-        if (mode_flag) {
-            res = snprintf(buf, bufsize,
-                    "%s %u %s " VERITY_TABLE_OPT_FEC_FORMAT,
-                    params->table, 1 + VERITY_TABLE_OPT_FEC_ARGS, mode_flag, params->ecc_dev,
-                    params->ecc.start / FEC_BLOCKSIZE, params->ecc.blocks, params->ecc.roots);
-        } else {
-            res = snprintf(buf, bufsize,
-                    "%s %u " VERITY_TABLE_OPT_FEC_FORMAT,
-                    params->table, VERITY_TABLE_OPT_FEC_ARGS, params->ecc_dev,
-                    params->ecc.start / FEC_BLOCKSIZE, params->ecc.blocks, params->ecc.roots);
-        }
-    } else if (mode_flag) {
-        res = snprintf(buf, bufsize, "%s 2 " VERITY_TABLE_OPT_IGNZERO " %s", params->table,
-                    mode_flag);
-    } else {
-        res = snprintf(buf, bufsize, "%s 1 " VERITY_TABLE_OPT_IGNZERO, params->table);
-    }
-
-    if (res < 0 || (size_t)res >= bufsize) {
-        LERROR << "Error building verity table; insufficient buffer size?";
-        return false;
-    }
-
-    return true;
-}
-
-static bool format_legacy_verity_table(char *buf, const size_t bufsize,
-        const struct verity_table_params *params)
-{
-    int res;
-
-    if (params->mode == VERITY_MODE_EIO) {
-        res = strlcpy(buf, params->table, bufsize);
-    } else {
-        res = snprintf(buf, bufsize, "%s %d", params->table, params->mode);
-    }
-
-    if (res < 0 || (size_t)res >= bufsize) {
-        LERROR << "Error building verity table; insufficient buffer size?";
-        return false;
-    }
-
-    return true;
-}
-
-static int load_verity_table(android::dm::DeviceMapper& dm, const std::string& name,
-                             uint64_t device_size, const struct verity_table_params* params,
-                             format_verity_table_func format) {
-    android::dm::DmTable table;
-    table.set_readonly(true);
-
-    char buffer[DM_BUF_SIZE];
-    if (!format(buffer, sizeof(buffer), params)) {
-        LERROR << "Failed to format verity parameters";
-        return -1;
-    }
-
-    android::dm::DmTargetVerityString target(0, device_size / 512, buffer);
-    if (!table.AddTarget(std::make_unique<decltype(target)>(target))) {
-        LERROR << "Failed to add verity target";
-        return -1;
-    }
-    if (!dm.CreateDevice(name, table)) {
-        LERROR << "Failed to create verity device \"" << name << "\"";
-        return -1;
-    }
-    return 0;
-}
-
-static int read_partition(const char *path, uint64_t size)
-{
-    char buf[READ_BUF_SIZE];
-    ssize_t size_read;
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC)));
-
-    if (fd == -1) {
-        PERROR << "Failed to open " << path;
-        return -errno;
-    }
-
-    while (size) {
-        size_read = TEMP_FAILURE_RETRY(read(fd, buf, READ_BUF_SIZE));
-        if (size_read == -1) {
-            PERROR << "Error in reading partition " << path;
-            return -errno;
-        }
-        size -= size_read;
-    }
-
-    return 0;
-}
-
-bool fs_mgr_load_verity_state(int* mode) {
-    // unless otherwise specified, use EIO mode.
-    *mode = VERITY_MODE_EIO;
-
-    // The bootloader communicates verity mode via the kernel commandline
-    std::string verity_mode;
-    if (!fs_mgr_get_boot_config("veritymode", &verity_mode)) {
-        return false;
-    }
-
-    if (verity_mode == "enforcing") {
-        *mode = VERITY_MODE_DEFAULT;
-    } else if (verity_mode == "logging") {
-        *mode = VERITY_MODE_LOGGING;
-    }
-
-    return true;
-}
-
-// Update the verity table using the actual block device path.
-// Two cases:
-// Case-1: verity table is shared for devices with different by-name prefix.
-// Example:
-//   verity table token:       /dev/block/bootdevice/by-name/vendor
-//   blk_device-1 (non-A/B):   /dev/block/platform/soc.0/7824900.sdhci/by-name/vendor
-//   blk_device-2 (A/B):       /dev/block/platform/soc.0/f9824900.sdhci/by-name/vendor_a
-//
-// Case-2: append A/B suffix in the verity table.
-// Example:
-//   verity table token: /dev/block/platform/soc.0/7824900.sdhci/by-name/vendor
-//   blk_device:         /dev/block/platform/soc.0/7824900.sdhci/by-name/vendor_a
-static void update_verity_table_blk_device(const std::string& blk_device, char** table,
-                                           bool slot_select) {
-    bool updated = false;
-    std::string result, ab_suffix;
-    auto tokens = android::base::Split(*table, " ");
-
-    // If slot_select is set, it means blk_device is already updated with ab_suffix.
-    if (slot_select) ab_suffix = fs_mgr_get_slot_suffix();
-
-    for (const auto& token : tokens) {
-        std::string new_token;
-        if (android::base::StartsWith(token, "/dev/block/")) {
-            if (token == blk_device) return;  // no need to update if they're already the same.
-            std::size_t found1 = blk_device.find("by-name");
-            std::size_t found2 = token.find("by-name");
-            if (found1 != std::string::npos && found2 != std::string::npos &&
-                blk_device.substr(found1) == token.substr(found2) + ab_suffix) {
-                new_token = blk_device;
-            }
-        }
-
-        if (!new_token.empty()) {
-            updated = true;
-            LINFO << "Verity table: updated block device from '" << token << "' to '" << new_token
-                  << "'";
-        } else {
-            new_token = token;
-        }
-
-        if (result.empty()) {
-            result = new_token;
-        } else {
-            result += " " + new_token;
-        }
-    }
-
-    if (!updated) {
-        return;
-    }
-
-    free(*table);
-    *table = strdup(result.c_str());
-}
-
-// prepares the verity enabled (MF_VERIFY / MF_VERIFYATBOOT) fstab record for
-// mount. The 'wait_for_verity_dev' parameter makes this function wait for the
-// verity device to get created before return
-int fs_mgr_setup_verity(FstabEntry* entry, bool wait_for_verity_dev) {
-    int retval = FS_MGR_SETUP_VERITY_FAIL;
-    int fd = -1;
-    std::string verity_blk_name;
-    struct fec_handle *f = NULL;
-    struct fec_verity_metadata verity;
-    struct verity_table_params params = { .table = NULL };
-
-    const std::string mount_point(basename(entry->mount_point.c_str()));
-    bool verified_at_boot = false;
-
-    android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();
-
-    if (fec_open(&f, entry->blk_device.c_str(), O_RDONLY, FEC_VERITY_DISABLE, FEC_DEFAULT_ROOTS) <
-        0) {
-        PERROR << "Failed to open '" << entry->blk_device << "'";
-        return retval;
-    }
-
-    // read verity metadata
-    if (fec_verity_get_metadata(f, &verity) < 0) {
-        PERROR << "Failed to get verity metadata '" << entry->blk_device << "'";
-        // Allow verity disabled when the device is unlocked without metadata
-        if (fs_mgr_is_device_unlocked()) {
-            retval = FS_MGR_SETUP_VERITY_SKIPPED;
-            LWARNING << "Allow invalid metadata when the device is unlocked";
-        }
-        goto out;
-    }
-
-#ifdef ALLOW_ADBD_DISABLE_VERITY
-    if (verity.disabled) {
-        retval = FS_MGR_SETUP_VERITY_DISABLED;
-        LINFO << "Attempt to cleanly disable verity - only works in USERDEBUG/ENG";
-        goto out;
-    }
-#endif
-
-    // read ecc metadata
-    if (fec_ecc_get_metadata(f, &params.ecc) < 0) {
-        params.ecc.valid = false;
-    }
-
-    params.ecc_dev = entry->blk_device.c_str();
-
-    if (!fs_mgr_load_verity_state(&params.mode)) {
-        /* if accessing or updating the state failed, switch to the default
-         * safe mode. This makes sure the device won't end up in an endless
-         * restart loop, and no corrupted data will be exposed to userspace
-         * without a warning. */
-        params.mode = VERITY_MODE_EIO;
-    }
-
-    if (!verity.table) {
-        goto out;
-    }
-
-    params.table = strdup(verity.table);
-    if (!params.table) {
-        goto out;
-    }
-
-    // verify the signature on the table
-    if (verify_verity_signature(verity) < 0) {
-        // Allow signature verification error when the device is unlocked
-        if (fs_mgr_is_device_unlocked()) {
-            retval = FS_MGR_SETUP_VERITY_SKIPPED;
-            LWARNING << "Allow signature verification error when the device is unlocked";
-            goto out;
-        }
-        if (params.mode == VERITY_MODE_LOGGING) {
-            // the user has been warned, allow mounting without dm-verity
-            retval = FS_MGR_SETUP_VERITY_SKIPPED;
-            goto out;
-        }
-
-        // invalidate root hash and salt to trigger device-specific recovery
-        if (invalidate_table(params.table, verity.table_length) < 0) {
-            goto out;
-        }
-    }
-
-    LINFO << "Enabling dm-verity for " << mount_point.c_str()
-          << " (mode " << params.mode << ")";
-
-    // Update the verity params using the actual block device path
-    update_verity_table_blk_device(entry->blk_device, &params.table,
-                                   entry->fs_mgr_flags.slot_select);
-
-    // load the verity mapping table
-    if (load_verity_table(dm, mount_point, verity.data_size, &params, format_verity_table) == 0) {
-        goto loaded;
-    }
-
-    if (params.ecc.valid) {
-        // kernel may not support error correction, try without
-        LINFO << "Disabling error correction for " << mount_point.c_str();
-        params.ecc.valid = false;
-
-        if (load_verity_table(dm, mount_point, verity.data_size, &params, format_verity_table) == 0) {
-            goto loaded;
-        }
-    }
-
-    // try the legacy format for backwards compatibility
-    if (load_verity_table(dm, mount_point, verity.data_size, &params, format_legacy_verity_table) ==
-        0) {
-        goto loaded;
-    }
-
-    if (params.mode != VERITY_MODE_EIO) {
-        // as a last resort, EIO mode should always be supported
-        LINFO << "Falling back to EIO mode for " << mount_point.c_str();
-        params.mode = VERITY_MODE_EIO;
-
-        if (load_verity_table(dm, mount_point, verity.data_size, &params,
-                              format_legacy_verity_table) == 0) {
-            goto loaded;
-        }
-    }
-
-    LERROR << "Failed to load verity table for " << mount_point.c_str();
-    goto out;
-
-loaded:
-    if (!dm.GetDmDevicePathByName(mount_point, &verity_blk_name)) {
-        LERROR << "Couldn't get verity device number!";
-        goto out;
-    }
-
-    // mark the underlying block device as read-only
-    fs_mgr_set_blk_ro(entry->blk_device);
-
-    // Verify the entire partition in one go
-    // If there is an error, allow it to mount as a normal verity partition.
-    if (entry->fs_mgr_flags.verify_at_boot) {
-        LINFO << "Verifying partition " << entry->blk_device << " at boot";
-        int err = read_partition(verity_blk_name.c_str(), verity.data_size);
-        if (!err) {
-            LINFO << "Verified verity partition " << entry->blk_device << " at boot";
-            verified_at_boot = true;
-        }
-    }
-
-    // assign the new verity block device as the block device
-    if (!verified_at_boot) {
-        entry->blk_device = verity_blk_name;
-    } else if (!dm.DeleteDevice(mount_point)) {
-        LERROR << "Failed to remove verity device " << mount_point.c_str();
-        goto out;
-    }
-
-    // make sure we've set everything up properly
-    if (wait_for_verity_dev && !WaitForFile(entry->blk_device, 1s)) {
-        goto out;
-    }
-
-    retval = FS_MGR_SETUP_VERITY_SUCCESS;
-
-out:
-    if (fd != -1) {
-        close(fd);
-    }
-
-    fec_close(f);
-    free(params.table);
-
-    return retval;
-}
-
-bool fs_mgr_teardown_verity(FstabEntry* entry) {
-    const std::string mount_point(basename(entry->mount_point.c_str()));
-    if (!android::fs_mgr::UnmapDevice(mount_point)) {
-        return false;
-    }
-    LINFO << "Unmapped verity device " << mount_point;
-    return true;
-}
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 4d3ecc9..29a5e60 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -22,6 +22,7 @@
 #include <linux/dm-ioctl.h>
 
 #include <functional>
+#include <optional>
 #include <string>
 
 #include <fstab/fstab.h>
@@ -55,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)
 
@@ -68,6 +66,13 @@
     bool userdata_mounted;
 };
 
+struct HashtreeInfo {
+    // The hash algorithm used to build the merkle tree.
+    std::string algorithm;
+    // The root digest of the merkle tree.
+    std::string root_digest;
+};
+
 // fs_mgr_mount_all() updates fstab entries that reference device-mapper.
 // Returns a |MountAllResult|. The first element is one of the FS_MNG_MNTALL_* return codes
 // defined above, and the second element tells whether this call to fs_mgr_mount_all was responsible
@@ -88,9 +93,9 @@
 bool fs_mgr_load_verity_state(int* mode);
 // Returns true if verity is enabled on this particular FstabEntry.
 bool fs_mgr_is_verity_enabled(const android::fs_mgr::FstabEntry& entry);
-// Returns the hash algorithm used to build the hashtree of this particular FstabEntry. Returns an
-// empty string if the input isn't a dm-verity entry, or if there is an error.
-std::string fs_mgr_get_hashtree_algorithm(const android::fs_mgr::FstabEntry& entry);
+// Returns the verity hashtree information of this particular FstabEntry. Returns std::nullopt
+// if the input isn't a dm-verity entry, or if there is an error.
+std::optional<HashtreeInfo> fs_mgr_get_hashtree_info(const android::fs_mgr::FstabEntry& entry);
 
 bool fs_mgr_swapon_all(const android::fs_mgr::Fstab& fstab);
 bool fs_mgr_update_logical_partition(android::fs_mgr::FstabEntry* entry);
@@ -99,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..b831d12 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,22 +59,18 @@
     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;
         bool quota : 1;
         bool avb : 1;
         bool logical : 1;
@@ -89,9 +84,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 +92,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/Android.bp b/fs_mgr/libdm/Android.bp
index 428a7f4..2bb9035 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -86,7 +86,9 @@
     name: "vts_libdm_test",
     defaults: ["libdm_test_defaults"],
     test_suites: ["vts"],
-    test_min_api_level: 29,
+    test_options: {
+        min_shipping_api_level: 29,
+    },
 }
 
 cc_fuzz {
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 8e3d5a5..4034e30 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -578,34 +578,30 @@
     return std::string{spec.target_type, sizeof(spec.target_type)};
 }
 
-static bool ExtractBlockDeviceName(const std::string& path, std::string* name) {
+std::optional<std::string> ExtractBlockDeviceName(const std::string& path) {
     static constexpr std::string_view kDevBlockPrefix("/dev/block/");
     if (android::base::StartsWith(path, kDevBlockPrefix)) {
-        *name = path.substr(kDevBlockPrefix.length());
-        return true;
+        return path.substr(kDevBlockPrefix.length());
     }
-    return false;
+    return {};
 }
 
 bool DeviceMapper::IsDmBlockDevice(const std::string& path) {
-    std::string name;
-    if (!ExtractBlockDeviceName(path, &name)) {
-        return false;
-    }
-    return android::base::StartsWith(name, "dm-");
+    std::optional<std::string> name = ExtractBlockDeviceName(path);
+    return name && android::base::StartsWith(*name, "dm-");
 }
 
 std::optional<std::string> DeviceMapper::GetDmDeviceNameByPath(const std::string& path) {
-    std::string name;
-    if (!ExtractBlockDeviceName(path, &name)) {
+    std::optional<std::string> name = ExtractBlockDeviceName(path);
+    if (!name) {
         LOG(WARNING) << path << " is not a block device";
         return std::nullopt;
     }
-    if (!android::base::StartsWith(name, "dm-")) {
+    if (!android::base::StartsWith(*name, "dm-")) {
         LOG(WARNING) << path << " is not a dm device";
         return std::nullopt;
     }
-    std::string dm_name_file = "/sys/block/" + name + "/dm/name";
+    std::string dm_name_file = "/sys/block/" + *name + "/dm/name";
     std::string dm_name;
     if (!android::base::ReadFileToString(dm_name_file, &dm_name)) {
         PLOG(ERROR) << "Failed to read file " << dm_name_file;
@@ -616,16 +612,16 @@
 }
 
 std::optional<std::string> DeviceMapper::GetParentBlockDeviceByPath(const std::string& path) {
-    std::string name;
-    if (!ExtractBlockDeviceName(path, &name)) {
+    std::optional<std::string> name = ExtractBlockDeviceName(path);
+    if (!name) {
         LOG(WARNING) << path << " is not a block device";
         return std::nullopt;
     }
-    if (!android::base::StartsWith(name, "dm-")) {
+    if (!android::base::StartsWith(*name, "dm-")) {
         // Reached bottom of the device mapper stack.
         return std::nullopt;
     }
-    auto slaves_dir = "/sys/block/" + name + "/slaves";
+    auto slaves_dir = "/sys/block/" + *name + "/slaves";
     auto dir = std::unique_ptr<DIR, decltype(&closedir)>(opendir(slaves_dir.c_str()), closedir);
     if (dir == nullptr) {
         PLOG(ERROR) << "Failed to open: " << slaves_dir;
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 31c3003..1057d7f 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -24,6 +24,7 @@
 #include <linux/types.h>
 #include <stdint.h>
 #include <sys/sysmacros.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #include <chrono>
@@ -51,7 +52,37 @@
 
 static constexpr uint64_t kSectorSize = 512;
 
-class DeviceMapper final {
+// Returns `path` without /dev/block prefix if and only if `path` starts with
+// that prefix.
+std::optional<std::string> ExtractBlockDeviceName(const std::string& path);
+
+// This interface is for testing purposes. See DeviceMapper proper for what these methods do.
+class IDeviceMapper {
+  public:
+    virtual ~IDeviceMapper() {}
+
+    struct TargetInfo {
+        struct dm_target_spec spec;
+        std::string data;
+        TargetInfo() {}
+        TargetInfo(const struct dm_target_spec& spec, const std::string& data)
+            : spec(spec), data(data) {}
+
+        bool IsOverflowSnapshot() const;
+    };
+
+    virtual bool CreateDevice(const std::string& name, const DmTable& table, std::string* path,
+                              const std::chrono::milliseconds& timeout_ms) = 0;
+    virtual DmDeviceState GetState(const std::string& name) const = 0;
+    virtual bool LoadTableAndActivate(const std::string& name, const DmTable& table) = 0;
+    virtual bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) = 0;
+    virtual bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) = 0;
+    virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) = 0;
+    virtual bool GetDeviceString(const std::string& name, std::string* dev) = 0;
+    virtual bool DeleteDeviceIfExists(const std::string& name) = 0;
+};
+
+class DeviceMapper final : public IDeviceMapper {
   public:
     class DmBlockDevice final {
       public:
@@ -91,7 +122,7 @@
     // Removes a device mapper device with the given name.
     // Returns 'true' on success, false otherwise.
     bool DeleteDevice(const std::string& name);
-    bool DeleteDeviceIfExists(const std::string& name);
+    bool DeleteDeviceIfExists(const std::string& name) override;
     // Removes a device mapper device with the given name and waits for |timeout_ms| milliseconds
     // for the corresponding block device to be deleted.
     bool DeleteDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms);
@@ -110,7 +141,7 @@
     // Returns the current state of the underlying device mapper device
     // with given name.
     // One of INVALID, SUSPENDED or ACTIVE.
-    DmDeviceState GetState(const std::string& name) const;
+    DmDeviceState GetState(const std::string& name) const override;
 
     // Puts the given device to the specified status, which must be either:
     // - SUSPENDED: suspend the device, or
@@ -154,7 +185,7 @@
     // not |path| is available. It is the caller's responsibility to ensure
     // there are no races.
     bool CreateDevice(const std::string& name, const DmTable& table, std::string* path,
-                      const std::chrono::milliseconds& timeout_ms);
+                      const std::chrono::milliseconds& timeout_ms) override;
 
     // Create a device and activate the given table, without waiting to acquire
     // a valid path. If the caller will use GetDmDevicePathByName(), it should
@@ -166,7 +197,7 @@
     // process. A device with the given name must already exist.
     //
     // Returns 'true' on success, false otherwise.
-    bool LoadTableAndActivate(const std::string& name, const DmTable& table);
+    bool LoadTableAndActivate(const std::string& name, const DmTable& table) override;
 
     // Returns true if a list of available device mapper targets registered in the kernel was
     // successfully read and stored in 'targets'. Returns 'false' otherwise.
@@ -212,7 +243,7 @@
 
     // Returns a major:minor string for the named device-mapper node, that can
     // be used as inputs to DmTargets that take a block device.
-    bool GetDeviceString(const std::string& name, std::string* dev);
+    bool GetDeviceString(const std::string& name, std::string* dev) override;
 
     // The only way to create a DeviceMapper object.
     static DeviceMapper& Instance();
@@ -227,20 +258,11 @@
     // contain one TargetInfo for each target in the table. If the device does
     // not exist, or there were too many targets, the call will fail and return
     // false.
-    struct TargetInfo {
-        struct dm_target_spec spec;
-        std::string data;
-        TargetInfo() {}
-        TargetInfo(const struct dm_target_spec& spec, const std::string& data)
-            : spec(spec), data(data) {}
-
-        bool IsOverflowSnapshot() const;
-    };
-    bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table);
+    bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) override;
 
     // Identical to GetTableStatus, except also retrives the active table for the device
     // mapper device from the kernel.
-    bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table);
+    bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) override;
 
     static std::string GetTargetType(const struct dm_target_spec& spec);
 
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/Android.bp b/fs_mgr/libfiemap/Android.bp
index d16b8d6..5deba65 100644
--- a/fs_mgr/libfiemap/Android.bp
+++ b/fs_mgr/libfiemap/Android.bp
@@ -90,7 +90,9 @@
 
     test_suites: ["vts", "device-tests"],
     auto_gen_config: true,
-    test_min_api_level: 29,
+    test_options: {
+        min_shipping_api_level: 29,
+    },
     require_root: true,
 }
 
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/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index 0cbd9db..a0ad208 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -152,6 +152,7 @@
     static_libs: [
         "libavb",
         "libdm",
+        "libext2_uuid",
         "libfs_avb",
         "libfstab",
     ],
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index e913d50..85dbb36 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -35,19 +35,6 @@
 namespace android {
 namespace fs_mgr {
 
-std::string GetAvbPropertyDescriptor(const std::string& key,
-                                     const std::vector<VBMetaData>& vbmeta_images) {
-    size_t value_size;
-    for (const auto& vbmeta : vbmeta_images) {
-        const char* value = avb_property_lookup(vbmeta.data(), vbmeta.size(), key.data(),
-                                                key.size(), &value_size);
-        if (value != nullptr) {
-            return {value, value_size};
-        }
-    }
-    return "";
-}
-
 // Constructs dm-verity arguments for sending DM_TABLE_LOAD ioctl to kernel.
 // See the following link for more details:
 // https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
@@ -130,64 +117,6 @@
     return true;
 }
 
-std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(
-        const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images) {
-    bool found = false;
-    const uint8_t* desc_partition_name;
-    auto hash_desc = std::make_unique<FsAvbHashDescriptor>();
-
-    for (const auto& vbmeta : vbmeta_images) {
-        size_t num_descriptors;
-        std::unique_ptr<const AvbDescriptor*[], decltype(&avb_free)> descriptors(
-                avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free);
-
-        if (!descriptors || num_descriptors < 1) {
-            continue;
-        }
-
-        for (size_t n = 0; n < num_descriptors && !found; n++) {
-            AvbDescriptor desc;
-            if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
-                LWARNING << "Descriptor[" << n << "] is invalid";
-                continue;
-            }
-            if (desc.tag == AVB_DESCRIPTOR_TAG_HASH) {
-                desc_partition_name = (const uint8_t*)descriptors[n] + sizeof(AvbHashDescriptor);
-                if (!avb_hash_descriptor_validate_and_byteswap((AvbHashDescriptor*)descriptors[n],
-                                                               hash_desc.get())) {
-                    continue;
-                }
-                if (hash_desc->partition_name_len != partition_name.length()) {
-                    continue;
-                }
-                // Notes that desc_partition_name is not NUL-terminated.
-                std::string hash_partition_name((const char*)desc_partition_name,
-                                                hash_desc->partition_name_len);
-                if (hash_partition_name == partition_name) {
-                    found = true;
-                }
-            }
-        }
-
-        if (found) break;
-    }
-
-    if (!found) {
-        LERROR << "Hash descriptor not found: " << partition_name;
-        return nullptr;
-    }
-
-    hash_desc->partition_name = partition_name;
-
-    const uint8_t* desc_salt = desc_partition_name + hash_desc->partition_name_len;
-    hash_desc->salt = BytesToHex(desc_salt, hash_desc->salt_len);
-
-    const uint8_t* desc_digest = desc_salt + hash_desc->salt_len;
-    hash_desc->digest = BytesToHex(desc_digest, hash_desc->digest_len);
-
-    return hash_desc;
-}
-
 std::unique_ptr<FsAvbHashtreeDescriptor> GetHashtreeDescriptor(
         const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images) {
     bool found = false;
diff --git a/fs_mgr/libfs_avb/avb_util.h b/fs_mgr/libfs_avb/avb_util.h
index e8f7c39..7941c70 100644
--- a/fs_mgr/libfs_avb/avb_util.h
+++ b/fs_mgr/libfs_avb/avb_util.h
@@ -37,12 +37,6 @@
         : partition_name(chain_partition_name), public_key_blob(chain_public_key_blob) {}
 };
 
-std::string GetAvbPropertyDescriptor(const std::string& key,
-                                     const std::vector<VBMetaData>& vbmeta_images);
-
-std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(
-        const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images);
-
 // AvbHashtreeDescriptor to dm-verity table setup.
 std::unique_ptr<FsAvbHashtreeDescriptor> GetHashtreeDescriptor(
         const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images);
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index 1da7117..a288876 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -37,6 +37,7 @@
 
 #include "avb_ops.h"
 #include "avb_util.h"
+#include "fs_avb/fs_avb_util.h"
 #include "sha.h"
 #include "util.h"
 
diff --git a/fs_mgr/libfs_avb/fs_avb_util.cpp b/fs_mgr/libfs_avb/fs_avb_util.cpp
index 1c14cc0..5326226 100644
--- a/fs_mgr/libfs_avb/fs_avb_util.cpp
+++ b/fs_mgr/libfs_avb/fs_avb_util.cpp
@@ -74,6 +74,64 @@
     return GetHashtreeDescriptor(avb_partition_name, vbmeta_images);
 }
 
+std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(
+        const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images) {
+    bool found = false;
+    const uint8_t* desc_partition_name;
+    auto hash_desc = std::make_unique<FsAvbHashDescriptor>();
+
+    for (const auto& vbmeta : vbmeta_images) {
+        size_t num_descriptors;
+        std::unique_ptr<const AvbDescriptor*[], decltype(&avb_free)> descriptors(
+                avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free);
+
+        if (!descriptors || num_descriptors < 1) {
+            continue;
+        }
+
+        for (size_t n = 0; n < num_descriptors && !found; n++) {
+            AvbDescriptor desc;
+            if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
+                LWARNING << "Descriptor[" << n << "] is invalid";
+                continue;
+            }
+            if (desc.tag == AVB_DESCRIPTOR_TAG_HASH) {
+                desc_partition_name = (const uint8_t*)descriptors[n] + sizeof(AvbHashDescriptor);
+                if (!avb_hash_descriptor_validate_and_byteswap((AvbHashDescriptor*)descriptors[n],
+                                                               hash_desc.get())) {
+                    continue;
+                }
+                if (hash_desc->partition_name_len != partition_name.length()) {
+                    continue;
+                }
+                // Notes that desc_partition_name is not NUL-terminated.
+                std::string hash_partition_name((const char*)desc_partition_name,
+                                                hash_desc->partition_name_len);
+                if (hash_partition_name == partition_name) {
+                    found = true;
+                }
+            }
+        }
+
+        if (found) break;
+    }
+
+    if (!found) {
+        LERROR << "Hash descriptor not found: " << partition_name;
+        return nullptr;
+    }
+
+    hash_desc->partition_name = partition_name;
+
+    const uint8_t* desc_salt = desc_partition_name + hash_desc->partition_name_len;
+    hash_desc->salt = BytesToHex(desc_salt, hash_desc->salt_len);
+
+    const uint8_t* desc_digest = desc_salt + hash_desc->salt_len;
+    hash_desc->digest = BytesToHex(desc_digest, hash_desc->digest_len);
+
+    return hash_desc;
+}
+
 // Given a path, loads and verifies the vbmeta, to extract the Avb Hash descriptor.
 std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(const std::string& avb_partition_name,
                                                        VBMetaData&& vbmeta) {
@@ -84,5 +142,18 @@
     return GetHashDescriptor(avb_partition_name, vbmeta_images);
 }
 
+std::string GetAvbPropertyDescriptor(const std::string& key,
+                                     const std::vector<VBMetaData>& vbmeta_images) {
+    size_t value_size;
+    for (const auto& vbmeta : vbmeta_images) {
+        const char* value = avb_property_lookup(vbmeta.data(), vbmeta.size(), key.data(),
+                                                key.size(), &value_size);
+        if (value != nullptr) {
+            return {value, value_size};
+        }
+    }
+    return "";
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h b/fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h
index 3f37bd7..1b15db7 100644
--- a/fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h
+++ b/fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h
@@ -43,9 +43,15 @@
 std::unique_ptr<FsAvbHashtreeDescriptor> GetHashtreeDescriptor(
         const std::string& avb_partition_name, VBMetaData&& vbmeta);
 
+std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(
+        const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images);
+
 // Gets the hash descriptor for avb_partition_name from the vbmeta.
 std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(const std::string& avb_partition_name,
                                                        VBMetaData&& vbmeta);
 
+std::string GetAvbPropertyDescriptor(const std::string& key,
+                                     const std::vector<VBMetaData>& vbmeta_images);
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/libfs_avb/run_tests.sh b/fs_mgr/libfs_avb/run_tests.sh
index 5d2ce3d..3e945a4 100755
--- a/fs_mgr/libfs_avb/run_tests.sh
+++ b/fs_mgr/libfs_avb/run_tests.sh
@@ -1,8 +1,13 @@
 #!/bin/sh
 #
 # Run host tests
-atest libfs_avb_test                 # Tests public libfs_avb APIs.
-atest libfs_avb_internal_test        # Tests libfs_avb private APIs.
+atest --host libfs_avb_test          # Tests public libfs_avb APIs.
+
+# Tests libfs_avb private APIs.
+# The tests need more time to finish, so increase the timeout to 5 mins.
+# The default timeout is only 60 seconds.
+atest --host libfs_avb_internal_test -- --test-arg \
+    com.android.tradefed.testtype.HostGTest:native-test-timeout:5m
 
 # Run device tests
 atest libfs_avb_device_test          # Test public libfs_avb APIs on a device.
diff --git a/fs_mgr/libfs_avb/tests/avb_util_test.cpp b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
index 0288d85..2e34920 100644
--- a/fs_mgr/libfs_avb/tests/avb_util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
@@ -23,6 +23,7 @@
 #include <libavb/libavb.h>
 
 #include "avb_util.h"
+#include "fs_avb/fs_avb_util.h"
 #include "fs_avb_test_util.h"
 
 // Target classes or functions to test:
@@ -779,7 +780,7 @@
                               nullptr /* out_public_key_data */, &verify_result);
     ASSERT_EQ(0, close(hash_modified_fd.release()));
     EXPECT_NE(nullptr, vbmeta);
-    EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+    // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962.
     EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result);
 
     // Modifies the auxiliary data block.
@@ -795,7 +796,7 @@
                               nullptr /* out_public_key_data */, &verify_result);
     ASSERT_EQ(0, close(aux_modified_fd.release()));
     EXPECT_NE(nullptr, vbmeta);
-    EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+    // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962.
     EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result);
 
     // Resets previous modification by setting offset to -1, and checks the verification can pass.
@@ -807,7 +808,7 @@
                               nullptr /* out_public_key_data */, &verify_result);
     ASSERT_EQ(0, close(ok_fd.release()));
     EXPECT_NE(nullptr, vbmeta);
-    EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+    // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962.
     EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result);
 }
 
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index fc2d8a1..4b81c2c 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -105,7 +105,9 @@
     defaults: ["liblp_test_defaults"],
     test_suites: ["vts"],
     auto_gen_config: true,
-    test_min_api_level: 29,
+    test_options: {
+        min_shipping_api_level: 29,
+    },
     require_root: true,
 }
 
diff --git a/fs_mgr/liblp/OWNERS b/fs_mgr/liblp/OWNERS
new file mode 100644
index 0000000..6a95eb2
--- /dev/null
+++ b/fs_mgr/liblp/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 391836
+dvander@google.com
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index df00f45..8b269cd 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -236,6 +236,7 @@
         "libbrotli",
         "libc++fs",
         "libfs_mgr_binder",
+        "libgflags",
         "libgsi",
         "libgmock",
         "liblp",
@@ -251,7 +252,9 @@
         "vts",
         "device-tests"
     ],
-    test_min_api_level: 29,
+    test_options: {
+        min_shipping_api_level: 29,
+    },
     auto_gen_config: true,
     require_root: true,
 }
@@ -261,6 +264,17 @@
     defaults: ["libsnapshot_test_defaults"],
 }
 
+sh_test {
+    name: "run_snapshot_tests",
+    src: "run_snapshot_tests.sh",
+    test_suites: [
+        "device-tests",
+    ],
+    required: [
+        "vts_libsnapshot_test",
+    ],
+}
+
 cc_binary {
     name: "snapshotctl",
     srcs: [
@@ -407,7 +421,9 @@
     test_suites: [
         "device-tests"
     ],
-    test_min_api_level: 30,
+    test_options: {
+        min_shipping_api_level: 30,
+    },
     auto_gen_config: true,
     require_root: false,
     host_supported: true,
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/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index e2abdba..532f66d 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -194,6 +194,9 @@
 
     // Source build fingerprint.
     string source_build_fingerprint = 8;
+
+    // user-space snapshots
+    bool userspace_snapshots = 9;
 }
 
 // Next: 10
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 6066309..ba4044f 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -1171,18 +1171,18 @@
     ASSERT_TRUE(writer.Initialize(cow_->fd));
 
     ASSERT_TRUE(writer.AddSequenceData(6, sequence));
-    ASSERT_TRUE(writer.AddCopy(6, 3));
+    ASSERT_TRUE(writer.AddCopy(6, 13));
     ASSERT_TRUE(writer.AddZeroBlocks(12, 1));
     ASSERT_TRUE(writer.AddZeroBlocks(8, 1));
     ASSERT_TRUE(writer.AddZeroBlocks(11, 1));
-    ASSERT_TRUE(writer.AddCopy(3, 5));
-    ASSERT_TRUE(writer.AddCopy(2, 1));
+    ASSERT_TRUE(writer.AddCopy(3, 15));
+    ASSERT_TRUE(writer.AddCopy(2, 11));
     ASSERT_TRUE(writer.AddZeroBlocks(4, 1));
     ASSERT_TRUE(writer.AddZeroBlocks(9, 1));
-    ASSERT_TRUE(writer.AddCopy(5, 6));
+    ASSERT_TRUE(writer.AddCopy(5, 16));
     ASSERT_TRUE(writer.AddZeroBlocks(1, 1));
-    ASSERT_TRUE(writer.AddCopy(10, 2));
-    ASSERT_TRUE(writer.AddCopy(7, 4));
+    ASSERT_TRUE(writer.AddCopy(10, 12));
+    ASSERT_TRUE(writer.AddCopy(7, 14));
     ASSERT_TRUE(writer.Finalize());
 
     // New block in cow order is 6, 12, 8, 11, 3, 2, 4, 9, 5, 1, 10, 7
@@ -1219,12 +1219,12 @@
 
     ASSERT_TRUE(writer.Initialize(cow_->fd));
 
-    ASSERT_TRUE(writer.AddCopy(2, 1));
-    ASSERT_TRUE(writer.AddCopy(10, 2));
-    ASSERT_TRUE(writer.AddCopy(6, 3));
-    ASSERT_TRUE(writer.AddCopy(7, 4));
-    ASSERT_TRUE(writer.AddCopy(3, 5));
-    ASSERT_TRUE(writer.AddCopy(5, 6));
+    ASSERT_TRUE(writer.AddCopy(2, 11));
+    ASSERT_TRUE(writer.AddCopy(10, 12));
+    ASSERT_TRUE(writer.AddCopy(6, 13));
+    ASSERT_TRUE(writer.AddCopy(7, 14));
+    ASSERT_TRUE(writer.AddCopy(3, 15));
+    ASSERT_TRUE(writer.AddCopy(5, 16));
     ASSERT_TRUE(writer.AddZeroBlocks(12, 1));
     ASSERT_TRUE(writer.AddZeroBlocks(8, 1));
     ASSERT_TRUE(writer.AddZeroBlocks(11, 1));
@@ -1260,6 +1260,39 @@
     ASSERT_TRUE(iter->Done());
 }
 
+TEST_F(CowTest, InvalidMergeOrderTest) {
+    CowOptions options;
+    options.cluster_ops = 5;
+    options.num_merge_ops = 1;
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+    auto writer = std::make_unique<CowWriter>(options);
+    CowReader reader;
+
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(writer->AddCopy(3, 2));
+    ASSERT_TRUE(writer->AddCopy(2, 1));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_TRUE(reader.VerifyMergeOps());
+
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(writer->AddCopy(4, 2));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_FALSE(reader.VerifyMergeOps());
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+    ASSERT_TRUE(writer->AddCopy(2, 1));
+    ASSERT_TRUE(writer->AddXorBlocks(3, &data, data.size(), 1, 1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_FALSE(reader.VerifyMergeOps());
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/cow_format.cpp b/fs_mgr/libsnapshot/cow_format.cpp
index 8e6bec7..94c4109 100644
--- a/fs_mgr/libsnapshot/cow_format.cpp
+++ b/fs_mgr/libsnapshot/cow_format.cpp
@@ -56,7 +56,10 @@
         os << (int)op.compression << "?, ";
     os << "data_length:" << op.data_length << ",\t";
     os << "new_block:" << op.new_block << ",\t";
-    os << "source:" << op.source << ")";
+    os << "source:" << op.source;
+    if (op.type == kCowXorOp)
+        os << " (block:" << op.source / BLOCK_SZ << " offset:" << op.source % BLOCK_SZ << ")";
+    os << ")";
     return os;
 }
 
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 773d978..9b5fd2a 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
@@ -58,6 +59,7 @@
     cow->last_label_ = last_label_;
     cow->ops_ = ops_;
     cow->merge_op_blocks_ = merge_op_blocks_;
+    cow->merge_op_start_ = merge_op_start_;
     cow->block_map_ = block_map_;
     cow->num_total_data_ops_ = num_total_data_ops_;
     cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
@@ -230,7 +232,7 @@
                 memcpy(&footer_->op, &current_op, sizeof(footer->op));
                 off_t offs = lseek(fd_.get(), pos, SEEK_SET);
                 if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
-                    PLOG(ERROR) << "lseek next op failed";
+                    PLOG(ERROR) << "lseek next op failed " << offs;
                     return false;
                 }
                 if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
@@ -250,7 +252,7 @@
         // Position for next cluster read
         off_t offs = lseek(fd_.get(), pos, SEEK_SET);
         if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
-            PLOG(ERROR) << "lseek next op failed";
+            PLOG(ERROR) << "lseek next op failed " << offs;
             return false;
         }
         ops_buffer->resize(current_op_num);
@@ -414,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;
@@ -445,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});
     }
@@ -461,15 +463,23 @@
     } else {
         num_ordered_ops_to_merge_ = 0;
     }
-    merge_op_blocks->reserve(merge_op_blocks->size() + other_ops.size());
-    for (auto block : other_ops) {
-        merge_op_blocks->emplace_back(block);
+
+    // 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->insert(merge_op_blocks->end(), other_ops.begin(), other_ops.end());
+
     num_total_data_ops_ = merge_op_blocks->size();
     if (header_.num_merge_ops > 0) {
-        merge_op_blocks->erase(merge_op_blocks->begin(),
-                               merge_op_blocks->begin() + header_.num_merge_ops);
+        merge_op_start_ = header_.num_merge_ops;
     }
 
     block_map_ = block_map;
@@ -477,6 +487,44 @@
     return true;
 }
 
+bool CowReader::VerifyMergeOps() {
+    auto itr = GetMergeOpIter(true);
+    std::unordered_map<uint64_t, CowOperation> overwritten_blocks;
+    while (!itr->Done()) {
+        CowOperation op = itr->Get();
+        uint64_t block;
+        bool offset;
+        if (op.type == kCowCopyOp) {
+            block = op.source;
+            offset = false;
+        } else if (op.type == kCowXorOp) {
+            block = op.source / BLOCK_SZ;
+            offset = (op.source % BLOCK_SZ) != 0;
+        } else {
+            itr->Next();
+            continue;
+        }
+
+        CowOperation* overwrite = nullptr;
+        if (overwritten_blocks.count(block)) {
+            overwrite = &overwritten_blocks[block];
+            LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
+                       << op << "\noverwritten by previously merged op:\n"
+                       << *overwrite;
+        }
+        if (offset && overwritten_blocks.count(block + 1)) {
+            overwrite = &overwritten_blocks[block + 1];
+            LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
+                       << op << "\noverwritten by previously merged op:\n"
+                       << *overwrite;
+        }
+        if (overwrite != nullptr) return false;
+        overwritten_blocks[op.new_block] = op;
+        itr->Next();
+    }
+    return true;
+}
+
 bool CowReader::GetHeader(CowHeader* header) {
     *header = header_;
     return true;
@@ -530,7 +578,8 @@
   public:
     explicit CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
                                std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                               std::shared_ptr<std::unordered_map<uint32_t, int>> map);
+                               std::shared_ptr<std::unordered_map<uint32_t, int>> map,
+                               uint64_t start);
 
     bool Done() override;
     const CowOperation& Get() override;
@@ -541,20 +590,67 @@
     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
     std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
     std::vector<uint32_t>::reverse_iterator block_riter_;
+    uint64_t start_;
 };
 
-CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                                     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                                     std::shared_ptr<std::unordered_map<uint32_t, int>> map) {
+class CowMergeOpIter final : public ICowOpIter {
+  public:
+    explicit CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
+                            std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
+                            std::shared_ptr<std::unordered_map<uint32_t, int>> map, uint64_t start);
+
+    bool Done() override;
+    const CowOperation& Get() override;
+    void Next() override;
+
+  private:
+    std::shared_ptr<std::vector<CowOperation>> ops_;
+    std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
+    std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
+    std::vector<uint32_t>::iterator block_iter_;
+    uint64_t start_;
+};
+
+CowMergeOpIter::CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
+                               std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
+                               std::shared_ptr<std::unordered_map<uint32_t, int>> map,
+                               uint64_t start) {
     ops_ = ops;
     merge_op_blocks_ = merge_op_blocks;
     map_ = map;
+    start_ = start;
+
+    block_iter_ = merge_op_blocks->begin() + start;
+}
+
+bool CowMergeOpIter::Done() {
+    return block_iter_ == merge_op_blocks_->end();
+}
+
+void CowMergeOpIter::Next() {
+    CHECK(!Done());
+    block_iter_++;
+}
+
+const CowOperation& CowMergeOpIter::Get() {
+    CHECK(!Done());
+    return ops_->data()[map_->at(*block_iter_)];
+}
+
+CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
+                                     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
+                                     std::shared_ptr<std::unordered_map<uint32_t, int>> map,
+                                     uint64_t start) {
+    ops_ = ops;
+    merge_op_blocks_ = merge_op_blocks;
+    map_ = map;
+    start_ = start;
 
     block_riter_ = merge_op_blocks->rbegin();
 }
 
 bool CowRevMergeOpIter::Done() {
-    return block_riter_ == merge_op_blocks_->rend();
+    return block_riter_ == merge_op_blocks_->rend() - start_;
 }
 
 void CowRevMergeOpIter::Next() {
@@ -571,8 +667,14 @@
     return std::make_unique<CowOpIter>(ops_);
 }
 
-std::unique_ptr<ICowOpIter> CowReader::GetRevMergeOpIter() {
-    return std::make_unique<CowRevMergeOpIter>(ops_, merge_op_blocks_, block_map_);
+std::unique_ptr<ICowOpIter> CowReader::GetRevMergeOpIter(bool ignore_progress) {
+    return std::make_unique<CowRevMergeOpIter>(ops_, merge_op_blocks_, block_map_,
+                                               ignore_progress ? 0 : merge_op_start_);
+}
+
+std::unique_ptr<ICowOpIter> CowReader::GetMergeOpIter(bool ignore_progress) {
+    return std::make_unique<CowMergeOpIter>(ops_, merge_op_blocks_, block_map_,
+                                            ignore_progress ? 0 : merge_op_start_);
 }
 
 bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
diff --git a/fs_mgr/libsnapshot/device_info.cpp b/fs_mgr/libsnapshot/device_info.cpp
index 14ce0ee..a6d96ed 100644
--- a/fs_mgr/libsnapshot/device_info.cpp
+++ b/fs_mgr/libsnapshot/device_info.cpp
@@ -139,5 +139,9 @@
     }
 }
 
+android::dm::IDeviceMapper& DeviceInfo::GetDeviceMapper() {
+    return android::dm::DeviceMapper::Instance();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/device_info.h b/fs_mgr/libsnapshot/device_info.h
index 7999c99..8aefb85 100644
--- a/fs_mgr/libsnapshot/device_info.h
+++ b/fs_mgr/libsnapshot/device_info.h
@@ -40,6 +40,7 @@
     bool IsRecovery() const override;
     std::unique_ptr<IImageManager> OpenImageManager() const override;
     bool IsFirstStageInit() const override;
+    android::dm::IDeviceMapper& GetDeviceMapper() override;
 
     void set_first_stage_init(bool value) { first_stage_init_ = value; }
 
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 0786e82..d5b4335 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -68,6 +68,7 @@
 
     // Return the file footer.
     virtual bool GetFooter(CowFooter* footer) = 0;
+    virtual bool VerifyMergeOps() = 0;
 
     // Return the last valid label
     virtual bool GetLastLabel(uint64_t* label) = 0;
@@ -75,8 +76,11 @@
     // Return an iterator for retrieving CowOperation entries.
     virtual std::unique_ptr<ICowOpIter> GetOpIter() = 0;
 
+    // Return an iterator for retrieving CowOperation entries in reverse merge order
+    virtual std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress) = 0;
+
     // Return an iterator for retrieving CowOperation entries in merge order
-    virtual std::unique_ptr<ICowOpIter> GetRevMergeOpIter() = 0;
+    virtual std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress) = 0;
 
     // Get decoded bytes from the data section, handling any decompression.
     // All retrieved data is passed to the sink.
@@ -98,9 +102,14 @@
     virtual void Next() = 0;
 };
 
-class CowReader : public ICowReader {
+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
@@ -109,6 +118,7 @@
     bool Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label = {});
 
     bool InitForMerge(android::base::unique_fd&& fd);
+    bool VerifyMergeOps() override;
 
     bool GetHeader(CowHeader* header) override;
     bool GetFooter(CowFooter* footer) override;
@@ -120,7 +130,8 @@
     // whose lifetime depends on the CowOpIter object; the return
     // value of these will never be null.
     std::unique_ptr<ICowOpIter> GetOpIter() override;
-    std::unique_ptr<ICowOpIter> GetRevMergeOpIter() override;
+    std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress = false) override;
+    std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress = false) override;
 
     bool ReadData(const CowOperation& op, IByteSink* sink) override;
 
@@ -139,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();
@@ -152,11 +165,13 @@
     std::optional<uint64_t> last_label_;
     std::shared_ptr<std::vector<CowOperation>> ops_;
     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
+    uint64_t merge_op_start_;
     std::shared_ptr<std::unordered_map<uint32_t, int>> block_map_;
     uint64_t num_total_data_ops_;
     uint64_t num_ordered_ops_to_merge_;
     bool has_seq_ops_;
     std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+    ReaderFlags reader_flag_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index ec58cca..ba62330 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -35,6 +35,7 @@
                 (override));
     MOCK_METHOD(UpdateState, GetUpdateState, (double* progress), (override));
     MOCK_METHOD(bool, UpdateUsesCompression, (), (override));
+    MOCK_METHOD(bool, UpdateUsesUserSnapshots, (), (override));
     MOCK_METHOD(Return, CreateUpdateSnapshots,
                 (const chromeos_update_engine::DeltaArchiveManifest& manifest), (override));
     MOCK_METHOD(bool, MapUpdateSnapshot,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
index 0457986..b0be5a5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
@@ -36,12 +36,15 @@
 
     MOCK_METHOD(bool, EmitCopy, (uint64_t, uint64_t), (override));
     MOCK_METHOD(bool, EmitRawBlocks, (uint64_t, const void*, size_t), (override));
+    MOCK_METHOD(bool, EmitXorBlocks, (uint32_t, const void*, size_t, uint32_t, uint16_t),
+                (override));
     MOCK_METHOD(bool, EmitZeroBlocks, (uint64_t, uint64_t), (override));
     MOCK_METHOD(bool, EmitLabel, (uint64_t), (override));
     MOCK_METHOD(bool, EmitSequenceData, (size_t, const uint32_t*), (override));
 
     // Open the writer in write mode (no append).
     MOCK_METHOD(bool, Initialize, (), (override));
+    MOCK_METHOD(bool, VerifyMergeOps, (), (override, const, noexcept));
 
     // Open the writer in append mode, with the last label to resume
     // from. See CowWriter::InitializeAppend.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index e60da31..120f95b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -110,6 +110,7 @@
         virtual bool IsTestDevice() const { return false; }
         virtual bool IsFirstStageInit() const = 0;
         virtual std::unique_ptr<IImageManager> OpenImageManager() const = 0;
+        virtual android::dm::IDeviceMapper& GetDeviceMapper() = 0;
 
         // Helper method for implementing OpenImageManager.
         std::unique_ptr<IImageManager> OpenImageManager(const std::string& gsid_dir) const;
@@ -192,6 +193,9 @@
     // UpdateState is None, or no snapshots have been created.
     virtual bool UpdateUsesCompression() = 0;
 
+    // Returns true if userspace snapshots is enabled for the current update.
+    virtual bool UpdateUsesUserSnapshots() = 0;
+
     // Create necessary COW device / files for OTA clients. New logical partitions will be added to
     // group "cow" in target_metadata. Regions of partitions of current_metadata will be
     // "write-protected" and snapshotted.
@@ -351,6 +355,7 @@
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
     bool UpdateUsesCompression() override;
+    bool UpdateUsesUserSnapshots() override;
     Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
                            std::string* snapshot_path) override;
@@ -386,6 +391,22 @@
     // first-stage to decide whether to launch snapuserd.
     bool IsSnapuserdRequired();
 
+    enum class SnapshotDriver {
+        DM_SNAPSHOT,
+        DM_USER,
+    };
+
+    // Add new public entries above this line.
+
+    // Helpers for failure injection.
+    using MergeConsistencyChecker =
+            std::function<MergeFailureCode(const std::string& name, const SnapshotStatus& status)>;
+
+    void set_merge_consistency_checker(MergeConsistencyChecker checker) {
+        merge_consistency_checker_ = checker;
+    }
+    MergeConsistencyChecker merge_consistency_checker() const { return merge_consistency_checker_; }
+
   private:
     FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
     FRIEND_TEST(SnapshotTest, CreateSnapshot);
@@ -399,6 +420,8 @@
     FRIEND_TEST(SnapshotTest, MergeFailureCode);
     FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
     FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
+    FRIEND_TEST(SnapshotUpdateTest, AddPartition);
+    FRIEND_TEST(SnapshotUpdateTest, ConsistencyCheckResume);
     FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
@@ -406,6 +429,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;
@@ -453,6 +477,8 @@
     };
     static std::unique_ptr<LockedFile> OpenFile(const std::string& file, int lock_flags);
 
+    SnapshotDriver GetSnapshotDriver(LockedFile* lock);
+
     // Create a new snapshot record. This creates the backing COW store and
     // persists information needed to map the device. The device can be mapped
     // with MapSnapshot().
@@ -488,8 +514,8 @@
 
     // Create a dm-user device for a given snapshot.
     bool MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file,
-                      const std::string& base_device, const std::chrono::milliseconds& timeout_ms,
-                      std::string* path);
+                      const std::string& base_device, const std::string& base_path_merge,
+                      const std::chrono::milliseconds& timeout_ms, std::string* path);
 
     // Map the source device used for dm-user.
     bool MapSourceDevice(LockedFile* lock, const std::string& name,
@@ -588,7 +614,8 @@
     // Internal callback for when merging is complete.
     bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
                                  const SnapshotStatus& status);
-    bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
+    bool CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
+                                const SnapshotStatus& status);
 
     struct MergeResult {
         explicit MergeResult(UpdateState state,
@@ -610,6 +637,14 @@
     MergeFailureCode CheckMergeConsistency(LockedFile* lock, const std::string& name,
                                            const SnapshotStatus& update_status);
 
+    // Get status or table information about a device-mapper node with a single target.
+    enum class TableQuery {
+        Table,
+        Status,
+    };
+    bool GetSingleTarget(const std::string& dm_name, TableQuery query,
+                         android::dm::DeviceMapper::TargetInfo* target);
+
     // Interact with status files under /metadata/ota/snapshots.
     bool WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status);
     bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status);
@@ -678,7 +713,10 @@
     bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
 
     // Unmap a dm-user device through snapuserd.
-    bool UnmapDmUserDevice(const std::string& snapshot_name);
+    bool UnmapDmUserDevice(const std::string& dm_user_name);
+
+    // Unmap a dm-user device for user space snapshots
+    bool UnmapUserspaceSnapshotDevice(LockedFile* lock, const std::string& snapshot_name);
 
     // If there isn't a previous update, return true. |needs_merge| is set to false.
     // If there is a previous update but the device has not boot into it, tries to cancel the
@@ -767,20 +805,25 @@
 
     // Helper of UpdateUsesCompression
     bool UpdateUsesCompression(LockedFile* lock);
+    // Locked and unlocked functions to test whether the current update uses
+    // userspace snapshots.
+    bool UpdateUsesUserSnapshots(LockedFile* lock);
 
     // Wrapper around libdm, with diagnostics.
     bool DeleteDeviceIfExists(const std::string& name,
                               const std::chrono::milliseconds& timeout_ms = {});
 
-    std::string gsid_dir_;
-    std::string metadata_dir_;
+    android::dm::IDeviceMapper& dm_;
     std::unique_ptr<IDeviceInfo> device_;
+    std::string metadata_dir_;
     std::unique_ptr<IImageManager> images_;
     bool use_first_stage_snapuserd_ = false;
     bool in_factory_data_reset_ = false;
     std::function<bool(const std::string&)> uevent_regen_callback_;
     std::unique_ptr<SnapuserdClient> snapuserd_client_;
     std::unique_ptr<LpMetadata> old_partition_metadata_;
+    std::optional<bool> is_snapshot_userspace_;
+    MergeConsistencyChecker merge_consistency_checker_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index 74b78c5..318e525 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -35,6 +35,7 @@
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
     bool UpdateUsesCompression() override;
+    bool UpdateUsesUserSnapshots() override;
     Return CreateUpdateSnapshots(
             const chromeos_update_engine::DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
index b09e1ae..545f117 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
@@ -47,6 +47,7 @@
     virtual bool InitializeAppend(uint64_t label) = 0;
 
     virtual std::unique_ptr<FileDescriptor> OpenReader() = 0;
+    virtual bool VerifyMergeOps() const noexcept = 0;
 
   protected:
     android::base::borrowed_fd GetSourceFd();
@@ -58,7 +59,7 @@
 };
 
 // Send writes to a COW or a raw device directly, based on a threshold.
-class CompressedSnapshotWriter : public ISnapshotWriter {
+class CompressedSnapshotWriter final : public ISnapshotWriter {
   public:
     CompressedSnapshotWriter(const CowOptions& options);
 
@@ -70,6 +71,7 @@
     bool Finalize() override;
     uint64_t GetCowSize() override;
     std::unique_ptr<FileDescriptor> OpenReader() override;
+    bool VerifyMergeOps() const noexcept;
 
   protected:
     bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
@@ -81,13 +83,14 @@
     bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
 
   private:
+    std::unique_ptr<CowReader> OpenCowReader() const;
     android::base::unique_fd cow_device_;
 
     std::unique_ptr<CowWriter> cow_;
 };
 
 // Write directly to a dm-snapshot device.
-class OnlineKernelSnapshotWriter : public ISnapshotWriter {
+class OnlineKernelSnapshotWriter final : public ISnapshotWriter {
   public:
     OnlineKernelSnapshotWriter(const CowOptions& options);
 
@@ -101,6 +104,10 @@
     uint64_t GetCowSize() override { return cow_size_; }
     std::unique_ptr<FileDescriptor> OpenReader() override;
 
+    // Online kernel snapshot writer doesn't care about merge sequences.
+    // So ignore.
+    bool VerifyMergeOps() const noexcept override { return true; }
+
   protected:
     bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
     bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 4e7ccf1..c3b40dc 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -99,6 +99,12 @@
     std::unique_ptr<IImageManager> OpenImageManager() const override {
         return IDeviceInfo::OpenImageManager("ota/test");
     }
+    android::dm::IDeviceMapper& GetDeviceMapper() override {
+        if (dm_) {
+            return *dm_;
+        }
+        return android::dm::DeviceMapper::Instance();
+    }
 
     bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; }
 
@@ -108,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:
@@ -117,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/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index ed86c87..548ba00 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -16,8 +16,10 @@
 #include <stdio.h>
 #include <unistd.h>
 
+#include <iomanip>
 #include <iostream>
 #include <string>
+#include <vector>
 
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
@@ -39,17 +41,26 @@
     LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
     LOG(ERROR) << "\t -s Run Silent";
     LOG(ERROR) << "\t -d Attempt to decompress";
-    LOG(ERROR) << "\t -b Show data for failed decompress\n";
-    LOG(ERROR) << "\t -m Show ops in reverse merge order\n";
+    LOG(ERROR) << "\t -b Show data for failed decompress";
+    LOG(ERROR) << "\t -l Show ops";
+    LOG(ERROR) << "\t -m Show ops in reverse merge order";
+    LOG(ERROR) << "\t -n Show ops in merge order";
+    LOG(ERROR) << "\t -a Include merged ops in any merge order listing";
+    LOG(ERROR) << "\t -o Shows sequence op block order";
+    LOG(ERROR) << "\t -v Verifies merge order has no conflicts\n";
 }
 
-enum OpIter { Normal, RevMerge };
+enum OpIter { Normal, RevMerge, Merge };
 
 struct Options {
     bool silent;
     bool decompress;
+    bool show_ops;
     bool show_bad;
+    bool show_seq;
+    bool verify_sequence;
     OpIter iter_type;
+    bool include_merged;
 };
 
 // Sink that always appends to the end of a string.
@@ -126,18 +137,28 @@
         }
     }
 
+    if (opt.verify_sequence) {
+        if (reader.VerifyMergeOps()) {
+            std::cout << "\nMerge sequence is consistent.\n";
+        } else {
+            std::cout << "\nMerge sequence is inconsistent!\n";
+        }
+    }
+
     std::unique_ptr<ICowOpIter> iter;
     if (opt.iter_type == Normal) {
         iter = reader.GetOpIter();
     } else if (opt.iter_type == RevMerge) {
-        iter = reader.GetRevMergeOpIter();
+        iter = reader.GetRevMergeOpIter(opt.include_merged);
+    } else if (opt.iter_type == Merge) {
+        iter = reader.GetMergeOpIter(opt.include_merged);
     }
     StringSink sink;
     bool success = true;
     while (!iter->Done()) {
         const CowOperation& op = iter->Get();
 
-        if (!opt.silent) std::cout << op << "\n";
+        if (!opt.silent && opt.show_ops) std::cout << op << "\n";
 
         if (opt.decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
             if (!reader.ReadData(op, &sink)) {
@@ -148,6 +169,24 @@
             sink.Reset();
         }
 
+        if (op.type == kCowSequenceOp && opt.show_seq) {
+            size_t read;
+            std::vector<uint32_t> merge_op_blocks;
+            size_t seq_len = op.data_length / sizeof(uint32_t);
+            merge_op_blocks.resize(seq_len);
+            if (!reader.GetRawBytes(op.source, merge_op_blocks.data(), op.data_length, &read)) {
+                PLOG(ERROR) << "Failed to read sequence op!";
+                return false;
+            }
+            if (!opt.silent) {
+                std::cout << "Sequence for " << op << " is :\n";
+                for (size_t i = 0; i < seq_len; i++) {
+                    std::cout << std::setfill('0') << std::setw(6) << merge_op_blocks[i] << ", ";
+                    if ((i + 1) % 10 == 0 || i + 1 == seq_len) std::cout << "\n";
+                }
+            }
+        }
+
         iter->Next();
     }
 
@@ -164,7 +203,9 @@
     opt.decompress = false;
     opt.show_bad = false;
     opt.iter_type = android::snapshot::Normal;
-    while ((ch = getopt(argc, argv, "sdbm")) != -1) {
+    opt.verify_sequence = false;
+    opt.include_merged = false;
+    while ((ch = getopt(argc, argv, "sdbmnolva")) != -1) {
         switch (ch) {
             case 's':
                 opt.silent = true;
@@ -178,6 +219,21 @@
             case 'm':
                 opt.iter_type = android::snapshot::RevMerge;
                 break;
+            case 'n':
+                opt.iter_type = android::snapshot::Merge;
+                break;
+            case 'o':
+                opt.show_seq = true;
+                break;
+            case 'l':
+                opt.show_ops = true;
+                break;
+            case 'v':
+                opt.verify_sequence = true;
+                break;
+            case 'a':
+                opt.include_merged = true;
+                break;
             default:
                 android::snapshot::usage();
         }
diff --git a/fs_mgr/libsnapshot/run_snapshot_tests.sh b/fs_mgr/libsnapshot/run_snapshot_tests.sh
new file mode 100644
index 0000000..b03a4e0
--- /dev/null
+++ b/fs_mgr/libsnapshot/run_snapshot_tests.sh
@@ -0,0 +1,35 @@
+#!/system/bin/sh
+
+# Detect host or AOSP.
+getprop ro.build.version.sdk > /dev/null 2>&1
+if [ $? -eq 0 ]; then
+    cmd_prefix=""
+    local_root=""
+else
+    cmd_prefix="adb shell"
+    local_root="${ANDROID_PRODUCT_OUT}"
+    set -e
+    set -x
+    adb root
+    adb sync data
+    set +x
+    set +e
+fi
+
+testpath64="/data/nativetest64/vts_libsnapshot_test/vts_libsnapshot_test"
+testpath32="/data/nativetest/vts_libsnapshot_test/vts_libsnapshot_test"
+if [ -f "${local_root}/${testpath64}" ]; then
+    testpath="${testpath64}"
+elif [ -f "${local_root}/${testpath32}" ]; then
+    testpath="${testpath32}"
+else
+    echo "ERROR: vts_libsnapshot_test not found." 1>&2
+    echo "Make sure to build vts_libsnapshot_test or snapshot_tests first." 1>&2
+    exit 1
+fi
+
+# Verbose, error on failure.
+set -x
+set -e
+
+time ${cmd_prefix} ${testpath}
diff --git a/fs_mgr/libsnapshot/scripts/Android.bp b/fs_mgr/libsnapshot/scripts/Android.bp
index 3e09cc3..829f5bc 100644
--- a/fs_mgr/libsnapshot/scripts/Android.bp
+++ b/fs_mgr/libsnapshot/scripts/Android.bp
@@ -14,6 +14,11 @@
 // limitations under the License.
 //
 
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 python_binary_host {
     name: "dump_snapshot_proto",
     main: "dump_snapshot_proto.py",
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index c3db32e..18a9d22 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -87,6 +87,8 @@
 static constexpr char kRollbackIndicatorPath[] = "/metadata/ota/rollback-indicator";
 static constexpr auto kUpdateStateCheckInterval = 2s;
 
+MergeFailureCode CheckMergeConsistency(const std::string& name, const SnapshotStatus& status);
+
 // Note: IImageManager is an incomplete type in the header, so the default
 // destructor doesn't work.
 SnapshotManager::~SnapshotManager() {}
@@ -95,6 +97,7 @@
     if (!info) {
         info = new DeviceInfo();
     }
+
     return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
 }
 
@@ -114,16 +117,43 @@
     return sm;
 }
 
-SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
-    metadata_dir_ = device_->GetMetadataDir();
+SnapshotManager::SnapshotManager(IDeviceInfo* device)
+    : dm_(device->GetDeviceMapper()), device_(device), metadata_dir_(device_->GetMetadataDir()) {
+    merge_consistency_checker_ = android::snapshot::CheckMergeConsistency;
 }
 
 static std::string GetCowName(const std::string& snapshot_name) {
     return snapshot_name + "-cow";
 }
 
-static std::string GetDmUserCowName(const std::string& snapshot_name) {
-    return snapshot_name + "-user-cow";
+SnapshotManager::SnapshotDriver SnapshotManager::GetSnapshotDriver(LockedFile* lock) {
+    if (UpdateUsesUserSnapshots(lock)) {
+        return SnapshotManager::SnapshotDriver::DM_USER;
+    } else {
+        return SnapshotManager::SnapshotDriver::DM_SNAPSHOT;
+    }
+}
+
+static std::string GetDmUserCowName(const std::string& snapshot_name,
+                                    SnapshotManager::SnapshotDriver driver) {
+    // dm-user block device will act as a snapshot device. We identify it with
+    // the same partition name so that when partitions can be mounted off
+    // dm-user.
+
+    switch (driver) {
+        case SnapshotManager::SnapshotDriver::DM_USER: {
+            return snapshot_name;
+        }
+
+        case SnapshotManager::SnapshotDriver::DM_SNAPSHOT: {
+            return snapshot_name + "-user-cow";
+        }
+
+        default: {
+            LOG(ERROR) << "Invalid snapshot driver";
+            return "";
+        }
+    }
 }
 
 static std::string GetCowImageDeviceName(const std::string& snapshot_name) {
@@ -399,10 +429,32 @@
 
 bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
                                    const std::string& cow_file, const std::string& base_device,
+                                   const std::string& base_path_merge,
                                    const std::chrono::milliseconds& timeout_ms, std::string* path) {
     CHECK(lock);
 
-    auto& dm = DeviceMapper::Instance();
+    if (UpdateUsesUserSnapshots(lock)) {
+        SnapshotStatus status;
+        if (!ReadSnapshotStatus(lock, name, &status)) {
+            LOG(ERROR) << "MapDmUserCow: ReadSnapshotStatus failed...";
+            return false;
+        }
+
+        if (status.state() == SnapshotState::NONE ||
+            status.state() == SnapshotState::MERGE_COMPLETED) {
+            LOG(ERROR) << "Should not create a snapshot device for " << name
+                       << " after merging has completed.";
+            return false;
+        }
+
+        SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+        if (update_status.state() == UpdateState::MergeCompleted ||
+            update_status.state() == UpdateState::MergeNeedsReboot) {
+            LOG(ERROR) << "Should not create a snapshot device for " << name
+                       << " after global merging has completed.";
+            return false;
+        }
+    }
 
     // Use an extra decoration for first-stage init, so we can transition
     // to a new table entry in second-stage.
@@ -415,18 +467,41 @@
         return false;
     }
 
-    uint64_t base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
-    if (base_sectors == 0) {
-        LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
-        return false;
+    uint64_t base_sectors = 0;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
+        if (base_sectors == 0) {
+            LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
+            return false;
+        }
+    } else {
+        // For userspace snapshots, the size of the base device is taken as the
+        // size of the dm-user block device. Since there is no pseudo mapping
+        // created in the daemon, we no longer need to rely on the daemon for
+        // sizing the dm-user block device.
+        unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge.c_str(), O_RDONLY | O_CLOEXEC)));
+        if (fd < 0) {
+            LOG(ERROR) << "Cannot open block device: " << base_path_merge;
+            return false;
+        }
+
+        uint64_t dev_sz = get_block_device_size(fd.get());
+        if (!dev_sz) {
+            LOG(ERROR) << "Failed to find block device size: " << base_path_merge;
+            return false;
+        }
+
+        base_sectors = dev_sz >> 9;
     }
 
     DmTable table;
     table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
-    if (!dm.CreateDevice(name, table, path, timeout_ms)) {
+    if (!dm_.CreateDevice(name, table, path, timeout_ms)) {
+        LOG(ERROR) << " dm-user: CreateDevice failed... ";
         return false;
     }
     if (!WaitForDevice(*path, timeout_ms)) {
+        LOG(ERROR) << " dm-user: timeout: Failed to create block device for: " << name;
         return false;
     }
 
@@ -435,6 +510,15 @@
         return false;
     }
 
+    if (UpdateUsesUserSnapshots(lock)) {
+        // Now that the dm-user device is created, initialize the daemon and
+        // spin up the worker threads.
+        if (!snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device, base_path_merge)) {
+            LOG(ERROR) << "InitDmUserCow failed";
+            return false;
+        }
+    }
+
     return snapuserd_client_->AttachDmUser(misc_name);
 }
 
@@ -490,8 +574,6 @@
 
     uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
 
-    auto& dm = DeviceMapper::Instance();
-
     // Note that merging is a global state. We do track whether individual devices
     // have completed merging, but the start of the merge process is considered
     // atomic.
@@ -518,10 +600,17 @@
             break;
     }
 
+    if (mode == SnapshotStorageMode::Persistent && status.state() == SnapshotState::MERGING) {
+        LOG(ERROR) << "Snapshot: " << name
+                   << " has snapshot status Merging but mode set to Persistent."
+                   << " Changing mode to Snapshot-Merge.";
+        mode = SnapshotStorageMode::Merge;
+    }
+
     DmTable table;
     table.Emplace<DmTargetSnapshot>(0, snapshot_sectors, base_device, cow_device, mode,
                                     kSnapshotChunkSize);
-    if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) {
+    if (!dm_.CreateDevice(name, table, dev_path, timeout_ms)) {
         LOG(ERROR) << "Could not create snapshot device: " << name;
         return false;
     }
@@ -583,9 +672,15 @@
 bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
     CHECK(lock);
 
-    if (!DeleteDeviceIfExists(name)) {
-        LOG(ERROR) << "Could not delete snapshot device: " << name;
-        return false;
+    if (UpdateUsesUserSnapshots(lock)) {
+        if (!UnmapUserspaceSnapshotDevice(lock, name)) {
+            return false;
+        }
+    } else {
+        if (!DeleteDeviceIfExists(name)) {
+            LOG(ERROR) << "Could not delete snapshot device: " << name;
+            return false;
+        }
     }
     return true;
 }
@@ -652,7 +747,6 @@
 
     auto other_suffix = device_->GetOtherSlotSuffix();
 
-    auto& dm = DeviceMapper::Instance();
     for (const auto& snapshot : snapshots) {
         if (android::base::EndsWith(snapshot, other_suffix)) {
             // Allow the merge to continue, but log this unexpected case.
@@ -664,7 +758,7 @@
         // the same time. This is a fairly serious error. We could forcefully
         // map everything here, but it should have been mapped during first-
         // stage init.
-        if (dm.GetState(snapshot) == DmDeviceState::INVALID) {
+        if (dm_.GetState(snapshot) == DmDeviceState::INVALID) {
             LOG(ERROR) << "Cannot begin merge; device " << snapshot << " is not mapped.";
             return false;
         }
@@ -697,13 +791,15 @@
 
     DmTargetSnapshot::Status initial_target_values = {};
     for (const auto& snapshot : snapshots) {
-        DmTargetSnapshot::Status current_status;
-        if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
-            return false;
+        if (!UpdateUsesUserSnapshots(lock.get())) {
+            DmTargetSnapshot::Status current_status;
+            if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
+                return false;
+            }
+            initial_target_values.sectors_allocated += current_status.sectors_allocated;
+            initial_target_values.total_sectors += current_status.total_sectors;
+            initial_target_values.metadata_sectors += current_status.metadata_sectors;
         }
-        initial_target_values.sectors_allocated += current_status.sectors_allocated;
-        initial_target_values.total_sectors += current_status.total_sectors;
-        initial_target_values.metadata_sectors += current_status.metadata_sectors;
 
         SnapshotStatus snapshot_status;
         if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
@@ -718,11 +814,14 @@
 
     SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
     initial_status.set_state(UpdateState::Merging);
-    initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
-    initial_status.set_total_sectors(initial_target_values.total_sectors);
-    initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
     initial_status.set_compression_enabled(compression_enabled);
 
+    if (!UpdateUsesUserSnapshots(lock.get())) {
+        initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
+        initial_status.set_total_sectors(initial_target_values.total_sectors);
+        initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
+    }
+
     // If any partitions shrunk, we need to merge them before we merge any other
     // partitions (see b/177935716). Otherwise, a merge from another partition
     // may overwrite the source block of a copy operation.
@@ -776,20 +875,36 @@
                      << " has unexpected state: " << SnapshotState_Name(status.state());
     }
 
-    // After this, we return true because we technically did switch to a merge
-    // target. Everything else we do here is just informational.
-    if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
-        return code;
+    if (UpdateUsesUserSnapshots(lock)) {
+        if (EnsureSnapuserdConnected()) {
+            // This is the point where we inform the daemon to initiate/resume
+            // the merge
+            if (!snapuserd_client_->InitiateMerge(name)) {
+                return MergeFailureCode::UnknownTable;
+            }
+        } else {
+            LOG(ERROR) << "Failed to connect to snapuserd daemon to initiate merge";
+            return MergeFailureCode::UnknownTable;
+        }
+    } else {
+        // After this, we return true because we technically did switch to a merge
+        // target. Everything else we do here is just informational.
+        if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
+            return code;
+        }
     }
 
     status.set_state(SnapshotState::MERGING);
 
-    DmTargetSnapshot::Status dm_status;
-    if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
-        LOG(ERROR) << "Could not query merge status for snapshot: " << name;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        DmTargetSnapshot::Status dm_status;
+        if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
+            LOG(ERROR) << "Could not query merge status for snapshot: " << name;
+        }
+        status.set_sectors_allocated(dm_status.sectors_allocated);
+        status.set_metadata_sectors(dm_status.metadata_sectors);
     }
-    status.set_sectors_allocated(dm_status.sectors_allocated);
-    status.set_metadata_sectors(dm_status.metadata_sectors);
+
     if (!WriteSnapshotStatus(lock, status)) {
         LOG(ERROR) << "Could not update status file for snapshot: " << name;
     }
@@ -797,10 +912,8 @@
 }
 
 MergeFailureCode SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
-    auto& dm = DeviceMapper::Instance();
-
     std::vector<DeviceMapper::TargetInfo> old_targets;
-    if (!dm.GetTableInfo(name, &old_targets)) {
+    if (!dm_.GetTableInfo(name, &old_targets)) {
         LOG(ERROR) << "Could not read snapshot device table: " << name;
         return MergeFailureCode::GetTableInfo;
     }
@@ -818,7 +931,7 @@
     DmTable table;
     table.Emplace<DmTargetSnapshot>(0, old_targets[0].spec.length, base_device, cow_device,
                                     SnapshotStorageMode::Merge, kSnapshotChunkSize);
-    if (!dm.LoadTableAndActivate(name, table)) {
+    if (!dm_.LoadTableAndActivate(name, table)) {
         LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << name;
         return MergeFailureCode::ActivateNewTable;
     }
@@ -826,24 +939,18 @@
     return MergeFailureCode::Ok;
 }
 
-enum class TableQuery {
-    Table,
-    Status,
-};
-
-static bool GetSingleTarget(const std::string& dm_name, TableQuery query,
-                            DeviceMapper::TargetInfo* target) {
-    auto& dm = DeviceMapper::Instance();
-    if (dm.GetState(dm_name) == DmDeviceState::INVALID) {
+bool SnapshotManager::GetSingleTarget(const std::string& dm_name, TableQuery query,
+                                      DeviceMapper::TargetInfo* target) {
+    if (dm_.GetState(dm_name) == DmDeviceState::INVALID) {
         return false;
     }
 
     std::vector<DeviceMapper::TargetInfo> targets;
     bool result;
     if (query == TableQuery::Status) {
-        result = dm.GetTableStatus(dm_name, &targets);
+        result = dm_.GetTableStatus(dm_name, &targets);
     } else {
-        result = dm.GetTableInfo(dm_name, &targets);
+        result = dm_.GetTableInfo(dm_name, &targets);
     }
     if (!result) {
         LOG(ERROR) << "Could not query device: " << dm_name;
@@ -863,9 +970,15 @@
         return false;
     }
     auto type = DeviceMapper::GetTargetType(snap_target.spec);
-    if (type != "snapshot" && type != "snapshot-merge") {
-        return false;
+
+    // If this is not a user-snapshot device then it should either
+    // be a dm-snapshot or dm-snapshot-merge target
+    if (type != "user") {
+        if (type != "snapshot" && type != "snapshot-merge") {
+            return false;
+        }
     }
+
     if (target) {
         *target = std::move(snap_target);
     }
@@ -886,6 +999,10 @@
     if (target_type) {
         *target_type = DeviceMapper::GetTargetType(target.spec);
     }
+    if (!status->error.empty()) {
+        LOG(ERROR) << "Snapshot: " << dm_name << " returned error code: " << status->error;
+        return false;
+    }
     return true;
 }
 
@@ -1097,34 +1214,86 @@
     DCHECK((current_metadata = ReadCurrentMetadata()) &&
            GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated);
 
-    std::string target_type;
-    DmTargetSnapshot::Status status;
-    if (!QuerySnapshotStatus(name, &target_type, &status)) {
-        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
-    }
-    if (target_type == "snapshot" &&
-        DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
-        update_status.merge_phase() == MergePhase::FIRST_PHASE) {
-        // The snapshot is not being merged because it's in the wrong phase.
-        return MergeResult(UpdateState::None);
-    }
-    if (target_type != "snapshot-merge") {
-        // We can get here if we failed to rewrite the target type in
-        // InitiateMerge(). If we failed to create the target in first-stage
-        // init, boot would not succeed.
-        LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
-        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+    if (UpdateUsesUserSnapshots(lock)) {
+        std::string merge_status;
+        if (EnsureSnapuserdConnected()) {
+            // Query the snapshot status from the daemon
+            merge_status = snapuserd_client_->QuerySnapshotStatus(name);
+        } else {
+            MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
+        }
+
+        if (merge_status == "snapshot-merge-failed") {
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
+        }
+
+        // This is the case when device reboots during merge. Once the device boots,
+        // snapuserd daemon will not resume merge immediately in first stage init.
+        // This is slightly different as compared to dm-snapshot-merge; In this
+        // case, metadata file will have "MERGING" state whereas the daemon will be
+        // waiting to resume the merge. Thus, we resume the merge at this point.
+        if (merge_status == "snapshot" && snapshot_status.state() == SnapshotState::MERGING) {
+            if (!snapuserd_client_->InitiateMerge(name)) {
+                return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
+
+        if (merge_status == "snapshot" &&
+            DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
+            update_status.merge_phase() == MergePhase::FIRST_PHASE) {
+            // The snapshot is not being merged because it's in the wrong phase.
+            return MergeResult(UpdateState::None);
+        }
+
+        if (merge_status == "snapshot-merge") {
+            if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
+                LOG(ERROR) << "Snapshot " << name
+                           << " is merging after being marked merge-complete.";
+                return MergeResult(UpdateState::MergeFailed,
+                                   MergeFailureCode::UnmergedSectorsAfterCompletion);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
+
+        if (merge_status != "snapshot-merge-complete") {
+            LOG(ERROR) << "Snapshot " << name << " has incorrect status: " << merge_status;
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+        }
+    } else {
+        // dm-snapshot in the kernel
+        std::string target_type;
+        DmTargetSnapshot::Status status;
+        if (!QuerySnapshotStatus(name, &target_type, &status)) {
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
+        }
+        if (target_type == "snapshot" &&
+            DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
+            update_status.merge_phase() == MergePhase::FIRST_PHASE) {
+            // The snapshot is not being merged because it's in the wrong phase.
+            return MergeResult(UpdateState::None);
+        }
+        if (target_type != "snapshot-merge") {
+            // We can get here if we failed to rewrite the target type in
+            // InitiateMerge(). If we failed to create the target in first-stage
+            // init, boot would not succeed.
+            LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+        }
+
+        // These two values are equal when merging is complete.
+        if (status.sectors_allocated != status.metadata_sectors) {
+            if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
+                LOG(ERROR) << "Snapshot " << name
+                           << " is merging after being marked merge-complete.";
+                return MergeResult(UpdateState::MergeFailed,
+                                   MergeFailureCode::UnmergedSectorsAfterCompletion);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
     }
 
-    // These two values are equal when merging is complete.
-    if (status.sectors_allocated != status.metadata_sectors) {
-        if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
-            LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
-            return MergeResult(UpdateState::MergeFailed,
-                               MergeFailureCode::UnmergedSectorsAfterCompletion);
-        }
-        return MergeResult(UpdateState::Merging);
-    }
+    // Merge is complete at this point
 
     auto code = CheckMergeConsistency(lock, name, snapshot_status);
     if (code != MergeFailureCode::Ok) {
@@ -1164,6 +1333,10 @@
                                                         const SnapshotStatus& status) {
     CHECK(lock);
 
+    return merge_consistency_checker_(name, status);
+}
+
+MergeFailureCode CheckMergeConsistency(const std::string& name, const SnapshotStatus& status) {
     if (!status.compression_enabled()) {
         // Do not try to verify old-style COWs yet.
         return MergeFailureCode::Ok;
@@ -1237,9 +1410,11 @@
     }
 
     SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
-    CHECK(update_status.state() == UpdateState::Merging);
+    CHECK(update_status.state() == UpdateState::Merging ||
+          update_status.state() == UpdateState::MergeFailed);
     CHECK(update_status.merge_phase() == MergePhase::FIRST_PHASE);
 
+    update_status.set_state(UpdateState::Merging);
     update_status.set_merge_phase(MergePhase::SECOND_PHASE);
     if (!WriteSnapshotUpdateStatus(lock, update_status)) {
         return MergeFailureCode::WriteStatus;
@@ -1316,30 +1491,40 @@
 
 bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
                                               const SnapshotStatus& status) {
-    if (IsSnapshotDevice(name)) {
-        // We are extra-cautious here, to avoid deleting the wrong table.
-        std::string target_type;
-        DmTargetSnapshot::Status dm_status;
-        if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
-            return false;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        if (IsSnapshotDevice(name)) {
+            // We are extra-cautious here, to avoid deleting the wrong table.
+            std::string target_type;
+            DmTargetSnapshot::Status dm_status;
+            if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
+                return false;
+            }
+            if (target_type != "snapshot-merge") {
+                LOG(ERROR) << "Unexpected target type " << target_type
+                           << " for snapshot device: " << name;
+                return false;
+            }
+            if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
+                LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
+                return false;
+            }
+            if (!CollapseSnapshotDevice(lock, name, status)) {
+                LOG(ERROR) << "Unable to collapse snapshot: " << name;
+                return false;
+            }
         }
-        if (target_type != "snapshot-merge") {
-            LOG(ERROR) << "Unexpected target type " << target_type
-                       << " for snapshot device: " << name;
-            return false;
-        }
-        if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
-            LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
-            return false;
-        }
-        if (!CollapseSnapshotDevice(name, status)) {
+    } else {
+        // Just collapse the device - no need to query again as we just did
+        // prior to calling this function
+        if (!CollapseSnapshotDevice(lock, name, status)) {
             LOG(ERROR) << "Unable to collapse snapshot: " << name;
             return false;
         }
-        // Note that collapsing is implicitly an Unmap, so we don't need to
-        // unmap the snapshot.
     }
 
+    // Note that collapsing is implicitly an Unmap, so we don't need to
+    // unmap the snapshot.
+
     if (!DeleteSnapshot(lock, name)) {
         LOG(ERROR) << "Could not delete snapshot: " << name;
         return false;
@@ -1347,25 +1532,26 @@
     return true;
 }
 
-bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
+bool SnapshotManager::CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
                                              const SnapshotStatus& status) {
-    auto& dm = DeviceMapper::Instance();
+    if (!UpdateUsesUserSnapshots(lock)) {
+        // Verify we have a snapshot-merge device.
+        DeviceMapper::TargetInfo target;
+        if (!GetSingleTarget(name, TableQuery::Table, &target)) {
+            return false;
+        }
+        if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
+            // This should be impossible, it was checked earlier.
+            LOG(ERROR) << "Snapshot device has invalid target type: " << name;
+            return false;
+        }
 
-    // Verify we have a snapshot-merge device.
-    DeviceMapper::TargetInfo target;
-    if (!GetSingleTarget(name, TableQuery::Table, &target)) {
-        return false;
-    }
-    if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
-        // This should be impossible, it was checked earlier.
-        LOG(ERROR) << "Snapshot device has invalid target type: " << name;
-        return false;
-    }
-
-    std::string base_device, cow_device;
-    if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
-        LOG(ERROR) << "Could not parse snapshot device " << name << " parameters: " << target.data;
-        return false;
+        std::string base_device, cow_device;
+        if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
+            LOG(ERROR) << "Could not parse snapshot device " << name
+                       << " parameters: " << target.data;
+            return false;
+        }
     }
 
     uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
@@ -1389,18 +1575,36 @@
         return false;
     }
 
-    if (!dm.LoadTableAndActivate(name, table)) {
+    if (!dm_.LoadTableAndActivate(name, table)) {
         return false;
     }
 
-    // Attempt to delete the snapshot device if one still exists. Nothing
-    // should be depending on the device, and device-mapper should have
-    // flushed remaining I/O. We could in theory replace with dm-zero (or
-    // re-use the table above), but for now it's better to know why this
-    // would fail.
-    if (status.compression_enabled()) {
-        UnmapDmUserDevice(name);
+    if (!UpdateUsesUserSnapshots(lock)) {
+        // Attempt to delete the snapshot device if one still exists. Nothing
+        // should be depending on the device, and device-mapper should have
+        // flushed remaining I/O. We could in theory replace with dm-zero (or
+        // re-use the table above), but for now it's better to know why this
+        // would fail.
+        //
+        // Furthermore, we should not be trying to unmap for userspace snapshot
+        // as unmap will fail since dm-user itself was a snapshot device prior
+        // to switching of tables. Unmap will fail as the device will be mounted
+        // by system partitions
+        if (status.compression_enabled()) {
+            auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
+            UnmapDmUserDevice(dm_user_name);
+        }
     }
+
+    // We can't delete base device immediately as daemon holds a reference.
+    // Make sure we wait for all the worker threads to terminate and release
+    // the reference
+    if (UpdateUsesUserSnapshots(lock) && EnsureSnapuserdConnected()) {
+        if (!snapuserd_client_->WaitForDeviceDelete(name)) {
+            LOG(ERROR) << "Failed to wait for " << name << " control device to delete";
+        }
+    }
+
     auto base_name = GetBaseDeviceName(name);
     if (!DeleteDeviceIfExists(base_name)) {
         LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
@@ -1452,7 +1656,7 @@
                                             std::vector<std::string>* snapuserd_argv) {
     LOG(INFO) << "Performing transition for snapuserd.";
 
-    // Don't use EnsuerSnapuserdConnected() because this is called from init,
+    // Don't use EnsureSnapuserdConnected() because this is called from init,
     // and attempting to do so will deadlock.
     if (!snapuserd_client_ && transition != InitTransition::SELINUX_DETACH) {
         snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
@@ -1462,8 +1666,6 @@
         }
     }
 
-    auto& dm = DeviceMapper::Instance();
-
     auto lock = LockExclusive();
     if (!lock) return false;
 
@@ -1473,11 +1675,16 @@
         return false;
     }
 
+    if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) {
+        snapuserd_argv->emplace_back("-user_snapshot");
+    }
+
     size_t num_cows = 0;
     size_t ok_cows = 0;
     for (const auto& snapshot : snapshots) {
-        std::string user_cow_name = GetDmUserCowName(snapshot);
-        if (dm.GetState(user_cow_name) == DmDeviceState::INVALID) {
+        std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get()));
+
+        if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) {
             continue;
         }
 
@@ -1504,13 +1711,26 @@
 
         DmTable table;
         table.Emplace<DmTargetUser>(0, target.spec.length, misc_name);
-        if (!dm.LoadTableAndActivate(user_cow_name, table)) {
+        if (!dm_.LoadTableAndActivate(user_cow_name, table)) {
             LOG(ERROR) << "Unable to swap tables for " << misc_name;
             continue;
         }
 
+        std::string source_device_name;
+        if (snapshot_status.old_partition_size() > 0) {
+            source_device_name = GetSourceDeviceName(snapshot);
+        } else {
+            source_device_name = GetBaseDeviceName(snapshot);
+        }
+
         std::string source_device;
-        if (!dm.GetDmDevicePathByName(GetSourceDeviceName(snapshot), &source_device)) {
+        if (!dm_.GetDmDevicePathByName(source_device_name, &source_device)) {
+            LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
+            continue;
+        }
+
+        std::string base_path_merge;
+        if (!dm_.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &base_path_merge)) {
             LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
             continue;
         }
@@ -1518,7 +1738,7 @@
         std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status);
 
         std::string cow_image_device;
-        if (!dm.GetDmDevicePathByName(cow_image_name, &cow_image_device)) {
+        if (!dm_.GetDmDevicePathByName(cow_image_name, &cow_image_device)) {
             LOG(ERROR) << "Could not get device path for " << cow_image_name;
             continue;
         }
@@ -1531,8 +1751,14 @@
         }
 
         if (transition == InitTransition::SELINUX_DETACH) {
-            auto message = misc_name + "," + cow_image_device + "," + source_device;
-            snapuserd_argv->emplace_back(std::move(message));
+            if (!UpdateUsesUserSnapshots(lock.get())) {
+                auto message = misc_name + "," + cow_image_device + "," + source_device;
+                snapuserd_argv->emplace_back(std::move(message));
+            } else {
+                auto message = misc_name + "," + cow_image_device + "," + source_device + "," +
+                               base_path_merge;
+                snapuserd_argv->emplace_back(std::move(message));
+            }
 
             // Do not attempt to connect to the new snapuserd yet, it hasn't
             // been started. We do however want to wait for the misc device
@@ -1541,8 +1767,15 @@
             continue;
         }
 
-        uint64_t base_sectors =
-                snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
+        uint64_t base_sectors;
+        if (!UpdateUsesUserSnapshots(lock.get())) {
+            base_sectors =
+                    snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
+        } else {
+            base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device,
+                                                            source_device, base_path_merge);
+        }
+
         if (base_sectors == 0) {
             // Unrecoverable as metadata reads from cow device failed
             LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
@@ -1693,8 +1926,7 @@
             // snapshot, but it's on the wrong slot. We can't unmap an active
             // partition. If this is not really a snapshot, skip the unmap
             // step.
-            auto& dm = DeviceMapper::Instance();
-            if (dm.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) {
+            if (dm_.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) {
                 LOG(ERROR) << "Detected snapshot " << name << " on " << current_slot << " slot"
                            << " for source partition; removing without unmap.";
                 should_unmap = false;
@@ -1778,30 +2010,36 @@
         return state;
     }
 
-    // Sum all the snapshot states as if the system consists of a single huge
-    // snapshots device, then compute the merge completion percentage of that
-    // device.
-    std::vector<std::string> snapshots;
-    if (!ListSnapshots(lock.get(), &snapshots)) {
-        LOG(ERROR) << "Could not list snapshots";
-        return state;
+    if (!UpdateUsesUserSnapshots(lock.get())) {
+        // Sum all the snapshot states as if the system consists of a single huge
+        // snapshots device, then compute the merge completion percentage of that
+        // device.
+        std::vector<std::string> snapshots;
+        if (!ListSnapshots(lock.get(), &snapshots)) {
+            LOG(ERROR) << "Could not list snapshots";
+            return state;
+        }
+
+        DmTargetSnapshot::Status fake_snapshots_status = {};
+        for (const auto& snapshot : snapshots) {
+            DmTargetSnapshot::Status current_status;
+
+            if (!IsSnapshotDevice(snapshot)) continue;
+            if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
+
+            fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
+            fake_snapshots_status.total_sectors += current_status.total_sectors;
+            fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
+        }
+
+        *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
+                                                   update_status.sectors_allocated());
+    } else {
+        if (EnsureSnapuserdConnected()) {
+            *progress = snapuserd_client_->GetMergePercent();
+        }
     }
 
-    DmTargetSnapshot::Status fake_snapshots_status = {};
-    for (const auto& snapshot : snapshots) {
-        DmTargetSnapshot::Status current_status;
-
-        if (!IsSnapshotDevice(snapshot)) continue;
-        if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
-
-        fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
-        fake_snapshots_status.total_sectors += current_status.total_sectors;
-        fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
-    }
-
-    *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
-                                               update_status.sectors_allocated());
-
     return state;
 }
 
@@ -1816,6 +2054,38 @@
     return update_status.compression_enabled();
 }
 
+bool SnapshotManager::UpdateUsesUserSnapshots() {
+    // This and the following function is constantly
+    // invoked during snapshot merge. We want to avoid
+    // constantly reading from disk. Hence, store this
+    // value in memory.
+    //
+    // Furthermore, this value in the disk is set
+    // only when OTA is applied and doesn't change
+    // during merge phase. Hence, once we know that
+    // the value is read from disk the very first time,
+    // it is safe to read successive checks from memory.
+    if (is_snapshot_userspace_.has_value()) {
+        return is_snapshot_userspace_.value();
+    }
+
+    auto lock = LockShared();
+    if (!lock) return false;
+
+    return UpdateUsesUserSnapshots(lock.get());
+}
+
+bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) {
+    // See UpdateUsesUserSnapshots()
+    if (is_snapshot_userspace_.has_value()) {
+        return is_snapshot_userspace_.value();
+    }
+
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    is_snapshot_userspace_ = update_status.userspace_snapshots();
+    return is_snapshot_userspace_.value();
+}
+
 bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
                                     const std::string& suffix) {
     CHECK(lock);
@@ -2031,19 +2301,28 @@
     // Create the base device for the snapshot, or if there is no snapshot, the
     // device itself. This device consists of the real blocks in the super
     // partition that this logical partition occupies.
-    auto& dm = DeviceMapper::Instance();
     std::string base_path;
     if (!CreateLogicalPartition(params, &base_path)) {
         LOG(ERROR) << "Could not create logical partition " << params.GetPartitionName()
                    << " as device " << params.GetDeviceName();
         return false;
     }
-    created_devices.EmplaceBack<AutoUnmapDevice>(&dm, params.GetDeviceName());
+    created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, params.GetDeviceName());
 
     if (paths) {
         paths->target_device = base_path;
     }
 
+    auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+    if (remaining_time.count() < 0) {
+        return false;
+    }
+
+    // Wait for the base device to appear
+    if (!WaitForDevice(base_path, remaining_time)) {
+        return false;
+    }
+
     if (!live_snapshot_status.has_value()) {
         created_devices.Release();
         return true;
@@ -2052,12 +2331,12 @@
     // We don't have ueventd in first-stage init, so use device major:minor
     // strings instead.
     std::string base_device;
-    if (!dm.GetDeviceString(params.GetDeviceName(), &base_device)) {
+    if (!dm_.GetDeviceString(params.GetDeviceName(), &base_device)) {
         LOG(ERROR) << "Could not determine major/minor for: " << params.GetDeviceName();
         return false;
     }
 
-    auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+    remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
     std::string cow_name;
@@ -2087,14 +2366,18 @@
     if (live_snapshot_status->compression_enabled()) {
         // Get the source device (eg the view of the partition from before it was resized).
         std::string source_device_path;
-        if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time,
-                             &source_device_path)) {
-            LOG(ERROR) << "Could not map source device for: " << cow_name;
-            return false;
-        }
+        if (live_snapshot_status->old_partition_size() > 0) {
+            if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time,
+                                 &source_device_path)) {
+                LOG(ERROR) << "Could not map source device for: " << cow_name;
+                return false;
+            }
 
-        auto source_device = GetSourceDeviceName(params.GetPartitionName());
-        created_devices.EmplaceBack<AutoUnmapDevice>(&dm, source_device);
+            auto source_device = GetSourceDeviceName(params.GetPartitionName());
+            created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, source_device);
+        } else {
+            source_device_path = base_path;
+        }
 
         if (!WaitForDevice(source_device_path, remaining_time)) {
             return false;
@@ -2109,16 +2392,16 @@
             return false;
         }
 
-        auto name = GetDmUserCowName(params.GetPartitionName());
+        auto name = GetDmUserCowName(params.GetPartitionName(), GetSnapshotDriver(lock));
 
         std::string new_cow_device;
-        if (!MapDmUserCow(lock, name, cow_path, source_device_path, remaining_time,
+        if (!MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time,
                           &new_cow_device)) {
             LOG(ERROR) << "Could not map dm-user device for partition "
                        << params.GetPartitionName();
             return false;
         }
-        created_devices.EmplaceBack<AutoUnmapDevice>(&dm, name);
+        created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, name);
 
         remaining_time = GetRemainingTime(params.timeout_ms, begin);
         if (remaining_time.count() < 0) return false;
@@ -2126,21 +2409,37 @@
         cow_device = new_cow_device;
     }
 
-    std::string path;
-    if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
-                     &path)) {
-        LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
-        return false;
-    }
-    // No need to add params.GetPartitionName() to created_devices since it is immediately released.
+    // For userspace snapshots, dm-user block device itself will act as a
+    // snapshot device. There is one subtle difference - MapSnapshot will create
+    // either snapshot target or snapshot-merge target based on the underlying
+    // state of the snapshot device. If snapshot-merge target is created, merge
+    // will immediately start in the kernel.
+    //
+    // This is no longer true with respect to userspace snapshots. When dm-user
+    // block device is created, we just have the snapshots ready but daemon in
+    // the user-space will not start the merge. We have to explicitly inform the
+    // daemon to resume the merge. Check ProcessUpdateState() call stack.
+    if (!UpdateUsesUserSnapshots(lock)) {
+        std::string path;
+        if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
+                         &path)) {
+            LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
+            return false;
+        }
+        // No need to add params.GetPartitionName() to created_devices since it is immediately
+        // released.
 
-    if (paths) {
-        paths->snapshot_device = path;
+        if (paths) {
+            paths->snapshot_device = path;
+        }
+        LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
+    } else {
+        LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at "
+                  << cow_device;
     }
 
     created_devices.Release();
 
-    LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
     return true;
 }
 
@@ -2184,8 +2483,6 @@
     std::string cow_image_name = GetCowImageDeviceName(partition_name);
     *cow_name = GetCowName(partition_name);
 
-    auto& dm = DeviceMapper::Instance();
-
     // Map COW image if necessary.
     if (snapshot_status.cow_file_size() > 0) {
         if (!EnsureImageManager()) return false;
@@ -2236,11 +2533,11 @@
 
     // We have created the DmTable now. Map it.
     std::string cow_path;
-    if (!dm.CreateDevice(*cow_name, table, &cow_path, remaining_time)) {
+    if (!dm_.CreateDevice(*cow_name, table, &cow_path, remaining_time)) {
         LOG(ERROR) << "Could not create COW device: " << *cow_name;
         return false;
     }
-    created_devices->EmplaceBack<AutoUnmapDevice>(&dm, *cow_name);
+    created_devices->EmplaceBack<AutoUnmapDevice>(&dm_, *cow_name);
     LOG(INFO) << "Mapped COW device for " << params.GetPartitionName() << " at " << cow_path;
     return true;
 }
@@ -2249,8 +2546,11 @@
     CHECK(lock);
     if (!EnsureImageManager()) return false;
 
-    if (UpdateUsesCompression(lock) && !UnmapDmUserDevice(name)) {
-        return false;
+    if (UpdateUsesCompression(lock) && !UpdateUsesUserSnapshots(lock)) {
+        auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
+        if (!UnmapDmUserDevice(dm_user_name)) {
+            return false;
+        }
     }
 
     if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
@@ -2266,11 +2566,8 @@
     return true;
 }
 
-bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
-    auto& dm = DeviceMapper::Instance();
-
-    auto dm_user_name = GetDmUserCowName(snapshot_name);
-    if (dm.GetState(dm_user_name) == DmDeviceState::INVALID) {
+bool SnapshotManager::UnmapDmUserDevice(const std::string& dm_user_name) {
+    if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
         return true;
     }
 
@@ -2295,6 +2592,46 @@
     return true;
 }
 
+bool SnapshotManager::UnmapUserspaceSnapshotDevice(LockedFile* lock,
+                                                   const std::string& snapshot_name) {
+    auto dm_user_name = GetDmUserCowName(snapshot_name, GetSnapshotDriver(lock));
+    if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
+        return true;
+    }
+
+    CHECK(lock);
+
+    SnapshotStatus snapshot_status;
+
+    if (!ReadSnapshotStatus(lock, snapshot_name, &snapshot_status)) {
+        return false;
+    }
+    // If the merge is complete, then we switch dm tables which is equivalent
+    // to unmap; hence, we can't be deleting the device
+    // as the table would be mounted off partitions and will fail.
+    if (snapshot_status.state() != SnapshotState::MERGE_COMPLETED) {
+        if (!DeleteDeviceIfExists(dm_user_name)) {
+            LOG(ERROR) << "Cannot unmap " << dm_user_name;
+            return false;
+        }
+    }
+
+    if (EnsureSnapuserdConnected()) {
+        if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
+            LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
+            return false;
+        }
+    }
+
+    // Ensure the control device is gone so we don't run into ABA problems.
+    auto control_device = "/dev/dm-user/" + dm_user_name;
+    if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
+        LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
+        return false;
+    }
+    return true;
+}
+
 bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) {
     auto lock = LockExclusive();
     if (!lock) return false;
@@ -2531,6 +2868,7 @@
         status.set_compression_enabled(old_status.compression_enabled());
         status.set_source_build_fingerprint(old_status.source_build_fingerprint());
         status.set_merge_phase(old_status.merge_phase());
+        status.set_userspace_snapshots(old_status.userspace_snapshots());
     }
     return WriteSnapshotUpdateStatus(lock, status);
 }
@@ -2848,6 +3186,43 @@
     SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
     status.set_state(update_state);
     status.set_compression_enabled(cow_creator.compression_enabled);
+    if (cow_creator.compression_enabled) {
+        if (!device()->IsTestDevice()) {
+            // Userspace snapshots is enabled only if compression is enabled
+            status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
+            if (IsUserspaceSnapshotsEnabled()) {
+                is_snapshot_userspace_ = true;
+                LOG(INFO) << "User-space snapshots enabled";
+            } else {
+                is_snapshot_userspace_ = false;
+                LOG(INFO) << "User-space snapshots disabled";
+            }
+
+            // Terminate stale daemon if any
+            std::unique_ptr<SnapuserdClient> snapuserd_client =
+                    SnapuserdClient::Connect(kSnapuserdSocket, 10s);
+            if (snapuserd_client) {
+                snapuserd_client->DetachSnapuserd();
+                snapuserd_client->CloseConnection();
+                snapuserd_client = nullptr;
+            }
+
+            // Clear the cached client if any
+            if (snapuserd_client_) {
+                snapuserd_client_->CloseConnection();
+                snapuserd_client_ = nullptr;
+            }
+        } else {
+            status.set_userspace_snapshots(!IsDmSnapshotTestingEnabled());
+            if (IsDmSnapshotTestingEnabled()) {
+                is_snapshot_userspace_ = false;
+                LOG(INFO) << "User-space snapshots disabled for testing";
+            } else {
+                is_snapshot_userspace_ = true;
+                LOG(INFO) << "User-space snapshots enabled for testing";
+            }
+        }
+    }
     if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
         LOG(ERROR) << "Unable to write new update state";
         return Return::Error();
@@ -3490,7 +3865,6 @@
         return false;
     }
 
-    auto& dm = DeviceMapper::Instance();
     for (const auto& snapshot : snapshots) {
         SnapshotStatus status;
         if (!ReadSnapshotStatus(lock, snapshot, &status)) {
@@ -3501,7 +3875,7 @@
         }
 
         std::vector<DeviceMapper::TargetInfo> targets;
-        if (!dm.GetTableStatus(snapshot, &targets)) {
+        if (!dm_.GetTableStatus(snapshot, &targets)) {
             LOG(ERROR) << "Could not read snapshot device table: " << snapshot;
             return false;
         }
@@ -3594,11 +3968,9 @@
 // isn't running yet.
 bool SnapshotManager::GetMappedImageDevicePath(const std::string& device_name,
                                                std::string* device_path) {
-    auto& dm = DeviceMapper::Instance();
-
     // Try getting the device string if it is a device mapper device.
-    if (dm.GetState(device_name) != DmDeviceState::INVALID) {
-        return dm.GetDmDevicePathByName(device_name, device_path);
+    if (dm_.GetState(device_name) != DmDeviceState::INVALID) {
+        return dm_.GetDmDevicePathByName(device_name, device_path);
     }
 
     // Otherwise, get path from IImageManager.
@@ -3607,10 +3979,9 @@
 
 bool SnapshotManager::GetMappedImageDeviceStringOrPath(const std::string& device_name,
                                                        std::string* device_string_or_mapped_path) {
-    auto& dm = DeviceMapper::Instance();
     // Try getting the device string if it is a device mapper device.
-    if (dm.GetState(device_name) != DmDeviceState::INVALID) {
-        return dm.GetDeviceString(device_name, device_string_or_mapped_path);
+    if (dm_.GetState(device_name) != DmDeviceState::INVALID) {
+        return dm_.GetDeviceString(device_name, device_string_or_mapped_path);
     }
 
     // Otherwise, get path from IImageManager.
@@ -3726,10 +4097,9 @@
 
 bool SnapshotManager::DeleteDeviceIfExists(const std::string& name,
                                            const std::chrono::milliseconds& timeout_ms) {
-    auto& dm = DeviceMapper::Instance();
     auto start = std::chrono::steady_clock::now();
     while (true) {
-        if (dm.DeleteDeviceIfExists(name)) {
+        if (dm_.DeleteDeviceIfExists(name)) {
             return true;
         }
         auto now = std::chrono::steady_clock::now();
@@ -3742,7 +4112,7 @@
 
     // Try to diagnose why this failed. First get the actual device path.
     std::string full_path;
-    if (!dm.GetDmDevicePathByName(name, &full_path)) {
+    if (!dm_.GetDmDevicePathByName(name, &full_path)) {
         LOG(ERROR) << "Unable to diagnose DM_DEV_REMOVE failure.";
         return false;
     }
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index 0096f85..54c6a00 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -139,7 +139,7 @@
     auto& dm = DeviceMapper::Instance();
     std::vector<DeviceMapper::TargetInfo> table;
     if (!dm.GetTableInfo(dev_name, &table)) {
-        PCHECK(errno == ENODEV);
+        PCHECK(errno == ENODEV || errno == ENXIO);
         return {};
     }
     return table;
@@ -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_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index 3ed27c8..c1a5af7 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -101,7 +101,8 @@
         : env_(env),
           data_(&data),
           partition_opener_(std::move(partition_opener)),
-          metadata_dir_(metadata_dir) {}
+          metadata_dir_(metadata_dir),
+          dm_(android::dm::DeviceMapper::Instance()) {}
 
     // Following APIs are mocked.
     std::string GetMetadataDir() const override { return metadata_dir_; }
@@ -125,6 +126,7 @@
     }
     bool IsRecovery() const override { return data_->is_recovery(); }
     bool IsFirstStageInit() const override { return false; }
+    android::dm::IDeviceMapper& GetDeviceMapper() override { return dm_; }
     std::unique_ptr<IImageManager> OpenImageManager() const {
         return env_->CheckCreateFakeImageManager();
     }
@@ -137,6 +139,7 @@
     std::unique_ptr<TestPartitionOpener> partition_opener_;
     std::string metadata_dir_;
     bool switched_slot_ = false;
+    android::dm::DeviceMapper& dm_;
 
     bool CurrentSlotIsA() const { return data_->slot_suffix_is_a() != switched_slot_; }
 };
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 078f16e..9adc655 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -155,8 +155,8 @@
     }
 
     std::string MakeXorBlockString() {
-        std::string data(100, -1);
-        data.resize(kBlockSize, 0);
+        std::string data(kBlockSize, 0);
+        memset(data.data(), 0xff, 100);
         return data;
     }
 
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index a8d5b8a..4af5367 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -121,6 +121,11 @@
     return false;
 }
 
+bool SnapshotManagerStub::UpdateUsesUserSnapshots() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
 class SnapshotMergeStatsStub : public ISnapshotMergeStats {
     bool Start() override { return false; }
     void set_state(android::snapshot::UpdateState, bool) override {}
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index b2203fe..11cebe1 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -34,6 +34,7 @@
 #include <fs_mgr/file_wait.h>
 #include <fs_mgr/roots.h>
 #include <fs_mgr_dm_linear.h>
+#include <gflags/gflags.h>
 #include <gtest/gtest.h>
 #include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
@@ -45,17 +46,22 @@
 #include "partition_cow_creator.h"
 #include "utility.h"
 
+#include <android-base/properties.h>
+
 // Mock classes are not used. Header included to ensure mocked class definition aligns with the
 // class itself.
 #include <libsnapshot/mock_device_info.h>
 #include <libsnapshot/mock_snapshot.h>
 
+DEFINE_string(force_config, "", "Force testing mode (dmsnap, vab, vabc) ignoring device config.");
+
 namespace android {
 namespace snapshot {
 
 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;
@@ -84,6 +90,8 @@
 std::string fake_super;
 
 void MountMetadata();
+bool ShouldUseCompression();
+bool ShouldUseUserspaceSnapshots();
 
 class SnapshotTest : public ::testing::Test {
   public:
@@ -136,11 +144,7 @@
         std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
                                               "test_partition_b"};
         for (const auto& snapshot : snapshots) {
-            ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
-            DeleteBackingImage(image_manager_, snapshot + "-cow-img");
-
-            auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
-            android::base::RemoveFileIfExists(status_file);
+            CleanupSnapshotArtifacts(snapshot);
         }
 
         // Remove stale partitions in fake super.
@@ -148,7 +152,7 @@
                 "base-device",
                 "test_partition_b",
                 "test_partition_b-base",
-                "test_partition_b-base",
+                "test_partition_b-cow",
         };
         for (const auto& partition : partitions) {
             DeleteDevice(partition);
@@ -160,6 +164,32 @@
         }
     }
 
+    void CleanupSnapshotArtifacts(const std::string& snapshot) {
+        // The device-mapper stack may have been collapsed to dm-linear, so it's
+        // necessary to check what state it's in before attempting a cleanup.
+        // SnapshotManager has no path like this because we'd never remove a
+        // merged snapshot (a live partition).
+        bool is_dm_user = false;
+        DeviceMapper::TargetInfo target;
+        if (sm->IsSnapshotDevice(snapshot, &target)) {
+            is_dm_user = (DeviceMapper::GetTargetType(target.spec) == "user");
+        }
+
+        if (is_dm_user) {
+            ASSERT_TRUE(sm->EnsureSnapuserdConnected());
+            ASSERT_TRUE(AcquireLock());
+
+            auto local_lock = std::move(lock_);
+            ASSERT_TRUE(sm->UnmapUserspaceSnapshotDevice(local_lock.get(), snapshot));
+        }
+
+        ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
+        DeleteBackingImage(image_manager_, snapshot + "-cow-img");
+
+        auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
+        android::base::RemoveFileIfExists(status_file);
+    }
+
     bool AcquireLock() {
         lock_ = sm->LockExclusive();
         return !!lock_;
@@ -271,7 +301,7 @@
     AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
         AssertionResult res = AssertionSuccess();
         if (!(res = DeleteDevice(snapshot))) return res;
-        if (!sm->UnmapDmUserDevice(snapshot)) {
+        if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) {
             return AssertionFailure() << "Cannot delete dm-user device for " << snapshot;
         }
         if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
@@ -425,7 +455,7 @@
     ASSERT_TRUE(AcquireLock());
 
     PartitionCowCreator cow_creator;
-    cow_creator.compression_enabled = IsCompressionEnabled();
+    cow_creator.compression_enabled = ShouldUseCompression();
     if (cow_creator.compression_enabled) {
         cow_creator.compression_algorithm = "gz";
     } else {
@@ -466,7 +496,7 @@
     ASSERT_TRUE(AcquireLock());
 
     PartitionCowCreator cow_creator;
-    cow_creator.compression_enabled = IsCompressionEnabled();
+    cow_creator.compression_enabled = ShouldUseCompression();
 
     static const uint64_t kDeviceSize = 1024 * 1024;
     SnapshotStatus status;
@@ -524,6 +554,8 @@
     std::unique_ptr<ISnapshotWriter> writer;
     ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
 
+    bool userspace_snapshots = sm->UpdateUsesUserSnapshots(lock_.get());
+
     // Release the lock.
     lock_ = nullptr;
 
@@ -545,7 +577,11 @@
     // The device should have been switched to a snapshot-merge target.
     DeviceMapper::TargetInfo target;
     ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+    if (userspace_snapshots) {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+    } else {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+    }
 
     // We should not be able to cancel an update now.
     ASSERT_FALSE(sm->CancelUpdate());
@@ -581,11 +617,13 @@
 
     ASSERT_TRUE(AcquireLock());
 
+    bool userspace_snapshots = init->UpdateUsesUserSnapshots(lock_.get());
+
     // Validate that we have a snapshot device.
     SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
     ASSERT_EQ(status.state(), SnapshotState::CREATED);
-    if (IsCompressionEnabled()) {
+    if (ShouldUseCompression()) {
         ASSERT_EQ(status.compression_algorithm(), "gz");
     } else {
         ASSERT_EQ(status.compression_algorithm(), "none");
@@ -593,7 +631,11 @@
 
     DeviceMapper::TargetInfo target;
     ASSERT_TRUE(init->IsSnapshotDevice("test_partition_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    if (userspace_snapshots) {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+    } else {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    }
 }
 
 TEST_F(SnapshotTest, FlashSuperDuringUpdate) {
@@ -855,7 +897,7 @@
         opener_ = std::make_unique<TestPartitionOpener>(fake_super);
 
         auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
-        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+        dynamic_partition_metadata->set_vabc_enabled(ShouldUseCompression());
         dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
 
         // Create a fake update package metadata.
@@ -894,9 +936,9 @@
         ASSERT_NE(nullptr, metadata);
         ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
 
-        // Map source partitions. Additionally, map sys_b to simulate system_other after flashing.
+        // Map source partitions.
         std::string path;
-        for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) {
+        for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
             ASSERT_TRUE(CreateLogicalPartition(
                     CreateLogicalPartitionParams{
                             .block_device = fake_super,
@@ -911,6 +953,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 +972,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());
@@ -963,7 +1018,7 @@
     }
 
     AssertionResult UnmapAll() {
-        for (const auto& name : {"sys", "vnd", "prd"}) {
+        for (const auto& name : {"sys", "vnd", "prd", "dlkm"}) {
             if (!dm_.DeleteDeviceIfExists(name + "_a"s)) {
                 return AssertionFailure() << "Cannot unmap " << name << "_a";
             }
@@ -975,7 +1030,7 @@
     }
 
     AssertionResult MapOneUpdateSnapshot(const std::string& name) {
-        if (IsCompressionEnabled()) {
+        if (ShouldUseCompression()) {
             std::unique_ptr<ISnapshotWriter> writer;
             return MapUpdateSnapshot(name, &writer);
         } else {
@@ -985,7 +1040,7 @@
     }
 
     AssertionResult WriteSnapshotAndHash(const std::string& name) {
-        if (IsCompressionEnabled()) {
+        if (ShouldUseCompression()) {
             std::unique_ptr<ISnapshotWriter> writer;
             auto res = MapUpdateSnapshot(name, &writer);
             if (!res) {
@@ -1097,11 +1152,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;
@@ -1158,7 +1208,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+    ASSERT_EQ(init->IsSnapuserdRequired(), ShouldUseUserspaceSnapshots());
     {
         // We should have started in SECOND_PHASE since nothing shrinks.
         ASSERT_TRUE(AcquireLock());
@@ -1185,15 +1235,10 @@
 }
 
 TEST_F(SnapshotUpdateTest, DuplicateOps) {
-    if (!IsCompressionEnabled()) {
+    if (!ShouldUseCompression()) {
         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_));
@@ -1234,16 +1279,11 @@
 // Test that shrinking and growing partitions at the same time is handled
 // correctly in VABC.
 TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
-    if (!IsCompressionEnabled()) {
+    if (!ShouldUseCompression()) {
         // b/179111359
         GTEST_SKIP() << "Skipping Virtual A/B Compression test";
     }
 
-    // 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_);
 
@@ -1302,7 +1342,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+    ASSERT_EQ(init->IsSnapuserdRequired(), ShouldUseUserspaceSnapshots());
     {
         // Check that the merge phase is FIRST_PHASE until at least one call
         // to ProcessUpdateState() occurs.
@@ -1319,11 +1359,21 @@
     // Check that we used the correct types after rebooting mid-merge.
     DeviceMapper::TargetInfo target;
     ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
-    ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
-    ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+
+    bool userspace_snapshots = init->UpdateUsesUserSnapshots();
+    if (userspace_snapshots) {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+        ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+        ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+    } else {
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+        ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+        ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
+        ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    }
 
     // Complete the merge.
     ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
@@ -1344,6 +1394,93 @@
     }
 }
 
+// Test that a transient merge consistency check failure can resume properly.
+TEST_F(SnapshotUpdateTest, ConsistencyCheckResume) {
+    if (!ShouldUseCompression()) {
+        // b/179111359
+        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+    }
+
+    auto old_sys_size = GetSize(sys_);
+    auto old_prd_size = GetSize(prd_);
+
+    // Grow |sys| but shrink |prd|.
+    SetSize(sys_, old_sys_size * 2);
+    sys_->set_estimate_cow_size(8_MiB);
+    SetSize(prd_, old_prd_size / 2);
+    prd_->set_estimate_cow_size(1_MiB);
+
+    AddOperationForPartitions();
+
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash("vnd_b"));
+    ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size));
+
+    sync();
+
+    // Assert that source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto init = NewManagerForFirstStageMount("_b");
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    // Check that the target partitions have the same content.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    auto old_checker = init->merge_consistency_checker();
+
+    init->set_merge_consistency_checker(
+            [](const std::string&, const SnapshotStatus&) -> MergeFailureCode {
+                return MergeFailureCode::WrongMergeCountConsistencyCheck;
+            });
+
+    // Initiate the merge and wait for it to be completed.
+    ASSERT_TRUE(init->InitiateMerge());
+    ASSERT_EQ(init->IsSnapuserdRequired(), ShouldUseUserspaceSnapshots());
+    {
+        // Check that the merge phase is FIRST_PHASE until at least one call
+        // to ProcessUpdateState() occurs.
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        auto status = init->ReadSnapshotUpdateStatus(local_lock.get());
+        ASSERT_EQ(status.merge_phase(), MergePhase::FIRST_PHASE);
+    }
+
+    // Merge should have failed.
+    ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState());
+
+    // Simulate shutting down the device and creating partitions again.
+    ASSERT_TRUE(UnmapAll());
+
+    // Restore the checker.
+    init->set_merge_consistency_checker(std::move(old_checker));
+
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    // Complete the merge.
+    ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+
+    // Check that the target partitions have the same content after the merge.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name))
+                << "Content of " << name << " changes after the merge";
+    }
+}
+
 // Test that if new system partitions uses empty space in super, that region is not snapshotted.
 TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) {
     GTEST_SKIP() << "b/141889746";
@@ -1630,11 +1767,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_);
 
@@ -1806,6 +1938,8 @@
 
     ASSERT_TRUE(new_sm->FinishMergeInRecovery());
 
+    ASSERT_TRUE(UnmapAll());
+
     auto mount = new_sm->EnsureMetadataMounted();
     ASSERT_TRUE(mount && mount->HasDevice());
     ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
@@ -1898,6 +2032,8 @@
     ASSERT_FALSE(test_device->IsSlotUnbootable(1));
     ASSERT_FALSE(test_device->IsSlotUnbootable(0));
 
+    ASSERT_TRUE(UnmapAll());
+
     // Now reboot into new slot.
     test_device = new TestDeviceInfo(fake_super, "_b");
     auto init = NewManagerForFirstStageMount(test_device);
@@ -1926,8 +2062,8 @@
         ASSERT_TRUE(AcquireLock());
 
         PartitionCowCreator cow_creator = {
-                .compression_enabled = IsCompressionEnabled(),
-                .compression_algorithm = IsCompressionEnabled() ? "gz" : "none",
+                .compression_enabled = ShouldUseCompression(),
+                .compression_algorithm = ShouldUseCompression() ? "gz" : "none",
         };
         SnapshotStatus status;
         status.set_name("sys_a");
@@ -1959,6 +2095,8 @@
     ASSERT_FALSE(test_device->IsSlotUnbootable(1));
     ASSERT_FALSE(test_device->IsSlotUnbootable(0));
 
+    ASSERT_TRUE(UnmapAll());
+
     // Now reboot into new slot.
     test_device = new TestDeviceInfo(fake_super, "_b");
     auto init = NewManagerForFirstStageMount(test_device);
@@ -2021,7 +2159,7 @@
 
 // Test for overflow bit after update
 TEST_F(SnapshotUpdateTest, Overflow) {
-    if (IsCompressionEnabled()) {
+    if (ShouldUseCompression()) {
         GTEST_SKIP() << "No overflow bit set for userspace COWs";
     }
 
@@ -2073,6 +2211,75 @@
     ASSERT_LT(res.required_size(), 40_MiB);
 }
 
+TEST_F(SnapshotUpdateTest, AddPartition) {
+    group_->add_partition_names("dlkm");
+
+    auto dlkm = manifest_.add_partitions();
+    dlkm->set_partition_name("dlkm");
+    dlkm->set_estimate_cow_size(2_MiB);
+    SetSize(dlkm, 3_MiB);
+
+    // 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);
+    SetSize(vnd_, partition_size);
+    SetSize(prd_, partition_size);
+    SetSize(dlkm, partition_size);
+
+    AddOperationForPartitions({sys_, vnd_, prd_, dlkm});
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    // Write some data to target partitions.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
+        ASSERT_TRUE(WriteSnapshotAndHash(name));
+    }
+
+    // Assert that source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto init = NewManagerForFirstStageMount("_b");
+    ASSERT_NE(init, nullptr);
+
+    ASSERT_TRUE(init->EnsureSnapuserdConnected());
+    init->set_use_first_stage_snapuserd(true);
+
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    // Check that the target partitions have the same content.
+    std::vector<std::string> partitions = {"sys_b", "vnd_b", "prd_b", "dlkm_b"};
+    for (const auto& name : partitions) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
+    for (const auto& name : partitions) {
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init"));
+    }
+
+    // Initiate the merge and wait for it to be completed.
+    ASSERT_TRUE(init->InitiateMerge());
+    ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+
+    // Check that the target partitions have the same content after the merge.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name))
+                << "Content of " << name << " changes after the merge";
+    }
+}
+
 class AutoKill final {
   public:
     explicit AutoKill(pid_t pid) : pid_(pid) {}
@@ -2087,7 +2294,7 @@
 };
 
 TEST_F(SnapshotUpdateTest, DaemonTransition) {
-    if (!IsCompressionEnabled()) {
+    if (!ShouldUseCompression()) {
         GTEST_SKIP() << "Skipping Virtual A/B Compression test";
     }
 
@@ -2113,21 +2320,38 @@
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
 
-    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow-init", F_OK), 0);
-    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), -1);
+    bool userspace_snapshots = init->UpdateUsesUserSnapshots();
+
+    if (userspace_snapshots) {
+        ASSERT_EQ(access("/dev/dm-user/sys_b-init", F_OK), 0);
+        ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), -1);
+    } else {
+        ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow-init", F_OK), 0);
+        ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), -1);
+    }
 
     ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
 
     // :TODO: this is a workaround to ensure the handler list stays empty. We
     // should make this test more like actual init, and spawn two copies of
     // snapuserd, given how many other tests we now have for normal snapuserd.
-    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init"));
-    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init"));
-    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init"));
+    if (userspace_snapshots) {
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-init"));
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-init"));
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-init"));
 
-    // The control device should have been renamed.
-    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s));
-    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), 0);
+        // The control device should have been renamed.
+        ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-init", 10s));
+        ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), 0);
+    } else {
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init"));
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init"));
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init"));
+
+        // The control device should have been renamed.
+        ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s));
+        ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), 0);
+    }
 }
 
 TEST_F(SnapshotUpdateTest, MapAllSnapshots) {
@@ -2150,6 +2374,8 @@
 TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) {
     AddOperationForPartitions();
 
+    ASSERT_TRUE(UnmapAll());
+
     // Execute the update from B->A.
     test_device->set_slot_suffix("_b");
     ASSERT_TRUE(sm->BeginUpdate());
@@ -2166,8 +2392,13 @@
             },
             &path));
 
-    // Hold sys_a open so it can't be unmapped.
-    unique_fd fd(open(path.c_str(), O_RDONLY));
+    bool userspace_snapshots = sm->UpdateUsesUserSnapshots();
+
+    unique_fd fd;
+    if (!userspace_snapshots) {
+        // Hold sys_a open so it can't be unmapped.
+        fd.reset(open(path.c_str(), O_RDONLY));
+    }
 
     // Switch back to "A", make sure we can cancel. Instead of unmapping sys_a
     // we should simply delete the old snapshots.
@@ -2175,6 +2406,65 @@
     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_));
+
+    if (sm->UpdateUsesUserSnapshots()) {
+        GTEST_SKIP() << "Test does not apply to userspace snapshots";
+    }
+
+    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+    ASSERT_TRUE(UnmapAll());
+
+    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:
@@ -2191,11 +2481,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_));
@@ -2441,11 +2726,53 @@
     }
 }
 
+bool ShouldUseUserspaceSnapshots() {
+    if (FLAGS_force_config == "dmsnap") {
+        return false;
+    }
+    if (!FLAGS_force_config.empty()) {
+        return true;
+    }
+    return IsUserspaceSnapshotsEnabled();
+}
+
+bool ShouldUseCompression() {
+    if (FLAGS_force_config == "vab" || FLAGS_force_config == "dmsnap") {
+        return false;
+    }
+    if (FLAGS_force_config == "vabc") {
+        return true;
+    }
+    return IsCompressionEnabled();
+}
+
 }  // namespace snapshot
 }  // namespace android
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
     ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
-    return RUN_ALL_TESTS();
+    gflags::ParseCommandLineFlags(&argc, &argv, false);
+
+    android::base::SetProperty("ctl.stop", "snapuserd");
+
+    std::unordered_set<std::string> configs = {"", "dmsnap", "vab", "vabc"};
+    if (configs.count(FLAGS_force_config) == 0) {
+        std::cerr << "Unexpected force_config argument\n";
+        return 1;
+    }
+
+    if (FLAGS_force_config == "dmsnap") {
+        if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) {
+            return testing::AssertionFailure()
+                   << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
+        }
+    }
+
+    int ret = RUN_ALL_TESTS();
+
+    if (FLAGS_force_config == "dmsnap") {
+        android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
+    }
+    return ret;
 }
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 3eda08e..48b7d80 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -67,7 +67,7 @@
     return cow_->GetCowSize();
 }
 
-std::unique_ptr<FileDescriptor> CompressedSnapshotWriter::OpenReader() {
+std::unique_ptr<CowReader> CompressedSnapshotWriter::OpenCowReader() const {
     unique_fd cow_fd(dup(cow_device_.get()));
     if (cow_fd < 0) {
         PLOG(ERROR) << "dup COW device";
@@ -79,6 +79,20 @@
         LOG(ERROR) << "Unable to read COW";
         return nullptr;
     }
+    return cow;
+}
+
+bool CompressedSnapshotWriter::VerifyMergeOps() const noexcept {
+    auto cow_reader = OpenCowReader();
+    if (cow_reader == nullptr) {
+        LOG(ERROR) << "Couldn't open CowReader";
+        return false;
+    }
+    return cow_reader->VerifyMergeOps();
+}
+
+std::unique_ptr<FileDescriptor> CompressedSnapshotWriter::OpenReader() {
+    auto cow = OpenCowReader();
 
     auto reader = std::make_unique<CompressedSnapshotReader>();
     if (!reader->SetCow(std::move(cow))) {
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 47268d4..84bcb94 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: [
@@ -88,11 +95,24 @@
     init_rc: [
         "snapuserd.rc",
     ],
+
+    // snapuserd is started during early boot by first-stage init. At that
+    // point, /system is mounted using the "dm-user" device-mapper kernel
+    // module. dm-user routes all I/O to userspace to be handled by
+    // snapuserd, which would lead to deadlock if we had to handle page
+    // faults for its code pages.
     static_executable: true,
+
     system_shared_libs: [],
     ramdisk_available: true,
     vendor_ramdisk_available: true,
     recovery_available: true,
+
+    // Snapuserd segfaults with ThinLTO
+    // http://b/208565717
+    lto: {
+         never: true,
+    }
 }
 
 cc_test {
@@ -101,9 +121,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",
@@ -128,7 +149,47 @@
         "libstorage_literals_headers",
         "libfiemap_headers",
     ],
-    test_min_api_level: 30,
+    test_options: {
+        min_shipping_api_level: 30,
+    },
+    auto_gen_config: true,
+    require_root: false,
+}
+
+cc_test {
+    name: "snapuserd_test",
+    defaults: [
+        "fs_mgr_defaults",
+    ],
+    srcs: [
+        "user-space-merge/snapuserd_test.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    static_libs: [
+        "libbrotli",
+        "libgtest",
+        "libsnapshot_cow",
+        "libsnapshot_snapuserd",
+        "libcutils_sockets",
+        "libz",
+        "libfs_mgr",
+        "libdm",
+        "libext4_utils",
+    ],
+    header_libs: [
+        "libstorage_literals_headers",
+        "libfiemap_headers",
+    ],
+    test_options: {
+        min_shipping_api_level: 30,
+    },
     auto_gen_config: true,
     require_root: false,
 }
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 93%
rename from fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index f4aef44..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>
 
@@ -108,6 +109,7 @@
     void MergeInterruptFixed(int duration);
     void MergeInterruptRandomly(int max_duration);
     void ReadDmUserBlockWithoutDaemon();
+    void ReadLastBlock();
 
     std::string snapshot_dev() const { return snapshot_dev_->path(); }
 
@@ -256,6 +258,73 @@
     }
 }
 
+void CowSnapuserdTest::ReadLastBlock() {
+    unique_fd rnd_fd;
+    total_base_size_ = BLOCK_SZ * 2;
+
+    base_fd_ = CreateTempFile("base_device", total_base_size_);
+    ASSERT_GE(base_fd_, 0);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
+
+    for (size_t j = 0; j < ((total_base_size_) / BLOCK_SZ); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), BLOCK_SZ, 0), true);
+        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), BLOCK_SZ), true);
+    }
+
+    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+
+    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
+    ASSERT_TRUE(base_loop_->valid());
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(total_base_size_);
+    loff_t offset = 0;
+
+    // Fill random data
+    for (size_t j = 0; j < (total_base_size_ / BLOCK_SZ); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, BLOCK_SZ, 0),
+                  true);
+
+        offset += BLOCK_SZ;
+    }
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1_.get(), BLOCK_SZ));
+    ASSERT_TRUE(writer.AddRawBlocks(1, (char*)random_buffer_1_.get() + BLOCK_SZ, BLOCK_SZ));
+
+    ASSERT_TRUE(writer.Finalize());
+
+    SetDeviceControlName();
+
+    StartSnapuserdDaemon();
+    InitCowDevice();
+
+    CreateDmUserDevice();
+    InitDaemon();
+
+    CreateSnapshotDevice();
+
+    unique_fd snapshot_fd(open(snapshot_dev_->path().c_str(), O_RDONLY));
+    ASSERT_TRUE(snapshot_fd > 0);
+
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
+
+    offset = 7680;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), 512, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)random_buffer_1_.get() + offset, 512), 0);
+}
+
 void CowSnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
 
@@ -1143,6 +1212,12 @@
     harness.Shutdown();
 }
 
+TEST(Snapuserd_Test, Snapshot_END_IO_TEST) {
+    CowSnapuserdTest harness;
+    harness.ReadLastBlock();
+    harness.Shutdown();
+}
+
 TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
     CowSnapuserdTest harness;
     ASSERT_TRUE(harness.SetupCopyOverlap_1());
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
similarity index 99%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 2abf431..5f4d706 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -691,7 +691,7 @@
 }
 
 void Snapuserd::ReadBlocksToCache(const std::string& dm_block_device,
-                                  const std::string partition_name, off_t offset, size_t size) {
+                                  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
@@ -726,7 +726,7 @@
                    << " offset: " << offset;
 }
 
-void Snapuserd::ReadBlocks(const std::string partition_name, const std::string& dm_block_device) {
+void Snapuserd::ReadBlocks(const std::string& partition_name, const std::string& dm_block_device) {
     SNAP_LOG(DEBUG) << "Reading partition: " << partition_name
                     << " Block-Device: " << dm_block_device;
 
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
similarity index 90%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd.h
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
index b30041d..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 {
@@ -316,8 +284,8 @@
     bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
     struct BufferState* GetBufferState();
 
-    void ReadBlocks(const std::string partition_name, const std::string& dm_block_device);
-    void ReadBlocksToCache(const std::string& dm_block_device, const std::string partition_name,
+    void ReadBlocks(const std::string& partition_name, const std::string& dm_block_device);
+    void ReadBlocksToCache(const std::string& dm_block_device, const std::string& partition_name,
                            off_t offset, size_t size);
 
     std::string cow_device_;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
similarity index 95%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index b868eed..c201b23 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -194,10 +194,12 @@
                                        std::vector<uint64_t>& blocks) {
     int num_ops = *pending_ops;
     int nr_consecutive = 0;
+    CHECK_NE(source_offset, nullptr);
 
     if (!RAIterDone() && num_ops) {
         // Get the first block with offset
         const CowOperation* cow_op = GetRAOpIter();
+        CHECK_NE(cow_op, nullptr);
         *source_offset = cow_op->source;
         if (cow_op->type == kCowCopyOp) {
             *source_offset *= BLOCK_SZ;
@@ -216,11 +218,12 @@
          */
         while (!RAIterDone() && num_ops) {
             const CowOperation* op = GetRAOpIter();
+            CHECK_NE(op, nullptr);
             uint64_t next_offset = op->source;
-            if (cow_op->type == kCowCopyOp) {
+            if (op->type == kCowCopyOp) {
                 next_offset *= BLOCK_SZ;
             }
-            if (next_offset != (*source_offset - nr_consecutive * BLOCK_SZ)) {
+            if (next_offset + nr_consecutive * BLOCK_SZ != *source_offset) {
                 break;
             }
             nr_consecutive += 1;
@@ -243,9 +246,15 @@
     int num_ops = 0;
     int total_blocks_merged = 0;
 
+    // This memcpy is important as metadata_buffer_ will be an unaligned address and will fault
+    // on 32-bit systems
+    std::unique_ptr<uint8_t[]> metadata_buffer =
+            std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+    memcpy(metadata_buffer.get(), metadata_buffer_, snapuserd_->GetBufferMetadataSize());
+
     while (true) {
         struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
-                (char*)metadata_buffer_ + metadata_offset);
+                (char*)metadata_buffer.get() + metadata_offset);
 
         // Done reading metadata
         if (bm->new_block == 0 && bm->file_offset == 0) {
diff --git a/fs_mgr/libsnapshot/snapuserd/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 cdf9fe7..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) {
@@ -350,16 +278,36 @@
     it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
                           Snapuserd::compare);
 
-    if (!(it != chunk_vec.end())) {
-        SNAP_LOG(ERROR) << "ReadData: Sector " << sector << " not found in chunk_vec";
-        return -1;
+    bool read_end_of_device = false;
+    if (it == chunk_vec.end()) {
+        // |-------|-------|-------|
+        // 0       1       2       3
+        //
+        // Block 0 - op 1
+        // Block 1 - op 2
+        // Block 2 - op 3
+        //
+        // chunk_vec will have block 0, 1, 2 which maps to relavant COW ops.
+        //
+        // Each block is 4k bytes. Thus, the last block will span 8 sectors
+        // ranging till block 3 (However, block 3 won't be in chunk_vec as
+        // it doesn't have any mapping to COW ops. Now, if we get an I/O request for a sector
+        // spanning between block 2 and block 3, we need to step back
+        // and get hold of the last element.
+        //
+        // Additionally, dm-snapshot makes sure that I/O request beyond block 3
+        // will not be routed to the daemon. Hence, it is safe to assume that
+        // if a sector is not available in the chunk_vec, the I/O falls in the
+        // end of region.
+        it = std::prev(chunk_vec.end());
+        read_end_of_device = true;
     }
 
     // We didn't find the required sector; hence find the previous sector
     // as lower_bound will gives us the value greater than
     // the requested sector
     if (it->first != sector) {
-        if (it != chunk_vec.begin()) {
+        if (it != chunk_vec.begin() && !read_end_of_device) {
             --it;
         }
 
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..cebda1c 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -63,7 +63,8 @@
     // The misc_name must be the "misc_name" given to dm-user in step 2.
     //
     uint64_t InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
-                           const std::string& backing_device);
+                           const std::string& backing_device,
+                           const std::string& base_path_merge = "");
     bool AttachDmUser(const std::string& misc_name);
 
     // Wait for snapuserd to disassociate with a dm-user control device. This
@@ -79,6 +80,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..7b1c7a3 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -195,8 +195,16 @@
 }
 
 uint64_t SnapuserdClient::InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
-                                        const std::string& backing_device) {
-    std::vector<std::string> parts = {"init", misc_name, cow_device, backing_device};
+                                        const std::string& backing_device,
+                                        const std::string& base_path_merge) {
+    std::vector<std::string> parts;
+
+    if (base_path_merge.empty()) {
+        parts = {"init", misc_name, cow_device, backing_device};
+    } else {
+        // For userspace snapshots
+        parts = {"init", misc_name, cow_device, backing_device, base_path_merge};
+    }
     std::string msg = android::base::Join(parts, ",");
     if (!Sendmsg(msg)) {
         LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon";
@@ -231,5 +239,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..ddb1f79 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -14,27 +14,95 @@
  * limitations under the License.
  */
 
-#include "snapuserd_daemon.h"
-
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <gflags/gflags.h>
 #include <snapuserd/snapuserd_client.h>
 
-#include "snapuserd_server.h"
+#include "snapuserd_daemon.h"
 
 DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
 DEFINE_bool(no_socket, false,
             "If true, no socket is used. Each additional argument is an INIT message.");
 DEFINE_bool(socket_handoff, false,
             "If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
+DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
 
 namespace android {
 namespace snapshot {
 
-bool Daemon::StartServer(int argc, char** argv) {
+bool Daemon::IsUserspaceSnapshotsEnabled() {
+    return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+}
+
+bool Daemon::IsDmSnapshotTestingEnabled() {
+    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
+}
+
+bool Daemon::StartDaemon(int argc, char** argv) {
     int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true);
 
+    // Daemon launched from first stage init and during selinux transition
+    // will have the command line "-user_snapshot" flag set if the user-space
+    // snapshots are enabled.
+    //
+    // Daemon launched as a init service during "socket-handoff" and when OTA
+    // is applied will check for the property. This is ok as the system
+    // properties are valid at this point. We can't do this during first
+    // stage init and hence use the command line flags to get the information.
+    if (!IsDmSnapshotTestingEnabled() && (FLAGS_user_snapshot || IsUserspaceSnapshotsEnabled())) {
+        LOG(INFO) << "Starting daemon for user-space snapshots.....";
+        return StartServerForUserspaceSnapshots(arg_start, argc, argv);
+    } else {
+        LOG(INFO) << "Starting daemon for dm-snapshots.....";
+        return StartServerForDmSnapshot(arg_start, argc, argv);
+    }
+}
+
+bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv) {
+    sigfillset(&signal_mask_);
+    sigdelset(&signal_mask_, SIGINT);
+    sigdelset(&signal_mask_, SIGTERM);
+    sigdelset(&signal_mask_, SIGUSR1);
+
+    // Masking signals here ensure that after this point, we won't handle INT/TERM
+    // until after we call into ppoll()
+    signal(SIGINT, Daemon::SignalHandler);
+    signal(SIGTERM, Daemon::SignalHandler);
+    signal(SIGPIPE, Daemon::SignalHandler);
+    signal(SIGUSR1, Daemon::SignalHandler);
+
+    MaskAllSignalsExceptIntAndTerm();
+
+    if (FLAGS_socket_handoff) {
+        return user_server_.RunForSocketHandoff();
+    }
+    if (!FLAGS_no_socket) {
+        if (!user_server_.Start(FLAGS_socket)) {
+            return false;
+        }
+        return user_server_.Run();
+    }
+
+    for (int i = arg_start; i < argc; i++) {
+        auto parts = android::base::Split(argv[i], ",");
+        if (parts.size() != 4) {
+            LOG(ERROR) << "Malformed message, expected three sub-arguments.";
+            return false;
+        }
+        auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
+        if (!handler || !user_server_.StartHandler(handler)) {
+            return false;
+        }
+    }
+
+    // Skip the accept() call to avoid spurious log spam. The server will still
+    // run until all handlers have completed.
+    return user_server_.WaitForSocket();
+}
+
+bool Daemon::StartServerForDmSnapshot(int arg_start, int argc, char** argv) {
     sigfillset(&signal_mask_);
     sigdelset(&signal_mask_, SIGINT);
     sigdelset(&signal_mask_, SIGTERM);
@@ -97,11 +165,19 @@
 }
 
 void Daemon::Interrupt() {
-    server_.Interrupt();
+    if (IsUserspaceSnapshotsEnabled()) {
+        user_server_.Interrupt();
+    } else {
+        server_.Interrupt();
+    }
 }
 
 void Daemon::ReceivedSocketSignal() {
-    server_.ReceivedSocketSignal();
+    if (IsUserspaceSnapshotsEnabled()) {
+        user_server_.ReceivedSocketSignal();
+    } else {
+        server_.ReceivedSocketSignal();
+    }
 }
 
 void Daemon::SignalHandler(int signal) {
@@ -135,9 +211,10 @@
 
     android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance();
 
-    if (!daemon.StartServer(argc, argv)) {
-        LOG(ERROR) << "Snapuserd daemon failed to start.";
+    if (!daemon.StartDaemon(argc, argv)) {
+        LOG(ERROR) << "Snapuserd daemon failed to start";
         exit(EXIT_FAILURE);
     }
+
     return 0;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
index b660ba2..cf3b917 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
@@ -19,7 +19,8 @@
 #include <string>
 #include <vector>
 
-#include "snapuserd_server.h"
+#include "dm-snapshot-merge/snapuserd_server.h"
+#include "user-space-merge/snapuserd_server.h"
 
 namespace android {
 namespace snapshot {
@@ -35,9 +36,13 @@
         return instance;
     }
 
-    bool StartServer(int argc, char** argv);
+    bool StartServerForDmSnapshot(int arg_start, int argc, char** argv);
+    bool StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv);
     void Interrupt();
     void ReceivedSocketSignal();
+    bool IsUserspaceSnapshotsEnabled();
+    bool IsDmSnapshotTestingEnabled();
+    bool StartDaemon(int argc, char** argv);
 
   private:
     // Signal mask used with ppoll()
@@ -47,6 +52,7 @@
     void operator=(Daemon const&) = delete;
 
     SnapuserdServer server_;
+    UserSnapshotServer user_server_;
     void MaskAllSignalsExceptIntAndTerm();
     void MaskAllSignals();
     static void SignalHandler(int signal);
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
new file mode 100644
index 0000000..95d95cd
--- /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 < kNumWorkerThreads; 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..1953316
--- /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_BUFFER_SZ = (1UL << 20);
+static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ);
+
+static constexpr int kNumWorkerThreads = 4;
+
+#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..1e300d2
--- /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_BUFFER_SZ.
+    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_BUFFER_SZ;
+    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_BUFFER_SZ, 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..fa055b7
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -0,0 +1,312 @@
+/*
+ * 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_BUFFER_SZ / BLOCK_SZ) * 8;
+    int num_ops_merged = 0;
+
+    while (!cowop_iter->Done()) {
+        int num_ops = PAYLOAD_BUFFER_SZ / 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...";
+        snapuserd_->MergeFailed();
+        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..9e8ccfb
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -0,0 +1,442 @@
+/*
+ * 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;
+
+    // This memcpy is important as metadata_buffer_ will be an unaligned address and will fault
+    // on 32-bit systems
+    std::unique_ptr<uint8_t[]> metadata_buffer =
+            std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+    memcpy(metadata_buffer.get(), metadata_buffer_, snapuserd_->GetBufferMetadataSize());
+
+    while (true) {
+        struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+                (char*)metadata_buffer.get() + metadata_offset);
+
+        // Done reading metadata
+        if (bm->new_block == 0 && bm->file_offset == 0) {
+            break;
+        }
+
+        loff_t buffer_offset = bm->file_offset - start_data_offset;
+        void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + buffer_offset);
+        read_ahead_buffer_map[bm->new_block] = bufptr;
+        num_ops += 1;
+        total_blocks_merged += 1;
+
+        metadata_offset += sizeof(struct ScratchMetadata);
+    }
+
+    // We are done re-constructing the mapping; however, we need to make sure
+    // all the COW operations to-be merged are present in the re-constructed
+    // mapping.
+    while (!RAIterDone()) {
+        const CowOperation* op = GetRAOpIter();
+        if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
+            num_ops -= 1;
+            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_BUFFER_SZ);
+}
+
+}  // 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 65%
copy from fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
copy to fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index 2f87557..a79e3e1 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 UserSnapshotServer::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() {
+UserSnapshotServer::~UserSnapshotServer() {
     // Close any client sockets that were added via AcceptClient().
     for (size_t i = 1; i < watched_fds_.size(); i++) {
         close(watched_fds_[i].fd);
     }
 }
 
-std::string SnapuserdServer::GetDaemonStatus() {
+std::string UserSnapshotServer::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 UserSnapshotServer::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 UserSnapshotServer::ShutdownThreads() {
+    terminating_ = true;
     JoinAllThreads();
 }
 
-DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd)
+UserSnapshotDmUserHandler::UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
     : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
 
-bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
+bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
     ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
     if (ret < 0) {
         PLOG(ERROR) << "Snapuserd:server: send() failed";
@@ -107,8 +109,8 @@
     return true;
 }
 
-bool SnapuserdServer::Recv(android::base::borrowed_fd fd, std::string* data) {
-    char msg[MAX_PACKET_SIZE];
+bool UserSnapshotServer::Recv(android::base::borrowed_fd fd, std::string* data) {
+    char msg[kMaxPacketSize];
     ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
     if (rv < 0) {
         PLOG(ERROR) << "recv failed";
@@ -118,25 +120,25 @@
     return true;
 }
 
-bool SnapuserdServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
+bool UserSnapshotServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
     const char delim = ',';
 
     std::vector<std::string> out;
     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 UserSnapshotServer::RunThread(std::shared_ptr<UserSnapshotDmUserHandler> 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 UserSnapshotServer::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 UserSnapshotServer::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 UserSnapshotServer::Run() {
     LOG(INFO) << "Now listening on snapuserd socket";
 
     while (!IsTerminating()) {
@@ -337,9 +406,9 @@
     return true;
 }
 
-void SnapuserdServer::JoinAllThreads() {
+void UserSnapshotServer::JoinAllThreads() {
     // Acquire the thread list within the lock.
-    std::vector<std::shared_ptr<DmUserHandler>> dm_users;
+    std::vector<std::shared_ptr<UserSnapshotDmUserHandler>> dm_users;
     {
         std::lock_guard<std::mutex> guard(lock_);
         dm_users = std::move(dm_users_);
@@ -352,14 +421,14 @@
     }
 }
 
-void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
+void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
     struct pollfd p = {};
     p.fd = fd.get();
     p.events = events;
     watched_fds_.emplace_back(std::move(p));
 }
 
-void SnapuserdServer::AcceptClient() {
+void UserSnapshotServer::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 UserSnapshotServer::HandleClient(android::base::borrowed_fd fd, int revents) {
     if (revents & POLLHUP) {
         LOG(DEBUG) << "Snapuserd client disconnected";
         return false;
@@ -386,16 +455,17 @@
     return true;
 }
 
-void SnapuserdServer::Interrupt() {
+void UserSnapshotServer::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<UserSnapshotDmUserHandler> UserSnapshotServer::AddHandler(
+        const std::string& misc_name, const std::string& cow_device_path,
+        const std::string& backing_device, const std::string& base_path_merge) {
+    auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
+                                                       base_path_merge);
     if (!snapuserd->InitCowDevice()) {
         LOG(ERROR) << "Failed to initialize Snapuserd";
         return nullptr;
@@ -406,7 +476,7 @@
         return nullptr;
     }
 
-    auto handler = std::make_shared<DmUserHandler>(snapuserd);
+    auto handler = std::make_shared<UserSnapshotDmUserHandler>(snapuserd);
     {
         std::lock_guard<std::mutex> lock(lock_);
         if (FindHandler(&lock, misc_name) != dm_users_.end()) {
@@ -418,7 +488,7 @@
     return handler;
 }
 
-bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
+bool UserSnapshotServer::StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
     if (handler->snapuserd()->IsAttached()) {
         LOG(ERROR) << "Handler already attached";
         return false;
@@ -426,12 +496,22 @@
 
     handler->snapuserd()->AttachControlDevice();
 
-    handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler));
+    handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler));
     return true;
 }
 
-auto SnapuserdServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                  const std::string& misc_name) -> HandlerList::iterator {
+bool UserSnapshotServer::StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
+    if (!handler->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
+        return false;
+    }
+
+    handler->snapuserd()->InitiateMerge();
+    return true;
+}
+
+auto UserSnapshotServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                     const std::string& misc_name) -> HandlerList::iterator {
     CHECK(proof_of_lock);
 
     for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@@ -442,8 +522,53 @@
     return dm_users_.end();
 }
 
-bool SnapuserdServer::RemoveAndJoinHandler(const std::string& misc_name) {
-    std::shared_ptr<DmUserHandler> handler;
+void UserSnapshotServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
+    CHECK(proof_of_lock);
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        if (!(*iter)->ThreadTerminated()) {
+            (*iter)->snapuserd()->NotifyIOTerminated();
+        }
+    }
+}
+
+std::string UserSnapshotServer::GetMergeStatus(
+        const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
+    return handler->snapuserd()->GetMergeStatus();
+}
+
+double UserSnapshotServer::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 UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) {
+    std::shared_ptr<UserSnapshotDmUserHandler> handler;
     {
         std::lock_guard<std::mutex> lock(lock_);
 
@@ -463,7 +588,7 @@
     return true;
 }
 
-bool SnapuserdServer::WaitForSocket() {
+bool UserSnapshotServer::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 UserSnapshotServer::RunForSocketHandoff() {
     unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
     if (proxy_fd < 0) {
         PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
new file mode 100644
index 0000000..c645456
--- /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 kMaxPacketSize = 512;
+
+enum class DaemonOps {
+    INIT,
+    START,
+    QUERY,
+    STOP,
+    DELETE,
+    DETACH,
+    SUPPORTS,
+    INITIATE,
+    PERCENTAGE,
+    GETSTATUS,
+    INVALID,
+};
+
+class UserSnapshotDmUserHandler {
+  public:
+    explicit UserSnapshotDmUserHandler(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 UserSnapshotServer {
+  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<UserSnapshotDmUserHandler>>;
+    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<UserSnapshotDmUserHandler> handler);
+    void JoinAllThreads();
+    bool StartWithSocket(bool start_listening);
+
+    // Find a UserSnapshotDmUserHandler 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:
+    UserSnapshotServer() { terminating_ = false; }
+    ~UserSnapshotServer();
+
+    bool Start(const std::string& socketname);
+    bool Run();
+    void Interrupt();
+    bool RunForSocketHandoff();
+    bool WaitForSocket();
+
+    std::shared_ptr<UserSnapshotDmUserHandler> AddHandler(const std::string& misc_name,
+                                                          const std::string& cow_device_path,
+                                                          const std::string& backing_device,
+                                                          const std::string& base_path_merge);
+    bool StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
+    bool StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
+    std::string GetMergeStatus(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
+
+    void SetTerminating() { terminating_ = true; }
+    void ReceivedSocketSignal() { received_socket_signal_ = true; }
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
new file mode 100644
index 0000000..1c3e04b
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -0,0 +1,861 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <linux/memfd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <fs_mgr/file_wait.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <libdm/loop_control.h>
+#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_client.h>
+#include <storage_literals/storage_literals.h>
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android::storage_literals;
+using android::base::unique_fd;
+using LoopDevice = android::dm::LoopDevice;
+using namespace std::chrono_literals;
+using namespace android::dm;
+using namespace std;
+
+static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
+
+class Tempdevice {
+  public:
+    Tempdevice(const std::string& name, const DmTable& table)
+        : dm_(DeviceMapper::Instance()), name_(name), valid_(false) {
+        valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5));
+    }
+    Tempdevice(Tempdevice&& other) noexcept
+        : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) {
+        other.valid_ = false;
+    }
+    ~Tempdevice() {
+        if (valid_) {
+            dm_.DeleteDevice(name_);
+        }
+    }
+    bool Destroy() {
+        if (!valid_) {
+            return false;
+        }
+        valid_ = false;
+        return dm_.DeleteDevice(name_);
+    }
+    const std::string& path() const { return path_; }
+    const std::string& name() const { return name_; }
+    bool valid() const { return valid_; }
+
+    Tempdevice(const Tempdevice&) = delete;
+    Tempdevice& operator=(const Tempdevice&) = delete;
+
+    Tempdevice& operator=(Tempdevice&& other) noexcept {
+        name_ = other.name_;
+        valid_ = other.valid_;
+        other.valid_ = false;
+        return *this;
+    }
+
+  private:
+    DeviceMapper& dm_;
+    std::string name_;
+    std::string path_;
+    bool valid_;
+};
+
+class SnapuserTest final {
+  public:
+    bool Setup();
+    bool SetupOrderedOps();
+    bool SetupOrderedOpsInverted();
+    bool SetupCopyOverlap_1();
+    bool SetupCopyOverlap_2();
+    bool Merge();
+    void ValidateMerge();
+    void ReadSnapshotDeviceAndValidate();
+    void Shutdown();
+    void MergeInterrupt();
+    void MergeInterruptFixed(int duration);
+    void MergeInterruptRandomly(int max_duration);
+    void StartMerge();
+    void CheckMergeCompletion();
+
+    static const uint64_t kSectorSize = 512;
+
+  private:
+    void SetupImpl();
+
+    void SimulateDaemonRestart();
+
+    void CreateCowDevice();
+    void CreateCowDeviceOrderedOps();
+    void CreateCowDeviceOrderedOpsInverted();
+    void CreateCowDeviceWithCopyOverlap_1();
+    void CreateCowDeviceWithCopyOverlap_2();
+    bool SetupDaemon();
+    void CreateBaseDevice();
+    void InitCowDevice();
+    void SetDeviceControlName();
+    void InitDaemon();
+    void CreateDmUserDevice();
+    void StartSnapuserdDaemon();
+
+    unique_ptr<LoopDevice> base_loop_;
+    unique_ptr<Tempdevice> dmuser_dev_;
+
+    std::string system_device_ctrl_name_;
+    std::string system_device_name_;
+
+    unique_fd base_fd_;
+    std::unique_ptr<TemporaryFile> cow_system_;
+    std::unique_ptr<SnapuserdClient> client_;
+    std::unique_ptr<uint8_t[]> orig_buffer_;
+    std::unique_ptr<uint8_t[]> merged_buffer_;
+    bool setup_ok_ = false;
+    bool merge_ok_ = false;
+    size_t size_ = 100_MiB;
+    int cow_num_sectors_;
+    int total_base_size_;
+};
+
+static unique_fd CreateTempFile(const std::string& name, size_t size) {
+    unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING));
+    if (fd < 0) {
+        return {};
+    }
+    if (size) {
+        if (ftruncate(fd, size) < 0) {
+            perror("ftruncate");
+            return {};
+        }
+        if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
+            perror("fcntl");
+            return {};
+        }
+    }
+    return fd;
+}
+
+void SnapuserTest::Shutdown() {
+    ASSERT_TRUE(dmuser_dev_->Destroy());
+
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
+    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
+    ASSERT_TRUE(client_->DetachSnapuserd());
+}
+
+bool SnapuserTest::Setup() {
+    SetupImpl();
+    return setup_ok_;
+}
+
+bool SnapuserTest::SetupOrderedOps() {
+    CreateBaseDevice();
+    CreateCowDeviceOrderedOps();
+    return SetupDaemon();
+}
+
+bool SnapuserTest::SetupOrderedOpsInverted() {
+    CreateBaseDevice();
+    CreateCowDeviceOrderedOpsInverted();
+    return SetupDaemon();
+}
+
+bool SnapuserTest::SetupCopyOverlap_1() {
+    CreateBaseDevice();
+    CreateCowDeviceWithCopyOverlap_1();
+    return SetupDaemon();
+}
+
+bool SnapuserTest::SetupCopyOverlap_2() {
+    CreateBaseDevice();
+    CreateCowDeviceWithCopyOverlap_2();
+    return SetupDaemon();
+}
+
+bool SnapuserTest::SetupDaemon() {
+    SetDeviceControlName();
+
+    StartSnapuserdDaemon();
+
+    CreateDmUserDevice();
+    InitCowDevice();
+    InitDaemon();
+
+    setup_ok_ = true;
+
+    return setup_ok_;
+}
+
+void SnapuserTest::StartSnapuserdDaemon() {
+    pid_t pid = fork();
+    ASSERT_GE(pid, 0);
+    if (pid == 0) {
+        std::string arg0 = "/system/bin/snapuserd";
+        std::string arg1 = "-socket="s + kSnapuserdSocketTest;
+        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
+        ASSERT_GE(execv(arg0.c_str(), argv), 0);
+    } else {
+        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
+        ASSERT_NE(client_, nullptr);
+    }
+}
+
+void SnapuserTest::CreateBaseDevice() {
+    unique_fd rnd_fd;
+
+    total_base_size_ = (size_ * 5);
+    base_fd_ = CreateTempFile("base_device", total_base_size_);
+    ASSERT_GE(base_fd_, 0);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+    for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
+        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true);
+    }
+
+    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+
+    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
+    ASSERT_TRUE(base_loop_->valid());
+}
+
+void SnapuserTest::ReadSnapshotDeviceAndValidate() {
+    unique_fd fd(open(dmuser_dev_->path().c_str(), O_RDONLY));
+    ASSERT_GE(fd, 0);
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
+
+    // COPY
+    loff_t offset = 0;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0);
+
+    // ZERO
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
+
+    // XOR
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
+}
+
+void SnapuserTest::CreateCowDeviceWithCopyOverlap_2() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t blk_src_copy = 0;
+
+    // Create overlapping copy operations
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
+        x -= 1;
+        if (x == 1) {
+            break;
+        }
+        blk_src_copy += 1;
+    }
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // Merged operations required for validation
+    int block_size = 4096;
+    x = num_blocks;
+    loff_t src_offset = block_size;
+    loff_t dest_offset = 0;
+
+    while (1) {
+        memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset,
+                block_size);
+        x -= 1;
+        if (x == 1) {
+            break;
+        }
+        src_offset += block_size;
+        dest_offset += block_size;
+    }
+}
+
+void SnapuserTest::CreateCowDeviceWithCopyOverlap_1() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t blk_src_copy = num_blocks - 1;
+
+    // Create overlapping copy operations
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            ASSERT_EQ(blk_src_copy, 0);
+            break;
+        }
+        blk_src_copy -= 1;
+    }
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // Merged operations
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
+              true);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(
+                      base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
+              true);
+}
+
+void SnapuserTest::CreateCowDeviceOrderedOpsInverted() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+
+    // Fill random data
+    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
+                  true);
+
+        offset += 1_MiB;
+    }
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t blk_end_copy = num_blocks * 3;
+    size_t source_blk = num_blocks - 1;
+    size_t blk_src_copy = blk_end_copy - 1;
+    uint16_t xor_offset = 5;
+
+    size_t x = num_blocks;
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            break;
+        }
+        source_blk -= 1;
+        blk_src_copy -= 1;
+    }
+
+    for (size_t i = num_blocks; i > 0; i--) {
+        ASSERT_TRUE(writer.AddXorBlocks(num_blocks + i - 1,
+                                        &random_buffer_1_.get()[options.block_size * (i - 1)],
+                                        options.block_size, 2 * num_blocks + i - 1, xor_offset));
+    }
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+    // Merged Buffer
+    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
+    memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
+    }
+}
+
+void SnapuserTest::CreateCowDeviceOrderedOps() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+
+    // Fill random data
+    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
+                  true);
+
+        offset += 1_MiB;
+    }
+    memset(random_buffer_1_.get(), 0, size_);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t source_blk = 0;
+    size_t blk_src_copy = 2 * num_blocks;
+    uint16_t xor_offset = 5;
+
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+
+        x -= 1;
+        if (x == 0) {
+            break;
+        }
+        source_blk += 1;
+        blk_src_copy += 1;
+    }
+
+    ASSERT_TRUE(writer.AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
+                                    xor_offset));
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+    // Merged Buffer
+    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
+    memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
+    }
+}
+
+void SnapuserTest::CreateCowDevice() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+
+    // Fill random data
+    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
+                  true);
+
+        offset += 1_MiB;
+    }
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t blk_end_copy = num_blocks * 2;
+    size_t source_blk = num_blocks - 1;
+    size_t blk_src_copy = blk_end_copy - 1;
+
+    uint32_t sequence[num_blocks * 2];
+    // Sequence for Copy ops
+    for (int i = 0; i < num_blocks; i++) {
+        sequence[i] = num_blocks - 1 - i;
+    }
+    // Sequence for Xor ops
+    for (int i = 0; i < num_blocks; i++) {
+        sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
+    }
+    ASSERT_TRUE(writer.AddSequenceData(2 * num_blocks, sequence));
+
+    size_t x = num_blocks;
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            break;
+        }
+        source_blk -= 1;
+        blk_src_copy -= 1;
+    }
+
+    source_blk = num_blocks;
+    blk_src_copy = blk_end_copy;
+
+    ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+
+    size_t blk_zero_copy_start = source_blk + num_blocks;
+    size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
+
+    ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
+
+    size_t blk_random2_replace_start = blk_zero_copy_end;
+
+    ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
+
+    size_t blk_xor_start = blk_random2_replace_start + num_blocks;
+    size_t xor_offset = BLOCK_SZ / 2;
+    ASSERT_TRUE(writer.AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
+                                    xor_offset));
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    std::string zero_buffer(size_, 0);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true);
+    memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_,
+                                               size_ + xor_offset),
+              true);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[(size_ * 4) + i] =
+                (uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]);
+    }
+}
+
+void SnapuserTest::InitCowDevice() {
+    uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
+                                                  base_loop_->device(), base_loop_->device());
+    ASSERT_NE(num_sectors, 0);
+}
+
+void SnapuserTest::SetDeviceControlName() {
+    system_device_name_.clear();
+    system_device_ctrl_name_.clear();
+
+    std::string str(cow_system_->path);
+    std::size_t found = str.find_last_of("/\\");
+    ASSERT_NE(found, std::string::npos);
+    system_device_name_ = str.substr(found + 1);
+
+    system_device_ctrl_name_ = system_device_name_ + "-ctrl";
+}
+
+void SnapuserTest::CreateDmUserDevice() {
+    unique_fd fd(TEMP_FAILURE_RETRY(open(base_loop_->device().c_str(), O_RDONLY | O_CLOEXEC)));
+    ASSERT_TRUE(fd > 0);
+
+    uint64_t dev_sz = get_block_device_size(fd.get());
+    ASSERT_TRUE(dev_sz > 0);
+
+    cow_num_sectors_ = dev_sz >> 9;
+
+    DmTable dmuser_table;
+    ASSERT_TRUE(dmuser_table.AddTarget(
+            std::make_unique<DmTargetUser>(0, cow_num_sectors_, system_device_ctrl_name_)));
+    ASSERT_TRUE(dmuser_table.valid());
+
+    dmuser_dev_ = std::make_unique<Tempdevice>(system_device_name_, dmuser_table);
+    ASSERT_TRUE(dmuser_dev_->valid());
+    ASSERT_FALSE(dmuser_dev_->path().empty());
+
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
+}
+
+void SnapuserTest::InitDaemon() {
+    bool ok = client_->AttachDmUser(system_device_ctrl_name_);
+    ASSERT_TRUE(ok);
+}
+
+void SnapuserTest::CheckMergeCompletion() {
+    while (true) {
+        double percentage = client_->GetMergePercent();
+        if ((int)percentage == 100) {
+            break;
+        }
+
+        std::this_thread::sleep_for(1s);
+    }
+}
+
+void SnapuserTest::SetupImpl() {
+    CreateBaseDevice();
+    CreateCowDevice();
+
+    SetDeviceControlName();
+
+    StartSnapuserdDaemon();
+
+    CreateDmUserDevice();
+    InitCowDevice();
+    InitDaemon();
+
+    setup_ok_ = true;
+}
+
+bool SnapuserTest::Merge() {
+    StartMerge();
+    CheckMergeCompletion();
+    merge_ok_ = true;
+    return merge_ok_;
+}
+
+void SnapuserTest::StartMerge() {
+    bool ok = client_->InitiateMerge(system_device_ctrl_name_);
+    ASSERT_TRUE(ok);
+}
+
+void SnapuserTest::ValidateMerge() {
+    merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
+              true);
+    ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
+}
+
+void SnapuserTest::SimulateDaemonRestart() {
+    Shutdown();
+    std::this_thread::sleep_for(500ms);
+    SetDeviceControlName();
+    StartSnapuserdDaemon();
+    CreateDmUserDevice();
+    InitCowDevice();
+    InitDaemon();
+}
+
+void SnapuserTest::MergeInterruptRandomly(int max_duration) {
+    std::srand(std::time(nullptr));
+    StartMerge();
+
+    for (int i = 0; i < 20; i++) {
+        int duration = std::rand() % max_duration;
+        std::this_thread::sleep_for(std::chrono::milliseconds(duration));
+        SimulateDaemonRestart();
+        StartMerge();
+    }
+
+    SimulateDaemonRestart();
+    ASSERT_TRUE(Merge());
+}
+
+void SnapuserTest::MergeInterruptFixed(int duration) {
+    StartMerge();
+
+    for (int i = 0; i < 25; i++) {
+        std::this_thread::sleep_for(std::chrono::milliseconds(duration));
+        SimulateDaemonRestart();
+        StartMerge();
+    }
+
+    SimulateDaemonRestart();
+    ASSERT_TRUE(Merge());
+}
+
+void SnapuserTest::MergeInterrupt() {
+    // Interrupt merge at various intervals
+    StartMerge();
+    std::this_thread::sleep_for(250ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(250ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(150ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(100ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(800ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(600ms);
+    SimulateDaemonRestart();
+
+    ASSERT_TRUE(Merge());
+}
+
+TEST(Snapuserd_Test, Snapshot_IO_TEST) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.Setup());
+    // I/O before merge
+    harness.ReadSnapshotDeviceAndValidate();
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    // I/O after merge - daemon should read directly
+    // from base device
+    harness.ReadSnapshotDeviceAndValidate();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.Setup());
+    // Issue I/O before merge begins
+    std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
+    // Start the merge
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST_1) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.Setup());
+    // Start the merge
+    harness.StartMerge();
+    // Issue I/O in parallel when merge is in-progress
+    std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
+    harness.CheckMergeCompletion();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Resume) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.Setup());
+    harness.MergeInterrupt();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_1());
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_2());
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_1());
+    harness.MergeInterrupt();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Ordered) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupOrderedOps());
+    harness.MergeInterruptFixed(300);
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Ordered) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupOrderedOps());
+    harness.MergeInterruptRandomly(500);
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Inverted) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupOrderedOpsInverted());
+    harness.MergeInterruptFixed(50);
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Inverted) {
+    SnapuserTest harness;
+    ASSERT_TRUE(harness.SetupOrderedOpsInverted());
+    harness.MergeInterruptRandomly(50);
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+}  // namespace snapshot
+}  // namespace android
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
new file mode 100644
index 0000000..6dec1e2
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -0,0 +1,646 @@
+/*
+ * 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);
+
+        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/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 4a2af1c..89d6145 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -22,6 +22,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <fs_mgr/roots.h>
@@ -187,6 +188,10 @@
     return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
 }
 
+bool IsUserspaceSnapshotsEnabled() {
+    return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+}
+
 std::string GetOtherPartitionName(const std::string& name) {
     auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
     CHECK(suffix == "_a" || suffix == "_b");
@@ -195,5 +200,9 @@
     return name.substr(0, name.size() - suffix.size()) + other_suffix;
 }
 
+bool IsDmSnapshotTestingEnabled() {
+    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index 671de9d..a032b68 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -57,14 +57,14 @@
 // Automatically unmap a device upon deletion.
 struct AutoUnmapDevice : AutoDevice {
     // On destruct, delete |name| from device mapper.
-    AutoUnmapDevice(android::dm::DeviceMapper* dm, const std::string& name)
+    AutoUnmapDevice(android::dm::IDeviceMapper* dm, const std::string& name)
         : AutoDevice(name), dm_(dm) {}
     AutoUnmapDevice(AutoUnmapDevice&& other) = default;
     ~AutoUnmapDevice();
 
   private:
     DISALLOW_COPY_AND_ASSIGN(AutoUnmapDevice);
-    android::dm::DeviceMapper* dm_ = nullptr;
+    android::dm::IDeviceMapper* dm_ = nullptr;
 };
 
 // Automatically unmap an image upon deletion.
@@ -131,8 +131,11 @@
 
 bool IsCompressionEnabled();
 
+bool IsUserspaceSnapshotsEnabled();
+
+bool IsDmSnapshotTestingEnabled();
+
 // Swap the suffix of a partition name.
 std::string GetOtherPartitionName(const std::string& name);
-
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 94e1abb..eccb902 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -192,17 +192,13 @@
            lhs.nonremovable == rhs.nonremovable &&
            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 &&
            lhs.quota == rhs.quota &&
            lhs.avb == rhs.avb &&
            lhs.logical == rhs.logical &&
@@ -411,7 +407,7 @@
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     std::string fstab_contents = R"fs(
-source none0       swap   defaults      wait,check,nonremovable,recoveryonly,verifyatboot,verify
+source none0       swap   defaults      wait,check,nonremovable,recoveryonly
 source none1       swap   defaults      avb,noemulatedsd,notrim,formattable,nofail
 source none2       swap   defaults      first_stage_mount,latemount,quota,logical
 source none3       swap   defaults      checkpoint=block
@@ -432,8 +428,6 @@
         flags.check = true;
         flags.nonremovable = true;
         flags.recovery_only = true;
-        flags.verify_at_boot = true;
-        flags.verify = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
 
@@ -488,18 +482,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 +499,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 +517,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 +534,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 +563,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 +715,6 @@
     EXPECT_EQ(0, entry->zram_size);
 }
 
-TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ForceEncrypt) {
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
-    std::string fstab_contents = R"fs(
-source none0       swap   defaults      forceencrypt=/dir/key
-)fs";
-
-    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
-
-    Fstab fstab;
-    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_LE(1U, fstab.size());
-
-    auto entry = fstab.begin();
-    EXPECT_EQ("none0", entry->mount_point);
-
-    FstabEntry::FsMgrFlags flags = {};
-    flags.force_crypt = true;
-    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
-
-    EXPECT_EQ("/dir/key", entry->key_loc);
-}
-
-TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ForceFdeOrFbe) {
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
-    std::string fstab_contents = R"fs(
-source none0       swap   defaults      forcefdeorfbe=/dir/key
-)fs";
-
-    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
-
-    Fstab fstab;
-    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_LE(1U, fstab.size());
-
-    auto entry = fstab.begin();
-    EXPECT_EQ("none0", entry->mount_point);
-
-    FstabEntry::FsMgrFlags flags = {};
-    flags.force_fde_or_fbe = true;
-    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
-
-    EXPECT_EQ("/dir/key", entry->key_loc);
-    EXPECT_EQ("aes-256-xts:aes-256-cts", entry->encryption_options);
-}
-
 TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FileEncryption) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index a7f0c0e..0aedc58 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -29,7 +29,9 @@
     srcs: [
         "gatekeeperd.cpp",
     ],
-
+    defaults: [
+        "keymint_use_latest_hal_aidl_ndk_shared",
+    ],
     shared_libs: [
         "libbinder",
         "libbinder_ndk",
@@ -43,7 +45,6 @@
         "libhidlbase",
         "android.hardware.gatekeeper@1.0",
         "libgatekeeper_aidl",
-        "android.hardware.security.keymint-V1-ndk",
         "android.security.authorization-ndk",
     ],
 
diff --git a/healthd/Android.bp b/healthd/Android.bp
index ec47f68..24777c8 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",
@@ -159,8 +216,6 @@
 
     shared_libs: [
         // common
-        "android.hardware.health@2.0",
-        "android.hardware.health@2.1",
         "libbase",
         "libcutils",
         "libhidlbase",
@@ -174,6 +229,7 @@
     static_libs: [
         // common
         "android.hardware.health@1.0-convert",
+        "android.hardware.health-V1-ndk",
         "libbatterymonitor",
         "libcharger_sysprop",
         "libhealthd_charger_nops",
@@ -183,6 +239,7 @@
         // system charger only
         "libhealthd_draw",
         "libhealthd_charger",
+        "libhealthd_charger_ui",
         "libminui",
         "libsuspend",
     ],
@@ -196,6 +253,10 @@
         "charger.cpp",
         "charger_utils.cpp",
     ],
+    shared_libs: [
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
+    ],
 
     target: {
         recovery: {
@@ -209,6 +270,7 @@
             exclude_static_libs: [
                 "libhealthd_draw",
                 "libhealthd_charger",
+                "libhealthd_charger_ui",
                 "libminui",
                 "libsuspend",
             ],
@@ -220,6 +282,11 @@
     name: "charger_test",
     defaults: ["charger_defaults"],
     srcs: ["charger_test.cpp"],
+    static_libs: [
+        "android.hardware.health@1.0",
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
+    ],
 }
 
 cc_test {
@@ -230,6 +297,9 @@
         "healthd_mode_charger_test.cpp"
     ],
     static_libs: [
+        "android.hardware.health@1.0",
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
         "libgmock",
     ],
     test_suites: [
@@ -265,3 +335,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 50eee19..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());
@@ -46,14 +57,6 @@
 
 HealthdDraw::HealthdDraw(animation* anim)
     : kSplitScreen(get_split_screen()), kSplitOffset(get_split_offset()) {
-    int ret = gr_init();
-
-    if (ret < 0) {
-        LOGE("gr_init failed\n");
-        graphics_available = false;
-        return;
-    }
-
     graphics_available = true;
     sys_font = gr_sys_font();
     if (sys_font == nullptr) {
@@ -235,3 +238,11 @@
       LOGW("Charging, level unknown\n");
   }
 }
+
+std::unique_ptr<HealthdDraw> HealthdDraw::Create(animation *anim) {
+    if (gr_init() < 0) {
+        LOGE("gr_init failed\n");
+        return nullptr;
+    }
+    return std::unique_ptr<HealthdDraw>(new HealthdDraw(anim));
+}
diff --git a/healthd/healthd_draw.h b/healthd/healthd_draw.h
index 7c847bd..0b48ce8 100644
--- a/healthd/healthd_draw.h
+++ b/healthd/healthd_draw.h
@@ -26,8 +26,6 @@
 
 class HealthdDraw {
  public:
-  // Configures font using given animation.
-  HealthdDraw(animation* anim);
   virtual ~HealthdDraw();
 
   // Redraws screen.
@@ -36,6 +34,8 @@
   // Blanks screen if true, unblanks if false.
   virtual void blank_screen(bool blank);
 
+  static std::unique_ptr<HealthdDraw> Create(animation *anim);
+
  protected:
   virtual void clear_screen();
 
@@ -76,6 +76,10 @@
 
   // true if minui init'ed OK, false if minui init failed
   bool graphics_available;
+
+ private:
+  // Configures font using given animation.
+  HealthdDraw(animation* anim);
 };
 
 #endif  // HEALTHD_DRAW_H
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index e95efc0..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() {}
 
@@ -218,9 +219,7 @@
     char* ptr;
     size_t len;
 
-    LOGW("\n");
     LOGW("*************** LAST KMSG ***************\n");
-    LOGW("\n");
     const char* kmsg[] = {
         // clang-format off
         "/sys/fs/pstore/console-ramoops-0",
@@ -263,20 +262,21 @@
     }
 
 out:
-    LOGW("\n");
     LOGW("************* END LAST KMSG *************\n");
-    LOGW("\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) {
@@ -295,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;
@@ -309,28 +309,28 @@
     }
 
     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;
             }
         }
 
-        healthd_draw_.reset(new HealthdDraw(&batt_anim_));
+        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 */
@@ -340,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;
     }
 
@@ -354,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 &&
@@ -366,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;
@@ -397,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 &&
@@ -495,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();
             }
         }
     }
@@ -519,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.
@@ -550,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_);
@@ -560,7 +562,7 @@
     }
 }
 
-void Charger::Heartbeat() {
+void Charger::OnHeartbeat() {
     // charger* charger = &charger_state;
     int64_t now = curr_time_ms();
 
@@ -573,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;
@@ -629,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);
@@ -644,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");
@@ -675,7 +692,7 @@
     }
 }
 
-void Charger::Init(struct healthd_config* config) {
+void Charger::OnInit(struct healthd_config* config) {
     int ret;
     int i;
     int epollfd;
@@ -688,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;
@@ -733,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;
 }
@@ -776,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 a04d2db..66427dc 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -89,7 +89,19 @@
     "host_init_verifier.cpp",
 ]
 
-cc_defaults {
+soong_config_module_type {
+    name: "libinit_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT",
+    ],
+    properties: [
+        "cflags",
+    ],
+}
+
+libinit_cc_defaults {
     name: "init_defaults",
     sanitize: {
         misc_undefined: ["signed-integer-overflow"],
@@ -109,6 +121,7 @@
         "-DDUMP_ON_UMOUNT_FAILURE=0",
         "-DSHUTDOWN_ZERO_TIMEOUT=0",
         "-DINIT_FULL_SOURCES",
+        "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=0",
     ],
     product_variables: {
         debuggable: {
@@ -137,6 +150,14 @@
             cppflags: ["-DUSER_MODE_LINUX"],
         },
     },
+    soong_config_variables: {
+        PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT: {
+            cflags: [
+                "-UINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT",
+                "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=1",
+            ],
+        },
+    },
     static_libs: [
         "libavb",
         "libc++fs",
@@ -236,6 +257,7 @@
                 "init.rc",
                 "ueventd.rc",
                 "e2fsdroid",
+                "extra_free_kbytes.sh",
                 "make_f2fs",
                 "mke2fs",
                 "sload_f2fs",
@@ -299,7 +321,6 @@
         "first_stage_mount.cpp",
         "reboot_utils.cpp",
         "selabel.cpp",
-        "selinux.cpp",
         "service_utils.cpp",
         "snapuserd_transition.cpp",
         "switch_root.cpp",
@@ -314,23 +335,16 @@
         "libfec",
         "libfec_rs",
         "libsquashfs_utils",
-        "liblogwrap",
-        "libext4_utils",
         "libcrypto_utils",
-        "libsparse",
         "libavb",
-        "libkeyutils",
         "liblp",
         "libcutils",
         "libbase",
         "liblog",
         "libcrypto_static",
-        "libdl",
-        "libz",
         "libselinux",
         "libcap",
         "libgsi",
-        "libcom.android.sysprop.apex",
         "liblzma",
         "libunwindstack_no_dex",
         "libbacktrace_no_dex",
@@ -434,6 +448,7 @@
 
     srcs: [
         "devices_test.cpp",
+        "epoll_test.cpp",
         "firmware_handler_test.cpp",
         "init_test.cpp",
         "keychords_test.cpp",
@@ -441,6 +456,7 @@
         "persistent_properties_test.cpp",
         "property_service_test.cpp",
         "property_type_test.cpp",
+        "reboot_test.cpp",
         "rlimit_parser_test.cpp",
         "service_test.cpp",
         "subcontext_test.cpp",
@@ -552,3 +568,8 @@
         },
     },
 }
+
+sh_binary {
+    name: "extra_free_kbytes.sh",
+    src: "extra_free_kbytes.sh",
+}
diff --git a/init/README.md b/init/README.md
index f447ab2..c102b1f 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
@@ -613,9 +641,10 @@
   configurations. Intended to be used only once when apexd notifies the mount
   event by setting `apexd.status` to ready.
 
-`restart <service>`
+`restart [--only-if-running] <service>`
 > Stops and restarts a running service, does nothing if the service is currently
-  restarting, otherwise, it just starts the service.
+  restarting, otherwise, it just starts the service. If "--only-if-running" is
+  specified, the service is only restarted if it is already running.
 
 `restorecon <path> [ <path>\* ]`
 > Restore the file named by _path_ to the security context specified
@@ -693,10 +722,13 @@
   fstab.${ro.hardware} or fstab.${ro.hardware.platform} will be scanned for
   under /odm/etc, /vendor/etc, or / at runtime, in that order.
 
-`verity_update_state <mount-point>`
+`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/TEST_MAPPING b/init/TEST_MAPPING
new file mode 100644
index 0000000..03b9eaa
--- /dev/null
+++ b/init/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsInitTestCases"
+    },
+    {
+      "name": "init_kill_services_test"
+    },
+    {
+      "name": "MicrodroidHostTestCases"
+    }
+  ]
+}
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 7633d9c..cc445be 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>
@@ -87,6 +88,7 @@
 using namespace std::literals::string_literals;
 
 using android::base::Basename;
+using android::base::ResultError;
 using android::base::SetProperty;
 using android::base::Split;
 using android::base::StartsWith;
@@ -115,7 +117,7 @@
                         android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
 
     template <typename T>
-    operator android::base::expected<T, ResultError>() {
+    operator android::base::expected<T, ResultError<int>>() {
         if (ignore_error_) {
             return {};
         }
@@ -129,7 +131,7 @@
     }
 
   private:
-    Error error_;
+    Error<> error_;
     bool ignore_error_;
 };
 
@@ -175,28 +177,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 +187,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 +575,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 {};
@@ -809,8 +775,21 @@
 }
 
 static Result<void> do_restart(const BuiltinArguments& args) {
-    Service* svc = ServiceList::GetInstance().FindService(args[1]);
-    if (!svc) return Error() << "service " << args[1] << " not found";
+    bool only_if_running = false;
+    if (args.size() == 3) {
+        if (args[1] == "--only-if-running") {
+            only_if_running = true;
+        } else {
+            return Error() << "Unknown argument to restart: " << args[1];
+        }
+    }
+
+    const auto& classname = args[args.size() - 1];
+    Service* svc = ServiceList::GetInstance().FindService(classname);
+    if (!svc) return Error() << "service " << classname << " not found";
+    if (only_if_running && !svc->IsRunning()) {
+        return {};
+    }
     svc->Restart();
     return {};
 }
@@ -894,9 +873,11 @@
         std::string partition = entry.mount_point == "/" ? "system" : Basename(entry.mount_point);
         SetProperty("partition." + partition + ".verified", std::to_string(mode));
 
-        std::string hash_alg = fs_mgr_get_hashtree_algorithm(entry);
-        if (!hash_alg.empty()) {
-            SetProperty("partition." + partition + ".verified.hash_alg", hash_alg);
+        auto hashtree_info = fs_mgr_get_hashtree_info(entry);
+        if (hashtree_info) {
+            SetProperty("partition." + partition + ".verified.hash_alg", hashtree_info->algorithm);
+            SetProperty("partition." + partition + ".verified.root_digest",
+                        hashtree_info->root_digest);
         }
     }
 
@@ -1115,17 +1096,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");
@@ -1311,7 +1281,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);
@@ -1328,17 +1298,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) {
@@ -1411,10 +1430,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}}},
@@ -1450,7 +1467,7 @@
         {"update_linker_config",    {0,     0,    {false,  do_update_linker_config}}},
         {"readahead",               {1,     2,    {true,   do_readahead}}},
         {"remount_userdata",        {0,     0,    {false,  do_remount_userdata}}},
-        {"restart",                 {1,     1,    {false,  do_restart}}},
+        {"restart",                 {1,     2,    {false,  do_restart}}},
         {"restorecon",              {1,     kMax, {true,   do_restorecon}}},
         {"restorecon_recursive",    {1,     kMax, {true,   do_restorecon_recursive}}},
         {"rm",                      {1,     1,    {true,   do_rm}}},
diff --git a/init/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/extra_free_kbytes.sh b/init/extra_free_kbytes.sh
new file mode 100755
index 0000000..aeaa912
--- /dev/null
+++ b/init/extra_free_kbytes.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+
+# Script implements watermark_scale calculation which results in the same low
+# watermark as if extra_free_kbytes tunable were to be used.
+#
+# Usage: extra_free_kbytes.sh <extra_free_kbytes value>
+#
+# extra_free_kbytes is distributed between zones based on
+# zone.managed_pages/vm_total_pages ratio, where vm_total_pages is the sum of
+# zone.managed_pages for all zones (zone.high used in this calculation is 0
+# when this is calculated). Therefore for each zone its share is calculated as:
+#
+# extra_free_pages = extra_free_kbytes / page_size
+# extra_share = extra_free_pages * managed_pages / vm_total_pages
+#
+# This extra_share is added to the low and high watermarks:
+#
+# low = min + max(min / 4, managed_pages * (watermark_scale / 10000)) + extra_share
+# high = min + 2 * max(min / 4, managed_pages * (watermark_scale / 10000)) + extra_share
+#
+# Because Android uses extra_free_kbytes to adjust the low watermark, we ignore
+# the difference in how watermark_scale and extra_free_kbytes affect the high
+# watermark and will match the low watermark only.
+#
+# To eliminate extra_share and compansate the difference with watermark_scale,
+# a new watermark_scale_new is calculated as:
+#
+# (1) max(min / 4, managed_pages * (watermark_scale / 10000)) + extra_share =
+#   max(min / 4, managed_pages * (watermark_scale_new / 10000))
+#
+# Two cases to consider:
+# A. managed_pages * (watermark_scale / 10000) > min / 4
+# The formula (1) becomes:
+#
+# managed_pages * (watermark_scale / 10000) + extra_share =
+#   managed_pages * (watermark_scale_new / 10000)
+#
+# after simplifying and substituting extra_share formula becomes:
+#
+# (2) watermark_scale_new = watermark_scale + extra_free_pages / vm_total_pages * 10000
+#
+# B. managed_pages * (watermark_scale / 10000) < min / 4
+# The formula (1) becomes:
+#
+# min / 4 + extra_share = max(min / 4, managed_pages * (watermark_scale_new / 10000))
+#
+# after calculating watermark_scale_new, if (managed_pages * (watermark_scale_new / 10000))
+# is still smaller than min / 4 then we can't compensate extra_share with
+# watermark_scale anyway. Therefore calculation becomes:
+#
+# watermark_scale_new = (min / 4 + extra_share) / managed_pages * 10000
+#
+# after simplifying and substituting extra_share formula becomes:
+#
+# (3) watermark_scale_new = (min / 4) * 10000 / managed_pages + extra_free_pages / vm_total_pages * 10000
+#
+# After defining watermark_delta = extra_free_pages / vm_total_pages * 10000:
+#
+# if (managed_pages * (watermark_scale / 10000) > min / 4)
+#     watermark_scale_new = watermark_scale + watermark_delta
+# else
+#     watermark_scale_new = (min / 4) * 10000 / managed_pages + watermark_delta
+#
+
+if [ "$#" -ne 1 ]
+then
+    echo "Usage: $0 <extra_free_kbytes value>"
+    exit
+fi
+
+extra_free_kbytes=$1
+
+# if extra_free_kbytes knob exists, use it and exit
+if [ -e /proc/sys/vm/extra_free_kbytes ]
+then
+    echo $extra_free_kbytes > /proc/sys/vm/extra_free_kbytes
+    exit
+fi
+
+watermark_scale=`cat /proc/sys/vm/watermark_scale_factor`
+
+# convert extra_free_kbytes to pages
+page_size=$(getconf PAGESIZE)
+page_size_kb=$((page_size/1024))
+extra_free_pg=$((extra_free_kbytes/page_size_kb))
+
+managed=($(grep managed /proc/zoneinfo | awk '{print $2}'))
+length=${#managed[@]}
+min=($(grep "min" /proc/zoneinfo | awk '{print $2}'))
+
+# calculate vm_total_pages.
+# WARNING: if the final low watermark differs from the original, the source of
+# the error is likely vm_total_pages which is impossible to get exact from the
+# userspace. Grep for "Total pages" in the kernel logs to see the actual
+# vm_total_pages and plug it in the calculation to confirm the source of the
+# error. Error caused by this inaccuracy is normally within 1% range.
+vm_total_pages=0
+i=0
+while [ $i -lt $length ]
+do
+    vm_total_pages=$((vm_total_pages + managed[i]))
+    i=$((i+1))
+done
+
+# calculate watermark_scale_new for each zone and choose the max
+max_watermark_scale=0
+i=0
+while [ $i -lt $length ]
+do
+    # skip unmanaged zones
+    if [ ${managed[i]} -eq 0 ]
+    then
+        i=$((i+1))
+        continue
+    fi
+
+    base_margin=$((min[i] / 4))
+    calc_margin=$(echo "${managed[i]} * $watermark_scale / 10000" | bc)
+    # round the value by adding 0.5 and truncating the decimal part
+    watermark_delta=$(echo "x=($extra_free_pg / ($vm_total_pages / 10000) + 0.5); scale = 0; x/1" | bc -l)
+    if [ $calc_margin -gt $base_margin ]
+    then
+        watermark_scale_new=$(echo "$watermark_scale + $watermark_delta" | bc)
+    else
+        watermark_scale_new=$(echo "$base_margin / (${managed[i]} / 10000) + $watermark_delta" | bc)
+    fi
+
+    if [ $max_watermark_scale -lt $watermark_scale_new ]
+    then
+        max_watermark_scale=$watermark_scale_new
+    fi
+
+    i=$((i+1))
+done
+
+echo $max_watermark_scale > /proc/sys/vm/watermark_scale_factor
diff --git a/init/first_stage_console.cpp b/init/first_stage_console.cpp
index e2ea0ab..67cac19 100644
--- a/init/first_stage_console.cpp
+++ b/init/first_stage_console.cpp
@@ -85,7 +85,10 @@
 
 void StartConsole(const std::string& cmdline) {
     bool console = KernelConsolePresent(cmdline);
+    // Use a simple sigchld handler -- first_stage_console doesn't need to track or log zombies
+    const struct sigaction chld_act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDWAIT };
 
+    sigaction(SIGCHLD, &chld_act, nullptr);
     pid_t pid = fork();
     if (pid != 0) {
         int status;
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 78e5b60..c7b7b0c 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -330,14 +330,21 @@
     // If "/force_debuggable" is present, the second-stage init will use a userdebug
     // sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked.
     if (access("/force_debuggable", F_OK) == 0) {
+        constexpr const char adb_debug_prop_src[] = "/adb_debug.prop";
+        constexpr const char userdebug_plat_sepolicy_cil_src[] = "/userdebug_plat_sepolicy.cil";
         std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.
-        if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
-            !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
-            LOG(ERROR) << "Failed to setup debug ramdisk";
-        } else {
-            // setenv for second-stage init to read above kDebugRamdisk* files.
-            setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
+        if (access(adb_debug_prop_src, F_OK) == 0 &&
+            !fs::copy_file(adb_debug_prop_src, kDebugRamdiskProp, ec)) {
+            LOG(WARNING) << "Can't copy " << adb_debug_prop_src << " to " << kDebugRamdiskProp
+                         << ": " << ec.message();
         }
+        if (access(userdebug_plat_sepolicy_cil_src, F_OK) == 0 &&
+            !fs::copy_file(userdebug_plat_sepolicy_cil_src, kDebugRamdiskSEPolicy, ec)) {
+            LOG(WARNING) << "Can't copy " << userdebug_plat_sepolicy_cil_src << " to "
+                         << kDebugRamdiskSEPolicy << ": " << ec.message();
+        }
+        // setenv for second-stage init to read above kDebugRamdisk* files.
+        setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
     }
 
     if (ForceNormalBoot(cmdline, bootconfig)) {
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index f5c10bb..042988e 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -81,8 +81,7 @@
     FirstStageMount(Fstab fstab);
     virtual ~FirstStageMount() = default;
 
-    // The factory method to create either FirstStageMountVBootV1 or FirstStageMountVBootV2
-    // based on device tree configurations.
+    // The factory method to create a FirstStageMountVBootV2 instance.
     static Result<std::unique_ptr<FirstStageMount>> Create();
     bool DoCreateDevices();    // Creates devices and logical partitions from storage devices
     bool DoFirstStageMount();  // Mounts fstab entries read from device tree.
@@ -125,16 +124,6 @@
     std::map<std::string, std::vector<std::string>> preload_avb_key_blobs_;
 };
 
-class FirstStageMountVBootV1 : public FirstStageMount {
-  public:
-    FirstStageMountVBootV1(Fstab fstab) : FirstStageMount(std::move(fstab)) {}
-    ~FirstStageMountVBootV1() override = default;
-
-  protected:
-    bool GetDmVerityDevices(std::set<std::string>* devices) override;
-    bool SetUpDmVerity(FstabEntry* fstab_entry) override;
-};
-
 class FirstStageMountVBootV2 : public FirstStageMount {
   public:
     friend void SetInitAvbVersionInRecovery();
@@ -202,8 +191,6 @@
     auto& dm = android::dm::DeviceMapper::Instance();
     if (dm.GetState("vroot") != android::dm::DmDeviceState::INVALID) {
         root_entry->fs_mgr_flags.avb = true;
-    } else {
-        root_entry->fs_mgr_flags.verify = true;
     }
     return true;
 }
@@ -243,11 +230,7 @@
         return fstab.error();
     }
 
-    if (IsDtVbmetaCompatible(*fstab)) {
-        return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
-    } else {
-        return std::make_unique<FirstStageMountVBootV1>(std::move(*fstab));
-    }
+    return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
 }
 
 bool FirstStageMount::DoCreateDevices() {
@@ -391,7 +374,11 @@
 
     use_snapuserd_ = sm->IsSnapuserdRequired();
     if (use_snapuserd_) {
-        LaunchFirstStageSnapuserd();
+        if (sm->UpdateUsesUserSnapshots()) {
+            LaunchFirstStageSnapuserd(SnapshotDriver::DM_USER);
+        } else {
+            LaunchFirstStageSnapuserd(SnapshotDriver::DM_SNAPSHOT);
+        }
     }
 
     sm->SetUeventRegenCallback([this](const std::string& device) -> bool {
@@ -675,56 +662,6 @@
     TransformFstabForDsu(&fstab_, active_dsu, dsu_partitions);
 }
 
-bool FirstStageMountVBootV1::GetDmVerityDevices(std::set<std::string>* devices) {
-    need_dm_verity_ = false;
-
-    for (const auto& fstab_entry : fstab_) {
-        // Don't allow verifyatboot in the first stage.
-        if (fstab_entry.fs_mgr_flags.verify_at_boot) {
-            LOG(ERROR) << "Partitions can't be verified at boot";
-            return false;
-        }
-        // Checks for verified partitions.
-        if (fstab_entry.fs_mgr_flags.verify) {
-            need_dm_verity_ = true;
-        }
-    }
-
-    // Includes the partition names of fstab records.
-    // Notes that fstab_rec->blk_device has A/B suffix updated by fs_mgr when A/B is used.
-    for (const auto& fstab_entry : fstab_) {
-        // Skip pseudo filesystems.
-        if (fstab_entry.fs_type == "overlay") {
-            continue;
-        }
-        if (!fstab_entry.fs_mgr_flags.logical) {
-            devices->emplace(basename(fstab_entry.blk_device.c_str()));
-        }
-    }
-
-    return true;
-}
-
-bool FirstStageMountVBootV1::SetUpDmVerity(FstabEntry* fstab_entry) {
-    if (fstab_entry->fs_mgr_flags.verify) {
-        int ret = fs_mgr_setup_verity(fstab_entry, false /* wait_for_verity_dev */);
-        switch (ret) {
-            case FS_MGR_SETUP_VERITY_SKIPPED:
-            case FS_MGR_SETUP_VERITY_DISABLED:
-                LOG(INFO) << "Verity disabled/skipped for '" << fstab_entry->mount_point << "'";
-                return true;
-            case FS_MGR_SETUP_VERITY_SUCCESS:
-                // The exact block device name (fstab_rec->blk_device) is changed to
-                // "/dev/block/dm-XX". Needs to create it because ueventd isn't started in init
-                // first stage.
-                return block_dev_init_.InitDmDevice(fstab_entry->blk_device);
-            default:
-                return false;
-        }
-    }
-    return true;  // Returns true to mount the partition.
-}
-
 // First retrieve any vbmeta partitions from device tree (legacy) then read through the fstab
 // for any further vbmeta partitions.
 FirstStageMountVBootV2::FirstStageMountVBootV2(Fstab fstab)
diff --git a/init/host_builtin_map.py b/init/host_builtin_map.py
index 6afcb17..41c86ac 100755
--- a/init/host_builtin_map.py
+++ b/init/host_builtin_map.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 """Generates the builtins map to be used by host_init_verifier.
 
 It copies the builtin function map from builtins.cpp, then replaces do_xxx() functions with the
@@ -39,8 +39,7 @@
   match = DO_REGEX.match(line)
   if match:
     if match.group(1) in check_functions:
-      print line.replace('do_', 'check_'),
+      line = line.replace('do_', 'check_')
     else:
-      print FUNCTION_REGEX.sub('check_stub', line),
-  else:
-    print line,
+      line = FUNCTION_REGEX.sub('check_stub', line)
+  print(line, end=' ')
diff --git a/init/init.cpp b/init/init.cpp
index bde8e04..e3596cb 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -27,6 +27,7 @@
 #include <sys/mount.h>
 #include <sys/signalfd.h>
 #include <sys/types.h>
+#include <sys/utsname.h>
 #include <unistd.h>
 
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
@@ -554,6 +555,19 @@
     }
 }
 
+/// Set ro.kernel.version property to contain the major.minor pair as returned
+/// by uname(2).
+static void SetKernelVersion() {
+    struct utsname uts;
+    unsigned int major, minor;
+
+    if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
+        LOG(ERROR) << "Could not parse the kernel version from uname";
+        return;
+    }
+    SetProperty("ro.kernel.version", android::base::StringPrintf("%u.%u", major, minor));
+}
+
 static void HandleSigtermSignal(const signalfd_siginfo& siginfo) {
     if (siginfo.ssi_pid != 0) {
         // Drop any userspace SIGTERM requests.
@@ -858,6 +872,7 @@
     export_oem_lock_status();
     MountHandler mount_handler(&epoll);
     SetUsbController();
+    SetKernelVersion();
 
     const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
     Action::set_function_map(&function_map);
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index 593e5ae..bce1cc3 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -82,11 +82,6 @@
     return updatable;
 }
 
-static bool IsMicrodroid() {
-    static bool is_microdroid = android::base::GetProperty("ro.hardware", "") == "microdroid";
-    return is_microdroid;
-}
-
 // In case we have two sets of APEXes (non-updatable, updatable), we need two separate mount
 // namespaces.
 static bool NeedsTwoMountNamespaces() {
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 2d67bf5..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,6 +1018,39 @@
     }
 }
 
+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";
+
+    // 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 << ")";
+    }
+}
+
 void PropertyLoadBootDefaults() {
     // We read the properties and their values into a map, in order to always allow properties
     // loaded in the later property files to override the properties in loaded in the earlier
@@ -1102,6 +1136,7 @@
     property_derive_build_fingerprint();
     property_derive_legacy_build_fingerprint();
     property_initialize_ro_cpu_abilist();
+    property_initialize_ro_vendor_api_level();
 
     update_sys_usb_config();
 }
@@ -1140,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) {
@@ -1158,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/reboot.cpp b/init/reboot.cpp
index 0e788e4..6aa9912 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -550,8 +550,8 @@
 
 // Like StopServices, but also logs all the services that failed to stop after the provided timeout.
 // Returns number of violators.
-static int StopServicesAndLogViolations(const std::set<std::string>& services,
-                                        std::chrono::milliseconds timeout, bool terminate) {
+int StopServicesAndLogViolations(const std::set<std::string>& services,
+                                 std::chrono::milliseconds timeout, bool terminate) {
     StopServices(services, timeout, terminate);
     int still_running = 0;
     for (const auto& s : ServiceList::GetInstance()) {
@@ -639,6 +639,7 @@
         abort();
     }
 
+    bool do_shutdown_animation = GetBoolProperty("ro.init.shutdown_animation", false);
     // watchdogd is a vendor specific component but should be alive to complete shutdown safely.
     const std::set<std::string> to_starts{"watchdogd"};
     std::set<std::string> stop_first;
@@ -652,6 +653,8 @@
                            << "': " << result.error();
             }
             s->SetShutdownCritical();
+        } else if (do_shutdown_animation) {
+            continue;
         } else if (s->IsShutdownCritical()) {
             // Start shutdown critical service if not started.
             if (auto result = s->Start(); !result.ok()) {
@@ -664,14 +667,13 @@
     }
 
     // remaining operations (specifically fsck) may take a substantial duration
-    if (cmd == ANDROID_RB_POWEROFF || is_thermal_shutdown) {
+    if (!do_shutdown_animation && (cmd == ANDROID_RB_POWEROFF || is_thermal_shutdown)) {
         TurnOffBacklight();
     }
 
     Service* boot_anim = ServiceList::GetInstance().FindService("bootanim");
     Service* surface_flinger = ServiceList::GetInstance().FindService("surfaceflinger");
     if (boot_anim != nullptr && surface_flinger != nullptr && surface_flinger->IsRunning()) {
-        bool do_shutdown_animation = GetBoolProperty("ro.init.shutdown_animation", false);
 
         if (do_shutdown_animation) {
             SetProperty("service.bootanim.exit", "0");
@@ -1035,6 +1037,20 @@
                         return;
                     }
                 }
+            } else if (reboot_target == "quiescent") {
+                bootloader_message boot = {};
+                if (std::string err; !read_bootloader_message(&boot, &err)) {
+                    LOG(ERROR) << "Failed to read bootloader message: " << err;
+                }
+                // Update the boot command field if it's empty, and preserve
+                // the other arguments in the bootloader message.
+                if (!CommandIsPresent(&boot)) {
+                    strlcpy(boot.command, "boot-quiescent", sizeof(boot.command));
+                    if (std::string err; !write_bootloader_message(boot, &err)) {
+                        LOG(ERROR) << "Failed to set bootloader message: " << err;
+                        return;
+                    }
+                }
             } else if (reboot_target == "sideload" || reboot_target == "sideload-auto-reboot" ||
                        reboot_target == "fastboot") {
                 std::string arg = reboot_target == "sideload-auto-reboot" ? "sideload_auto_reboot"
diff --git a/init/reboot.h b/init/reboot.h
index 81c3edc..551a114 100644
--- a/init/reboot.h
+++ b/init/reboot.h
@@ -17,11 +17,17 @@
 #ifndef _INIT_REBOOT_H
 #define _INIT_REBOOT_H
 
+#include <chrono>
+#include <set>
 #include <string>
 
 namespace android {
 namespace init {
 
+// Like StopServices, but also logs all the services that failed to stop after the provided timeout.
+// Returns number of violators.
+int StopServicesAndLogViolations(const std::set<std::string>& services,
+                                 std::chrono::milliseconds timeout, bool terminate);
 // Parses and handles a setprop sys.powerctl message.
 void HandlePowerctlMessage(const std::string& command);
 
diff --git a/init/reboot_test.cpp b/init/reboot_test.cpp
new file mode 100644
index 0000000..b3d038d
--- /dev/null
+++ b/init/reboot_test.cpp
@@ -0,0 +1,196 @@
+/*
+ * 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 "reboot.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <selinux/selinux.h>
+
+#include "builtin_arguments.h"
+#include "builtins.h"
+#include "parser.h"
+#include "service_list.h"
+#include "service_parser.h"
+#include "subcontext.h"
+#include "util.h"
+
+using namespace std::literals;
+
+using android::base::GetProperty;
+using android::base::Join;
+using android::base::SetProperty;
+using android::base::Split;
+using android::base::StringReplace;
+using android::base::WaitForProperty;
+using android::base::WriteStringToFd;
+
+namespace android {
+namespace init {
+
+class RebootTest : public ::testing::Test {
+  public:
+    RebootTest() {
+        std::vector<std::string> names = GetServiceNames();
+        if (!names.empty()) {
+            ADD_FAILURE() << "Expected empty ServiceList but found: [" << Join(names, ',') << "]";
+        }
+    }
+
+    ~RebootTest() {
+        std::vector<std::string> names = GetServiceNames();
+        for (const auto& name : names) {
+            auto s = ServiceList::GetInstance().FindService(name);
+            auto pid = s->pid();
+            ServiceList::GetInstance().RemoveService(*s);
+            if (pid > 0) {
+                kill(pid, SIGTERM);
+                kill(pid, SIGKILL);
+            }
+        }
+    }
+
+  private:
+    std::vector<std::string> GetServiceNames() const {
+        std::vector<std::string> names;
+        for (const auto& s : ServiceList::GetInstance()) {
+            names.push_back(s->name());
+        }
+        return names;
+    }
+};
+
+std::string GetSecurityContext() {
+    char* ctx;
+    if (getcon(&ctx) == -1) {
+        ADD_FAILURE() << "Failed to call getcon : " << strerror(errno);
+    }
+    std::string result = std::string(ctx);
+    freecon(ctx);
+    return result;
+}
+
+void AddTestService(const std::string& name) {
+    static constexpr std::string_view kScriptTemplate = R"init(
+service $name /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+
+    std::string script = StringReplace(StringReplace(kScriptTemplate, "$name", name, false),
+                                       "$selabel", GetSecurityContext(), false);
+    ServiceList& service_list = ServiceList::GetInstance();
+    Parser parser;
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    ASSERT_TRUE(WriteStringToFd(script, tf.fd));
+    ASSERT_TRUE(parser.ParseConfig(tf.path));
+}
+
+TEST_F(RebootTest, StopServicesSIGTERM) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Skipping test, must be run as root.";
+        return;
+    }
+
+    AddTestService("A");
+    AddTestService("B");
+
+    auto service_a = ServiceList::GetInstance().FindService("A");
+    ASSERT_NE(nullptr, service_a);
+    auto service_b = ServiceList::GetInstance().FindService("B");
+    ASSERT_NE(nullptr, service_b);
+
+    ASSERT_RESULT_OK(service_a->Start());
+    ASSERT_TRUE(service_a->IsRunning());
+    ASSERT_RESULT_OK(service_b->Start());
+    ASSERT_TRUE(service_b->IsRunning());
+
+    std::unique_ptr<Service> oneshot_service;
+    {
+        auto result = Service::MakeTemporaryOneshotService(
+                {"exec", GetSecurityContext(), "--", "/system/bin/yes"});
+        ASSERT_RESULT_OK(result);
+        oneshot_service = std::move(*result);
+    }
+    std::string oneshot_service_name = oneshot_service->name();
+    oneshot_service->Start();
+    ASSERT_TRUE(oneshot_service->IsRunning());
+    ServiceList::GetInstance().AddService(std::move(oneshot_service));
+
+    EXPECT_EQ(0, StopServicesAndLogViolations({"A", "B", oneshot_service_name}, 10s,
+                                              /* terminate= */ true));
+    EXPECT_FALSE(service_a->IsRunning());
+    EXPECT_FALSE(service_b->IsRunning());
+    // Oneshot services are deleted from the ServiceList after they are destroyed.
+    auto oneshot_service_after_stop = ServiceList::GetInstance().FindService(oneshot_service_name);
+    EXPECT_EQ(nullptr, oneshot_service_after_stop);
+}
+
+TEST_F(RebootTest, StopServicesSIGKILL) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Skipping test, must be run as root.";
+        return;
+    }
+
+    AddTestService("A");
+    AddTestService("B");
+
+    auto service_a = ServiceList::GetInstance().FindService("A");
+    ASSERT_NE(nullptr, service_a);
+    auto service_b = ServiceList::GetInstance().FindService("B");
+    ASSERT_NE(nullptr, service_b);
+
+    ASSERT_RESULT_OK(service_a->Start());
+    ASSERT_TRUE(service_a->IsRunning());
+    ASSERT_RESULT_OK(service_b->Start());
+    ASSERT_TRUE(service_b->IsRunning());
+
+    std::unique_ptr<Service> oneshot_service;
+    {
+        auto result = Service::MakeTemporaryOneshotService(
+                {"exec", GetSecurityContext(), "--", "/system/bin/yes"});
+        ASSERT_RESULT_OK(result);
+        oneshot_service = std::move(*result);
+    }
+    std::string oneshot_service_name = oneshot_service->name();
+    oneshot_service->Start();
+    ASSERT_TRUE(oneshot_service->IsRunning());
+    ServiceList::GetInstance().AddService(std::move(oneshot_service));
+
+    EXPECT_EQ(0, StopServicesAndLogViolations({"A", "B", oneshot_service_name}, 10s,
+                                              /* terminate= */ false));
+    EXPECT_FALSE(service_a->IsRunning());
+    EXPECT_FALSE(service_b->IsRunning());
+    // Oneshot services are deleted from the ServiceList after they are destroyed.
+    auto oneshot_service_after_stop = ServiceList::GetInstance().FindService(oneshot_service_name);
+    EXPECT_EQ(nullptr, oneshot_service_after_stop);
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 42d3023..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
@@ -295,28 +295,44 @@
     return access(plat_policy_cil_file, R_OK) != -1;
 }
 
+std::optional<const char*> GetUserdebugPlatformPolicyFile() {
+    // See if we need to load userdebug_plat_sepolicy.cil instead of plat_sepolicy.cil.
+    const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
+    if (force_debuggable_env && "true"s == force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
+        const std::vector<const char*> debug_policy_candidates = {
+#if INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT == 1
+            "/system_ext/etc/selinux/userdebug_plat_sepolicy.cil",
+#endif
+            kDebugRamdiskSEPolicy,
+        };
+        for (const char* debug_policy : debug_policy_candidates) {
+            if (access(debug_policy, F_OK) == 0) {
+                return debug_policy;
+            }
+        }
+    }
+    return std::nullopt;
+}
+
 struct PolicyFile {
     unique_fd fd;
     std::string path;
 };
 
 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.
 
-    // See if we need to load userdebug_plat_sepolicy.cil instead of plat_sepolicy.cil.
-    const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
-    bool use_userdebug_policy =
-            ((force_debuggable_env && "true"s == force_debuggable_env) &&
-             AvbHandle::IsDeviceUnlocked() && access(kDebugRamdiskSEPolicy, F_OK) == 0);
+    const auto userdebug_plat_sepolicy = GetUserdebugPlatformPolicyFile();
+    const bool use_userdebug_policy = userdebug_plat_sepolicy.has_value();
     if (use_userdebug_policy) {
-        LOG(WARNING) << "Using userdebug system sepolicy";
+        LOG(INFO) << "Using userdebug system sepolicy " << *userdebug_plat_sepolicy;
     }
 
     // Load precompiled policy from vendor image, if a matching policy is found there. The policy
@@ -388,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;
     }
@@ -413,7 +426,7 @@
     // clang-format off
     std::vector<const char*> compile_args {
         "/system/bin/secilc",
-        use_userdebug_policy ? kDebugRamdiskSEPolicy: plat_policy_cil_file,
+        use_userdebug_policy ? *userdebug_plat_sepolicy : plat_policy_cil_file,
         "-m", "-M", "true", "-G", "-N",
         "-c", version_as_string.c_str(),
         plat_mapping_file.c_str(),
diff --git a/init/service.cpp b/init/service.cpp
index 489dd67..f7318cb 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -661,25 +661,6 @@
     StopOrReset(SVC_RESET);
 }
 
-void Service::ResetIfPostData() {
-    if (post_data_) {
-        if (flags_ & SVC_RUNNING) {
-            running_at_post_data_reset_ = true;
-        }
-        StopOrReset(SVC_RESET);
-    }
-}
-
-Result<void> Service::StartIfPostData() {
-    // Start the service, but only if it was started after /data was mounted,
-    // and it was still running when we reset the post-data services.
-    if (running_at_post_data_reset_) {
-        return Start();
-    }
-
-    return {};
-}
-
 void Service::Stop() {
     StopOrReset(SVC_DISABLED);
 }
diff --git a/init/service.h b/init/service.h
index ccf6899..3289f54 100644
--- a/init/service.h
+++ b/init/service.h
@@ -80,10 +80,8 @@
     Result<void> ExecStart();
     Result<void> Start();
     Result<void> StartIfNotDisabled();
-    Result<void> StartIfPostData();
     Result<void> Enable();
     void Reset();
-    void ResetIfPostData();
     void Stop();
     void Terminate();
     void Timeout();
@@ -214,8 +212,6 @@
 
     bool post_data_ = false;
 
-    bool running_at_post_data_reset_ = false;
-
     std::optional<std::string> on_failure_reboot_target_;
 
     bool from_apex_ = false;
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
index b8c2fd2..e11510e 100644
--- a/init/snapuserd_transition.cpp
+++ b/init/snapuserd_transition.cpp
@@ -58,7 +58,7 @@
 static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0";
 static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0";
 
-void LaunchFirstStageSnapuserd() {
+void LaunchFirstStageSnapuserd(SnapshotDriver driver) {
     SocketDescriptor socket_desc;
     socket_desc.name = android::snapshot::kSnapuserdSocket;
     socket_desc.type = SOCK_STREAM;
@@ -80,12 +80,23 @@
     }
     if (pid == 0) {
         socket->Publish();
-        char arg0[] = "/system/bin/snapuserd";
-        char* const argv[] = {arg0, nullptr};
-        if (execv(arg0, argv) < 0) {
-            PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+
+        if (driver == SnapshotDriver::DM_USER) {
+            char arg0[] = "/system/bin/snapuserd";
+            char arg1[] = "-user_snapshot";
+            char* const argv[] = {arg0, arg1, nullptr};
+            if (execv(arg0, argv) < 0) {
+                PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+            }
+            _exit(127);
+        } else {
+            char arg0[] = "/system/bin/snapuserd";
+            char* const argv[] = {arg0, nullptr};
+            if (execv(arg0, argv) < 0) {
+                PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+            }
+            _exit(127);
         }
-        _exit(127);
     }
 
     auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 10s);
diff --git a/init/snapuserd_transition.h b/init/snapuserd_transition.h
index 62aee83..be22afd 100644
--- a/init/snapuserd_transition.h
+++ b/init/snapuserd_transition.h
@@ -29,8 +29,13 @@
 namespace android {
 namespace init {
 
+enum class SnapshotDriver {
+    DM_SNAPSHOT,
+    DM_USER,
+};
+
 // Fork and exec a new copy of snapuserd.
-void LaunchFirstStageSnapuserd();
+void LaunchFirstStageSnapuserd(SnapshotDriver driver);
 
 class SnapuserdSelinuxHelper final {
     using SnapshotManager = android::snapshot::SnapshotManager;
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index f1fbffe..7aa4a9d 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -44,6 +44,7 @@
 #endif
 
 using android::base::GetExecutablePath;
+using android::base::GetProperty;
 using android::base::Join;
 using android::base::Socketpair;
 using android::base::Split;
@@ -296,7 +297,7 @@
 
     if (subcontext_reply->reply_case() == SubcontextReply::kFailure) {
         auto& failure = subcontext_reply->failure();
-        return ResultError(failure.error_string(), failure.error_errno());
+        return ResultError<>(failure.error_string(), failure.error_errno());
     }
 
     if (subcontext_reply->reply_case() != SubcontextReply::kSuccess) {
@@ -320,7 +321,7 @@
 
     if (subcontext_reply->reply_case() == SubcontextReply::kFailure) {
         auto& failure = subcontext_reply->failure();
-        return ResultError(failure.error_string(), failure.error_errno());
+        return ResultError<>(failure.error_string(), failure.error_errno());
     }
 
     if (subcontext_reply->reply_case() != SubcontextReply::kExpandArgsReply) {
@@ -337,6 +338,11 @@
 }
 
 void InitializeSubcontext() {
+    if (IsMicrodroid()) {
+        LOG(INFO) << "Not using subcontext for microdroid";
+        return;
+    }
+
     if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_P__) {
         subcontext.reset(
                 new Subcontext(std::vector<std::string>{"/vendor", "/odm"}, kVendorContext));
@@ -351,6 +357,9 @@
 }
 
 bool SubcontextChildReap(pid_t pid) {
+    if (!subcontext) {
+        return false;
+    }
     if (subcontext->pid() == pid) {
         if (!subcontext_terminated_by_shutdown) {
             subcontext->Restart();
diff --git a/init/ueventd_test.cpp b/init/ueventd_test.cpp
index fc3cdfb..1ac6d8e 100644
--- a/init/ueventd_test.cpp
+++ b/init/ueventd_test.cpp
@@ -99,7 +99,7 @@
     const char* const contexts[] = {
         "u:object_r:audio_device:s0",
         "u:object_r:sensors_device:s0",
-        "u:object_r:video_device:s0"
+        "u:object_r:video_device:s0",
         "u:object_r:zero_device:s0",
     };
 
diff --git a/init/util.cpp b/init/util.cpp
index 9f7bfdb..d1e518b 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -757,5 +757,10 @@
     is_default_mount_namespace_ready = true;
 }
 
+bool IsMicrodroid() {
+    static bool is_microdroid = android::base::GetProperty("ro.hardware", "") == "microdroid";
+    return is_microdroid;
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/util.h b/init/util.h
index daba852..bf53675 100644
--- a/init/util.h
+++ b/init/util.h
@@ -103,5 +103,7 @@
 
 bool IsDefaultMountNamespaceReady();
 void SetDefaultMountNamespaceReady();
+
+bool IsMicrodroid();
 }  // namespace init
 }  // namespace android
diff --git a/libasyncio/Android.bp b/libasyncio/Android.bp
index 692e223..296e207 100644
--- a/libasyncio/Android.bp
+++ b/libasyncio/Android.bp
@@ -32,6 +32,7 @@
     defaults: ["libasyncio_defaults"],
     vendor_available: true,
     recovery_available: true,
+    min_sdk_version: "apex_inherit",
     apex_available: [
         "//apex_available:platform",
         "com.android.adbd",
diff --git a/libcrypto_utils/Android.bp b/libcrypto_utils/Android.bp
index a9f6958..2559137 100644
--- a/libcrypto_utils/Android.bp
+++ b/libcrypto_utils/Android.bp
@@ -44,6 +44,7 @@
             enabled: true,
         },
     },
+    min_sdk_version: "apex_inherit",
     apex_available: [
         "//apex_available:platform",
         "com.android.adbd",
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0d9f2c7..c8bfb01 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -156,7 +156,6 @@
     },
     srcs: [
         "config_utils.cpp",
-        "canned_fs_config.cpp",
         "iosched_policy.cpp",
         "load_file.cpp",
         "native_handle.cpp",
@@ -173,6 +172,7 @@
         not_windows: {
             srcs: libcutils_nonwindows_sources + [
                 "ashmem-host.cpp",
+                "canned_fs_config.cpp",
                 "fs_config.cpp",
                 "trace-host.cpp",
             ],
@@ -193,6 +193,7 @@
             srcs: libcutils_nonwindows_sources + [
                 "android_reboot.cpp",
                 "ashmem-dev.cpp",
+                "canned_fs_config.cpp",
                 "fs_config.cpp",
                 "klog.cpp",
                 "partition_utils.cpp",
diff --git a/libcutils/TEST_MAPPING b/libcutils/TEST_MAPPING
new file mode 100644
index 0000000..e512ab7
--- /dev/null
+++ b/libcutils/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "libcutils_test"
+    }
+  ]
+}
diff --git a/libcutils/canned_fs_config.cpp b/libcutils/canned_fs_config.cpp
index 2772ef0..b677949 100644
--- a/libcutils/canned_fs_config.cpp
+++ b/libcutils/canned_fs_config.cpp
@@ -18,6 +18,8 @@
 #include <private/canned_fs_config.h>
 #include <private/fs_config.h>
 
+#include <android-base/strings.h>
+
 #include <errno.h>
 #include <inttypes.h>
 #include <limits.h>
@@ -25,104 +27,107 @@
 #include <stdlib.h>
 #include <string.h>
 
-typedef struct {
-    const char* path;
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+using android::base::ConsumePrefix;
+using android::base::StartsWith;
+using android::base::Tokenize;
+
+struct Entry {
+    std::string path;
     unsigned uid;
     unsigned gid;
     unsigned mode;
     uint64_t capabilities;
-} Path;
+};
 
-static Path* canned_data = NULL;
-static int canned_alloc = 0;
-static int canned_used = 0;
-
-static int path_compare(const void* a, const void* b) {
-    return strcmp(((Path*)a)->path, ((Path*)b)->path);
-}
+static std::vector<Entry> canned_data;
 
 int load_canned_fs_config(const char* fn) {
-    char buf[PATH_MAX + 200];
-    FILE* f;
-
-    f = fopen(fn, "r");
-    if (f == NULL) {
-        fprintf(stderr, "failed to open %s: %s\n", fn, strerror(errno));
-        return -1;
-    }
-
-    while (fgets(buf, sizeof(buf), f)) {
-        Path* p;
-        char* token;
-        char* line = buf;
-        bool rootdir;
-
-        while (canned_used >= canned_alloc) {
-            canned_alloc = (canned_alloc+1) * 2;
-            canned_data = (Path*) realloc(canned_data, canned_alloc * sizeof(Path));
+    std::ifstream input(fn);
+    for (std::string line; std::getline(input, line);) {
+        // Historical: the root dir can be represented as a space character.
+        // e.g. " 1000 1000 0755" is parsed as
+        // path = " ", uid = 1000, gid = 1000, mode = 0755.
+        // But at the same time, we also have accepted
+        // "/ 1000 1000 0755".
+        if (StartsWith(line, " ")) {
+            line.insert(line.begin(), '/');
         }
-        p = canned_data + canned_used;
-        if (line[0] == '/') line++;
-        rootdir = line[0] == ' ';
-        p->path = strdup(rootdir ? "" : strtok(line, " "));
-        p->uid = atoi(strtok(rootdir ? line : NULL, " "));
-        p->gid = atoi(strtok(NULL, " "));
-        p->mode = strtol(strtok(NULL, " "), NULL, 8);   // mode is in octal
-        p->capabilities = 0;
 
-        do {
-            token = strtok(NULL, " ");
-            if (token && strncmp(token, "capabilities=", 13) == 0) {
-                p->capabilities = strtoll(token+13, NULL, 0);
+        std::vector<std::string> tokens = Tokenize(line, " ");
+        if (tokens.size() < 4) {
+            std::cerr << "Ill-formed line: " << line << " in " << fn << std::endl;
+            return -1;
+        }
+
+        // Historical: remove the leading '/' if exists.
+        std::string path(tokens[0].front() == '/' ? std::string(tokens[0], 1) : tokens[0]);
+
+        Entry e{
+                .path = std::move(path),
+                .uid = static_cast<unsigned int>(atoi(tokens[1].c_str())),
+                .gid = static_cast<unsigned int>(atoi(tokens[2].c_str())),
+                // mode is in octal
+                .mode = static_cast<unsigned int>(strtol(tokens[3].c_str(), nullptr, 8)),
+                .capabilities = 0,
+        };
+
+        for (size_t i = 4; i < tokens.size(); i++) {
+            std::string_view sv = tokens[i];
+            if (ConsumePrefix(&sv, "capabilities=")) {
+                e.capabilities = strtoll(std::string(sv).c_str(), nullptr, 0);
                 break;
             }
-        } while (token);
+            // Historical: there can be tokens like "selabel=..." here. They have been ignored.
+            // It's not an error because selabels are applied separately in e2fsdroid using the
+            // file_contexts files set via -S option.
+            std::cerr << "info: ignored token \"" << sv << "\" in " << fn << std::endl;
+        }
 
-        canned_used++;
+        canned_data.emplace_back(std::move(e));
     }
 
-    fclose(f);
+    // Note: we used to sort the entries by path names. This was to improve the lookup performance
+    // by doing binary search. However, this is no longer the case. The lookup performance is not
+    // critical because this tool runs on the host, not on the device. Now, there can be multiple
+    // entries for the same path. Then the one that comes the last wins. This is to allow overriding
+    // platform provided fs_config with a user provided fs_config by appending the latter to the
+    // former.
+    //
+    // To implement the strategy, reverse the entries order, and search from the top.
+    std::reverse(canned_data.begin(), canned_data.end());
 
-    qsort(canned_data, canned_used, sizeof(Path), path_compare);
-    printf("loaded %d fs_config entries\n", canned_used);
-
+    std::cout << "loaded " << canned_data.size() << " fs_config entries" << std::endl;
     return 0;
 }
 
-static const int kDebugCannedFsConfig = 0;
+void canned_fs_config(const char* path, [[maybe_unused]] int dir,
+                      [[maybe_unused]] const char* target_out_path, unsigned* uid, unsigned* gid,
+                      unsigned* mode, uint64_t* capabilities) {
+    if (path != nullptr && path[0] == '/') path++;  // canned paths lack the leading '/'
 
-void canned_fs_config(const char* path, int dir, const char* target_out_path,
-                      unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities) {
-    Path key, *p;
+    const Entry* found = nullptr;
+    // canned_data is already reversed. First match wins.
+    for (const auto& entry : canned_data) {
+        if (path == entry.path) {
+            found = &entry;
+            break;
+        }
+        continue;
+    }
 
-    key.path = path;
-    if (path[0] == '/') key.path++; // canned paths lack the leading '/'
-    p = (Path*) bsearch(&key, canned_data, canned_used, sizeof(Path), path_compare);
-    if (p == NULL) {
-        fprintf(stderr, "failed to find [%s] in canned fs_config\n", path);
+    if (found == nullptr) {
+        std::cerr << "failed to find " << path << " in canned fs_config" << std::endl;
         exit(1);
     }
-    *uid = p->uid;
-    *gid = p->gid;
-    *mode = p->mode;
-    *capabilities = p->capabilities;
 
-    if (kDebugCannedFsConfig) {
-        // for debugging, run the built-in fs_config and compare the results.
-
-        unsigned c_uid, c_gid, c_mode;
-        uint64_t c_capabilities;
-
-        fs_config(path, dir, target_out_path, &c_uid, &c_gid, &c_mode, &c_capabilities);
-
-        if (c_uid != *uid) printf("%s uid %d %d\n", path, *uid, c_uid);
-        if (c_gid != *gid) printf("%s gid %d %d\n", path, *gid, c_gid);
-        if (c_mode != *mode) printf("%s mode 0%o 0%o\n", path, *mode, c_mode);
-        if (c_capabilities != *capabilities) {
-            printf("%s capabilities %" PRIx64 " %" PRIx64 "\n",
-                path,
-                *capabilities,
-                c_capabilities);
-        }
-    }
+    *uid = found->uid;
+    *gid = found->gid;
+    *mode = found->mode;
+    *capabilities = found->capabilities;
 }
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index e9497a8..a6835fc 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -211,6 +211,7 @@
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/resize2fs" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/snapuserd" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/tune2fs" },
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/fsck.f2fs" },
     // generic defaults
     { 00755, AID_ROOT,      AID_ROOT,      0, "bin/*" },
     { 00640, AID_ROOT,      AID_SHELL,     0, "fstab.*" },
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/cutils/trace.h b/libcutils/include/cutils/trace.h
index 24c6ae6..97e93f8 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -208,6 +208,39 @@
 }
 
 /**
+ * Trace an instantaneous context. name is used to identify the context.
+ *
+ * An "instant" is an event with no defined duration. Visually is displayed like a single marker
+ * in the timeline (rather than a span, in the case of begin/end events).
+ *
+ * By default, instant events are added into a dedicated track that has the same name of the event.
+ * Use atrace_instant_for_track to put different instant events into the same timeline track/row.
+ */
+#define ATRACE_INSTANT(name) atrace_instant(ATRACE_TAG, name)
+static inline void atrace_instant(uint64_t tag, const char* name) {
+    if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+        void atrace_instant_body(const char*);
+        atrace_instant_body(name);
+    }
+}
+
+/**
+ * Trace an instantaneous context. name is used to identify the context.
+ * trackName is the name of the row where the event should be recorded.
+ *
+ * An "instant" is an event with no defined duration. Visually is displayed like a single marker
+ * in the timeline (rather than a span, in the case of begin/end events).
+ */
+#define ATRACE_INSTANT_FOR_TRACK(trackName, name) \
+    atrace_instant_for_track(ATRACE_TAG, trackName, name)
+static inline void atrace_instant_for_track(uint64_t tag, const char* trackName, const char* name) {
+    if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+        void atrace_instant_for_track_body(const char*, const char*);
+        atrace_instant_for_track_body(trackName, name);
+    }
+}
+
+/**
  * Traces an integer counter value.  name is used to identify the counter.
  * This can be used to track how a value changes over time.
  */
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index c471fa0..23d1415 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -130,6 +130,8 @@
 #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 */
+#define AID_DICED 1085            /* Android's DICE daemon */
 /* Changes to this file must be made in AOSP, *not* in internal branches. */
 
 #define AID_SHELL 2000 /* adb and debug shell user */
@@ -157,6 +159,7 @@
 #define AID_READPROC 3009     /* Allow /proc read access */
 #define AID_WAKELOCK 3010     /* Allow system wakelock read/write access */
 #define AID_UHID 3011         /* Allow read/write to /dev/uhid node */
+#define AID_READTRACEFS 3012  /* Allow tracefs read */
 
 /* The range 5000-5999 is also reserved for vendor partition. */
 #define AID_OEM_RESERVED_2_START 5000
diff --git a/libcutils/trace-container.cpp b/libcutils/trace-container.cpp
index f7eed48..ef7c72d 100644
--- a/libcutils/trace-container.cpp
+++ b/libcutils/trace-container.cpp
@@ -217,6 +217,28 @@
     WRITE_MSG("F|%d|", "|%" PRId32, name, cookie);
 }
 
+void atrace_instant_body(const char* name) {
+    if (CC_LIKELY(atrace_use_container_sock)) {
+        WRITE_MSG_IN_CONTAINER("I", "|", "%s", name, "");
+        return;
+    }
+
+    if (atrace_marker_fd < 0) return;
+
+    WRITE_MSG("I|%d|", "%s", name, "");
+}
+
+void atrace_instant_for_track_body(const char* trackName, const char* name) {
+    if (CC_LIKELY(atrace_use_container_sock)) {
+        WRITE_MSG_IN_CONTAINER("N", "|", "|%s", trackName, name);
+        return;
+    }
+
+    if (atrace_marker_fd < 0) return;
+
+    WRITE_MSG("N|%d|", "|%s", name, trackName);
+}
+
 void atrace_int_body(const char* name, int32_t value)
 {
     if (CC_LIKELY(atrace_use_container_sock)) {
diff --git a/libcutils/trace-dev.cpp b/libcutils/trace-dev.cpp
index 1ab63dc..25c86f4 100644
--- a/libcutils/trace-dev.cpp
+++ b/libcutils/trace-dev.cpp
@@ -89,6 +89,14 @@
     WRITE_MSG("F|%d|", "|%" PRId32, name, cookie);
 }
 
+void atrace_instant_body(const char* name) {
+    WRITE_MSG("I|%d|", "%s", name, "");
+}
+
+void atrace_instant_for_track_body(const char* trackName, const char* name) {
+    WRITE_MSG("N|%d|", "|%s", trackName, name);
+}
+
 void atrace_int_body(const char* name, int32_t value)
 {
     WRITE_MSG("C|%d|", "|%" PRId32, name, value);
diff --git a/libcutils/trace-dev_test.cpp b/libcutils/trace-dev_test.cpp
index 832b36a..ff6d202 100644
--- a/libcutils/trace-dev_test.cpp
+++ b/libcutils/trace-dev_test.cpp
@@ -195,6 +195,106 @@
   ASSERT_STREQ(expected.c_str(), actual.c_str());
 }
 
+TEST_F(TraceDevTest, atrace_instant_body_normal) {
+    atrace_instant_body("fake_name");
+
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    std::string expected = android::base::StringPrintf("I|%d|fake_name", getpid());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_body_exact) {
+    std::string expected = android::base::StringPrintf("I|%d|", getpid());
+    std::string name = MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1);
+    atrace_instant_body(name.c_str());
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    expected += name;
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+    // Add a single character and verify we get the exact same value as before.
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    name += '*';
+    atrace_instant_body(name.c_str());
+    EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_body_truncated) {
+    std::string expected = android::base::StringPrintf("I|%d|", getpid());
+    std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_instant_body(name.c_str());
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 1;
+    expected += android::base::StringPrintf("%.*s", expected_len, name.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_for_track_body_normal) {
+    atrace_instant_for_track_body("fake_track", "fake_name");
+
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    std::string expected = android::base::StringPrintf("N|%d|fake_track|fake_name", getpid());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_for_track_body_exact) {
+    const int nameSize = 5;
+    std::string expected = android::base::StringPrintf("N|%d|", getpid());
+    std::string trackName = MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - nameSize);
+    atrace_instant_for_track_body(trackName.c_str(), "name");
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    expected += trackName + "|name";
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+    // Add a single character and verify we get the exact same value as before.
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    trackName += '*';
+    atrace_instant_for_track_body(trackName.c_str(), "name");
+    EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_for_track_body_truncated) {
+    const int nameSize = 5;
+    std::string expected = android::base::StringPrintf("N|%d|", getpid());
+    std::string trackName = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_instant_for_track_body(trackName.c_str(), "name");
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 1 - nameSize;
+    expected += android::base::StringPrintf("%.*s|name", expected_len, trackName.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
 TEST_F(TraceDevTest, atrace_int_body_normal) {
   atrace_int_body("fake_name", 12345);
 
diff --git a/libcutils/trace-host.cpp b/libcutils/trace-host.cpp
index 9781ad3..b01a0ec 100644
--- a/libcutils/trace-host.cpp
+++ b/libcutils/trace-host.cpp
@@ -28,6 +28,9 @@
 void atrace_end_body() { }
 void atrace_async_begin_body(const char* /*name*/, int32_t /*cookie*/) {}
 void atrace_async_end_body(const char* /*name*/, int32_t /*cookie*/) {}
+void atrace_instant_body(const char* /*name*/) {}
+void atrace_instant_for_track_body(const char* /*trackName*/,
+                                   const char* /*name*/) {}
 void atrace_int_body(const char* /*name*/, int32_t /*value*/) {}
 void atrace_int64_body(const char* /*name*/, int64_t /*value*/) {}
 void atrace_init() {}
diff --git a/libmodprobe/TEST_MAPPING b/libmodprobe/TEST_MAPPING
new file mode 100644
index 0000000..526b1e4
--- /dev/null
+++ b/libmodprobe/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "libmodprobe_tests"
+    }
+  ]
+}
diff --git a/libnetutils/Android.bp b/libnetutils/Android.bp
index 2864ad0..02bd2e3 100644
--- a/libnetutils/Android.bp
+++ b/libnetutils/Android.bp
@@ -23,7 +23,6 @@
     },
 
     srcs: [
-        "checksum.c",
         "dhcpclient.c",
         "dhcpmsg.c",
         "ifc_utils.c",
@@ -35,6 +34,10 @@
         "liblog",
     ],
 
+    static_libs: [
+        "libip_checksum",
+    ],
+
     cflags: ["-Werror"],
 
     export_include_dirs: ["include"],
@@ -45,21 +48,6 @@
     ],
 }
 
-cc_library_static {
-    name: "libipchecksum",
-
-    srcs: [
-        "checksum.c",
-    ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    export_include_dirs: ["include"],
-}
-
 cc_binary {
     name: "dhcpdbg",
 
diff --git a/libnetutils/checksum.c b/libnetutils/checksum.c
deleted file mode 100644
index 74b5fdd..0000000
--- a/libnetutils/checksum.c
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 2011 Daniel Drown
- *
- * 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.
- *
- * checksum.c - ipv4/ipv6 checksum calculation
- */
-#include <netinet/icmp6.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/ip6.h>
-#include <netinet/ip_icmp.h>
-#include <netinet/tcp.h>
-#include <netinet/udp.h>
-
-#include "netutils/checksum.h"
-
-/* function: ip_checksum_add
- * adds data to a checksum. only known to work on little-endian hosts
- * current - the current checksum (or 0 to start a new checksum)
- *   data        - the data to add to the checksum
- *   len         - length of data
- */
-uint32_t ip_checksum_add(uint32_t current, const void* data, int len) {
-    uint32_t checksum = current;
-    int left = len;
-    const uint16_t* data_16 = data;
-
-    while (left > 1) {
-        checksum += *data_16;
-        data_16++;
-        left -= 2;
-    }
-    if (left) {
-        checksum += *(uint8_t*)data_16;
-    }
-
-    return checksum;
-}
-
-/* function: ip_checksum_fold
- * folds a 32-bit partial checksum into 16 bits
- *   temp_sum - sum from ip_checksum_add
- *   returns: the folded checksum in network byte order
- */
-uint16_t ip_checksum_fold(uint32_t temp_sum) {
-    while (temp_sum > 0xffff) {
-        temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
-    }
-    return temp_sum;
-}
-
-/* function: ip_checksum_finish
- * folds and closes the checksum
- *   temp_sum - sum from ip_checksum_add
- *   returns: a header checksum value in network byte order
- */
-uint16_t ip_checksum_finish(uint32_t temp_sum) {
-    return ~ip_checksum_fold(temp_sum);
-}
-
-/* function: ip_checksum
- * combined ip_checksum_add and ip_checksum_finish
- *   data - data to checksum
- *   len  - length of data
- */
-uint16_t ip_checksum(const void* data, int len) {
-    // TODO: consider starting from 0xffff so the checksum of a buffer entirely consisting of zeros
-    // is correctly calculated as 0.
-    uint32_t temp_sum;
-
-    temp_sum = ip_checksum_add(0, data, len);
-    return ip_checksum_finish(temp_sum);
-}
-
-/* function: ipv6_pseudo_header_checksum
- * calculate the pseudo header checksum for use in tcp/udp/icmp headers
- *   ip6      - the ipv6 header
- *   len      - the transport length (transport header + payload)
- *   protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments
- */
-uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol) {
-    uint32_t checksum_len = htonl(len);
-    uint32_t checksum_next = htonl(protocol);
-
-    uint32_t current = 0;
-
-    current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr));
-    current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr));
-    current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len));
-    current = ip_checksum_add(current, &checksum_next, sizeof(checksum_next));
-
-    return current;
-}
-
-/* function: ipv4_pseudo_header_checksum
- * calculate the pseudo header checksum for use in tcp/udp headers
- *   ip      - the ipv4 header
- *   len     - the transport length (transport header + payload)
- */
-uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len) {
-    uint16_t temp_protocol, temp_length;
-
-    temp_protocol = htons(ip->protocol);
-    temp_length = htons(len);
-
-    uint32_t current = 0;
-
-    current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t));
-    current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t));
-    current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t));
-    current = ip_checksum_add(current, &temp_length, sizeof(uint16_t));
-
-    return current;
-}
-
-/* function: ip_checksum_adjust
- * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums
- *   checksum    - the header checksum in the original packet in network byte order
- *   old_hdr_sum - the pseudo-header checksum of the original packet
- *   new_hdr_sum - the pseudo-header checksum of the translated packet
- *   returns: the new header checksum in network byte order
- */
-uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) {
-    // Algorithm suggested in RFC 1624.
-    // http://tools.ietf.org/html/rfc1624#section-3
-    checksum = ~checksum;
-    uint16_t folded_sum = ip_checksum_fold(checksum + new_hdr_sum);
-    uint16_t folded_old = ip_checksum_fold(old_hdr_sum);
-    if (folded_sum > folded_old) {
-        return ~(folded_sum - folded_old);
-    } else {
-        return ~(folded_sum - folded_old - 1);  // end-around borrow
-    }
-}
diff --git a/libnetutils/include/netutils/checksum.h b/libnetutils/include/netutils/checksum.h
deleted file mode 100644
index 868217c..0000000
--- a/libnetutils/include/netutils/checksum.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2011 Daniel Drown
- *
- * 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.
- *
- * checksum.h - checksum functions
- */
-#ifndef __CHECKSUM_H__
-#define __CHECKSUM_H__
-
-#include <netinet/ip.h>
-#include <netinet/ip6.h>
-#include <stdint.h>
-
-uint32_t ip_checksum_add(uint32_t current, const void* data, int len);
-uint16_t ip_checksum_finish(uint32_t temp_sum);
-uint16_t ip_checksum(const void* data, int len);
-
-uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol);
-uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len);
-
-uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum);
-
-#endif /* __CHECKSUM_H__ */
diff --git a/libpackagelistparser/TEST_MAPPING b/libpackagelistparser/TEST_MAPPING
new file mode 100644
index 0000000..51773f9
--- /dev/null
+++ b/libpackagelistparser/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "libpackagelistparser_test"
+    }
+  ]
+}
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/cgrouprc/include/android/cgrouprc.h b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
index 100d60e..e704a36 100644
--- a/libprocessgroup/cgrouprc/include/android/cgrouprc.h
+++ b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <sys/cdefs.h>
 #include <stdint.h>
 
 __BEGIN_DECLS
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..b668dcb 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,11 @@
     },
     {
       "Name": "Dex2OatBootComplete",
-      "Profiles": [ "SCHED_SP_BACKGROUND" ]
+      "Profiles": [ "Dex2oatPerformance", "LowIoPriority", "TimerSlackHigh" ]
+    },
+    {
+      "Name": "OtaProfiles",
+      "Profiles": [ "ServiceCapacityLow", "LowIoPriority", "HighEnergySaving" ]
     }
   ]
 }
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/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index 1a4196a..169b1d3 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -165,27 +165,7 @@
     return 0;
 }
 
-int get_sched_policy(int tid, SchedPolicy* policy) {
-    if (tid == 0) {
-        tid = GetThreadId();
-    }
-
-    std::string group;
-    if (schedboost_enabled()) {
-        if ((getCGroupSubsys(tid, "schedtune", group) < 0) &&
-            (getCGroupSubsys(tid, "cpu", group) < 0)) {
-                LOG(ERROR) << "Failed to find cpu cgroup for tid " << tid;
-                return -1;
-        }
-    }
-    if (group.empty() && cpusets_enabled()) {
-        if (getCGroupSubsys(tid, "cpuset", group) < 0) {
-            LOG(ERROR) << "Failed to find cpuset cgroup for tid " << tid;
-            return -1;
-        }
-    }
-
-    // TODO: replace hardcoded directories
+static int get_sched_policy_from_group(const std::string& group, SchedPolicy* policy) {
     if (group.empty()) {
         *policy = SP_FOREGROUND;
     } else if (group == "foreground") {
@@ -205,6 +185,35 @@
     return 0;
 }
 
+int get_sched_policy(int tid, SchedPolicy* policy) {
+    if (tid == 0) {
+        tid = GetThreadId();
+    }
+
+    std::string group;
+    if (schedboost_enabled()) {
+        if ((getCGroupSubsys(tid, "schedtune", group) < 0) &&
+            (getCGroupSubsys(tid, "cpu", group) < 0)) {
+            LOG(ERROR) << "Failed to find cpu cgroup for tid " << tid;
+            return -1;
+        }
+        // Wipe invalid group to fallback to cpuset
+        if (!group.empty()) {
+            if (get_sched_policy_from_group(group, policy) < 0) {
+                group.clear();
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    if (cpusets_enabled() && getCGroupSubsys(tid, "cpuset", group) < 0) {
+        LOG(ERROR) << "Failed to find cpuset cgroup for tid " << tid;
+        return -1;
+    }
+    return get_sched_policy_from_group(group, policy);
+}
+
 #else
 
 /* Stubs for non-Android targets. */
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index cf74e65..3834f91 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -144,30 +144,13 @@
     return true;
 }
 
-bool SetCgroupAction::IsAppDependentPath(const std::string& path) {
-    return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
-}
-
-SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
-    : controller_(c), path_(p) {
-    // file descriptors for app-dependent paths can't be cached
-    if (IsAppDependentPath(path_)) {
-        // file descriptor is not cached
-        fd_.reset(FDS_APP_DEPENDENT);
-        return;
-    }
-
-    // file descriptor can be cached later on request
-    fd_.reset(FDS_NOT_CACHED);
-}
-
-void SetCgroupAction::EnableResourceCaching() {
+void CachedFdProfileAction::EnableResourceCaching() {
     std::lock_guard<std::mutex> lock(fd_mutex_);
     if (fd_ != FDS_NOT_CACHED) {
         return;
     }
 
-    std::string tasks_path = controller_.GetTasksFilePath(path_);
+    std::string tasks_path = GetPath();
 
     if (access(tasks_path.c_str(), W_OK) != 0) {
         // file is not accessible
@@ -185,7 +168,7 @@
     fd_ = std::move(fd);
 }
 
-void SetCgroupAction::DropResourceCaching() {
+void CachedFdProfileAction::DropResourceCaching() {
     std::lock_guard<std::mutex> lock(fd_mutex_);
     if (fd_ == FDS_NOT_CACHED) {
         return;
@@ -194,22 +177,59 @@
     fd_.reset(FDS_NOT_CACHED);
 }
 
-bool SetCgroupAction::AddTidToCgroup(int tid, int fd) {
+bool CachedFdProfileAction::IsAppDependentPath(const std::string& path) {
+    return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
+}
+
+void CachedFdProfileAction::InitFd(const std::string& path) {
+    // file descriptors for app-dependent paths can't be cached
+    if (IsAppDependentPath(path)) {
+        // file descriptor is not cached
+        fd_.reset(FDS_APP_DEPENDENT);
+        return;
+    }
+    // file descriptor can be cached later on request
+    fd_.reset(FDS_NOT_CACHED);
+}
+
+SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
+    : controller_(c), path_(p) {
+    InitFd(controller_.GetTasksFilePath(path_));
+}
+
+bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
     if (tid <= 0) {
         return true;
     }
 
     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 +239,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 +251,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;
         }
@@ -253,10 +273,10 @@
     std::string tasks_path = controller()->GetTasksFilePath(path_);
     unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
     if (tmp_fd < 0) {
-        PLOG(WARNING) << "Failed to open " << tasks_path << ": " << strerror(errno);
+        PLOG(WARNING) << "Failed to open " << tasks_path;
         return false;
     }
-    if (!AddTidToCgroup(tid, tmp_fd)) {
+    if (!AddTidToCgroup(tid, tmp_fd, controller()->name())) {
         LOG(ERROR) << "Failed to add task into cgroup";
         return false;
     }
@@ -264,37 +284,73 @@
     return true;
 }
 
-bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
-    std::string filepath(filepath_), value(value_);
+WriteFileAction::WriteFileAction(const std::string& path, const std::string& value,
+                                 bool logfailures)
+    : path_(path), value_(value), logfailures_(logfailures) {
+    InitFd(path_);
+}
 
-    filepath = StringReplace(filepath, "<uid>", std::to_string(uid), true);
-    filepath = StringReplace(filepath, "<pid>", std::to_string(pid), true);
-    value = StringReplace(value, "<uid>", std::to_string(uid), true);
-    value = StringReplace(value, "<pid>", std::to_string(pid), true);
+bool WriteFileAction::WriteValueToFile(const std::string& value, const std::string& path,
+                                       bool logfailures) {
+    // Use WriteStringToFd instead of WriteStringToFile because the latter will open file with
+    // O_TRUNC which causes kernfs_mutex contention
+    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_WRONLY | O_CLOEXEC)));
 
-    if (!WriteStringToFile(value, filepath)) {
-        if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+    if (tmp_fd < 0) {
+        if (logfailures) PLOG(WARNING) << "Failed to open " << path;
+        return false;
+    }
+
+    if (!WriteStringToFd(value, tmp_fd)) {
+        if (logfailures) PLOG(ERROR) << "Failed to write '" << value << "' to " << path;
         return false;
     }
 
     return true;
 }
 
+bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    std::string value(value_);
+    std::string path(path_);
+
+    value = StringReplace(value, "<uid>", std::to_string(uid), true);
+    value = StringReplace(value, "<pid>", std::to_string(pid), true);
+    path = StringReplace(path, "<uid>", std::to_string(uid), true);
+    path = StringReplace(path, "<pid>", std::to_string(pid), true);
+
+    return WriteValueToFile(value, path, logfailures_);
+}
+
 bool WriteFileAction::ExecuteForTask(int tid) const {
-    std::string filepath(filepath_), value(value_);
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    std::string value(value_);
     int uid = getuid();
 
-    filepath = StringReplace(filepath, "<uid>", std::to_string(uid), true);
-    filepath = StringReplace(filepath, "<pid>", std::to_string(tid), true);
     value = StringReplace(value, "<uid>", std::to_string(uid), true);
     value = StringReplace(value, "<pid>", std::to_string(tid), true);
 
-    if (!WriteStringToFile(value, filepath)) {
-        if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+    if (IsFdValid()) {
+        // fd is cached, reuse it
+        if (!WriteStringToFd(value, fd_)) {
+            if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << path_;
+            return false;
+        }
+        return true;
+    }
+
+    if (fd_ == FDS_INACCESSIBLE) {
+        // no permissions to access the file, ignore
+        return true;
+    }
+
+    if (fd_ == FDS_APP_DEPENDENT) {
+        // application-dependent path can't be used with tid
+        PLOG(ERROR) << "Application profile can't be applied to a thread";
         return false;
     }
 
-    return true;
+    return WriteValueToFile(value, path_, logfailures_);
 }
 
 bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 25a84b0..278892d 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -108,50 +108,67 @@
     std::string value_;
 };
 
-// Set cgroup profile element
-class SetCgroupAction : public ProfileAction {
+// Abstract profile element for cached fd
+class CachedFdProfileAction : public ProfileAction {
   public:
-    SetCgroupAction(const CgroupController& c, const std::string& p);
-
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
     virtual void EnableResourceCaching();
     virtual void DropResourceCaching();
 
-    const CgroupController* controller() const { return &controller_; }
-    std::string path() const { return path_; }
-
-  private:
+  protected:
     enum FdState {
         FDS_INACCESSIBLE = -1,
         FDS_APP_DEPENDENT = -2,
         FDS_NOT_CACHED = -3,
     };
 
-    CgroupController controller_;
-    std::string path_;
     android::base::unique_fd fd_;
     mutable std::mutex fd_mutex_;
 
     static bool IsAppDependentPath(const std::string& path);
-    static bool AddTidToCgroup(int tid, int fd);
 
+    void InitFd(const std::string& path);
     bool IsFdValid() const { return fd_ > FDS_INACCESSIBLE; }
+
+    virtual const std::string GetPath() const = 0;
 };
 
-// Write to file action
-class WriteFileAction : public ProfileAction {
+// Set cgroup profile element
+class SetCgroupAction : public CachedFdProfileAction {
   public:
-    WriteFileAction(const std::string& filepath, const std::string& value,
-                    bool logfailures) noexcept
-        : filepath_(filepath), value_(value), logfailures_(logfailures) {}
+    SetCgroupAction(const CgroupController& c, const std::string& p);
 
     virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     virtual bool ExecuteForTask(int tid) const;
 
+    const CgroupController* controller() const { return &controller_; }
+
+  protected:
+    const std::string GetPath() const override { return controller_.GetTasksFilePath(path_); }
+
   private:
-    std::string filepath_, value_;
+    CgroupController controller_;
+    std::string path_;
+
+    static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
+};
+
+// Write to file action
+class WriteFileAction : public CachedFdProfileAction {
+  public:
+    WriteFileAction(const std::string& path, const std::string& value, bool logfailures);
+
+    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
+    virtual bool ExecuteForTask(int tid) const;
+
+  protected:
+    const std::string GetPath() const override { return path_; }
+
+  private:
+    std::string path_, value_;
     bool logfailures_;
+
+    static bool WriteValueToFile(const std::string& value, const std::string& path,
+                                 bool logfailures);
 };
 
 class TaskProfile {
diff --git a/libprocessgroup/tools/Android.bp b/libprocessgroup/tools/Android.bp
new file mode 100644
index 0000000..91418e1
--- /dev/null
+++ b/libprocessgroup/tools/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "settaskprofile",
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    srcs: ["settaskprofile.cpp"],
+    shared_libs: [
+        "libprocessgroup",
+    ],
+}
diff --git a/libprocessgroup/tools/settaskprofile.cpp b/libprocessgroup/tools/settaskprofile.cpp
new file mode 100644
index 0000000..f83944a
--- /dev/null
+++ b/libprocessgroup/tools/settaskprofile.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <iostream>
+
+#include <processgroup/processgroup.h>
+
+[[noreturn]] static void usage(int exit_status) {
+    std::cerr << "Usage: " << getprogname() << " <tid> <profile> [... profileN]" << std::endl
+              << "    tid      Thread ID to apply the profiles to." << std::endl
+              << "    profile  Name of the profile to apply." << std::endl
+              << "Applies listed profiles to the thread with specified ID." << std::endl
+              << "Profiles are applied in the order specified in the command line." << std::endl
+              << "If applying a profile fails, remaining profiles are ignored." << std::endl;
+    exit(exit_status);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 3) {
+        usage(EXIT_FAILURE);
+    }
+
+    int tid = atoi(argv[1]);
+    if (tid == 0) {
+        std::cerr << "Invalid thread id" << std::endl;
+        exit(EXIT_FAILURE);
+    }
+
+    for (int i = 2; i < argc; i++) {
+        if (!SetTaskProfiles(tid, {argv[i]})) {
+            std::cerr << "Failed to apply " << argv[i] << " profile" << std::endl;
+            exit(EXIT_FAILURE);
+        }
+        std::cout << "Profile " << argv[i] << " is applied successfully!" << std::endl;
+    }
+
+    return 0;
+}
diff --git a/libqtaguid/Android.bp b/libqtaguid/Android.bp
deleted file mode 100644
index 64db095..0000000
--- a/libqtaguid/Android.bp
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-// Copyright (C) 2017 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_library_headers {
-    name: "libqtaguid_headers",
-    vendor_available: false,
-    host_supported: false,
-    export_include_dirs: ["include"],
-    target: {
-        linux_bionic: {
-            enabled: true,
-        },
-    },
-}
-
-cc_library {
-    name: "libqtaguid",
-    vendor_available: false,
-    host_supported: false,
-    target: {
-        android: {
-            srcs: [
-                "qtaguid.c",
-            ],
-            sanitize: {
-                misc_undefined: ["integer"],
-            },
-        },
-    },
-
-    shared_libs: ["liblog"],
-    header_libs: [
-        "libqtaguid_headers",
-    ],
-    export_header_lib_headers: ["libqtaguid_headers"],
-    local_include_dirs: ["include"],
-
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-Wextra",
-    ],
-}
diff --git a/libqtaguid/include/qtaguid/qtaguid.h b/libqtaguid/include/qtaguid/qtaguid.h
deleted file mode 100644
index 72285e5..0000000
--- a/libqtaguid/include/qtaguid/qtaguid.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __LEGACY_QTAGUID_H
-#define __LEGACY_QTAGUID_H
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Set tags (and owning UIDs) for network sockets. The socket must be untagged
- * by calling qtaguid_untagSocket() before closing it, otherwise the qtaguid
- * module will keep a reference to it even after close.
- */
-extern int legacy_tagSocket(int sockfd, int tag, uid_t uid);
-
-/*
- * Untag a network socket before closing.
- */
-extern int legacy_untagSocket(int sockfd);
-
-/*
- * For the given uid, switch counter sets.
- * The kernel only keeps a limited number of sets.
- * 2 for now.
- */
-extern int legacy_setCounterSet(int counterSetNum, uid_t uid);
-
-/*
- * Delete all tag info that relates to the given tag an uid.
- * If the tag is 0, then ALL info about the uid is freeded.
- * The delete data also affects active tagged socketd, which are
- * then untagged.
- * The calling process can only operate on its own tags.
- * Unless it is part of the happy AID_NET_BW_ACCT group.
- * In which case it can clobber everything.
- */
-extern int legacy_deleteTagData(int tag, uid_t uid);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __LEGACY_QTAGUID_H */
diff --git a/libqtaguid/qtaguid.c b/libqtaguid/qtaguid.c
deleted file mode 100644
index cd38bad..0000000
--- a/libqtaguid/qtaguid.c
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-// #define LOG_NDEBUG 0
-
-#define LOG_TAG "qtaguid"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <log/log.h>
-#include <qtaguid/qtaguid.h>
-
-static const char* CTRL_PROCPATH = "/proc/net/xt_qtaguid/ctrl";
-static const int CTRL_MAX_INPUT_LEN = 128;
-
-/*
- * One per proccess.
- * Once the device is open, this process will have its socket tags tracked.
- * And on exit or untimely death, all socket tags will be removed.
- * A process can only open /dev/xt_qtaguid once.
- * It should not close it unless it is really done with all the socket tags.
- * Failure to open it will be visible when socket tagging will be attempted.
- */
-static int resTrackFd = -1;
-pthread_once_t resTrackInitDone = PTHREAD_ONCE_INIT;
-
-/* Only call once per process. */
-void legacy_resTrack(void) {
-    resTrackFd = TEMP_FAILURE_RETRY(open("/dev/xt_qtaguid", O_RDONLY | O_CLOEXEC));
-}
-
-/*
- * Returns:
- *   0 on success.
- *   -errno on failure.
- */
-static int write_ctrl(const char* cmd) {
-    int fd, res, savedErrno;
-
-    ALOGV("write_ctrl(%s)", cmd);
-
-    fd = TEMP_FAILURE_RETRY(open(CTRL_PROCPATH, O_WRONLY | O_CLOEXEC));
-    if (fd < 0) {
-        return -errno;
-    }
-
-    res = TEMP_FAILURE_RETRY(write(fd, cmd, strlen(cmd)));
-    if (res < 0) {
-        savedErrno = errno;
-    } else {
-        savedErrno = 0;
-    }
-    if (res < 0) {
-        // ALOGV is enough because all the callers also log failures
-        ALOGV("Failed write_ctrl(%s) res=%d errno=%d", cmd, res, savedErrno);
-    }
-    close(fd);
-    return -savedErrno;
-}
-
-int legacy_tagSocket(int sockfd, int tag, uid_t uid) {
-    char lineBuf[CTRL_MAX_INPUT_LEN];
-    int res;
-    uint64_t kTag = ((uint64_t)tag << 32);
-
-    pthread_once(&resTrackInitDone, legacy_resTrack);
-
-    snprintf(lineBuf, sizeof(lineBuf), "t %d %" PRIu64 " %d", sockfd, kTag, uid);
-
-    ALOGV("Tagging socket %d with tag %" PRIx64 "{%u,0} for uid %d", sockfd, kTag, tag, uid);
-
-    res = write_ctrl(lineBuf);
-    if (res < 0) {
-        ALOGI("Tagging socket %d with tag %" PRIx64 "(%d) for uid %d failed errno=%d", sockfd, kTag,
-              tag, uid, res);
-    }
-
-    return res;
-}
-
-int legacy_untagSocket(int sockfd) {
-    char lineBuf[CTRL_MAX_INPUT_LEN];
-    int res;
-
-    ALOGV("Untagging socket %d", sockfd);
-
-    snprintf(lineBuf, sizeof(lineBuf), "u %d", sockfd);
-    res = write_ctrl(lineBuf);
-    if (res < 0) {
-        ALOGI("Untagging socket %d failed errno=%d", sockfd, res);
-    }
-
-    return res;
-}
-
-int legacy_setCounterSet(int counterSetNum, uid_t uid) {
-    char lineBuf[CTRL_MAX_INPUT_LEN];
-    int res;
-
-    ALOGV("Setting counters to set %d for uid %d", counterSetNum, uid);
-
-    snprintf(lineBuf, sizeof(lineBuf), "s %d %d", counterSetNum, uid);
-    res = write_ctrl(lineBuf);
-    return res;
-}
-
-int legacy_deleteTagData(int tag, uid_t uid) {
-    char lineBuf[CTRL_MAX_INPUT_LEN];
-    int cnt = 0, res = 0;
-    uint64_t kTag = (uint64_t)tag << 32;
-
-    ALOGV("Deleting tag data with tag %" PRIx64 "{%d,0} for uid %d", kTag, tag, uid);
-
-    pthread_once(&resTrackInitDone, legacy_resTrack);
-
-    snprintf(lineBuf, sizeof(lineBuf), "d %" PRIu64 " %d", kTag, uid);
-    res = write_ctrl(lineBuf);
-    if (res < 0) {
-        ALOGI("Deleting tag data with tag %" PRIx64 "/%d for uid %d failed with cnt=%d errno=%d",
-              kTag, tag, uid, cnt, errno);
-    }
-
-    return res;
-}
diff --git a/libsparse/Android.bp b/libsparse/Android.bp
index 0b4b640..3f9aeb2 100644
--- a/libsparse/Android.bp
+++ b/libsparse/Android.bp
@@ -85,11 +85,11 @@
     srcs: ["simg_dump.py"],
     version: {
         py2: {
-            embedded_launcher: true,
-            enabled: true,
+            enabled: false,
         },
         py3: {
-            enabled: false,
+            embedded_launcher: true,
+            enabled: true,
         },
     },
 }
diff --git a/libsparse/simg_dump.py b/libsparse/simg_dump.py
index 82a03ad..b0b3b22 100755
--- a/libsparse/simg_dump.py
+++ b/libsparse/simg_dump.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
 
 # Copyright (C) 2012 The Android Open Source Project
 #
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from __future__ import print_function
 import csv
 import getopt
 import hashlib
@@ -47,7 +46,7 @@
     opts, args = getopt.getopt(sys.argv[1:],
                                "vsc:",
                                ["verbose", "showhash", "csvfile"])
-  except getopt.GetoptError, e:
+  except getopt.GetoptError as e:
     print(e)
     usage(me)
   for o, a in opts:
diff --git a/libstats/bootstrap/Android.bp b/libstats/bootstrap/Android.bp
new file mode 100644
index 0000000..332d9c8
--- /dev/null
+++ b/libstats/bootstrap/Android.bp
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// =========================================================================
+// Native library that provide a client to StatsBootstrapAtomService.
+// This library should only be used by processes that start in the bootstrap namespace.
+// All other clients should use libstatssocket, provided by the statsd apex.
+// =========================================================================
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+    name: "libstatsbootstrap_defaults",
+    srcs: [
+        "BootstrapClientInternal.cpp",
+        "StatsBootstrapAtomClient.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libutils",
+        "android.os.statsbootstrap_aidl-cpp",
+    ],
+}
+
+cc_library {
+    name: "libstatsbootstrap",
+    defaults: ["libstatsbootstrap_defaults"],
+    export_include_dirs: ["include"],
+}
+
+
diff --git a/libstats/bootstrap/BootstrapClientInternal.cpp b/libstats/bootstrap/BootstrapClientInternal.cpp
new file mode 100644
index 0000000..b02e116
--- /dev/null
+++ b/libstats/bootstrap/BootstrapClientInternal.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BootstrapClientInternal.h"
+
+#include <binder/IServiceManager.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+sp<BootstrapClientInternal> BootstrapClientInternal::getInstance() {
+    static sp<BootstrapClientInternal> client = new BootstrapClientInternal();
+    return client;
+}
+
+sp<IStatsBootstrapAtomService> BootstrapClientInternal::getServiceNonBlocking() {
+    std::lock_guard<std::mutex> lock(mLock);
+    if (mService != nullptr) {
+        return mService;
+    }
+    connectNonBlockingLocked();
+    return mService;
+}
+
+void BootstrapClientInternal::binderDied(const wp<IBinder>&) {
+    std::lock_guard<std::mutex> lock(mLock);
+    mService = nullptr;
+    connectNonBlockingLocked();
+}
+
+void BootstrapClientInternal::connectNonBlockingLocked() {
+    const String16 name("statsbootstrap");
+    mService =
+            interface_cast<IStatsBootstrapAtomService>(defaultServiceManager()->checkService(name));
+    if (mService != nullptr) {
+        // Set up binder death.
+        IInterface::asBinder(mService)->linkToDeath(this);
+    }
+}
+
+}  // namespace stats
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/libstats/bootstrap/BootstrapClientInternal.h b/libstats/bootstrap/BootstrapClientInternal.h
new file mode 100644
index 0000000..96238da
--- /dev/null
+++ b/libstats/bootstrap/BootstrapClientInternal.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/os/IStatsBootstrapAtomService.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+class BootstrapClientInternal : public IBinder::DeathRecipient {
+  public:
+    static sp<BootstrapClientInternal> getInstance();
+    void binderDied(const wp<IBinder>& who) override;
+    sp<IStatsBootstrapAtomService> getServiceNonBlocking();
+
+  private:
+    BootstrapClientInternal() {}
+    void connectNonBlockingLocked();
+
+    mutable std::mutex mLock;
+    sp<IStatsBootstrapAtomService> mService;
+};
+
+}  // namespace stats
+}  // namespace os
+}  // namespace android
diff --git a/libstats/bootstrap/StatsBootstrapAtomClient.cpp b/libstats/bootstrap/StatsBootstrapAtomClient.cpp
new file mode 100644
index 0000000..348b7fa
--- /dev/null
+++ b/libstats/bootstrap/StatsBootstrapAtomClient.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "include/StatsBootstrapAtomClient.h"
+
+#include <android/os/IStatsBootstrapAtomService.h>
+
+#include "BootstrapClientInternal.h"
+
+namespace android {
+namespace os {
+namespace stats {
+
+bool StatsBootstrapAtomClient::reportBootstrapAtom(const StatsBootstrapAtom& atom) {
+    sp<IStatsBootstrapAtomService> service =
+            BootstrapClientInternal::getInstance()->getServiceNonBlocking();
+    if (service == nullptr) {
+        return false;
+    }
+    return service->reportBootstrapAtom(atom).isOk();
+}
+
+}  // namespace stats
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/fs_mgr/clean_scratch_files.cpp b/libstats/bootstrap/include/StatsBootstrapAtomClient.h
similarity index 61%
copy from fs_mgr/clean_scratch_files.cpp
copy to libstats/bootstrap/include/StatsBootstrapAtomClient.h
index 42fe35a..87930fd 100644
--- a/fs_mgr/clean_scratch_files.cpp
+++ b/libstats/bootstrap/include/StatsBootstrapAtomClient.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,9 +14,19 @@
  * limitations under the License.
  */
 
-#include <fs_mgr_overlayfs.h>
+#pragma once
 
-int main() {
-    android::fs_mgr::CleanupOldScratchFiles();
-    return 0;
-}
+#include <android/os/StatsBootstrapAtom.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+class StatsBootstrapAtomClient {
+  public:
+    static bool reportBootstrapAtom(const StatsBootstrapAtom& atom);
+};
+
+}  // namespace stats
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index 2a89e29..f07e32b 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -57,3 +57,13 @@
         "libstatspull_bindgen",
     ],
 }
+
+rust_test {
+    name: "libstatspull_bindgen_test",
+    srcs: [":libstatspull_bindgen"],
+    crate_name: "statspull_bindgen_test",
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    clippy_lints: "none",
+    lints: "none",
+}
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/Android.bp b/libutils/Android.bp
index 13e4c02..bda9d6b 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -99,7 +99,6 @@
 
             shared_libs: [
                 "libprocessgroup",
-                "libdl",
                 "libvndksupport",
             ],
 
@@ -178,6 +177,8 @@
         "//apex_available:platform",
     ],
     min_sdk_version: "apex_inherit",
+
+    afdo: true,
 }
 
 cc_library {
diff --git a/libutils/Looper.cpp b/libutils/Looper.cpp
index 14e3e35..292425a 100644
--- a/libutils/Looper.cpp
+++ b/libutils/Looper.cpp
@@ -20,6 +20,16 @@
 
 namespace android {
 
+namespace {
+
+constexpr uint64_t WAKE_EVENT_FD_SEQ = 1;
+
+epoll_event createEpollEvent(uint32_t events, uint64_t seq) {
+    return {.events = events, .data = {.u64 = seq}};
+}
+
+}  // namespace
+
 // --- WeakMessageHandler ---
 
 WeakMessageHandler::WeakMessageHandler(const wp<MessageHandler>& handler) :
@@ -64,7 +74,7 @@
       mSendingMessage(false),
       mPolling(false),
       mEpollRebuildRequired(false),
-      mNextRequestSeq(0),
+      mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),
       mResponseIndex(0),
       mNextMessageUptime(LLONG_MAX) {
     mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
@@ -137,22 +147,17 @@
         mEpollFd.reset();
     }
 
-    // Allocate the new epoll instance and register the wake pipe.
+    // Allocate the new epoll instance and register the WakeEventFd.
     mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
     LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
 
-    struct epoll_event eventItem;
-    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
-    eventItem.events = EPOLLIN;
-    eventItem.data.fd = mWakeEventFd.get();
-    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
+    epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
+    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                         strerror(errno));
 
-    for (size_t i = 0; i < mRequests.size(); i++) {
-        const Request& request = mRequests.valueAt(i);
-        struct epoll_event eventItem;
-        request.initEventItem(&eventItem);
+    for (const auto& [seq, request] : mRequests) {
+        epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
 
         int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
         if (epollResult < 0) {
@@ -276,26 +281,28 @@
 #endif
 
     for (int i = 0; i < eventCount; i++) {
-        int fd = eventItems[i].data.fd;
+        const SequenceNumber seq = eventItems[i].data.u64;
         uint32_t epollEvents = eventItems[i].events;
-        if (fd == mWakeEventFd.get()) {
+        if (seq == WAKE_EVENT_FD_SEQ) {
             if (epollEvents & EPOLLIN) {
                 awoken();
             } else {
                 ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
             }
         } else {
-            ssize_t requestIndex = mRequests.indexOfKey(fd);
-            if (requestIndex >= 0) {
+            const auto& request_it = mRequests.find(seq);
+            if (request_it != mRequests.end()) {
+                const auto& request = request_it->second;
                 int events = 0;
                 if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                 if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                 if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                 if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
-                pushResponse(events, mRequests.valueAt(requestIndex));
+                mResponses.push({.seq = seq, .events = events, .request = request});
             } else {
-                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
-                        "no longer registered.", epollEvents, fd);
+                ALOGW("Ignoring unexpected epoll events 0x%x for sequence number %" PRIu64
+                      " that is no longer registered.",
+                      epollEvents, seq);
             }
         }
     }
@@ -354,7 +361,8 @@
             // we need to be a little careful when removing the file descriptor afterwards.
             int callbackResult = response.request.callback->handleEvent(fd, events, data);
             if (callbackResult == 0) {
-                removeFd(fd, response.request.seq);
+                AutoMutex _l(mLock);
+                removeSequenceNumberLocked(response.seq);
             }
 
             // Clear the callback reference in the response structure promptly because we
@@ -416,13 +424,6 @@
     TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
 }
 
-void Looper::pushResponse(int events, const Request& request) {
-    Response response;
-    response.events = events;
-    response.request = request;
-    mResponses.push(response);
-}
-
 int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) {
     return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : nullptr, data);
 }
@@ -449,27 +450,27 @@
 
     { // acquire lock
         AutoMutex _l(mLock);
+        // There is a sequence number reserved for the WakeEventFd.
+        if (mNextRequestSeq == WAKE_EVENT_FD_SEQ) mNextRequestSeq++;
+        const SequenceNumber seq = mNextRequestSeq++;
 
         Request request;
         request.fd = fd;
         request.ident = ident;
         request.events = events;
-        request.seq = mNextRequestSeq++;
         request.callback = callback;
         request.data = data;
-        if (mNextRequestSeq == -1) mNextRequestSeq = 0; // reserve sequence number -1
 
-        struct epoll_event eventItem;
-        request.initEventItem(&eventItem);
-
-        ssize_t requestIndex = mRequests.indexOfKey(fd);
-        if (requestIndex < 0) {
+        epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
+        auto seq_it = mSequenceNumberByFd.find(fd);
+        if (seq_it == mSequenceNumberByFd.end()) {
             int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &eventItem);
             if (epollResult < 0) {
                 ALOGE("Error adding epoll events for fd %d: %s", fd, strerror(errno));
                 return -1;
             }
-            mRequests.add(fd, request);
+            mRequests.emplace(seq, request);
+            mSequenceNumberByFd.emplace(fd, seq);
         } else {
             int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_MOD, fd, &eventItem);
             if (epollResult < 0) {
@@ -486,7 +487,7 @@
                     // set from scratch because it may contain an old file handle that we are
                     // now unable to remove since its file descriptor is no longer valid.
                     // No such problem would have occurred if we were using the poll system
-                    // call instead, but that approach carries others disadvantages.
+                    // call instead, but that approach carries other disadvantages.
 #if DEBUG_CALLBACKS
                     ALOGD("%p ~ addFd - EPOLL_CTL_MOD failed due to file descriptor "
                             "being recycled, falling back on EPOLL_CTL_ADD: %s",
@@ -504,71 +505,69 @@
                     return -1;
                 }
             }
-            mRequests.replaceValueAt(requestIndex, request);
+            const SequenceNumber oldSeq = seq_it->second;
+            mRequests.erase(oldSeq);
+            mRequests.emplace(seq, request);
+            seq_it->second = seq;
         }
     } // release lock
     return 1;
 }
 
 int Looper::removeFd(int fd) {
-    return removeFd(fd, -1);
+    AutoMutex _l(mLock);
+    const auto& it = mSequenceNumberByFd.find(fd);
+    if (it == mSequenceNumberByFd.end()) {
+        return 0;
+    }
+    return removeSequenceNumberLocked(it->second);
 }
 
-int Looper::removeFd(int fd, int seq) {
+int Looper::removeSequenceNumberLocked(SequenceNumber seq) {
 #if DEBUG_CALLBACKS
-    ALOGD("%p ~ removeFd - fd=%d, seq=%d", this, fd, seq);
+    ALOGD("%p ~ removeFd - fd=%d, seq=%u", this, fd, seq);
 #endif
 
-    { // acquire lock
-        AutoMutex _l(mLock);
-        ssize_t requestIndex = mRequests.indexOfKey(fd);
-        if (requestIndex < 0) {
-            return 0;
-        }
+    const auto& request_it = mRequests.find(seq);
+    if (request_it == mRequests.end()) {
+        return 0;
+    }
+    const int fd = request_it->second.fd;
 
-        // Check the sequence number if one was given.
-        if (seq != -1 && mRequests.valueAt(requestIndex).seq != seq) {
+    // Always remove the FD from the request map even if an error occurs while
+    // updating the epoll set so that we avoid accidentally leaking callbacks.
+    mRequests.erase(request_it);
+    mSequenceNumberByFd.erase(fd);
+
+    int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_DEL, fd, nullptr);
+    if (epollResult < 0) {
+        if (errno == EBADF || errno == ENOENT) {
+            // Tolerate EBADF or ENOENT because it means that the file descriptor was closed
+            // before its callback was unregistered. This error may occur naturally when a
+            // callback has the side-effect of closing the file descriptor before returning and
+            // unregistering itself.
+            //
+            // Unfortunately due to kernel limitations we need to rebuild the epoll
+            // set from scratch because it may contain an old file handle that we are
+            // now unable to remove since its file descriptor is no longer valid.
+            // No such problem would have occurred if we were using the poll system
+            // call instead, but that approach carries other disadvantages.
 #if DEBUG_CALLBACKS
-            ALOGD("%p ~ removeFd - sequence number mismatch, oldSeq=%d",
-                    this, mRequests.valueAt(requestIndex).seq);
+            ALOGD("%p ~ removeFd - EPOLL_CTL_DEL failed due to file descriptor "
+                  "being closed: %s",
+                  this, strerror(errno));
 #endif
-            return 0;
+            scheduleEpollRebuildLocked();
+        } else {
+            // Some other error occurred.  This is really weird because it means
+            // our list of callbacks got out of sync with the epoll set somehow.
+            // We defensively rebuild the epoll set to avoid getting spurious
+            // notifications with nowhere to go.
+            ALOGE("Error removing epoll events for fd %d: %s", fd, strerror(errno));
+            scheduleEpollRebuildLocked();
+            return -1;
         }
-
-        // Always remove the FD from the request map even if an error occurs while
-        // updating the epoll set so that we avoid accidentally leaking callbacks.
-        mRequests.removeItemsAt(requestIndex);
-
-        int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_DEL, fd, nullptr);
-        if (epollResult < 0) {
-            if (seq != -1 && (errno == EBADF || errno == ENOENT)) {
-                // Tolerate EBADF or ENOENT when the sequence number is known because it
-                // means that the file descriptor was closed before its callback was
-                // unregistered.  This error may occur naturally when a callback has the
-                // side-effect of closing the file descriptor before returning and
-                // unregistering itself.
-                //
-                // Unfortunately due to kernel limitations we need to rebuild the epoll
-                // set from scratch because it may contain an old file handle that we are
-                // now unable to remove since its file descriptor is no longer valid.
-                // No such problem would have occurred if we were using the poll system
-                // call instead, but that approach carries others disadvantages.
-#if DEBUG_CALLBACKS
-                ALOGD("%p ~ removeFd - EPOLL_CTL_DEL failed due to file descriptor "
-                        "being closed: %s", this, strerror(errno));
-#endif
-                scheduleEpollRebuildLocked();
-            } else {
-                // Some other error occurred.  This is really weird because it means
-                // our list of callbacks got out of sync with the epoll set somehow.
-                // We defensively rebuild the epoll set to avoid getting spurious
-                // notifications with nowhere to go.
-                ALOGE("Error removing epoll events for fd %d: %s", fd, strerror(errno));
-                scheduleEpollRebuildLocked();
-                return -1;
-            }
-        }
-    } // release lock
+    }
     return 1;
 }
 
@@ -656,14 +655,11 @@
     return mPolling;
 }
 
-void Looper::Request::initEventItem(struct epoll_event* eventItem) const {
-    int epollEvents = 0;
+uint32_t Looper::Request::getEpollEvents() const {
+    uint32_t epollEvents = 0;
     if (events & EVENT_INPUT) epollEvents |= EPOLLIN;
     if (events & EVENT_OUTPUT) epollEvents |= EPOLLOUT;
-
-    memset(eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
-    eventItem->events = epollEvents;
-    eventItem->data.fd = fd;
+    return epollEvents;
 }
 
 MessageHandler::~MessageHandler() { }
diff --git a/libutils/Looper_test.cpp b/libutils/Looper_test.cpp
index 34f424b..c859f9c 100644
--- a/libutils/Looper_test.cpp
+++ b/libutils/Looper_test.cpp
@@ -8,6 +8,9 @@
 #include <utils/Looper.h>
 #include <utils/StopWatch.h>
 #include <utils/Timers.h>
+#include <thread>
+#include <unordered_map>
+#include <utility>
 #include "Looper_test_pipe.h"
 
 #include <utils/threads.h>
@@ -710,4 +713,123 @@
             << "no more messages to handle";
 }
 
+class LooperEventCallback : public LooperCallback {
+  public:
+    using Callback = std::function<int(int fd, int events)>;
+    explicit LooperEventCallback(Callback callback) : mCallback(std::move(callback)) {}
+    int handleEvent(int fd, int events, void* /*data*/) override { return mCallback(fd, events); }
+
+  private:
+    Callback mCallback;
+};
+
+// A utility class that allows for pipes to be added and removed from the looper, and polls the
+// looper from a different thread.
+class ThreadedLooperUtil {
+  public:
+    explicit ThreadedLooperUtil(const sp<Looper>& looper) : mLooper(looper), mRunning(true) {
+        mThread = std::thread([this]() {
+            while (mRunning) {
+                static constexpr std::chrono::milliseconds POLL_TIMEOUT(500);
+                mLooper->pollOnce(POLL_TIMEOUT.count());
+            }
+        });
+    }
+
+    ~ThreadedLooperUtil() {
+        mRunning = false;
+        mThread.join();
+    }
+
+    // Create a new pipe, and return the write end of the pipe and the id used to track the pipe.
+    // The read end of the pipe is added to the looper.
+    std::pair<int /*id*/, base::unique_fd> createPipe() {
+        int pipeFd[2];
+        if (pipe(pipeFd)) {
+            ADD_FAILURE() << "pipe() failed.";
+            return {};
+        }
+        const int readFd = pipeFd[0];
+        const int writeFd = pipeFd[1];
+
+        int id;
+        {  // acquire lock
+            std::scoped_lock l(mLock);
+
+            id = mNextId++;
+            mFds.emplace(id, readFd);
+
+            auto removeCallback = [this, id, readFd](int fd, int events) {
+                EXPECT_EQ(readFd, fd) << "Received callback for incorrect fd.";
+                if ((events & Looper::EVENT_HANGUP) == 0) {
+                    return 1;  // Not a hangup, keep the callback.
+                }
+                removePipe(id);
+                return 0;  // Remove the callback.
+            };
+
+            mLooper->addFd(readFd, 0, Looper::EVENT_INPUT,
+                           new LooperEventCallback(std::move(removeCallback)), nullptr);
+        }  // release lock
+
+        return {id, base::unique_fd(writeFd)};
+    }
+
+    // Remove the pipe with the given id.
+    void removePipe(int id) {
+        std::scoped_lock l(mLock);
+        if (mFds.find(id) == mFds.end()) {
+            return;
+        }
+        mLooper->removeFd(mFds[id].get());
+        mFds.erase(id);
+    }
+
+    // Check if the pipe with the given id exists and has not been removed.
+    bool hasPipe(int id) {
+        std::scoped_lock l(mLock);
+        return mFds.find(id) != mFds.end();
+    }
+
+  private:
+    sp<Looper> mLooper;
+    std::atomic<bool> mRunning;
+    std::thread mThread;
+
+    std::mutex mLock;
+    std::unordered_map<int, base::unique_fd> mFds GUARDED_BY(mLock);
+    int mNextId GUARDED_BY(mLock) = 0;
+};
+
+TEST_F(LooperTest, MultiThreaded_NoUnexpectedFdRemoval) {
+    ThreadedLooperUtil util(mLooper);
+
+    // Iterate repeatedly to try to recreate a flaky instance.
+    for (int i = 0; i < 1000; i++) {
+        auto [firstPipeId, firstPipeFd] = util.createPipe();
+        const int firstFdNumber = firstPipeFd.get();
+
+        // Close the first pipe's fd, causing a fd hangup.
+        firstPipeFd.reset();
+
+        // Request to remove the pipe from this test thread. This causes a race for pipe removal
+        // between the hangup in the looper's thread and this remove request from the test thread.
+        util.removePipe(firstPipeId);
+
+        // Create the second pipe. Since the fds for the first pipe are closed, this pipe should
+        // have the same fd numbers as the first pipe because the lowest unused fd number is used.
+        const auto [secondPipeId, fd] = util.createPipe();
+        EXPECT_EQ(firstFdNumber, fd.get())
+                << "The first and second fds must match for the purposes of this test.";
+
+        // Wait for unexpected hangup to occur.
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+
+        ASSERT_TRUE(util.hasPipe(secondPipeId)) << "The second pipe was removed unexpectedly.";
+
+        util.removePipe(secondPipeId);
+    }
+    SUCCEED() << "No unexpectedly removed fds.";
+}
+
 } // namespace android
diff --git a/libutils/RefBase.cpp b/libutils/RefBase.cpp
index b57e287..0518927 100644
--- a/libutils/RefBase.cpp
+++ b/libutils/RefBase.cpp
@@ -170,7 +170,7 @@
         : mStrong(INITIAL_STRONG_VALUE)
         , mWeak(0)
         , mBase(base)
-        , mFlags(0)
+        , mFlags(OBJECT_LIFETIME_STRONG)
     {
     }
 
@@ -189,7 +189,7 @@
         : mStrong(INITIAL_STRONG_VALUE)
         , mWeak(0)
         , mBase(base)
-        , mFlags(0)
+        , mFlags(OBJECT_LIFETIME_STRONG)
         , mStrongRefs(NULL)
         , mWeakRefs(NULL)
         , mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
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/TEST_MAPPING b/libutils/TEST_MAPPING
new file mode 100644
index 0000000..c8ef45c
--- /dev/null
+++ b/libutils/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "libutils_test"
+    }
+  ]
+}
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 540dcf4..3bf5779 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,12 +315,9 @@
     }
 
     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.
-        get_sched_policy(getpid(), &policy);
-        rc = SetTaskProfiles(tid, {get_sched_policy_profile_name(policy)}, true) ? 0 : -1;
+        rc = SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true) ? 0 : -1;
     }
 
     if (rc) {
diff --git a/libutils/include/utils/Looper.h b/libutils/include/utils/Looper.h
index 466fbb7..b387d68 100644
--- a/libutils/include/utils/Looper.h
+++ b/libutils/include/utils/Looper.h
@@ -17,15 +17,16 @@
 #ifndef UTILS_LOOPER_H
 #define UTILS_LOOPER_H
 
-#include <utils/threads.h>
 #include <utils/RefBase.h>
-#include <utils/KeyedVector.h>
 #include <utils/Timers.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
 
 #include <sys/epoll.h>
 
 #include <android-base/unique_fd.h>
 
+#include <unordered_map>
 #include <utility>
 
 namespace android {
@@ -421,18 +422,20 @@
     static sp<Looper> getForThread();
 
 private:
-    struct Request {
-        int fd;
-        int ident;
-        int events;
-        int seq;
-        sp<LooperCallback> callback;
-        void* data;
+  using SequenceNumber = uint64_t;
 
-        void initEventItem(struct epoll_event* eventItem) const;
-    };
+  struct Request {
+      int fd;
+      int ident;
+      int events;
+      sp<LooperCallback> callback;
+      void* data;
+
+      uint32_t getEpollEvents() const;
+  };
 
     struct Response {
+        SequenceNumber seq;
         int events;
         Request request;
     };
@@ -463,9 +466,14 @@
     android::base::unique_fd mEpollFd;  // guarded by mLock but only modified on the looper thread
     bool mEpollRebuildRequired; // guarded by mLock
 
-    // Locked list of file descriptor monitoring requests.
-    KeyedVector<int, Request> mRequests;  // guarded by mLock
-    int mNextRequestSeq;
+    // Locked maps of fds and sequence numbers monitoring requests.
+    // Both maps must be kept in sync at all times.
+    std::unordered_map<SequenceNumber, Request> mRequests;               // guarded by mLock
+    std::unordered_map<int /*fd*/, SequenceNumber> mSequenceNumberByFd;  // guarded by mLock
+
+    // The sequence number to use for the next fd that is added to the looper.
+    // The sequence number 0 is reserved for the WakeEventFd.
+    SequenceNumber mNextRequestSeq;  // guarded by mLock
 
     // This state is only used privately by pollOnce and does not require a lock since
     // it runs on a single thread.
@@ -474,9 +482,8 @@
     nsecs_t mNextMessageUptime; // set to LLONG_MAX when none
 
     int pollInner(int timeoutMillis);
-    int removeFd(int fd, int seq);
+    int removeSequenceNumberLocked(SequenceNumber seq);  // requires mLock
     void awoken();
-    void pushResponse(int events, const Request& request);
     void rebuildEpollLocked();
     void scheduleEpollRebuildLocked();
 
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/mini_keyctl/Android.bp b/mini_keyctl/Android.bp
index 417ddac..0325c5b 100644
--- a/mini_keyctl/Android.bp
+++ b/mini_keyctl/Android.bp
@@ -13,6 +13,7 @@
     ],
     cflags: ["-Werror", "-Wall", "-Wextra"],
     export_include_dirs: ["."],
+    recovery_available: true,
 }
 
 cc_binary {
diff --git a/mini_keyctl/OWNERS b/mini_keyctl/OWNERS
new file mode 100644
index 0000000..f9e7b25
--- /dev/null
+++ b/mini_keyctl/OWNERS
@@ -0,0 +1,5 @@
+alanstokes@google.com
+ebiggers@google.com
+jeffv@google.com
+jiyong@google.com
+victorhsieh@google.com
diff --git a/property_service/TEST_MAPPING b/property_service/TEST_MAPPING
new file mode 100644
index 0000000..fcdc86a
--- /dev/null
+++ b/property_service/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "propertyinfoserializer_tests"
+    }
+  ]
+}
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 6861456..87646f9 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -18,7 +18,11 @@
         "-Werror",
     ],
     stl: "none",
-    system_shared_libs: [],
-    header_libs: ["libc_headers"],
+    target: {
+        bionic: {
+            system_shared_libs: [],
+            header_libs: ["libc_headers"],
+        },
+    },
     export_include_dirs: ["include"],
 }
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/Android.mk b/rootdir/Android.mk
index 9b80575..2ed9eec 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -75,9 +75,14 @@
   EXPORT_GLOBAL_GCOV_OPTIONS := export GCOV_PREFIX /data/misc/trace
 endif
 
-EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS :=
 ifeq ($(CLANG_COVERAGE),true)
-  EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%20m.profraw
+  ifeq ($(BIONIC_COVERAGE),false)
+    # http://b/210012154 Disable continuous coverage if instrumentation is on
+    # for bionic/libc
+    EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang%c-%20m.profraw
+  else
+    EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%20m.profraw
+  endif
 endif
 
 # Put it here instead of in init.rc module definition,
@@ -110,6 +115,9 @@
 ifdef BOARD_USES_METADATA_PARTITION
   LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/metadata
 endif
+ifdef BOARD_USES_SYSTEM_DLKM_PARTITION
+  LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/system_dlkm
+endif
 
 # For /odm partition.
 LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/odm
diff --git a/rootdir/avb/Android.bp b/rootdir/avb/Android.bp
deleted file mode 100644
index cfc59a7..0000000
--- a/rootdir/avb/Android.bp
+++ /dev/null
@@ -1,31 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
-    name: "q-gsi_avbpubkey",
-    srcs: [
-        "q-gsi.avbpubkey",
-    ],
-}
-
-filegroup {
-    name: "r-gsi_avbpubkey",
-    srcs: [
-        "r-gsi.avbpubkey",
-    ],
-}
-
-filegroup {
-    name: "s-gsi_avbpubkey",
-    srcs: [
-        "s-gsi.avbpubkey",
-    ],
-}
-
-filegroup {
-    name: "qcar-gsi_avbpubkey",
-    srcs: [
-        "qcar-gsi.avbpubkey",
-    ],
-}
diff --git a/rootdir/avb/Android.mk b/rootdir/avb/Android.mk
index 647cfa2..8cf3172 100644
--- a/rootdir/avb/Android.mk
+++ b/rootdir/avb/Android.mk
@@ -15,19 +15,6 @@
 endif
 
 #######################################
-# q-gsi.avbpubkey
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := q-gsi.avbpubkey
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(my_gsi_avb_keys_path)
-
-include $(BUILD_PREBUILT)
-
-#######################################
 # q-developer-gsi.avbpubkey
 include $(CLEAR_VARS)
 
@@ -41,19 +28,6 @@
 include $(BUILD_PREBUILT)
 
 #######################################
-# r-gsi.avbpubkey
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := r-gsi.avbpubkey
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(my_gsi_avb_keys_path)
-
-include $(BUILD_PREBUILT)
-
-#######################################
 # r-developer-gsi.avbpubkey
 include $(CLEAR_VARS)
 
@@ -67,19 +41,6 @@
 include $(BUILD_PREBUILT)
 
 #######################################
-# s-gsi.avbpubkey
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := s-gsi.avbpubkey
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(my_gsi_avb_keys_path)
-
-include $(BUILD_PREBUILT)
-
-#######################################
 # s-developer-gsi.avbpubkey
 include $(CLEAR_VARS)
 
@@ -92,17 +53,4 @@
 
 include $(BUILD_PREBUILT)
 
-#######################################
-# qcar-gsi.avbpubkey
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := qcar-gsi.avbpubkey
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(my_gsi_avb_keys_path)
-
-include $(BUILD_PREBUILT)
-
 my_gsi_avb_keys_path :=
diff --git a/rootdir/avb/q-gsi.avbpubkey b/rootdir/avb/q-gsi.avbpubkey
deleted file mode 100644
index 5ed7543..0000000
--- a/rootdir/avb/q-gsi.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/rootdir/avb/qcar-gsi.avbpubkey b/rootdir/avb/qcar-gsi.avbpubkey
deleted file mode 100644
index ce56646..0000000
--- a/rootdir/avb/qcar-gsi.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/rootdir/avb/r-gsi.avbpubkey b/rootdir/avb/r-gsi.avbpubkey
deleted file mode 100644
index 2609b30..0000000
--- a/rootdir/avb/r-gsi.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/rootdir/avb/s-gsi.avbpubkey b/rootdir/avb/s-gsi.avbpubkey
deleted file mode 100644
index 9065fb8..0000000
--- a/rootdir/avb/s-gsi.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/rootdir/init.rc b/rootdir/init.rc
index ab0aff5..cd73498 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -78,8 +78,8 @@
     mkdir /dev/boringssl 0755 root root
     mkdir /dev/boringssl/selftest 0755 root root
 
-    # Mount tracefs
-    mount tracefs tracefs /sys/kernel/tracing
+    # Mount tracefs (with GID=AID_READTRACEFS)
+    mount tracefs tracefs /sys/kernel/tracing gid=3012
 
     # create sys dirctory
     mkdir /dev/sys 0755 system system
@@ -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.
@@ -591,7 +588,20 @@
     # Load trusted keys from dm-verity protected partitions
     exec -- /system/bin/fsverity_init --load-verified-keys
 
-on late-fs && property:ro.product.cpu.abilist64=*
+# Only enable the bootreceiver tracing instance for kernels 5.10 and above.
+on late-fs && property:ro.kernel.version=4.9
+    setprop bootreceiver.enable 0
+on late-fs && property:ro.kernel.version=4.14
+    setprop bootreceiver.enable 0
+on late-fs && property:ro.kernel.version=4.19
+    setprop bootreceiver.enable 0
+on late-fs && property:ro.kernel.version=5.4
+    setprop bootreceiver.enable 0
+on late-fs
+    # Bootreceiver tracing instance is enabled by default.
+    setprop bootreceiver.enable ${bootreceiver.enable:-1}
+
+on property:ro.product.cpu.abilist64=* && property:bootreceiver.enable=1
     # Set up a tracing instance for system_server to monitor error_report_end events.
     # These are sent by kernel tools like KASAN and KFENCE when a memory corruption
     # is detected. This is only needed for 64-bit systems.
@@ -659,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
@@ -776,11 +798,13 @@
     # Create directories to push tests to for each linker namespace.
     # Create the subdirectories in case the first test is run as root
     # so it doesn't end up owned by root.
-    mkdir /data/local/tests 0700 shell shell
-    mkdir /data/local/tests/product 0700 shell shell
-    mkdir /data/local/tests/system 0700 shell shell
-    mkdir /data/local/tests/unrestricted 0700 shell shell
-    mkdir /data/local/tests/vendor 0700 shell shell
+    # Set directories to be executable by any process so that debuggerd,
+    # aka crash_dump, can read any executables/shared libraries.
+    mkdir /data/local/tests 0701 shell shell
+    mkdir /data/local/tests/product 0701 shell shell
+    mkdir /data/local/tests/system 0701 shell shell
+    mkdir /data/local/tests/unrestricted 0701 shell shell
+    mkdir /data/local/tests/vendor 0701 shell shell
 
     # create dalvik-cache, so as to enforce our permissions
     mkdir /data/dalvik-cache 0771 root root encryption=Require
@@ -890,6 +914,9 @@
     exec - media_rw media_rw -- /system/bin/chattr +F /data/media
     mkdir /data/media/obb 0770 media_rw media_rw encryption=Attempt
 
+    # Create directories for boot animation.
+    mkdir /data/bootanim 0755 system system encryption=None
+
     exec_start derive_sdk
 
     init_user0
@@ -1082,6 +1109,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
 
@@ -1097,37 +1127,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
@@ -1138,7 +1137,7 @@
 # and chown/chmod does not work for /proc/sys/ entries.
 # So proxy writes through init.
 on property:sys.sysctl.extra_free_kbytes=*
-    write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}
+    exec -- /system/bin/extra_free_kbytes.sh ${sys.sysctl.extra_free_kbytes}
 
 # Allow users to drop caches
 on property:perf.drop_caches=3
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/set-verity-state/set-verity-state.cpp b/set-verity-state/set-verity-state.cpp
index 0a26aba..52a7f74 100644
--- a/set-verity-state/set-verity-state.cpp
+++ b/set-verity-state/set-verity-state.cpp
@@ -244,25 +244,6 @@
       any_changed = true;
     }
     avb_ops_user_free(ops);
-  } else {
-    // Not using AVB - assume VB1.0.
-
-    // read all fstab entries at once from all sources
-    android::fs_mgr::Fstab fstab;
-    if (!android::fs_mgr::ReadDefaultFstab(&fstab)) {
-      printf("Failed to read fstab\n");
-      suggest_run_adb_root();
-      return 0;
-    }
-
-    // Loop through entries looking for ones that verity manages.
-    for (const auto& entry : fstab) {
-      if (entry.fs_mgr_flags.verify) {
-        if (set_verity_enabled_state(entry.blk_device.c_str(), entry.mount_point.c_str(), enable)) {
-          any_changed = true;
-        }
-      }
-    }
   }
   if (!any_changed) any_changed = overlayfs_setup(enable);
 
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index 97e8d8e..d85f6ed 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -26,6 +26,7 @@
         "mkshrc",
         "newfs_msdos",
         "reboot",
+        "settaskprofile",
         "sh",
         "simpleperf",
         "simpleperf_app_runner",
diff --git a/storaged/Android.bp b/storaged/Android.bp
index 9d5cb48..7960af3 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -24,14 +24,23 @@
     shared_libs: [
         "android.hardware.health@1.0",
         "android.hardware.health@2.0",
+        "android.hardware.health-V1-ndk",
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "libhidlbase",
         "liblog",
         "libprotobuf-cpp-lite",
         "libutils",
         "libz",
+        "packagemanager_aidl-cpp",
+    ],
+
+    static_libs: [
+        "android.hardware.health-translate-ndk",
+        "libhealthhalutils",
+        "libhealthshim",
     ],
 
     cflags: [
@@ -66,7 +75,6 @@
         ":storaged_aidl_private",
     ],
 
-    static_libs: ["libhealthhalutils"],
     header_libs: ["libbatteryservice_headers"],
 
     logtags: ["EventLogTags.logtags"],
@@ -89,7 +97,6 @@
     srcs: ["main.cpp"],
 
     static_libs: [
-        "libhealthhalutils",
         "libstoraged",
     ],
 }
@@ -106,9 +113,11 @@
     srcs: ["tests/storaged_test.cpp"],
 
     static_libs: [
-        "libhealthhalutils",
         "libstoraged",
     ],
+    test_suites: [
+        "general-tests",
+    ],
 }
 
 // AIDL interface between storaged and framework.jar
diff --git a/storaged/include/storaged.h b/storaged/include/storaged.h
index 79b5d41..e120271 100644
--- a/storaged/include/storaged.h
+++ b/storaged/include/storaged.h
@@ -28,6 +28,7 @@
 
 #include <utils/Mutex.h>
 
+#include <aidl/android/hardware/health/IHealth.h>
 #include <android/hardware/health/2.0/IHealth.h>
 
 #define FRIEND_TEST(test_case_name, test_name) \
@@ -67,6 +68,8 @@
 // UID IO threshold in bytes
 #define DEFAULT_PERIODIC_CHORES_UID_IO_THRESHOLD ( 1024 * 1024 * 1024ULL )
 
+class storaged_t;
+
 struct storaged_config {
     int periodic_chores_interval_unit;
     int periodic_chores_interval_disk_stats_publish;
@@ -75,15 +78,33 @@
     int event_time_check_usec;  // check how much cputime spent in event loop
 };
 
-class storaged_t : public android::hardware::health::V2_0::IHealthInfoCallback,
-                   public android::hardware::hidl_death_recipient {
+struct HealthServicePair {
+    std::shared_ptr<aidl::android::hardware::health::IHealth> aidl_health;
+    android::sp<android::hardware::health::V2_0::IHealth> hidl_health;
+    static HealthServicePair get();
+};
+
+class hidl_health_death_recipient : public android::hardware::hidl_death_recipient {
+  public:
+    hidl_health_death_recipient(const android::sp<android::hardware::health::V2_0::IHealth>& health)
+        : mHealth(health) {}
+    void serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who);
+
+  private:
+    android::sp<android::hardware::health::V2_0::IHealth> mHealth;
+};
+
+class storaged_t : public RefBase {
   private:
     time_t mTimer;
     storaged_config mConfig;
     unique_ptr<disk_stats_monitor> mDsm;
     uid_monitor mUidm;
     time_t mStarttime;
-    sp<android::hardware::health::V2_0::IHealth> health;
+    std::shared_ptr<aidl::android::hardware::health::IHealth> health;
+    sp<android::hardware::hidl_death_recipient> hidl_death_recp;
+    ndk::ScopedAIBinder_DeathRecipient aidl_death_recp;
+    shared_ptr<aidl::android::hardware::health::IHealthInfoCallback> aidl_health_callback;
     unique_ptr<storage_info_t> storage_info;
     static const uint32_t current_version;
     Mutex proto_lock;
@@ -135,10 +156,6 @@
     void add_user_ce(userid_t user_id);
     void remove_user_ce(userid_t user_id);
 
-    virtual ::android::hardware::Return<void> healthInfoChanged(
-        const ::android::hardware::health::V2_0::HealthInfo& info);
-    void serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who);
-
     void report_storage_info();
 
     void flush_protos(unordered_map<int, StoragedProto>* protos);
diff --git a/storaged/include/storaged_diskstats.h b/storaged/include/storaged_diskstats.h
index 0b93ba6..3996ef6 100644
--- a/storaged/include/storaged_diskstats.h
+++ b/storaged/include/storaged_diskstats.h
@@ -19,7 +19,7 @@
 
 #include <stdint.h>
 
-#include <android/hardware/health/2.0/IHealth.h>
+#include <aidl/android/hardware/health/IHealth.h>
 
 // number of attributes diskstats has
 #define DISK_STATS_SIZE ( 11 )
@@ -162,7 +162,7 @@
     const double mSigma;
     struct disk_perf mMean;
     struct disk_perf mStd;
-    android::sp<android::hardware::health::V2_0::IHealth> mHealth;
+    std::shared_ptr<aidl::android::hardware::health::IHealth> mHealth;
 
     void update_mean();
     void update_std();
@@ -173,14 +173,15 @@
     void update(struct disk_stats* stats);
 
 public:
-  disk_stats_monitor(const android::sp<android::hardware::health::V2_0::IHealth>& healthService,
+  disk_stats_monitor(const std::shared_ptr<aidl::android::hardware::health::IHealth>& healthService,
                      uint32_t window_size = 5, double sigma = 1.0)
       : DISK_STATS_PATH(
-            healthService != nullptr
-                ? nullptr
-                : (access(MMC_DISK_STATS_PATH, R_OK) == 0
-                       ? MMC_DISK_STATS_PATH
-                       : (access(SDA_DISK_STATS_PATH, R_OK) == 0 ? SDA_DISK_STATS_PATH : nullptr))),
+                healthService != nullptr
+                        ? nullptr
+                        : (access(MMC_DISK_STATS_PATH, R_OK) == 0
+                                   ? MMC_DISK_STATS_PATH
+                                   : (access(SDA_DISK_STATS_PATH, R_OK) == 0 ? SDA_DISK_STATS_PATH
+                                                                             : nullptr))),
         mPrevious(),
         mAccumulate(),
         mAccumulate_pub(),
diff --git a/storaged/include/storaged_info.h b/storaged/include/storaged_info.h
index 9c3d0e7..83c97ad 100644
--- a/storaged/include/storaged_info.h
+++ b/storaged/include/storaged_info.h
@@ -21,7 +21,7 @@
 
 #include <chrono>
 
-#include <android/hardware/health/2.0/IHealth.h>
+#include <aidl/android/hardware/health/IHealth.h>
 #include <utils/Mutex.h>
 
 #include "storaged.h"
@@ -71,8 +71,8 @@
 
   public:
     static storage_info_t* get_storage_info(
-        const sp<android::hardware::health::V2_0::IHealth>& healthService);
-    virtual ~storage_info_t() {};
+            const shared_ptr<aidl::android::hardware::health::IHealth>& healthService);
+    virtual ~storage_info_t(){};
     virtual void report() {};
     void load_perf_history_proto(const IOPerfHistory& perf_history);
     void refresh(IOPerfHistory* perf_history);
@@ -105,14 +105,14 @@
 
 class health_storage_info_t : public storage_info_t {
   private:
-    using IHealth = hardware::health::V2_0::IHealth;
-    using StorageInfo = hardware::health::V2_0::StorageInfo;
+    using IHealth = aidl::android::hardware::health::IHealth;
+    using StorageInfo = aidl::android::hardware::health::StorageInfo;
 
-    sp<IHealth> mHealth;
+    shared_ptr<IHealth> mHealth;
     void set_values_from_hal_storage_info(const StorageInfo& halInfo);
 
   public:
-    health_storage_info_t(const sp<IHealth>& service) : mHealth(service){};
+    health_storage_info_t(const shared_ptr<IHealth>& service) : mHealth(service){};
     virtual ~health_storage_info_t() {}
     virtual void report();
 };
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index b7aa89f..fb855f7 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -28,12 +28,16 @@
 #include <sstream>
 #include <string>
 
+#include <aidl/android/hardware/health/BnHealthInfoCallback.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
 #include <batteryservice/BatteryServiceConstants.h>
 #include <cutils/properties.h>
+#include <health-shim/shim.h>
 #include <healthhalutils/HealthHalUtils.h>
 #include <hidl/HidlTransportSupport.h>
 #include <hwbinder/IPCThreadState.h>
@@ -64,26 +68,59 @@
 
 const uint32_t storaged_t::current_version = 4;
 
+using aidl::android::hardware::health::BatteryStatus;
+using aidl::android::hardware::health::BnHealthInfoCallback;
+using aidl::android::hardware::health::HealthInfo;
+using aidl::android::hardware::health::IHealth;
+using aidl::android::hardware::health::IHealthInfoCallback;
 using android::hardware::interfacesEqual;
-using android::hardware::Return;
-using android::hardware::health::V1_0::BatteryStatus;
-using android::hardware::health::V1_0::toString;
 using android::hardware::health::V2_0::get_health_service;
-using android::hardware::health::V2_0::HealthInfo;
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
 using android::hidl::manager::V1_0::IServiceManager;
+using HidlHealth = android::hardware::health::V2_0::IHealth;
+using aidl::android::hardware::health::HealthShim;
+using ndk::ScopedAIBinder_DeathRecipient;
+using ndk::ScopedAStatus;
 
+HealthServicePair HealthServicePair::get() {
+    HealthServicePair ret;
+    auto service_name = IHealth::descriptor + "/default"s;
+    if (AServiceManager_isDeclared(service_name.c_str())) {
+        ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+        ret.aidl_health = IHealth::fromBinder(binder);
+        if (ret.aidl_health == nullptr) {
+            LOG(WARNING) << "AIDL health service is declared, but it cannot be retrieved.";
+        }
+    }
+    if (ret.aidl_health == nullptr) {
+        LOG(INFO) << "Unable to get AIDL health service, trying HIDL...";
+        ret.hidl_health = get_health_service();
+        if (ret.hidl_health != nullptr) {
+            ret.aidl_health = ndk::SharedRefBase::make<HealthShim>(ret.hidl_health);
+        }
+    }
+    if (ret.aidl_health == nullptr) {
+        LOG(WARNING) << "health: failed to find IHealth service";
+        return {};
+    }
+    return ret;
+}
 
 inline charger_stat_t is_charger_on(BatteryStatus prop) {
     return (prop == BatteryStatus::CHARGING || prop == BatteryStatus::FULL) ?
         CHARGER_ON : CHARGER_OFF;
 }
 
-Return<void> storaged_t::healthInfoChanged(const HealthInfo& props) {
-    mUidm.set_charger_state(is_charger_on(props.legacy.batteryStatus));
-    return android::hardware::Void();
-}
+class HealthInfoCallback : public BnHealthInfoCallback {
+  public:
+    HealthInfoCallback(uid_monitor* uidm) : mUidm(uidm) {}
+    ScopedAStatus healthInfoChanged(const HealthInfo& info) override {
+        mUidm->set_charger_state(is_charger_on(info.batteryStatus));
+        return ScopedAStatus::ok();
+    }
+
+  private:
+    uid_monitor* mUidm;
+};
 
 void storaged_t::init() {
     init_health_service();
@@ -91,42 +128,59 @@
     storage_info.reset(storage_info_t::get_storage_info(health));
 }
 
+static void onHealthBinderDied(void*) {
+    LOG(ERROR) << "health service died, exiting";
+    android::hardware::IPCThreadState::self()->stopProcess();
+    exit(1);
+}
+
 void storaged_t::init_health_service() {
     if (!mUidm.enabled())
         return;
 
-    health = get_health_service();
-    if (health == NULL) {
-        LOG(WARNING) << "health: failed to find IHealth service";
-        return;
-    }
+    auto [aidlHealth, hidlHealth] = HealthServicePair::get();
+    health = aidlHealth;
+    if (health == nullptr) return;
 
     BatteryStatus status = BatteryStatus::UNKNOWN;
-    auto ret = health->getChargeStatus([&](Result r, BatteryStatus v) {
-        if (r != Result::SUCCESS) {
-            LOG(WARNING) << "health: cannot get battery status " << toString(r);
-            return;
-        }
-        if (v == BatteryStatus::UNKNOWN) {
-            LOG(WARNING) << "health: invalid battery status";
-        }
-        status = v;
-    });
+    auto ret = health->getChargeStatus(&status);
     if (!ret.isOk()) {
-        LOG(WARNING) << "health: get charge status transaction error " << ret.description();
+        LOG(WARNING) << "health: cannot get battery status: " << ret.getDescription();
+    }
+    if (status == BatteryStatus::UNKNOWN) {
+        LOG(WARNING) << "health: invalid battery status";
     }
 
     mUidm.init(is_charger_on(status));
     // register listener after init uid_monitor
-    health->registerCallback(this);
-    health->linkToDeath(this, 0 /* cookie */);
+    aidl_health_callback = std::make_shared<HealthInfoCallback>(&mUidm);
+    ret = health->registerCallback(aidl_health_callback);
+    if (!ret.isOk()) {
+        LOG(WARNING) << "health: failed to register callback: " << ret.getDescription();
+    }
+
+    if (hidlHealth != nullptr) {
+        hidl_death_recp = new hidl_health_death_recipient(hidlHealth);
+        auto ret = hidlHealth->linkToDeath(hidl_death_recp, 0 /* cookie */);
+        if (!ret.isOk()) {
+            LOG(WARNING) << "Failed to link to death (HIDL): " << ret.description();
+        }
+    } else {
+        aidl_death_recp =
+                ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new(onHealthBinderDied));
+        auto ret = AIBinder_linkToDeath(health->asBinder().get(), aidl_death_recp.get(),
+                                        nullptr /* cookie */);
+        if (ret != STATUS_OK) {
+            LOG(WARNING) << "Failed to link to death (AIDL): "
+                         << ScopedAStatus(AStatus_fromStatus(ret)).getDescription();
+        }
+    }
 }
 
-void storaged_t::serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who) {
-    if (health != NULL && interfacesEqual(health, who.promote())) {
-        LOG(ERROR) << "health service died, exiting";
-        android::hardware::IPCThreadState::self()->stopProcess();
-        exit(1);
+void hidl_health_death_recipient::serviceDied(uint64_t cookie,
+                                              const wp<::android::hidl::base::V1_0::IBase>& who) {
+    if (mHealth != nullptr && interfacesEqual(mHealth, who.promote())) {
+        onHealthBinderDied(reinterpret_cast<void*>(cookie));
     } else {
         LOG(ERROR) << "unknown service died";
     }
diff --git a/storaged/storaged_diskstats.cpp b/storaged/storaged_diskstats.cpp
index 52bd4e0..1eae5a1 100644
--- a/storaged/storaged_diskstats.cpp
+++ b/storaged/storaged_diskstats.cpp
@@ -30,11 +30,8 @@
 
 namespace {
 
-using android::sp;
-using android::hardware::health::V2_0::DiskStats;
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_0::toString;
+using aidl::android::hardware::health::DiskStats;
+using aidl::android::hardware::health::IHealth;
 
 #ifdef DEBUG
 void log_debug_disk_perf(struct disk_perf* perf, const char* type) {
@@ -121,39 +118,30 @@
     dst->io_in_queue = src.ioInQueue;
 }
 
-bool get_disk_stats_from_health_hal(const sp<IHealth>& service, struct disk_stats* stats) {
+bool get_disk_stats_from_health_hal(const std::shared_ptr<IHealth>& service,
+                                    struct disk_stats* stats) {
     struct timespec ts;
     if (!get_time(&ts)) {
         return false;
     }
 
-    bool success = false;
-    auto ret = service->getDiskStats([&success, stats](auto result, const auto& halStats) {
-        if (result == Result::NOT_SUPPORTED) {
-            LOG(DEBUG) << "getDiskStats is not supported on health HAL.";
-            return;
+    std::vector<DiskStats> halStats;
+    auto ret = service->getDiskStats(&halStats);
+    if (ret.isOk()) {
+        if (halStats.size() > 0) {
+            convert_hal_disk_stats(stats, halStats[0]);
+            init_disk_stats_other(ts, stats);
+            return true;
         }
-        if (result != Result::SUCCESS || halStats.size() == 0) {
-            LOG(ERROR) << "getDiskStats failed with result " << toString(result) << " and size "
-                       << halStats.size();
-            return;
-        }
-
-        convert_hal_disk_stats(stats, halStats[0]);
-        success = true;
-    });
-
-    if (!ret.isOk()) {
-        LOG(ERROR) << "getDiskStats failed with " << ret.description();
+        LOG(ERROR) << "getDiskStats succeeded but size is 0";
         return false;
     }
-
-    if (!success) {
+    if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+        LOG(DEBUG) << "getDiskStats is not supported on health HAL.";
         return false;
     }
-
-    init_disk_stats_other(ts, stats);
-    return true;
+    LOG(ERROR) << "getDiskStats failed with " << ret.getDescription();
+    return false;
 }
 
 struct disk_perf get_disk_perf(struct disk_stats* stats)
diff --git a/storaged/storaged_info.cpp b/storaged/storaged_info.cpp
index bb21829..3e646e0 100644
--- a/storaged/storaged_info.cpp
+++ b/storaged/storaged_info.cpp
@@ -36,9 +36,8 @@
 using namespace android::base;
 using namespace storaged_proto;
 
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_0::StorageInfo;
+using aidl::android::hardware::health::IHealth;
+using aidl::android::hardware::health::StorageInfo;
 
 const string emmc_info_t::emmc_sysfs = "/sys/bus/mmc/devices/mmc0:0001/";
 const char* emmc_info_t::emmc_ver_str[9] = {
@@ -57,7 +56,7 @@
 
 } // namespace
 
-storage_info_t* storage_info_t::get_storage_info(const sp<IHealth>& healthService) {
+storage_info_t* storage_info_t::get_storage_info(const shared_ptr<IHealth>& healthService) {
     if (healthService != nullptr) {
         return new health_storage_info_t(healthService);
     }
@@ -326,23 +325,22 @@
 }
 
 void health_storage_info_t::report() {
-    auto ret = mHealth->getStorageInfo([this](auto result, const auto& halInfos) {
-        if (result == Result::NOT_SUPPORTED) {
-            LOG(DEBUG) << "getStorageInfo is not supported on health HAL.";
+    vector<StorageInfo> halInfos;
+    auto ret = mHealth->getStorageInfo(&halInfos);
+    if (ret.isOk()) {
+        if (halInfos.size() != 0) {
+            set_values_from_hal_storage_info(halInfos[0]);
+            publish();
             return;
         }
-        if (result != Result::SUCCESS || halInfos.size() == 0) {
-            LOG(ERROR) << "getStorageInfo failed with result " << toString(result) << " and size "
-                       << halInfos.size();
-            return;
-        }
-        set_values_from_hal_storage_info(halInfos[0]);
-        publish();
-    });
-
-    if (!ret.isOk()) {
-        LOG(ERROR) << "getStorageInfo failed with " << ret.description();
+        LOG(ERROR) << "getStorageInfo succeeded but size is 0";
+        return;
     }
+    if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+        LOG(DEBUG) << "getStorageInfo is not supported on health HAL.";
+        return;
+    }
+    LOG(ERROR) << "getStorageInfo failed with " << ret.getDescription();
 }
 
 void health_storage_info_t::set_values_from_hal_storage_info(const StorageInfo& halInfo) {
diff --git a/storaged/tests/storaged_test.cpp b/storaged/tests/storaged_test.cpp
index 64009c2..bb71bf3 100644
--- a/storaged/tests/storaged_test.cpp
+++ b/storaged/tests/storaged_test.cpp
@@ -25,6 +25,7 @@
 
 #include <gtest/gtest.h>
 
+#include <aidl/android/hardware/health/IHealth.h>
 #include <healthhalutils/HealthHalUtils.h>
 #include <storaged.h>               // data structures
 #include <storaged_utils.h>         // functions to test
@@ -64,20 +65,23 @@
 } // namespace
 
 // the return values of the tested functions should be the expected ones
-const char* DISK_STATS_PATH;
+const char* get_disk_stats_path() {
+    if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
+        return MMC_DISK_STATS_PATH;
+    } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) {
+        return SDA_DISK_STATS_PATH;
+    } else {
+        return nullptr;
+    }
+}
 TEST(storaged_test, retvals) {
     struct disk_stats stats;
     memset(&stats, 0, sizeof(struct disk_stats));
 
-    if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
-        DISK_STATS_PATH = MMC_DISK_STATS_PATH;
-    } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) {
-        DISK_STATS_PATH = SDA_DISK_STATS_PATH;
-    } else {
-        return;
-    }
+    auto disk_stats_path = get_disk_stats_path();
+    if (disk_stats_path == nullptr) GTEST_SKIP();
 
-    EXPECT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats));
+    EXPECT_TRUE(parse_disk_stats(disk_stats_path, &stats));
 
     struct disk_stats old_stats;
     memset(&old_stats, 0, sizeof(struct disk_stats));
@@ -92,7 +96,9 @@
 
 TEST(storaged_test, disk_stats) {
     struct disk_stats stats = {};
-    ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats));
+    auto disk_stats_path = get_disk_stats_path();
+    if (disk_stats_path == nullptr) GTEST_SKIP();
+    ASSERT_TRUE(parse_disk_stats(disk_stats_path, &stats));
 
     // every entry of stats (except io_in_flight) should all be greater than 0
     for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
@@ -103,7 +109,7 @@
     // accumulation of the increments should be the same with the overall increment
     struct disk_stats base = {}, tmp = {}, curr, acc = {}, inc[5];
     for (uint i = 0; i < 5; ++i) {
-        ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &curr));
+        ASSERT_TRUE(parse_disk_stats(disk_stats_path, &curr));
         if (i == 0) {
             base = curr;
             tmp = curr;
@@ -235,9 +241,7 @@
 }
 
 TEST(storaged_test, disk_stats_monitor) {
-    using android::hardware::health::V2_0::get_health_service;
-
-    auto healthService = get_health_service();
+    auto [healthService, hidlHealth] = HealthServicePair::get();
 
     // asserting that there is one file for diskstats
     ASSERT_TRUE(healthService != nullptr || access(MMC_DISK_STATS_PATH, R_OK) >= 0 ||
@@ -246,6 +250,13 @@
     // testing if detect() will return the right value
     disk_stats_monitor dsm_detect{healthService};
     ASSERT_TRUE(dsm_detect.enabled());
+
+    // Even if enabled(), healthService may not support disk stats. Check if it is supported.
+    std::vector<aidl::android::hardware::health::DiskStats> halStats;
+    if (healthService->getDiskStats(&halStats).getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+        GTEST_SKIP();
+    }
+
     // feed monitor with constant perf data for io perf baseline
     // using constant perf is reasonable since the functionality of stream_stats
     // has already been tested
diff --git a/toolbox/generate-input.h-labels.py b/toolbox/generate-input.h-labels.py
index c0e9fce..20db638 100755
--- a/toolbox/generate-input.h-labels.py
+++ b/toolbox/generate-input.h-labels.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2015 The Android Open Source Project
 #
@@ -16,7 +16,7 @@
 #
 # pylint: disable=bad-indentation,bad-continuation
 
-from __future__ import print_function
+
 import os
 import re
 import sys
diff --git a/trusty/Android.bp b/trusty/Android.bp
index 38c6204..e733839 100644
--- a/trusty/Android.bp
+++ b/trusty/Android.bp
@@ -14,29 +14,5 @@
 // limitations under the License.
 
 package {
-    default_applicable_licenses: ["system_core_trusty_license"],
-}
-
-// Added automatically by a large-scale-change that took the approach of
-// 'apply every license found to every target'. While this makes sure we respect
-// every license restriction, it may not be entirely correct.
-//
-// e.g. GPL in an MIT project might only apply to the contrib/ directory.
-//
-// Please consider splitting the single license below into multiple licenses,
-// taking care not to lose any license_kind information, and overriding the
-// default license using the 'licenses: [...]' property on targets as needed.
-//
-// For unused files, consider creating a 'fileGroup' with "//visibility:private"
-// to attach the license to, and including a comment whether the files may be
-// used in the current project.
-// See: http://go/android-license-faq
-license {
-    name: "system_core_trusty_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-        "SPDX-license-identifier-MIT",
-    ],
-    // large-scale-change unable to identify any license_text files
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index e4d9b39..c72af40 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -245,6 +245,8 @@
     tipc_fd = tipc_connect(dev_name, APPLOADER_PORT);
     if (tipc_fd < 0) {
         LOG(ERROR) << "Failed to connect to Trusty app loader: " << strerror(-tipc_fd);
+        // print this to stderr too to avoid silently exiting when run as non-root
+        fprintf(stderr, "Failed to connect to Trusty app loader: %s\n", strerror(-tipc_fd));
         rc = tipc_fd;
         goto err_tipc_connect;
     }
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index 155363c..0e916ef 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -105,8 +105,10 @@
         "keymint/TrustySharedSecret.cpp",
         "keymint/service.cpp",
     ],
+    defaults: [
+        "keymint_use_latest_hal_aidl_ndk_shared",
+    ],
     shared_libs: [
-        "android.hardware.security.keymint-V1-ndk",
         "android.hardware.security.secureclock-V1-ndk",
         "android.hardware.security.sharedsecret-V1-ndk",
         "lib_android_keymaster_keymint_utils",
@@ -117,9 +119,11 @@
         "libkeymint",
         "liblog",
         "libtrusty",
+        "libutils",
     ],
     required: [
         "android.hardware.hardware_keystore.xml",
+        "RemoteProvisioner",
     ],
 }
 
@@ -141,6 +145,7 @@
         "libtrusty",
         "libhardware",
         "libkeymaster_messages",
+        "libutils",
         "libxml2",
     ],
     export_include_dirs: ["include"],
@@ -168,6 +173,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 0956fe6..db1a9f4 100644
--- a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
+++ b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
@@ -19,23 +19,30 @@
 // TODO: make this generic in libtrusty
 
 #include <errno.h>
+#include <poll.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/uio.h>
 #include <unistd.h>
 
 #include <algorithm>
+#include <variant>
+#include <vector>
 
 #include <log/log.h>
 #include <trusty/tipc.h>
 
 #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) {
@@ -46,8 +53,27 @@
     return 0;
 }
 
-int trusty_keymaster_call(uint32_t cmd, void* in, uint32_t in_size, uint8_t* out,
-                          uint32_t* out_size) {
+class VectorEraser {
+  public:
+    VectorEraser(std::vector<uint8_t>* v) : _v(v) {}
+    ~VectorEraser() {
+        if (_v) {
+            std::fill(const_cast<volatile uint8_t*>(_v->data()),
+                      const_cast<volatile uint8_t*>(_v->data() + _v->size()), 0);
+        }
+    }
+    void disarm() { _v = nullptr; }
+    VectorEraser(const VectorEraser&) = delete;
+    VectorEraser& operator=(const VectorEraser&) = delete;
+    VectorEraser(VectorEraser&& other) = delete;
+    VectorEraser& operator=(VectorEraser&&) = delete;
+
+  private:
+    std::vector<uint8_t>* _v;
+};
+
+std::variant<int, std::vector<uint8_t>> trusty_keymaster_call_2(uint32_t cmd, void* in,
+                                                                uint32_t in_size) {
     if (handle_ < 0) {
         ALOGE("not connected\n");
         return -EINVAL;
@@ -63,23 +89,106 @@
     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) {
         ALOGE("failed to send cmd (%d) to %s: %s\n", cmd, KEYMASTER_PORT, strerror(errno));
         return -errno;
     }
-    size_t out_max_size = *out_size;
-    *out_size = 0;
+
+    std::vector<uint8_t> out(TRUSTY_KEYMASTER_RECV_BUF_SIZE);
+    VectorEraser out_eraser(&out);
+    uint8_t* write_pos = out.data();
+    uint8_t* out_end = out.data() + out.size();
+
     struct iovec iov[2];
     struct keymaster_message header;
     iov[0] = {.iov_base = &header, .iov_len = sizeof(struct keymaster_message)};
     while (true) {
-        iov[1] = {.iov_base = out + *out_size,
-                  .iov_len = std::min<uint32_t>(KEYMASTER_MAX_BUFFER_LENGTH,
-                                                out_max_size - *out_size)};
+        if (out_end - write_pos < KEYMASTER_MAX_BUFFER_LENGTH) {
+            // In stead of using std::vector.resize(), allocate a new one to have chance
+            // at zeroing the old buffer.
+            std::vector<uint8_t> new_out(out.size() + KEYMASTER_MAX_BUFFER_LENGTH);
+            // After the swap below this erases the old out buffer.
+            VectorEraser new_out_eraser(&new_out);
+            std::copy(out.data(), write_pos, new_out.begin());
+
+            auto write_offset = write_pos - out.data();
+
+            std::swap(new_out, out);
+
+            write_pos = out.data() + write_offset;
+            out_end = out.data() + out.size();
+        }
+        size_t buffer_size = 0;
+        if (__builtin_sub_overflow(reinterpret_cast<uintptr_t>(out_end),
+                                   reinterpret_cast<uintptr_t>(write_pos), &buffer_size)) {
+            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));
@@ -95,13 +204,36 @@
             ALOGE("invalid command (%d)", header.cmd);
             return -EINVAL;
         }
-        *out_size += ((size_t)rc - sizeof(struct keymaster_message));
+        write_pos += ((size_t)rc - sizeof(struct keymaster_message));
         if (header.cmd & KEYMASTER_STOP_BIT) {
             break;
         }
     }
 
-    return rc;
+    out.resize(write_pos - out.data());
+    out_eraser.disarm();
+    return out;
+}
+
+int trusty_keymaster_call(uint32_t cmd, void* in, uint32_t in_size, uint8_t* out,
+                          uint32_t* out_size) {
+    auto result = trusty_keymaster_call_2(cmd, in, in_size);
+    if (auto out_buffer = std::get_if<std::vector<uint8_t>>(&result)) {
+        if (out_buffer->size() <= *out_size) {
+            std::copy(out_buffer->begin(), out_buffer->end(), out);
+            std::fill(const_cast<volatile uint8_t*>(&*out_buffer->begin()),
+                      const_cast<volatile uint8_t*>(&*out_buffer->end()), 0);
+
+            *out_size = out_buffer->size();
+            return 0;
+        } else {
+            ALOGE("Message was to large (%zu) for the provided buffer (%u)", out_buffer->size(),
+                  *out_size);
+            return -EMSGSIZE;
+        }
+    } else {
+        return std::get<int>(result);
+    }
 }
 
 void trusty_keymaster_disconnect() {
@@ -155,28 +287,27 @@
     req.Serialize(send_buf, send_buf + req_size);
 
     // Send it
-    uint8_t recv_buf[TRUSTY_KEYMASTER_RECV_BUF_SIZE];
-    keymaster::Eraser recv_buf_eraser(recv_buf, TRUSTY_KEYMASTER_RECV_BUF_SIZE);
-    uint32_t rsp_size = TRUSTY_KEYMASTER_RECV_BUF_SIZE;
-    int rc = trusty_keymaster_call(command, send_buf, req_size, recv_buf, &rsp_size);
-    if (rc < 0) {
+    auto response = trusty_keymaster_call_2(command, send_buf, req_size);
+    if (auto response_buffer = std::get_if<std::vector<uint8_t>>(&response)) {
+        keymaster::Eraser response_buffer_erasor(response_buffer->data(), response_buffer->size());
+        ALOGV("Received %zu byte response\n", response_buffer->size());
+
+        const uint8_t* p = response_buffer->data();
+        if (!rsp->Deserialize(&p, p + response_buffer->size())) {
+            ALOGE("Error deserializing response of size %zu\n", response_buffer->size());
+            return KM_ERROR_UNKNOWN_ERROR;
+        } else if (rsp->error != KM_ERROR_OK) {
+            ALOGE("Response of size %zu contained error code %d\n", response_buffer->size(),
+                  (int)rsp->error);
+        }
+        return rsp->error;
+    } else {
+        auto rc = std::get<int>(response);
         // Reset the connection on tipc error
         trusty_keymaster_disconnect();
         trusty_keymaster_connect();
         ALOGE("tipc error: %d\n", rc);
         // TODO(swillden): Distinguish permanent from transient errors and set error_ appropriately.
         return translate_error(rc);
-    } else {
-        ALOGV("Received %d byte response\n", rsp_size);
     }
-
-    const uint8_t* p = recv_buf;
-    if (!rsp->Deserialize(&p, p + rsp_size)) {
-        ALOGE("Error deserializing response of size %d\n", (int)rsp_size);
-        return KM_ERROR_UNKNOWN_ERROR;
-    } else if (rsp->error != KM_ERROR_OK) {
-        ALOGE("Response of size %d contained error code %d\n", (int)rsp_size, (int)rsp->error);
-        return rsp->error;
-    }
-    return rsp->error;
 }
diff --git a/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp b/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
index 5f8524b..68a7912 100644
--- a/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
+++ b/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
@@ -91,7 +91,7 @@
 }  // namespace
 
 ScopedAStatus TrustyKeyMintDevice::getHardwareInfo(KeyMintHardwareInfo* info) {
-    info->versionNumber = 1;
+    info->versionNumber = 2;
     info->securityLevel = kSecurityLevel;
     info->keyMintName = "TrustyKeyMintDevice";
     info->keyMintAuthorName = "Google";
diff --git a/trusty/keymaster/keymint/service.cpp b/trusty/keymaster/keymint/service.cpp
index 4060278..3447b27 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());
@@ -41,7 +41,7 @@
 
 int main() {
     auto trustyKeymaster = std::make_shared<keymaster::TrustyKeymaster>();
-    int err = trustyKeymaster->Initialize(keymaster::KmVersion::KEYMINT_1);
+    int err = trustyKeymaster->Initialize(keymaster::KmVersion::KEYMINT_2);
     if (err != 0) {
         LOG(FATAL) << "Could not initialize TrustyKeymaster for KeyMint (" << err << ")";
         return -1;
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index 29c6f93..eb0acb5 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -45,36 +45,40 @@
 static const char *main_ctrl_name = "com.android.ipc-unittest.ctrl";
 static const char* receiver_name = "com.android.trusty.memref.receiver";
 
-static const char *_sopts = "hsvD:t:r:m:b:";
+static const char* _sopts = "hsvDS:t:r:m:b:";
+/* clang-format off */
 static const struct option _lopts[] =  {
-	{"help",    no_argument,       0, 'h'},
-	{"silent",  no_argument,       0, 's'},
-	{"variable",no_argument,       0, 'v'},
-	{"dev",     required_argument, 0, 'D'},
-	{"repeat",  required_argument, 0, 'r'},
-	{"burst",   required_argument, 0, 'b'},
-	{"msgsize", required_argument, 0, 'm'},
-	{0, 0, 0, 0}
+    {"help",    no_argument,       0, 'h'},
+    {"silent",  no_argument,       0, 's'},
+    {"variable",no_argument,       0, 'v'},
+    {"dev",     required_argument, 0, 'D'},
+    {"srv",     required_argument, 0, 'S'},
+    {"repeat",  required_argument, 0, 'r'},
+    {"burst",   required_argument, 0, 'b'},
+    {"msgsize", required_argument, 0, 'm'},
+    {0, 0, 0, 0}
 };
+/* clang-format on */
 
-static const char *usage =
-"Usage: %s [options]\n"
-"\n"
-"options:\n"
-"  -h, --help            prints this message and exit\n"
-"  -D, --dev name        device name\n"
-"  -t, --test name       test to run\n"
-"  -r, --repeat cnt      repeat count\n"
-"  -m, --msgsize size    max message size\n"
-"  -v, --variable        variable message size\n"
-"  -s, --silent          silent\n"
-"\n"
-;
+static const char* usage =
+        "Usage: %s [options]\n"
+        "\n"
+        "options:\n"
+        "  -h, --help            prints this message and exit\n"
+        "  -D, --dev name        device name\n"
+        "  -S, --srv name        service name\n"
+        "  -t, --test name       test to run\n"
+        "  -r, --repeat cnt      repeat count\n"
+        "  -b, --burst cnt       burst count\n"
+        "  -m, --msgsize size    max message size\n"
+        "  -v, --variable        variable message size\n"
+        "  -s, --silent          silent\n"
+        "\n";
 
 static const char* usage_long =
         "\n"
         "The following tests are available:\n"
-        "   connect      - connect to datasink service\n"
+        "   connect      - connect to specified service, defaults to echo+datasink\n"
         "   connect_foo  - connect to non existing service\n"
         "   burst_write  - send messages to datasink service\n"
         "   echo         - send/receive messages to echo service\n"
@@ -96,798 +100,774 @@
 static uint opt_msgburst = 32;
 static bool opt_variable = false;
 static bool opt_silent = false;
+static char* srv_name = NULL;
 
 static void print_usage_and_exit(const char *prog, int code, bool verbose)
 {
-	fprintf (stderr, usage, prog);
-	if (verbose)
-		fprintf (stderr, "%s", usage_long);
-	exit(code);
+    fprintf(stderr, usage, prog);
+    if (verbose) fprintf(stderr, "%s", usage_long);
+    exit(code);
 }
 
 static void parse_options(int argc, char **argv)
 {
-	int c;
-	int oidx = 0;
+    int c;
+    int oidx = 0;
 
-	while (1)
-	{
-		c = getopt_long (argc, argv, _sopts, _lopts, &oidx);
-		if (c == -1)
-			break; /* done */
+    while (1) {
+        c = getopt_long(argc, argv, _sopts, _lopts, &oidx);
+        if (c == -1) break; /* done */
 
-		switch (c) {
+        switch (c) {
+            case 'D':
+                dev_name = strdup(optarg);
+                break;
 
-		case 'D':
-			dev_name = strdup(optarg);
-		break;
+            case 'S':
+                srv_name = strdup(optarg);
+                break;
 
-		case 't':
-			test_name = strdup(optarg);
-		break;
+            case 't':
+                test_name = strdup(optarg);
+                break;
 
-		case 'v':
-			opt_variable = true;
-		break;
+            case 'v':
+                opt_variable = true;
+                break;
 
-		case 'r':
-			opt_repeat = atoi(optarg);
-		break;
+            case 'r':
+                opt_repeat = atoi(optarg);
+                break;
 
-		case 'm':
-			opt_msgsize = atoi(optarg);
-		break;
+            case 'm':
+                opt_msgsize = atoi(optarg);
+                break;
 
-		case 'b':
-			opt_msgburst = atoi(optarg);
-		break;
+            case 'b':
+                opt_msgburst = atoi(optarg);
+                break;
 
-		case 's':
-			opt_silent = true;
-		break;
+            case 's':
+                opt_silent = true;
+                break;
 
-		case 'h':
-		      print_usage_and_exit(argv[0], EXIT_SUCCESS, true);
-		break;
+            case 'h':
+                print_usage_and_exit(argv[0], EXIT_SUCCESS, true);
+                break;
 
-		default:
-		      print_usage_and_exit(argv[0], EXIT_FAILURE, false);
-		}
-	}
+            default:
+                print_usage_and_exit(argv[0], EXIT_FAILURE, false);
+        }
+    }
 }
 
 static int connect_test(uint repeat)
 {
-	uint i;
-	int  echo_fd;
-	int  dsink_fd;
+    uint i;
+    int echo_fd;
+    int dsink_fd;
+    int custom_fd;
 
-	if (!opt_silent) {
-		printf("%s: repeat = %u\n", __func__, repeat);
-	}
+    if (!opt_silent) {
+        printf("%s: repeat = %u\n", __func__, repeat);
+    }
 
-	for (i = 0; i < repeat; i++) {
-		echo_fd = tipc_connect(dev_name, echo_name);
-		if (echo_fd < 0) {
-			fprintf(stderr, "Failed to connect to '%s' service\n",
-				"echo");
-		}
-		dsink_fd = tipc_connect(dev_name, datasink_name);
-		if (dsink_fd < 0) {
-			fprintf(stderr, "Failed to connect to '%s' service\n",
-				"datasink");
-		}
+    for (i = 0; i < repeat; i++) {
+        if (srv_name) {
+            custom_fd = tipc_connect(dev_name, srv_name);
+            if (custom_fd < 0) {
+                fprintf(stderr, "Failed to connect to '%s' service\n", srv_name);
+            }
+            if (custom_fd >= 0) {
+                tipc_close(custom_fd);
+            }
+        } else {
+            echo_fd = tipc_connect(dev_name, echo_name);
+            if (echo_fd < 0) {
+                fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
+            }
+            dsink_fd = tipc_connect(dev_name, datasink_name);
+            if (dsink_fd < 0) {
+                fprintf(stderr, "Failed to connect to '%s' service\n", "datasink");
+            }
 
-		if (echo_fd >= 0) {
-			tipc_close(echo_fd);
-		}
-		if (dsink_fd >= 0) {
-			tipc_close(dsink_fd);
-		}
-	}
+            if (echo_fd >= 0) {
+                tipc_close(echo_fd);
+            }
+            if (dsink_fd >= 0) {
+                tipc_close(dsink_fd);
+            }
+        }
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n", __func__);
-	}
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	return 0;
+    return 0;
 }
 
 static int connect_foo(uint repeat)
 {
-	uint i;
-	int  fd;
+    uint i;
+    int fd;
 
-	if (!opt_silent) {
-		printf("%s: repeat = %u\n", __func__, repeat);
-	}
+    if (!opt_silent) {
+        printf("%s: repeat = %u\n", __func__, repeat);
+    }
 
-	for (i = 0; i < repeat; i++) {
-		fd = tipc_connect(dev_name, "foo");
-		if (fd >= 0) {
-			fprintf(stderr, "succeeded to connect to '%s' service\n",
-				"foo");
-			tipc_close(fd);
-		}
-	}
+    for (i = 0; i < repeat; i++) {
+        fd = tipc_connect(dev_name, "foo");
+        if (fd >= 0) {
+            fprintf(stderr, "succeeded to connect to '%s' service\n", "foo");
+            tipc_close(fd);
+        }
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n", __func__);
-	}
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	return 0;
+    return 0;
 }
 
 
 static int closer1_test(uint repeat)
 {
-	uint i;
-	int  fd;
+    uint i;
+    int fd;
 
-	if (!opt_silent) {
-		printf("%s: repeat = %u\n", __func__, repeat);
-	}
+    if (!opt_silent) {
+        printf("%s: repeat = %u\n", __func__, repeat);
+    }
 
-	for (i = 0; i < repeat; i++) {
-		fd = tipc_connect(dev_name, closer1_name);
-		if (fd < 0) {
-			fprintf(stderr, "Failed to connect to '%s' service\n",
-				"closer1");
-			continue;
-		}
-		if (!opt_silent) {
-			printf("%s: connected\n", __func__);
-		}
-		tipc_close(fd);
-	}
+    for (i = 0; i < repeat; i++) {
+        fd = tipc_connect(dev_name, closer1_name);
+        if (fd < 0) {
+            fprintf(stderr, "Failed to connect to '%s' service\n", "closer1");
+            continue;
+        }
+        if (!opt_silent) {
+            printf("%s: connected\n", __func__);
+        }
+        tipc_close(fd);
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n", __func__);
-	}
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	return 0;
+    return 0;
 }
 
 static int closer2_test(uint repeat)
 {
-	uint i;
-	int  fd;
+    uint i;
+    int fd;
 
-	if (!opt_silent) {
-		printf("%s: repeat = %u\n", __func__, repeat);
-	}
+    if (!opt_silent) {
+        printf("%s: repeat = %u\n", __func__, repeat);
+    }
 
-	for (i = 0; i < repeat; i++) {
-		fd = tipc_connect(dev_name, closer2_name);
-		if (fd < 0) {
-			if (!opt_silent) {
-				printf("failed to connect to '%s' service\n", "closer2");
-			}
-		} else {
-			/* this should always fail */
-			fprintf(stderr, "connected to '%s' service\n", "closer2");
-			tipc_close(fd);
-		}
-	}
+    for (i = 0; i < repeat; i++) {
+        fd = tipc_connect(dev_name, closer2_name);
+        if (fd < 0) {
+            if (!opt_silent) {
+                printf("failed to connect to '%s' service\n", "closer2");
+            }
+        } else {
+            /* this should always fail */
+            fprintf(stderr, "connected to '%s' service\n", "closer2");
+            tipc_close(fd);
+        }
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n", __func__);
-	}
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	return 0;
+    return 0;
 }
 
 static int closer3_test(uint repeat)
 {
-	uint i, j;
-	ssize_t rc;
-	int  fd[4];
-	char buf[64];
+    uint i, j;
+    ssize_t rc;
+    int fd[4];
+    char buf[64];
 
-	if (!opt_silent) {
-		printf("%s: repeat = %u\n", __func__, repeat);
-	}
+    if (!opt_silent) {
+        printf("%s: repeat = %u\n", __func__, repeat);
+    }
 
-	for (i = 0; i < repeat; i++) {
+    for (i = 0; i < repeat; i++) {
+        /* open 4 connections to closer3 service */
+        for (j = 0; j < 4; j++) {
+            fd[j] = tipc_connect(dev_name, closer3_name);
+            if (fd[j] < 0) {
+                fprintf(stderr, "fd[%d]: failed to connect to '%s' service\n", j, "closer3");
+            } else {
+                if (!opt_silent) {
+                    printf("%s: fd[%d]=%d: connected\n", __func__, j, fd[j]);
+                }
+                memset(buf, i + j, sizeof(buf));
+                rc = write(fd[j], buf, sizeof(buf));
+                if (rc != sizeof(buf)) {
+                    if (!opt_silent) {
+                        printf("%s: fd[%d]=%d: write returned  = %zd\n", __func__, j, fd[j], rc);
+                    }
+                    perror("closer3_test: write");
+                }
+            }
+        }
 
-		/* open 4 connections to closer3 service */
-		for (j = 0; j < 4; j++) {
-			fd[j] = tipc_connect(dev_name, closer3_name);
-			if (fd[j] < 0) {
-				fprintf(stderr, "fd[%d]: failed to connect to '%s' service\n", j, "closer3");
-			} else {
-				if (!opt_silent) {
-					printf("%s: fd[%d]=%d: connected\n", __func__, j, fd[j]);
-				}
-				memset(buf, i + j, sizeof(buf));
-				rc = write(fd[j], buf, sizeof(buf));
-				if (rc != sizeof(buf)) {
-					if (!opt_silent) {
-						printf("%s: fd[%d]=%d: write returned  = %zd\n",
-							__func__, j, fd[j], rc);
-					}
-					perror("closer3_test: write");
-				}
-			}
-		}
+        /* sleep a bit */
+        sleep(1);
 
-		/* sleep a bit */
-		sleep(1);
+        /* It is expected that they will be closed by remote */
+        for (j = 0; j < 4; j++) {
+            if (fd[j] < 0) continue;
+            rc = write(fd[j], buf, sizeof(buf));
+            if (rc != sizeof(buf)) {
+                if (!opt_silent) {
+                    printf("%s: fd[%d]=%d: write returned = %zd\n", __func__, j, fd[j], rc);
+                }
+                perror("closer3_test: write");
+            }
+        }
 
-		/* It is expected that they will be closed by remote */
-		for (j = 0; j < 4; j++) {
-			if (fd[j] < 0)
-				continue;
-			rc = write(fd[j], buf, sizeof(buf));
-			if (rc != sizeof(buf)) {
-				if (!opt_silent) {
-					printf("%s: fd[%d]=%d: write returned = %zd\n",
-						__func__, j, fd[j], rc);
-				}
-				perror("closer3_test: write");
-			}
-		}
+        /* then they have to be closed by remote */
+        for (j = 0; j < 4; j++) {
+            if (fd[j] >= 0) {
+                tipc_close(fd[j]);
+            }
+        }
+    }
 
-		/* then they have to be closed by remote */
-		for (j = 0; j < 4; j++) {
-			if (fd[j] >= 0) {
-				tipc_close(fd[j]);
-			}
-		}
-	}
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n", __func__);
-	}
-
-	return 0;
+    return 0;
 }
 
 
 static int echo_test(uint repeat, uint msgsz, bool var)
 {
-	uint i;
-	ssize_t rc;
-	size_t  msg_len;
-	int  echo_fd =-1;
-	char tx_buf[msgsz];
-	char rx_buf[msgsz];
+    uint i;
+    ssize_t rc;
+    size_t msg_len;
+    int echo_fd = -1;
+    char tx_buf[msgsz];
+    char rx_buf[msgsz];
 
-	if (!opt_silent) {
-		printf("%s: repeat %u: msgsz %u: variable %s\n",
-			__func__, repeat, msgsz, var ? "true" : "false");
-	}
+    if (!opt_silent) {
+        printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
+               var ? "true" : "false");
+    }
 
-	echo_fd = tipc_connect(dev_name, echo_name);
-	if (echo_fd < 0) {
-		fprintf(stderr, "Failed to connect to service\n");
-		return echo_fd;
-	}
+    echo_fd = tipc_connect(dev_name, echo_name);
+    if (echo_fd < 0) {
+        fprintf(stderr, "Failed to connect to service\n");
+        return echo_fd;
+    }
 
-	for (i = 0; i < repeat; i++) {
+    for (i = 0; i < repeat; i++) {
+        msg_len = msgsz;
+        if (opt_variable && msgsz) {
+            msg_len = rand() % msgsz;
+        }
 
-		msg_len = msgsz;
-		if (opt_variable && msgsz) {
-			msg_len = rand() % msgsz;
-		}
+        memset(tx_buf, i + 1, msg_len);
 
-		memset(tx_buf, i + 1, msg_len);
+        rc = write(echo_fd, tx_buf, msg_len);
+        if ((size_t)rc != msg_len) {
+            perror("echo_test: write");
+            break;
+        }
 
-		rc = write(echo_fd, tx_buf, msg_len);
-		if ((size_t)rc != msg_len) {
-			perror("echo_test: write");
-			break;
-		}
+        rc = read(echo_fd, rx_buf, msg_len);
+        if (rc < 0) {
+            perror("echo_test: read");
+            break;
+        }
 
-		rc = read(echo_fd, rx_buf, msg_len);
-		if (rc < 0) {
-			perror("echo_test: read");
-			break;
-		}
+        if ((size_t)rc != msg_len) {
+            fprintf(stderr, "data truncated (%zu vs. %zu)\n", rc, msg_len);
+            continue;
+        }
 
-		if ((size_t)rc != msg_len) {
-			fprintf(stderr, "data truncated (%zu vs. %zu)\n",
-			                 rc, msg_len);
-			continue;
-		}
+        if (memcmp(tx_buf, rx_buf, (size_t)rc)) {
+            fprintf(stderr, "data mismatch\n");
+            continue;
+        }
+    }
 
-		if (memcmp(tx_buf, rx_buf, (size_t) rc)) {
-			fprintf(stderr, "data mismatch\n");
-			continue;
-		}
-	}
+    tipc_close(echo_fd);
 
-	tipc_close(echo_fd);
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n",__func__);
-	}
-
-	return 0;
+    return 0;
 }
 
 static int burst_write_test(uint repeat, uint msgburst, uint msgsz, bool var)
 {
-	int fd;
-	uint i, j;
-	ssize_t rc;
-	size_t  msg_len;
-	char tx_buf[msgsz];
+    int fd;
+    uint i, j;
+    ssize_t rc;
+    size_t msg_len;
+    char tx_buf[msgsz];
 
-	if (!opt_silent) {
-		printf("%s: repeat %u: burst %u: msgsz %u: variable %s\n",
-			__func__, repeat, msgburst, msgsz,
-			var ? "true" : "false");
-	}
+    if (!opt_silent) {
+        printf("%s: repeat %u: burst %u: msgsz %u: variable %s\n", __func__, repeat, msgburst,
+               msgsz, var ? "true" : "false");
+    }
 
-	for (i = 0; i < repeat; i++) {
+    for (i = 0; i < repeat; i++) {
+        fd = tipc_connect(dev_name, datasink_name);
+        if (fd < 0) {
+            fprintf(stderr, "Failed to connect to '%s' service\n", "datasink");
+            break;
+        }
 
-		fd = tipc_connect(dev_name, datasink_name);
-		if (fd < 0) {
-			fprintf(stderr, "Failed to connect to '%s' service\n",
-				"datasink");
-			break;
-		}
+        for (j = 0; j < msgburst; j++) {
+            msg_len = msgsz;
+            if (var && msgsz) {
+                msg_len = rand() % msgsz;
+            }
 
-		for (j = 0; j < msgburst; j++) {
-			msg_len = msgsz;
-			if (var && msgsz) {
-				msg_len = rand() % msgsz;
-			}
+            memset(tx_buf, i + 1, msg_len);
+            rc = write(fd, tx_buf, msg_len);
+            if ((size_t)rc != msg_len) {
+                perror("burst_test: write");
+                break;
+            }
+        }
 
-			memset(tx_buf, i + 1, msg_len);
-			rc = write(fd, tx_buf, msg_len);
-			if ((size_t)rc != msg_len) {
-				perror("burst_test: write");
-				break;
-			}
-		}
+        tipc_close(fd);
+    }
 
-		tipc_close(fd);
-	}
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n",__func__);
-	}
-
-	return 0;
+    return 0;
 }
 
 
 static int _wait_for_msg(int fd, uint msgsz, int timeout)
 {
-	int rc;
-	fd_set rfds;
-	uint msgcnt = 0;
-	char rx_buf[msgsz];
-	struct timeval tv;
+    int rc;
+    fd_set rfds;
+    uint msgcnt = 0;
+    char rx_buf[msgsz];
+    struct timeval tv;
 
-	if (!opt_silent) {
-		printf("waiting (%d) for msg\n", timeout);
-	}
+    if (!opt_silent) {
+        printf("waiting (%d) for msg\n", timeout);
+    }
 
-	FD_ZERO(&rfds);
-	FD_SET(fd, &rfds);
+    FD_ZERO(&rfds);
+    FD_SET(fd, &rfds);
 
-	tv.tv_sec = timeout;
-	tv.tv_usec = 0;
+    tv.tv_sec = timeout;
+    tv.tv_usec = 0;
 
-	for(;;) {
-		rc = select(fd+1, &rfds, NULL, NULL, &tv);
+    for (;;) {
+        rc = select(fd + 1, &rfds, NULL, NULL, &tv);
 
-		if (rc == 0) {
-			if (!opt_silent) {
-				printf("select timedout\n");
-			}
-			break;
-		}
+        if (rc == 0) {
+            if (!opt_silent) {
+                printf("select timedout\n");
+            }
+            break;
+        }
 
-		if (rc == -1) {
-			perror("select_test: select");
-			return rc;
-		}
+        if (rc == -1) {
+            perror("select_test: select");
+            return rc;
+        }
 
-		rc = read(fd, rx_buf, sizeof(rx_buf));
-		if (rc < 0) {
-			perror("select_test: read");
-			return rc;
-		} else {
-			if (rc > 0) {
-				msgcnt++;
-			}
-		}
-	}
+        rc = read(fd, rx_buf, sizeof(rx_buf));
+        if (rc < 0) {
+            perror("select_test: read");
+            return rc;
+        } else {
+            if (rc > 0) {
+                msgcnt++;
+            }
+        }
+    }
 
-	if (!opt_silent) {
-		printf("got %u messages\n", msgcnt);
-	}
+    if (!opt_silent) {
+        printf("got %u messages\n", msgcnt);
+    }
 
-	return 0;
+    return 0;
 }
 
 
 static int select_test(uint repeat, uint msgburst, uint msgsz)
 {
-	int fd;
-	uint i, j;
-	ssize_t rc;
-	char tx_buf[msgsz];
+    int fd;
+    uint i, j;
+    ssize_t rc;
+    char tx_buf[msgsz];
 
-	if (!opt_silent) {
-		printf("%s: repeat %u\n", __func__, repeat);
-	}
+    if (!opt_silent) {
+        printf("%s: repeat %u\n", __func__, repeat);
+    }
 
-	fd = tipc_connect(dev_name, echo_name);
-	if (fd < 0) {
-		fprintf(stderr, "Failed to connect to '%s' service\n",
-			"echo");
-		return fd;
-	}
+    fd = tipc_connect(dev_name, echo_name);
+    if (fd < 0) {
+        fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
+        return fd;
+    }
 
-	for (i = 0; i < repeat; i++) {
+    for (i = 0; i < repeat; i++) {
+        _wait_for_msg(fd, msgsz, 1);
 
-		_wait_for_msg(fd, msgsz, 1);
+        if (!opt_silent) {
+            printf("sending burst: %u msg\n", msgburst);
+        }
 
-		if (!opt_silent) {
-			printf("sending burst: %u msg\n", msgburst);
-		}
+        for (j = 0; j < msgburst; j++) {
+            memset(tx_buf, i + j, msgsz);
+            rc = write(fd, tx_buf, msgsz);
+            if ((size_t)rc != msgsz) {
+                perror("burst_test: write");
+                break;
+            }
+        }
+    }
 
-		for (j = 0; j < msgburst; j++) {
-			memset(tx_buf, i + j, msgsz);
-			rc = write(fd, tx_buf, msgsz);
-			if ((size_t)rc != msgsz) {
-				perror("burst_test: write");
-				break;
-			}
-		}
-	}
+    tipc_close(fd);
 
-	tipc_close(fd);
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n",__func__);
-	}
-
-	return 0;
+    return 0;
 }
 
 static int blocked_read_test(uint repeat)
 {
-	int fd;
-	uint i;
-	ssize_t rc;
-	char rx_buf[512];
+    int fd;
+    uint i;
+    ssize_t rc;
+    char rx_buf[512];
 
-	if (!opt_silent) {
-		printf("%s: repeat %u\n", __func__, repeat);
-	}
+    if (!opt_silent) {
+        printf("%s: repeat %u\n", __func__, repeat);
+    }
 
-	fd = tipc_connect(dev_name, echo_name);
-	if (fd < 0) {
-		fprintf(stderr, "Failed to connect to '%s' service\n",
-			"echo");
-		return fd;
-	}
+    fd = tipc_connect(dev_name, echo_name);
+    if (fd < 0) {
+        fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
+        return fd;
+    }
 
-	for (i = 0; i < repeat; i++) {
-		rc = read(fd, rx_buf, sizeof(rx_buf));
-		if (rc < 0) {
-			perror("select_test: read");
-			break;
-		} else {
-			if (!opt_silent) {
-				printf("got %zd bytes\n", rc);
-			}
-		}
-	}
+    for (i = 0; i < repeat; i++) {
+        rc = read(fd, rx_buf, sizeof(rx_buf));
+        if (rc < 0) {
+            perror("select_test: read");
+            break;
+        } else {
+            if (!opt_silent) {
+                printf("got %zd bytes\n", rc);
+            }
+        }
+    }
 
-	tipc_close(fd);
+    tipc_close(fd);
 
-	if (!opt_silent) {
-		printf("%s: done\n",__func__);
-	}
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	return 0;
+    return 0;
 }
 
 static int ta2ta_ipc_test(void)
 {
-	enum test_message_header {
-		TEST_PASSED = 0,
-		TEST_FAILED = 1,
-		TEST_MESSAGE = 2,
-	};
+    enum test_message_header {
+        TEST_PASSED = 0,
+        TEST_FAILED = 1,
+        TEST_MESSAGE = 2,
+    };
 
-	int fd;
-	int ret;
-	unsigned char rx_buf[256];
+    int fd;
+    int ret;
+    unsigned char rx_buf[256];
 
-	if (!opt_silent) {
-		printf("%s:\n", __func__);
-	}
+    if (!opt_silent) {
+        printf("%s:\n", __func__);
+    }
 
-	fd = tipc_connect(dev_name, main_ctrl_name);
-	if (fd < 0) {
-		fprintf(stderr, "Failed to connect to '%s' service\n",
-			"main_ctrl");
-		return fd;
-	}
+    fd = tipc_connect(dev_name, main_ctrl_name);
+    if (fd < 0) {
+        fprintf(stderr, "Failed to connect to '%s' service\n", "main_ctrl");
+        return fd;
+    }
 
-	/* Wait for tests to complete and read status */
-	while (true) {
-		ret = read(fd, rx_buf, sizeof(rx_buf));
-		if (ret <= 0 || ret >= (int)sizeof(rx_buf)) {
-			fprintf(stderr, "%s: Read failed: %d\n", __func__, ret);
-			tipc_close(fd);
-			return -1;
-		}
+    /* Wait for tests to complete and read status */
+    while (true) {
+        ret = read(fd, rx_buf, sizeof(rx_buf));
+        if (ret <= 0 || ret >= (int)sizeof(rx_buf)) {
+            fprintf(stderr, "%s: Read failed: %d\n", __func__, ret);
+            tipc_close(fd);
+            return -1;
+        }
 
-		if (rx_buf[0] == TEST_PASSED) {
-			break;
-		} else if (rx_buf[0] == TEST_FAILED) {
-			break;
-		} else if (rx_buf[0] == TEST_MESSAGE) {
-			write(STDOUT_FILENO, rx_buf + 1, ret - 1);
-		} else {
-			fprintf(stderr, "%s: Bad message header: %d\n",
-			        __func__, rx_buf[0]);
-			break;
-		}
-	}
+        if (rx_buf[0] == TEST_PASSED) {
+            break;
+        } else if (rx_buf[0] == TEST_FAILED) {
+            break;
+        } else if (rx_buf[0] == TEST_MESSAGE) {
+            write(STDOUT_FILENO, rx_buf + 1, ret - 1);
+        } else {
+            fprintf(stderr, "%s: Bad message header: %d\n", __func__, rx_buf[0]);
+            break;
+        }
+    }
 
-	tipc_close(fd);
+    tipc_close(fd);
 
-	return rx_buf[0] == TEST_PASSED ? 0 : -1;
+    return rx_buf[0] == TEST_PASSED ? 0 : -1;
 }
 
 typedef struct uuid
 {
-	uint32_t time_low;
-	uint16_t time_mid;
-	uint16_t time_hi_and_version;
-	uint8_t clock_seq_and_node[8];
+    uint32_t time_low;
+    uint16_t time_mid;
+    uint16_t time_hi_and_version;
+    uint8_t clock_seq_and_node[8];
 } uuid_t;
 
 static void print_uuid(const char *dev, uuid_t *uuid)
 {
-	printf("%s:", dev);
-	printf("uuid: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
-	       uuid->time_low,
-	       uuid->time_mid,
-	       uuid->time_hi_and_version,
-	       uuid->clock_seq_and_node[0],
-	       uuid->clock_seq_and_node[1],
-	       uuid->clock_seq_and_node[2],
-	       uuid->clock_seq_and_node[3],
-	       uuid->clock_seq_and_node[4],
-	       uuid->clock_seq_and_node[5],
-	       uuid->clock_seq_and_node[6],
-	       uuid->clock_seq_and_node[7]
-	       );
+    printf("%s:", dev);
+    printf("uuid: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", uuid->time_low,
+           uuid->time_mid, uuid->time_hi_and_version, uuid->clock_seq_and_node[0],
+           uuid->clock_seq_and_node[1], uuid->clock_seq_and_node[2], uuid->clock_seq_and_node[3],
+           uuid->clock_seq_and_node[4], uuid->clock_seq_and_node[5], uuid->clock_seq_and_node[6],
+           uuid->clock_seq_and_node[7]);
 }
 
 static int dev_uuid_test(void)
 {
-	int fd;
-	ssize_t rc;
-	uuid_t uuid;
+    int fd;
+    ssize_t rc;
+    uuid_t uuid;
 
-	fd = tipc_connect(dev_name, uuid_name);
-	if (fd < 0) {
-		fprintf(stderr, "Failed to connect to '%s' service\n",
-			"uuid");
-		return fd;
-	}
+    fd = tipc_connect(dev_name, uuid_name);
+    if (fd < 0) {
+        fprintf(stderr, "Failed to connect to '%s' service\n", "uuid");
+        return fd;
+    }
 
-	/* wait for test to complete */
-	rc = read(fd, &uuid, sizeof(uuid));
-	if (rc < 0) {
-		perror("dev_uuid_test: read");
-	} else if (rc != sizeof(uuid)) {
-		fprintf(stderr, "unexpected uuid size (%d vs. %d)\n",
-			(int)rc, (int)sizeof(uuid));
-	} else {
-		print_uuid(dev_name, &uuid);
-	}
+    /* wait for test to complete */
+    rc = read(fd, &uuid, sizeof(uuid));
+    if (rc < 0) {
+        perror("dev_uuid_test: read");
+    } else if (rc != sizeof(uuid)) {
+        fprintf(stderr, "unexpected uuid size (%d vs. %d)\n", (int)rc, (int)sizeof(uuid));
+    } else {
+        print_uuid(dev_name, &uuid);
+    }
 
-	tipc_close(fd);
+    tipc_close(fd);
 
-	return 0;
+    return 0;
 }
 
 static int ta_access_test(void)
 {
-	int fd;
+    int fd;
 
-	if (!opt_silent) {
-		printf("%s:\n", __func__);
-	}
+    if (!opt_silent) {
+        printf("%s:\n", __func__);
+    }
 
-	fd = tipc_connect(dev_name, ta_only_name);
-	if (fd >= 0) {
-		fprintf(stderr, "Succeed to connect to '%s' service\n",
-			"ta_only");
-		tipc_close(fd);
-	}
+    fd = tipc_connect(dev_name, ta_only_name);
+    if (fd >= 0) {
+        fprintf(stderr, "Succeed to connect to '%s' service\n", "ta_only");
+        tipc_close(fd);
+    }
 
-	fd = tipc_connect(dev_name, ns_only_name);
-	if (fd < 0) {
-		fprintf(stderr, "Failed to connect to '%s' service\n",
-			"ns_only");
-		return fd;
-	}
-	tipc_close(fd);
+    fd = tipc_connect(dev_name, ns_only_name);
+    if (fd < 0) {
+        fprintf(stderr, "Failed to connect to '%s' service\n", "ns_only");
+        return fd;
+    }
+    tipc_close(fd);
 
-	if (!opt_silent) {
-		printf("%s: done\n",__func__);
-	}
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	return 0;
+    return 0;
 }
 
 
 static int writev_test(uint repeat, uint msgsz, bool var)
 {
-	uint i;
-	ssize_t rc;
-	size_t  msg_len;
-	int  echo_fd = -1;
-	char tx0_buf[msgsz];
-	char tx1_buf[msgsz];
-	char rx_buf [msgsz];
-	struct iovec iovs[2]= {{tx0_buf, 0}, {tx1_buf, 0}};
+    uint i;
+    ssize_t rc;
+    size_t msg_len;
+    int echo_fd = -1;
+    char tx0_buf[msgsz];
+    char tx1_buf[msgsz];
+    char rx_buf[msgsz];
+    struct iovec iovs[2] = {{tx0_buf, 0}, {tx1_buf, 0}};
 
-	if (!opt_silent) {
-		printf("%s: repeat %u: msgsz %u: variable %s\n",
-			__func__, repeat, msgsz, var ? "true" : "false");
-	}
+    if (!opt_silent) {
+        printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
+               var ? "true" : "false");
+    }
 
-	echo_fd = tipc_connect(dev_name, echo_name);
-	if (echo_fd < 0) {
-		fprintf(stderr, "Failed to connect to service\n");
-		return echo_fd;
-	}
+    echo_fd = tipc_connect(dev_name, echo_name);
+    if (echo_fd < 0) {
+        fprintf(stderr, "Failed to connect to service\n");
+        return echo_fd;
+    }
 
-	for (i = 0; i < repeat; i++) {
+    for (i = 0; i < repeat; i++) {
+        msg_len = msgsz;
+        if (opt_variable && msgsz) {
+            msg_len = rand() % msgsz;
+        }
 
-		msg_len = msgsz;
-		if (opt_variable && msgsz) {
-			msg_len = rand() % msgsz;
-		}
+        iovs[0].iov_len = msg_len / 3;
+        iovs[1].iov_len = msg_len - iovs[0].iov_len;
 
-		iovs[0].iov_len = msg_len / 3;
-		iovs[1].iov_len = msg_len - iovs[0].iov_len;
+        memset(tx0_buf, i + 1, iovs[0].iov_len);
+        memset(tx1_buf, i + 2, iovs[1].iov_len);
+        memset(rx_buf, i + 3, sizeof(rx_buf));
 
-		memset(tx0_buf, i + 1, iovs[0].iov_len);
-		memset(tx1_buf, i + 2, iovs[1].iov_len);
-		memset(rx_buf,  i + 3, sizeof(rx_buf));
+        rc = writev(echo_fd, iovs, 2);
+        if (rc < 0) {
+            perror("writev_test: writev");
+            break;
+        }
 
-		rc = writev(echo_fd, iovs, 2);
-		if (rc < 0) {
-			perror("writev_test: writev");
-			break;
-		}
+        if ((size_t)rc != msg_len) {
+            fprintf(stderr, "%s: %s: data size mismatch (%zd vs. %zd)\n", __func__, "writev",
+                    (size_t)rc, msg_len);
+            break;
+        }
 
-		if ((size_t)rc != msg_len) {
-			fprintf(stderr,
-				"%s: %s: data size mismatch (%zd vs. %zd)\n",
-				__func__, "writev", (size_t)rc, msg_len);
-			break;
-		}
+        rc = read(echo_fd, rx_buf, sizeof(rx_buf));
+        if (rc < 0) {
+            perror("writev_test: read");
+            break;
+        }
 
-		rc = read(echo_fd, rx_buf, sizeof(rx_buf));
-		if (rc < 0) {
-			perror("writev_test: read");
-			break;
-		}
+        if ((size_t)rc != msg_len) {
+            fprintf(stderr, "%s: %s: data size mismatch (%zd vs. %zd)\n", __func__, "read",
+                    (size_t)rc, msg_len);
+            break;
+        }
 
-		if ((size_t)rc != msg_len) {
-			fprintf(stderr,
-				"%s: %s: data size mismatch (%zd vs. %zd)\n",
-				__func__, "read", (size_t)rc, msg_len);
-			break;
-		}
+        if (memcmp(tx0_buf, rx_buf, iovs[0].iov_len)) {
+            fprintf(stderr, "%s: data mismatch: buf 0\n", __func__);
+            break;
+        }
 
-		if (memcmp(tx0_buf, rx_buf, iovs[0].iov_len)) {
-			fprintf(stderr, "%s: data mismatch: buf 0\n", __func__);
-			break;
-		}
+        if (memcmp(tx1_buf, rx_buf + iovs[0].iov_len, iovs[1].iov_len)) {
+            fprintf(stderr, "%s: data mismatch, buf 1\n", __func__);
+            break;
+        }
+    }
 
-		if (memcmp(tx1_buf, rx_buf + iovs[0].iov_len, iovs[1].iov_len)) {
-			fprintf(stderr, "%s: data mismatch, buf 1\n", __func__);
-			break;
-		}
-	}
+    tipc_close(echo_fd);
 
-	tipc_close(echo_fd);
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n",__func__);
-	}
-
-	return 0;
+    return 0;
 }
 
 static int readv_test(uint repeat, uint msgsz, bool var)
 {
-	uint i;
-	ssize_t rc;
-	size_t  msg_len;
-	int  echo_fd = -1;
-	char tx_buf [msgsz];
-	char rx0_buf[msgsz];
-	char rx1_buf[msgsz];
-	struct iovec iovs[2]= {{rx0_buf, 0}, {rx1_buf, 0}};
+    uint i;
+    ssize_t rc;
+    size_t msg_len;
+    int echo_fd = -1;
+    char tx_buf[msgsz];
+    char rx0_buf[msgsz];
+    char rx1_buf[msgsz];
+    struct iovec iovs[2] = {{rx0_buf, 0}, {rx1_buf, 0}};
 
-	if (!opt_silent) {
-		printf("%s: repeat %u: msgsz %u: variable %s\n",
-			__func__, repeat, msgsz, var ? "true" : "false");
-	}
+    if (!opt_silent) {
+        printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
+               var ? "true" : "false");
+    }
 
-	echo_fd = tipc_connect(dev_name, echo_name);
-	if (echo_fd < 0) {
-		fprintf(stderr, "Failed to connect to service\n");
-		return echo_fd;
-	}
+    echo_fd = tipc_connect(dev_name, echo_name);
+    if (echo_fd < 0) {
+        fprintf(stderr, "Failed to connect to service\n");
+        return echo_fd;
+    }
 
-	for (i = 0; i < repeat; i++) {
+    for (i = 0; i < repeat; i++) {
+        msg_len = msgsz;
+        if (opt_variable && msgsz) {
+            msg_len = rand() % msgsz;
+        }
 
-		msg_len = msgsz;
-		if (opt_variable && msgsz) {
-			msg_len = rand() % msgsz;
-		}
+        iovs[0].iov_len = msg_len / 3;
+        iovs[1].iov_len = msg_len - iovs[0].iov_len;
 
-		iovs[0].iov_len = msg_len / 3;
-		iovs[1].iov_len = msg_len - iovs[0].iov_len;
+        memset(tx_buf, i + 1, sizeof(tx_buf));
+        memset(rx0_buf, i + 2, iovs[0].iov_len);
+        memset(rx1_buf, i + 3, iovs[1].iov_len);
 
-		memset(tx_buf,  i + 1, sizeof(tx_buf));
-		memset(rx0_buf, i + 2, iovs[0].iov_len);
-		memset(rx1_buf, i + 3, iovs[1].iov_len);
+        rc = write(echo_fd, tx_buf, msg_len);
+        if (rc < 0) {
+            perror("readv_test: write");
+            break;
+        }
 
-		rc = write(echo_fd, tx_buf, msg_len);
-		if (rc < 0) {
-			perror("readv_test: write");
-			break;
-		}
+        if ((size_t)rc != msg_len) {
+            fprintf(stderr, "%s: %s: data size mismatch (%zd vs. %zd)\n", __func__, "write",
+                    (size_t)rc, msg_len);
+            break;
+        }
 
-		if ((size_t)rc != msg_len) {
-			fprintf(stderr,
-				"%s: %s: data size mismatch (%zd vs. %zd)\n",
-				__func__, "write", (size_t)rc, msg_len);
-			break;
-		}
+        rc = readv(echo_fd, iovs, 2);
+        if (rc < 0) {
+            perror("readv_test: readv");
+            break;
+        }
 
-		rc = readv(echo_fd, iovs, 2);
-		if (rc < 0) {
-			perror("readv_test: readv");
-			break;
-		}
+        if ((size_t)rc != msg_len) {
+            fprintf(stderr, "%s: %s: data size mismatch (%zd vs. %zd)\n", __func__, "write",
+                    (size_t)rc, msg_len);
+            break;
+        }
 
-		if ((size_t)rc != msg_len) {
-			fprintf(stderr,
-				"%s: %s: data size mismatch (%zd vs. %zd)\n",
-				__func__, "write", (size_t)rc, msg_len);
-			break;
-		}
+        if (memcmp(rx0_buf, tx_buf, iovs[0].iov_len)) {
+            fprintf(stderr, "%s: data mismatch: buf 0\n", __func__);
+            break;
+        }
 
-		if (memcmp(rx0_buf, tx_buf, iovs[0].iov_len)) {
-			fprintf(stderr, "%s: data mismatch: buf 0\n", __func__);
-			break;
-		}
+        if (memcmp(rx1_buf, tx_buf + iovs[0].iov_len, iovs[1].iov_len)) {
+            fprintf(stderr, "%s: data mismatch, buf 1\n", __func__);
+            break;
+        }
+    }
 
-		if (memcmp(rx1_buf, tx_buf + iovs[0].iov_len, iovs[1].iov_len)) {
-			fprintf(stderr, "%s: data mismatch, buf 1\n", __func__);
-			break;
-		}
-	}
+    tipc_close(echo_fd);
 
-	tipc_close(echo_fd);
+    if (!opt_silent) {
+        printf("%s: done\n", __func__);
+    }
 
-	if (!opt_silent) {
-		printf("%s: done\n",__func__);
-	}
-
-	return 0;
+    return 0;
 }
 
 static int send_fd_test(void) {
@@ -964,51 +944,51 @@
 
 int main(int argc, char **argv)
 {
-	int rc = 0;
+    int rc = 0;
 
-	if (argc <= 1) {
-		print_usage_and_exit(argv[0], EXIT_FAILURE, false);
-	}
+    if (argc <= 1) {
+        print_usage_and_exit(argv[0], EXIT_FAILURE, false);
+    }
 
-	parse_options(argc, argv);
+    parse_options(argc, argv);
 
-	if (!dev_name) {
-		dev_name = TIPC_DEFAULT_DEVNAME;
-	}
+    if (!dev_name) {
+        dev_name = TIPC_DEFAULT_DEVNAME;
+    }
 
-	if (!test_name) {
-		fprintf(stderr, "need a Test to run\n");
-		print_usage_and_exit(argv[0], EXIT_FAILURE, true);
-	}
+    if (!test_name) {
+        fprintf(stderr, "need a Test to run\n");
+        print_usage_and_exit(argv[0], EXIT_FAILURE, true);
+    }
 
-	if (strcmp(test_name, "connect") == 0) {
-		rc = connect_test(opt_repeat);
-	} else if (strcmp(test_name, "connect_foo") == 0) {
-		rc = connect_foo(opt_repeat);
-	} else if (strcmp(test_name, "burst_write") == 0) {
-		rc = burst_write_test(opt_repeat, opt_msgburst, opt_msgsize, opt_variable);
-	} else if (strcmp(test_name, "select") == 0) {
-		rc = select_test(opt_repeat, opt_msgburst,  opt_msgsize);
-	} else if (strcmp(test_name, "blocked_read") == 0) {
-		rc = blocked_read_test(opt_repeat);
-	} else if (strcmp(test_name, "closer1") == 0) {
-		rc = closer1_test(opt_repeat);
-	} else if (strcmp(test_name, "closer2") == 0) {
-		rc = closer2_test(opt_repeat);
-	} else if (strcmp(test_name, "closer3") == 0) {
-		rc = closer3_test(opt_repeat);
-	} else if (strcmp(test_name, "echo") == 0) {
-		rc = echo_test(opt_repeat, opt_msgsize, opt_variable);
-	} else if(strcmp(test_name, "ta2ta-ipc") == 0) {
-		rc = ta2ta_ipc_test();
-	} else if (strcmp(test_name, "dev-uuid") == 0) {
-		rc = dev_uuid_test();
-	} else if (strcmp(test_name, "ta-access") == 0) {
-		rc = ta_access_test();
-	} else if (strcmp(test_name, "writev") == 0) {
-		rc = writev_test(opt_repeat, opt_msgsize, opt_variable);
-	} else if (strcmp(test_name, "readv") == 0) {
-		rc = readv_test(opt_repeat, opt_msgsize, opt_variable);
+    if (strcmp(test_name, "connect") == 0) {
+        rc = connect_test(opt_repeat);
+    } else if (strcmp(test_name, "connect_foo") == 0) {
+        rc = connect_foo(opt_repeat);
+    } else if (strcmp(test_name, "burst_write") == 0) {
+        rc = burst_write_test(opt_repeat, opt_msgburst, opt_msgsize, opt_variable);
+    } else if (strcmp(test_name, "select") == 0) {
+        rc = select_test(opt_repeat, opt_msgburst, opt_msgsize);
+    } else if (strcmp(test_name, "blocked_read") == 0) {
+        rc = blocked_read_test(opt_repeat);
+    } else if (strcmp(test_name, "closer1") == 0) {
+        rc = closer1_test(opt_repeat);
+    } else if (strcmp(test_name, "closer2") == 0) {
+        rc = closer2_test(opt_repeat);
+    } else if (strcmp(test_name, "closer3") == 0) {
+        rc = closer3_test(opt_repeat);
+    } else if (strcmp(test_name, "echo") == 0) {
+        rc = echo_test(opt_repeat, opt_msgsize, opt_variable);
+    } else if (strcmp(test_name, "ta2ta-ipc") == 0) {
+        rc = ta2ta_ipc_test();
+    } else if (strcmp(test_name, "dev-uuid") == 0) {
+        rc = dev_uuid_test();
+    } else if (strcmp(test_name, "ta-access") == 0) {
+        rc = ta_access_test();
+    } else if (strcmp(test_name, "writev") == 0) {
+        rc = writev_test(opt_repeat, opt_msgsize, opt_variable);
+    } else if (strcmp(test_name, "readv") == 0) {
+        rc = readv_test(opt_repeat, opt_msgsize, opt_variable);
     } else if (strcmp(test_name, "send-fd") == 0) {
         rc = send_fd_test();
     } else {
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);
diff --git a/trusty/storage/proxy/rpmb.c b/trusty/storage/proxy/rpmb.c
index b59fb67..f059935 100644
--- a/trusty/storage/proxy/rpmb.c
+++ b/trusty/storage/proxy/rpmb.c
@@ -16,7 +16,10 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_proto.h>
 #include <scsi/sg.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -55,6 +58,17 @@
 #define MMC_BLOCK_SIZE 512
 
 /*
+ * Number of retry attempts when an RPMB authenticated write triggers a UNIT
+ * ATTENTION
+ */
+#define UFS_RPMB_WRITE_RETRY_COUNT 1
+/*
+ * Number of retry attempts when an RPMB read operation triggers a UNIT
+ * ATTENTION
+ */
+#define UFS_RPMB_READ_RETRY_COUNT 3
+
+/*
  * There should be no timeout for security protocol ioctl call, so we choose a
  * large number for timeout.
  * 20000 millisecs == 20 seconds
@@ -104,21 +118,62 @@
 
 static const char* UFS_WAKE_LOCK_NAME = "ufs_seq_wakelock";
 
-#ifdef RPMB_DEBUG
-
-static void print_buf(const char* prefix, const uint8_t* buf, size_t size) {
+/**
+ * log_buf - Log a byte buffer to the android log.
+ * @priority: One of ANDROID_LOG_* priority levels from android_LogPriority in
+ *            android/log.h
+ * @prefix:   A null-terminated string that identifies this buffer. Must be less
+ *            than 128 bytes.
+ * @buf:      Buffer to dump.
+ * @size:     Length of @buf in bytes.
+ */
+#define LOG_BUF_SIZE 256
+static int log_buf(int priority, const char* prefix, const uint8_t* buf, size_t size) {
+    int rc;
     size_t i;
+    char line[LOG_BUF_SIZE] = {0};
+    char* cur = line;
 
-    printf("%s @%p [%zu]", prefix, buf, size);
-    for (i = 0; i < size; i++) {
-        if (i && i % 32 == 0) printf("\n%*s", (int)strlen(prefix), "");
-        printf(" %02x", buf[i]);
+    rc = snprintf(line, LOG_BUF_SIZE, "%s @%p [%zu]", prefix, buf, size);
+    if (rc < 0 || rc >= LOG_BUF_SIZE) {
+        goto err;
     }
-    printf("\n");
-    fflush(stdout);
-}
+    cur += rc;
+    for (i = 0; i < size; i++) {
+        if (i % 32 == 0) {
+            /*
+             * Flush the line out to the log after we have printed 32 bytes
+             * (also flushes the header line on the first iteration and sets up
+             * for printing the buffer itself)
+             */
+            LOG_PRI(priority, LOG_TAG, "%s", line);
+            memset(line, 0, LOG_BUF_SIZE);
+            cur = line;
+            /* Shift output over by the length of the prefix */
+            rc = snprintf(line, LOG_BUF_SIZE, "%*s", (int)strlen(prefix), "");
+            if (rc < 0 || rc >= LOG_BUF_SIZE) {
+                goto err;
+            }
+            cur += rc;
+        }
+        rc = snprintf(cur, LOG_BUF_SIZE - (cur - line), "%02x ", buf[i]);
+        if (rc < 0 || rc >= LOG_BUF_SIZE - (cur - line)) {
+            goto err;
+        }
+        cur += rc;
+    }
+    LOG_PRI(priority, LOG_TAG, "%s", line);
 
-#endif
+    return 0;
+
+err:
+    if (rc < 0) {
+        return rc;
+    } else {
+        ALOGE("log_buf prefix was too long");
+        return -1;
+    }
+}
 
 static void set_sg_io_hdr(sg_io_hdr_t* io_hdrp, int dxfer_direction, unsigned char cmd_len,
                           unsigned char mx_sb_len, unsigned int dxfer_len, void* dxferp,
@@ -135,6 +190,137 @@
     io_hdrp->timeout = TIMEOUT;
 }
 
+/**
+ * enum scsi_result - Results of checking the SCSI status and sense buffer
+ *
+ * @SCSI_RES_OK:    SCSI status and sense are good
+ * @SCSI_RES_ERR:   SCSI status or sense contain an unhandled error
+ * @SCSI_RES_RETRY: SCSI sense buffer contains a status that indicates that the
+ *                  command should be retried
+ */
+enum scsi_result {
+    SCSI_RES_OK = 0,
+    SCSI_RES_ERR,
+    SCSI_RES_RETRY,
+};
+
+static enum scsi_result check_scsi_sense(const uint8_t* sense_buf, size_t len) {
+    uint8_t response_code = 0;
+    uint8_t sense_key = 0;
+    uint8_t additional_sense_code = 0;
+    uint8_t additional_sense_code_qualifier = 0;
+    uint8_t additional_length = 0;
+
+    if (!sense_buf || len == 0) {
+        ALOGE("Invalid SCSI sense buffer, length: %zu\n", len);
+        return SCSI_RES_ERR;
+    }
+
+    response_code = 0x7f & sense_buf[0];
+
+    if (response_code < 0x70 || response_code > 0x73) {
+        ALOGE("Invalid SCSI sense response code: %hhu\n", response_code);
+        return SCSI_RES_ERR;
+    }
+
+    if (response_code >= 0x72) {
+        /* descriptor format, SPC-6 4.4.2 */
+        if (len > 1) {
+            sense_key = 0xf & sense_buf[1];
+        }
+        if (len > 2) {
+            additional_sense_code = sense_buf[2];
+        }
+        if (len > 3) {
+            additional_sense_code_qualifier = sense_buf[3];
+        }
+        if (len > 7) {
+            additional_length = sense_buf[7];
+        }
+    } else {
+        /* fixed format, SPC-6 4.4.3 */
+        if (len > 2) {
+            sense_key = 0xf & sense_buf[2];
+        }
+        if (len > 7) {
+            additional_length = sense_buf[7];
+        }
+        if (len > 12) {
+            additional_sense_code = sense_buf[12];
+        }
+        if (len > 13) {
+            additional_sense_code_qualifier = sense_buf[13];
+        }
+    }
+
+    switch (sense_key) {
+        case NO_SENSE:
+        case 0x0f: /* COMPLETED, not present in kernel headers */
+            ALOGD("SCSI success with sense data: key=%hhu, asc=%hhu, ascq=%hhu\n", sense_key,
+                  additional_sense_code, additional_sense_code_qualifier);
+            return SCSI_RES_OK;
+        case UNIT_ATTENTION:
+            ALOGD("UNIT ATTENTION with sense data: key=%hhu, asc=%hhu, ascq=%hhu\n", sense_key,
+                  additional_sense_code, additional_sense_code_qualifier);
+            if (additional_sense_code == 0x29) {
+                /* POWER ON or RESET condition */
+                return SCSI_RES_RETRY;
+            }
+
+            /* treat this UNIT ATTENTION as an error if we don't recognize it */
+            break;
+    }
+
+    ALOGE("Unexpected SCSI sense data: key=%hhu, asc=%hhu, ascq=%hhu\n", sense_key,
+          additional_sense_code, additional_sense_code_qualifier);
+    log_buf(ANDROID_LOG_ERROR, "sense buffer: ", sense_buf, len);
+    return SCSI_RES_ERR;
+}
+
+static enum scsi_result check_sg_io_hdr(const sg_io_hdr_t* io_hdrp) {
+    if (io_hdrp->status == 0 && io_hdrp->host_status == 0 && io_hdrp->driver_status == 0) {
+        return SCSI_RES_OK;
+    }
+
+    if (io_hdrp->status & 0x01) {
+        ALOGE("SG_IO received unknown status, LSB is set: %hhu", io_hdrp->status);
+    }
+
+    if (io_hdrp->masked_status != GOOD && io_hdrp->sb_len_wr > 0) {
+        enum scsi_result scsi_res = check_scsi_sense(io_hdrp->sbp, io_hdrp->sb_len_wr);
+        if (scsi_res == SCSI_RES_RETRY) {
+            return SCSI_RES_RETRY;
+        } else if (scsi_res != SCSI_RES_OK) {
+            ALOGE("Unexpected SCSI sense. masked_status: %hhu, host_status: %hu, driver_status: "
+                  "%hu\n",
+                  io_hdrp->masked_status, io_hdrp->host_status, io_hdrp->driver_status);
+            return scsi_res;
+        }
+    }
+
+    switch (io_hdrp->masked_status) {
+        case GOOD:
+            break;
+        case CHECK_CONDITION:
+            /* handled by check_sg_sense above */
+            break;
+        default:
+            ALOGE("SG_IO failed with masked_status: %hhu, host_status: %hu, driver_status: %hu\n",
+                  io_hdrp->masked_status, io_hdrp->host_status, io_hdrp->driver_status);
+            return SCSI_RES_ERR;
+    }
+
+    if (io_hdrp->host_status != 0) {
+        ALOGE("SG_IO failed with host_status: %hu, driver_status: %hu\n", io_hdrp->host_status,
+              io_hdrp->driver_status);
+    }
+
+    if (io_hdrp->resid != 0) {
+        ALOGE("SG_IO resid was non-zero: %d\n", io_hdrp->resid);
+    }
+    return SCSI_RES_ERR;
+}
+
 static int send_mmc_rpmb_req(int mmc_fd, const struct storage_rpmb_send_req* req) {
     struct {
         struct mmc_ioc_multi_cmd multi;
@@ -153,7 +339,7 @@
         mmc_ioc_cmd_set_data((*cmd), write_buf);
 #ifdef RPMB_DEBUG
         ALOGI("opcode: 0x%x, write_flag: 0x%x\n", cmd->opcode, cmd->write_flag);
-        print_buf("request: ", write_buf, req->reliable_write_size);
+        log_buf(ANDROID_LOG_INFO, "request: ", write_buf, req->reliable_write_size);
 #endif
         write_buf += req->reliable_write_size;
         mmc.multi.num_of_cmds++;
@@ -169,7 +355,7 @@
         mmc_ioc_cmd_set_data((*cmd), write_buf);
 #ifdef RPMB_DEBUG
         ALOGI("opcode: 0x%x, write_flag: 0x%x\n", cmd->opcode, cmd->write_flag);
-        print_buf("request: ", write_buf, req->write_size);
+        log_buf(ANDROID_LOG_INFO, "request: ", write_buf, req->write_size);
 #endif
         write_buf += req->write_size;
         mmc.multi.num_of_cmds++;
@@ -207,6 +393,8 @@
     struct sec_proto_cdb out_cdb = {0xB5, 0xEC, 0x00, 0x01, 0x00, 0x00, 0, 0x00, 0x00};
     unsigned char sense_buffer[32];
 
+    bool is_request_write = req->reliable_write_size > 0;
+
     wl_rc = acquire_wake_lock(PARTIAL_WAKE_LOCK, UFS_WAKE_LOCK_NAME);
     if (wl_rc < 0) {
         ALOGE("%s: failed to acquire wakelock: %d, %s\n", __func__, wl_rc, strerror(errno));
@@ -215,30 +403,44 @@
 
     if (req->reliable_write_size) {
         /* Prepare SECURITY PROTOCOL OUT command. */
-        out_cdb.length = __builtin_bswap32(req->reliable_write_size);
         sg_io_hdr_t io_hdr;
-        set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
-                      req->reliable_write_size, (void*)write_buf, (unsigned char*)&out_cdb,
-                      sense_buffer);
-        rc = ioctl(sg_fd, SG_IO, &io_hdr);
-        if (rc < 0) {
-            ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
-            goto err_op;
-        }
+        int retry_count = UFS_RPMB_WRITE_RETRY_COUNT;
+        do {
+            out_cdb.length = __builtin_bswap32(req->reliable_write_size);
+            set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
+                          req->reliable_write_size, (void*)write_buf, (unsigned char*)&out_cdb,
+                          sense_buffer);
+            rc = ioctl(sg_fd, SG_IO, &io_hdr);
+            if (rc < 0) {
+                ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
+                goto err_op;
+            }
+        } while (check_sg_io_hdr(&io_hdr) == SCSI_RES_RETRY && retry_count-- > 0);
         write_buf += req->reliable_write_size;
     }
 
     if (req->write_size) {
         /* Prepare SECURITY PROTOCOL OUT command. */
-        out_cdb.length = __builtin_bswap32(req->write_size);
         sg_io_hdr_t io_hdr;
-        set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
-                      req->write_size, (void*)write_buf, (unsigned char*)&out_cdb, sense_buffer);
-        rc = ioctl(sg_fd, SG_IO, &io_hdr);
-        if (rc < 0) {
-            ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
-            goto err_op;
-        }
+        /*
+         * We don't retry write response request messages (is_request_write ==
+         * true) because a unit attention condition between the write and
+         * requesting a response means that the device was reset and we can't
+         * get a response to our original write. We can only retry this SG_IO
+         * call when it is the first call in our sequence.
+         */
+        int retry_count = is_request_write ? 0 : UFS_RPMB_READ_RETRY_COUNT;
+        do {
+            out_cdb.length = __builtin_bswap32(req->write_size);
+            set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
+                          req->write_size, (void*)write_buf, (unsigned char*)&out_cdb,
+                          sense_buffer);
+            rc = ioctl(sg_fd, SG_IO, &io_hdr);
+            if (rc < 0) {
+                ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
+                goto err_op;
+            }
+        } while (check_sg_io_hdr(&io_hdr) == SCSI_RES_RETRY && retry_count-- > 0);
         write_buf += req->write_size;
     }
 
@@ -252,6 +454,7 @@
         if (rc < 0) {
             ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
         }
+        check_sg_io_hdr(&io_hdr);
     }
 
 err_op:
@@ -353,7 +556,7 @@
         goto err_response;
     }
 #ifdef RPMB_DEBUG
-    if (req->read_size) print_buf("response: ", read_buf, req->read_size);
+    if (req->read_size) log_buf(ANDROID_LOG_INFO, "response: ", read_buf, req->read_size);
 #endif
 
     if (msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) {
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index 6cd381f..d40a59e 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -23,7 +23,7 @@
 # HAL loading of gatekeeper.trusty.
 
 PRODUCT_PACKAGES += \
-	android.hardware.keymaster@4.0-service.trusty \
+	android.hardware.security.keymint-service.trusty \
 	android.hardware.gatekeeper@1.0-service.trusty \
 	trusty_apploader
 
@@ -31,3 +31,6 @@
 	ro.hardware.keystore_desede=true \
 	ro.hardware.keystore=trusty \
 	ro.hardware.gatekeeper=trusty
+
+PRODUCT_COPY_FILES += \
+	frameworks/native/data/etc/android.hardware.keystore.app_attest_key.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.keystore.app_attest_key.xml
diff --git a/trusty/utils/rpmb_dev/Android.bp b/trusty/utils/rpmb_dev/Android.bp
index c5853ef..a270087 100644
--- a/trusty/utils/rpmb_dev/Android.bp
+++ b/trusty/utils/rpmb_dev/Android.bp
@@ -12,13 +12,7 @@
 // limitations under the License.
 
 package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_core_trusty_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    default_applicable_licenses: ["system_core_trusty_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 cc_binary {
diff --git a/trusty/utils/rpmb_dev/rpmb_dev.c b/trusty/utils/rpmb_dev/rpmb_dev.c
index 2025621..0a9e6a1 100644
--- a/trusty/utils/rpmb_dev/rpmb_dev.c
+++ b/trusty/utils/rpmb_dev/rpmb_dev.c
@@ -1,25 +1,17 @@
 /*
  * Copyright (C) 2018 The Android Open Source Project
  *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #define LOG_TAG "rpmb_mock"