Merge changes from topic "fastboot_fetch"

* changes:
  fuzzy_fastboot: Add tests for fetch:vendor_boot
  fuzzy_fastboot: Add conformance test for getvar:max-fetch-size.
  fastboot driver: repack vendor boot ramdisk
  fastboot driver: Allow colon in partition name when flashing
  fastboot driver: add fetch command in driver
  fastboot driver: RunAndReadBuffer don't allocate too much mem
  fastboot driver: add RunAndReadBuffer helper
  fastboot driver: Fix ownership of fd in fastboot_buffer.
  fastboot driver: fix message
  fastbootd: add O_CLOEXEC/O_BINARY for OpenPartition
  fastbootd: add read arg to OpenPartition
  fastbootd: Add fetch command on device
  fastbootd: Add getvar max-fetch-size.
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 8e8a91c..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,16 +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.
-
--------------------------------------------------------------------
-
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 68a43cf..04e1e4e 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -153,14 +153,14 @@
   }
 
   struct timeval tv = {
-      .tv_sec = 1 * android::base::TimeoutMultiplier(),
+      .tv_sec = 1 * android::base::HwTimeoutMultiplier(),
       .tv_usec = 0,
   };
   if (setsockopt(amfd.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) {
     PLOG(ERROR) << "failed to set send timeout on activity manager socket";
     return false;
   }
-  tv.tv_sec = 3 * android::base::TimeoutMultiplier();  // 3 seconds on handshake read
+  tv.tv_sec = 3 * android::base::HwTimeoutMultiplier();  // 3 seconds on handshake read
   if (setsockopt(amfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
     PLOG(ERROR) << "failed to set receive timeout on activity manager socket";
     return false;
@@ -303,6 +303,7 @@
       process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata;
       process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot;
       process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
+      process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer;
       FALLTHROUGH_INTENDED;
     case 1:
     case 2:
@@ -390,7 +391,7 @@
 
   // There appears to be a bug in the kernel where our death causes SIGHUP to
   // be sent to our process group if we exit while it has stopped jobs (e.g.
-  // because of wait_for_gdb). Use setsid to create a new process group to
+  // because of wait_for_debugger). Use setsid to create a new process group to
   // avoid hitting this.
   setsid();
 
@@ -447,7 +448,7 @@
   //
   // Note: processes with many threads and minidebug-info can take a bit to
   //       unwind, do not make this too small. b/62828735
-  alarm(30 * android::base::TimeoutMultiplier());
+  alarm(30 * android::base::HwTimeoutMultiplier());
 
   // Get the process name (aka cmdline).
   std::string process_name = get_process_name(g_target_thread);
@@ -547,15 +548,17 @@
   fork_exit_write.reset();
 
   // Defer the message until later, for readability.
-  bool wait_for_gdb = android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false);
+  bool wait_for_debugger = android::base::GetBoolProperty(
+      "debug.debuggerd.wait_for_debugger",
+      android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false));
   if (siginfo.si_signo == BIONIC_SIGNAL_DEBUGGER) {
-    wait_for_gdb = false;
+    wait_for_debugger = false;
   }
 
   // Detach from all of our attached threads before resuming.
   for (const auto& [tid, thread] : thread_info) {
     int resume_signal = thread.signo == BIONIC_SIGNAL_DEBUGGER ? 0 : thread.signo;
-    if (wait_for_gdb) {
+    if (wait_for_debugger) {
       resume_signal = 0;
       if (tgkill(target_process, tid, SIGSTOP) != 0) {
         PLOG(WARNING) << "failed to send SIGSTOP to " << tid;
@@ -640,12 +643,12 @@
     }
   }
 
-  if (wait_for_gdb) {
+  if (wait_for_debugger) {
     // Use ALOGI to line up with output from engrave_tombstone.
     ALOGI(
         "***********************************************************\n"
         "* Process %d has been suspended while crashing.\n"
-        "* To attach gdbserver and start gdb, run this on the host:\n"
+        "* To attach the debugger, run this on the host:\n"
         "*\n"
         "*     gdbclient.py -p %d\n"
         "*\n"
diff --git a/debuggerd/crasher/Android.bp b/debuggerd/crasher/Android.bp
index 7975a3a..23b106e 100644
--- a/debuggerd/crasher/Android.bp
+++ b/debuggerd/crasher/Android.bp
@@ -13,7 +13,6 @@
         "-Werror",
         "-O0",
         "-fstack-protector-all",
-        "-Wno-free-nonheap-object",
         "-Wno-date-time",
     ],
     srcs: ["crasher.cpp"],
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index a2b13a3..db30b8f 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -134,10 +134,14 @@
     return a*2;
 }
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wfree-nonheap-object"
+
 noinline void abuse_heap() {
     char buf[16];
     free(buf); // GCC is smart enough to warn about this, but we're doing it deliberately.
 }
+#pragma clang diagnostic pop
 
 noinline void leak() {
     while (true) {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 9e9557f..de37a5b 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -69,7 +69,7 @@
 #define ARCH_SUFFIX ""
 #endif
 
-constexpr char kWaitForGdbKey[] = "debug.debuggerd.wait_for_gdb";
+constexpr char kWaitForDebuggerKey[] = "debug.debuggerd.wait_for_debugger";
 
 #define TIMEOUT(seconds, expr)                                     \
   [&]() {                                                          \
@@ -157,7 +157,7 @@
 class CrasherTest : public ::testing::Test {
  public:
   pid_t crasher_pid = -1;
-  bool previous_wait_for_gdb;
+  bool previous_wait_for_debugger;
   unique_fd crasher_pipe;
   unique_fd intercept_fd;
 
@@ -178,8 +178,13 @@
 };
 
 CrasherTest::CrasherTest() {
-  previous_wait_for_gdb = android::base::GetBoolProperty(kWaitForGdbKey, false);
-  android::base::SetProperty(kWaitForGdbKey, "0");
+  previous_wait_for_debugger = android::base::GetBoolProperty(kWaitForDebuggerKey, false);
+  android::base::SetProperty(kWaitForDebuggerKey, "0");
+
+  // Clear the old property too, just in case someone's been using it
+  // on this device. (We only document the new name, but we still support
+  // the old name so we don't break anyone's existing setups.)
+  android::base::SetProperty("debug.debuggerd.wait_for_gdb", "0");
 }
 
 CrasherTest::~CrasherTest() {
@@ -189,7 +194,7 @@
     TEMP_FAILURE_RETRY(waitpid(crasher_pid, &status, WUNTRACED));
   }
 
-  android::base::SetProperty(kWaitForGdbKey, previous_wait_for_gdb ? "1" : "0");
+  android::base::SetProperty(kWaitForDebuggerKey, previous_wait_for_debugger ? "1" : "0");
 }
 
 void CrasherTest::StartIntercept(unique_fd* output_fd, DebuggerdDumpType intercept_type) {
@@ -392,7 +397,11 @@
 }
 #endif
 
-TEST_F(CrasherTest, mte_uaf) {
+struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
+
+INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(16, 131072));
+
+TEST_P(SizeParamCrasherTest, mte_uaf) {
 #if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
@@ -400,9 +409,9 @@
 
   int intercept_result;
   unique_fd output_fd;
-  StartProcess([]() {
+  StartProcess([&]() {
     SetTagCheckingLevelSync();
-    volatile int* p = (volatile int*)malloc(16);
+    volatile int* p = (volatile int*)malloc(GetParam());
     free((void *)p);
     p[0] = 42;
   });
@@ -417,8 +426,9 @@
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
 
-  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
-  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a 16-byte allocation.*
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
+                           std::to_string(GetParam()) + R"(-byte allocation.*
 
 allocated by thread .*
       #00 pc)");
@@ -429,7 +439,7 @@
 #endif
 }
 
-TEST_F(CrasherTest, mte_overflow) {
+TEST_P(SizeParamCrasherTest, mte_overflow) {
 #if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
@@ -437,10 +447,10 @@
 
   int intercept_result;
   unique_fd output_fd;
-  StartProcess([]() {
+  StartProcess([&]() {
     SetTagCheckingLevelSync();
-    volatile int* p = (volatile int*)malloc(16);
-    p[4] = 42;
+    volatile char* p = (volatile char*)malloc(GetParam());
+    p[GetParam()] = 42;
   });
 
   StartIntercept(&output_fd);
@@ -454,7 +464,8 @@
   ConsumeFd(std::move(output_fd), &result);
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
-  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation.*
+  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
+                           std::to_string(GetParam()) + R"(-byte allocation.*
 
 allocated by thread .*
       #00 pc)");
@@ -463,7 +474,7 @@
 #endif
 }
 
-TEST_F(CrasherTest, mte_underflow) {
+TEST_P(SizeParamCrasherTest, mte_underflow) {
 #if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
@@ -471,9 +482,9 @@
 
   int intercept_result;
   unique_fd output_fd;
-  StartProcess([]() {
+  StartProcess([&]() {
     SetTagCheckingLevelSync();
-    volatile int* p = (volatile int*)malloc(16);
+    volatile int* p = (volatile int*)malloc(GetParam());
     p[-1] = 42;
   });
 
@@ -488,7 +499,8 @@
   ConsumeFd(std::move(output_fd), &result);
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
-  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a 16-byte allocation.*
+  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
+                           std::to_string(GetParam()) + R"(-byte allocation.*
 
 allocated by thread .*
       #00 pc)");
@@ -727,9 +739,9 @@
   AssertDeath(SIGABRT);
 }
 
-TEST_F(CrasherTest, wait_for_gdb) {
-  if (!android::base::SetProperty(kWaitForGdbKey, "1")) {
-    FAIL() << "failed to enable wait_for_gdb";
+TEST_F(CrasherTest, wait_for_debugger) {
+  if (!android::base::SetProperty(kWaitForDebuggerKey, "1")) {
+    FAIL() << "failed to enable wait_for_debugger";
   }
   sleep(1);
 
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index ca809e4..b607397 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -274,7 +274,7 @@
 
     // There appears to be a bug in the kernel where our death causes SIGHUP to
     // be sent to our process group if we exit while it has stopped jobs (e.g.
-    // because of wait_for_gdb). Use setsid to create a new process group to
+    // because of wait_for_debugger). Use setsid to create a new process group to
     // avoid hitting this.
     setsid();
 
@@ -600,7 +600,7 @@
     // starting to dump right before our death.
     pthread_mutex_unlock(&crash_mutex);
   } else {
-    // Resend the signal, so that either gdb or the parent's waitpid sees it.
+    // Resend the signal, so that either the debugger or the parent's waitpid sees it.
     resend_signal(info);
   }
 }
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index 254ed4f..bc08327 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -42,6 +42,7 @@
   const gwp_asan::AllocationMetadata* gwp_asan_metadata;
   const char* scudo_stack_depot;
   const char* scudo_region_info;
+  const char* scudo_ring_buffer;
 };
 
 // These callbacks are called in a signal handler, and thus must be async signal safe.
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index d5b0735..dcb52f9 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -46,6 +46,7 @@
   uintptr_t gwp_asan_metadata = 0;
   uintptr_t scudo_stack_depot = 0;
   uintptr_t scudo_region_info = 0;
+  uintptr_t scudo_ring_buffer = 0;
 
   bool has_fault_address = false;
   uintptr_t untagged_fault_address = 0;
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index 141c3bd..1c3437f 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -43,6 +43,8 @@
                                        __scudo_get_stack_depot_size());
   auto region_info = AllocAndReadFully(process_memory, process_info.scudo_region_info,
                                        __scudo_get_region_info_size());
+  auto ring_buffer = AllocAndReadFully(process_memory, process_info.scudo_ring_buffer,
+                                       __scudo_get_ring_buffer_size());
 
   untagged_fault_addr_ = process_info.untagged_fault_address;
   uintptr_t fault_page = untagged_fault_addr_ & ~(PAGE_SIZE - 1);
@@ -68,8 +70,8 @@
   }
 
   __scudo_get_error_info(&error_info_, process_info.maybe_tagged_fault_address, stack_depot.get(),
-                         region_info.get(), memory.get(), memory_tags.get(), memory_begin,
-                         memory_end - memory_begin);
+                         region_info.get(), ring_buffer.get(), memory.get(), memory_tags.get(),
+                         memory_begin, memory_end - memory_begin);
 }
 
 bool ScudoCrashData::CrashIsMine() const {
diff --git a/debuggerd/libdebuggerd/test/sys/system_properties.h b/debuggerd/libdebuggerd/test/sys/system_properties.h
deleted file mode 100644
index 1f4f58a..0000000
--- a/debuggerd/libdebuggerd/test/sys/system_properties.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#ifndef _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
-#define _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
-
-// This is just enough to get the property code to compile on
-// the host.
-
-#define PROP_VALUE_MAX  92
-
-#endif // _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index 53a76ea..f33b2f0 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -97,6 +97,7 @@
   uintptr_t gwp_asan_metadata;
   uintptr_t scudo_stack_depot;
   uintptr_t scudo_region_info;
+  uintptr_t scudo_ring_buffer;
 };
 
 struct __attribute__((__packed__)) CrashInfo {
diff --git a/debuggerd/tombstoned/intercept_manager.cpp b/debuggerd/tombstoned/intercept_manager.cpp
index 4d4646a..613e6f5 100644
--- a/debuggerd/tombstoned/intercept_manager.cpp
+++ b/debuggerd/tombstoned/intercept_manager.cpp
@@ -163,7 +163,7 @@
     event_assign(intercept->intercept_event, intercept_manager->base, sockfd, EV_READ | EV_TIMEOUT,
                  intercept_close_cb, arg);
 
-    struct timeval timeout = {.tv_sec = 10 * android::base::TimeoutMultiplier(), .tv_usec = 0};
+    struct timeval timeout = {.tv_sec = 10 * android::base::HwTimeoutMultiplier(), .tv_usec = 0};
     event_add(intercept->intercept_event, &timeout);
   }
 
@@ -179,7 +179,7 @@
   intercept->intercept_manager = static_cast<InterceptManager*>(arg);
   intercept->sockfd.reset(sockfd);
 
-  struct timeval timeout = {1 * android::base::TimeoutMultiplier(), 0};
+  struct timeval timeout = {1 * android::base::HwTimeoutMultiplier(), 0};
   event_base* base = evconnlistener_get_base(listener);
   event* intercept_event =
     event_new(base, sockfd, EV_TIMEOUT | EV_READ, intercept_request_cb, intercept);
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index bc2d33d..0b87b7a 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -320,7 +320,7 @@
   }
 
   // TODO: Make this configurable by the interceptor?
-  struct timeval timeout = {10 * android::base::TimeoutMultiplier(), 0};
+  struct timeval timeout = {10 * android::base::HwTimeoutMultiplier(), 0};
 
   event_base* base = event_get_base(crash->crash_event);
 
@@ -340,7 +340,7 @@
 
   // TODO: Make sure that only java crashes come in on the java socket
   // and only native crashes on the native socket.
-  struct timeval timeout = {1 * android::base::TimeoutMultiplier(), 0};
+  struct timeval timeout = {1 * android::base::HwTimeoutMultiplier(), 0};
   event* crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash);
   crash->crash_socket_fd.reset(sockfd);
   crash->crash_event = crash_event;
diff --git a/fs_mgr/fs_mgr_boot_config.cpp b/fs_mgr/fs_mgr_boot_config.cpp
index 75d1e0d..e3ef232 100644
--- a/fs_mgr/fs_mgr_boot_config.cpp
+++ b/fs_mgr/fs_mgr_boot_config.cpp
@@ -91,6 +91,12 @@
         if (key == bootconfig_key) {
             *out_val = value;
             return true;
+        } else if (android_key == "hardware" && android_key == key) {
+            // bootconfig doesn't allow subkeys and values to coexist, so
+            // "androidboot.hardware" cannot be used. It is replaced in
+            // bootconfig with "hardware"
+            *out_val = value;
+            return true;
         }
     }
 
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 623293e..6cb2c51 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -383,11 +383,6 @@
                << " partition alignment is not sector-aligned.";
         return false;
     }
-    if (device_info.alignment_offset > device_info.alignment) {
-        LERROR << "Block device " << device_info.partition_name
-               << " partition alignment offset is greater than its alignment.";
-        return false;
-    }
     return true;
 }
 
@@ -489,7 +484,7 @@
     // Compute the first free sector, factoring in alignment.
     uint64_t free_area_start = total_reserved;
     bool ok;
-    if (super.alignment || super.alignment_offset) {
+    if (super.alignment) {
         ok = AlignTo(free_area_start, super.alignment, &free_area_start);
     } else {
         ok = AlignTo(free_area_start, logical_block_size, &free_area_start);
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index e4b617a..72827eb 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -176,10 +176,10 @@
     ASSERT_NE(super_device, nullptr);
     EXPECT_EQ(super_device->first_logical_sector, 1536);
 
-    // Alignment offset without alignment doesn't mean anything.
+    // Alignment offset without alignment is ignored.
     device_info.alignment = 0;
     builder = MetadataBuilder::New(device_info, 1024, 2);
-    ASSERT_EQ(builder, nullptr);
+    ASSERT_NE(builder, nullptr);
 
     // Test a small alignment with an alignment offset.
     device_info.alignment = 12 * 1024;
@@ -444,11 +444,6 @@
     device_info.alignment = 131072;
     builder = MetadataBuilder::New(device_info, kMetadataSize, 1);
     EXPECT_EQ(builder, nullptr);
-
-    device_info.alignment = 0;
-    device_info.alignment_offset = 32768 - LP_SECTOR_SIZE;
-    builder = MetadataBuilder::New(device_info, kMetadataSize, 1);
-    EXPECT_EQ(builder, nullptr);
 }
 
 TEST_F(BuilderTest, UpdateBlockDeviceInfo) {
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index b808609..ea92d25 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -416,6 +416,7 @@
         "snapuserd_server.cpp",
         "snapuserd.cpp",
         "snapuserd_daemon.cpp",
+	"snapuserd_worker.cpp",
     ],
 
     cflags: [
@@ -554,6 +555,7 @@
     srcs: [
         "cow_snapuserd_test.cpp",
         "snapuserd.cpp",
+	"snapuserd_worker.cpp",
     ],
     cflags: [
         "-Wall",
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index b4e92a2..1ebc29f 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -46,7 +46,7 @@
     SECOND_PHASE = 2;
 }
 
-// Next: 12
+// Next: 13
 message SnapshotStatus {
     // Name of the snapshot. This is usually the name of the snapshotted
     // logical partition; for example, "system_b".
@@ -105,6 +105,9 @@
 
     // Compression algorithm (none, gz, or brotli).
     string compression_algorithm = 11;
+
+    // Estimated COW size from OTA manifest.
+    uint64 estimated_cow_size = 12;
 }
 
 // Next: 8
@@ -159,7 +162,7 @@
     MergePhase merge_phase = 6;
 }
 
-// Next: 5
+// Next: 9
 message SnapshotMergeReport {
     // Status of the update after the merge attempts.
     UpdateState state = 1;
@@ -173,4 +176,16 @@
 
     // Whether compression/dm-user was used for any snapshots.
     bool compression_enabled = 4;
+
+    // Total size used by COWs, including /data and the super partition.
+    uint64 total_cow_size_bytes = 5;
+
+    // Sum of the estimated COW fields in the OTA manifest.
+    uint64 estimated_cow_size_bytes = 6;
+
+    // Time from boot to sys.boot_completed, in milliseconds.
+    uint32 boot_complete_time_ms = 7;
+
+    // Time from sys.boot_completed to merge start, in milliseconds.
+    uint32 boot_complete_to_merge_start_time_ms = 8;
 }
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index a96352a..5d63220 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -757,6 +757,30 @@
     ASSERT_TRUE(iter->Done());
 }
 
+TEST_F(CowTest, AppendAfterFinalize) {
+    CowOptions options;
+    options.cluster_ops = 0;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
+    ASSERT_TRUE(writer->AddLabel(3));
+    ASSERT_TRUE(writer->Finalize());
+
+    std::string data2 = "More data!";
+    data2.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    // COW should be valid.
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index cf9f6ea..44a423c 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -42,6 +42,29 @@
 #endif
 }
 
+bool CowReader::InitForMerge(android::base::unique_fd&& fd) {
+    owned_fd_ = std::move(fd);
+    fd_ = owned_fd_.get();
+
+    auto pos = lseek(fd_.get(), 0, SEEK_END);
+    if (pos < 0) {
+        PLOG(ERROR) << "lseek end failed";
+        return false;
+    }
+    fd_size_ = pos;
+
+    if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+    if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) {
+        PLOG(ERROR) << "read header failed";
+        return false;
+    }
+
+    return true;
+}
+
 bool CowReader::Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label) {
     owned_fd_ = std::move(fd);
     return Parse(android::base::borrowed_fd{owned_fd_}, label);
@@ -206,7 +229,8 @@
 
     if (footer_) {
         if (ops_buffer->size() != footer_->op.num_ops) {
-            LOG(ERROR) << "num ops does not match";
+            LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
+                       << ops_buffer->size();
             return false;
         }
         if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 81edc79..59f6d6f 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -376,6 +376,7 @@
     auto continue_data_pos = next_data_pos_;
     auto continue_op_pos = next_op_pos_;
     auto continue_size = ops_.size();
+    auto continue_num_ops = footer_.op.num_ops;
     bool extra_cluster = false;
 
     // Footer should be at the end of a file, so if there is data after the current block, end it
@@ -408,9 +409,9 @@
         current_data_size_ = continue_data_size;
         next_data_pos_ = continue_data_pos;
         next_op_pos_ = continue_op_pos;
+        footer_.op.num_ops = continue_num_ops;
         ops_.resize(continue_size);
     }
-
     return Sync();
 }
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 1de7473..552fd96 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -116,12 +116,15 @@
 class CowReader : public ICowReader {
   public:
     CowReader();
+    ~CowReader() { owned_fd_ = {}; }
 
     // Parse the COW, optionally, up to the given label. If no label is
     // specified, the COW must have an intact footer.
     bool Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label = {});
     bool Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label = {});
 
+    bool InitForMerge(android::base::unique_fd&& fd);
+
     bool GetHeader(CowHeader* header) override;
     bool GetFooter(CowFooter* footer) override;
 
@@ -146,6 +149,8 @@
 
     uint64_t total_data_ops() { return total_data_ops_; }
 
+    void CloseCowFd() { owned_fd_ = {}; }
+
   private:
     bool ParseOps(std::optional<uint64_t> label);
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 1e420cb..1cb966b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -26,7 +26,8 @@
     MOCK_METHOD(bool, BeginUpdate, (), (override));
     MOCK_METHOD(bool, CancelUpdate, (), (override));
     MOCK_METHOD(bool, FinishedSnapshotWrites, (bool wipe), (override));
-    MOCK_METHOD(bool, InitiateMerge, (uint64_t * cow_file_size), (override));
+    MOCK_METHOD(void, UpdateCowStats, (ISnapshotMergeStats * stats), (override));
+    MOCK_METHOD(bool, InitiateMerge, (), (override));
 
     MOCK_METHOD(UpdateState, ProcessUpdateState,
                 (const std::function<bool()>& callback, const std::function<bool()>& before_cancel),
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index a79a86d..7e74fac 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -127,9 +127,14 @@
     // may need to be merged before wiping.
     virtual bool FinishedSnapshotWrites(bool wipe) = 0;
 
+    // Update an ISnapshotMergeStats object with statistics about COW usage.
+    // This should be called before the merge begins as otherwise snapshots
+    // may be deleted.
+    virtual void UpdateCowStats(ISnapshotMergeStats* stats) = 0;
+
     // Initiate a merge on all snapshot devices. This should only be used after an
     // update has been marked successful after booting.
-    virtual bool InitiateMerge(uint64_t* cow_file_size = nullptr) = 0;
+    virtual bool InitiateMerge() = 0;
 
     // Perform any necessary post-boot actions. This should be run soon after
     // /data is mounted.
@@ -326,7 +331,8 @@
     bool BeginUpdate() override;
     bool CancelUpdate() override;
     bool FinishedSnapshotWrites(bool wipe) override;
-    bool InitiateMerge(uint64_t* cow_file_size = nullptr) override;
+    void UpdateCowStats(ISnapshotMergeStats* stats) override;
+    bool InitiateMerge() override;
     UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
@@ -491,7 +497,8 @@
     bool RemoveAllSnapshots(LockedFile* lock);
 
     // List the known snapshot names.
-    bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots);
+    bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
+                       const std::string& suffix = "");
 
     // Check for a cancelled or rolled back merge, returning true if such a
     // condition was detected and handled.
@@ -679,6 +686,9 @@
     friend std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot);
     Slot GetCurrentSlot();
 
+    // Return the suffix we expect snapshots to have.
+    std::string GetSnapshotSlotSuffix();
+
     std::string ReadUpdateSourceSlotSuffix();
 
     // Helper for RemoveAllSnapshots.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index 96d2deb..e617d7a 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -30,7 +30,15 @@
     virtual bool Start() = 0;
     virtual void set_state(android::snapshot::UpdateState state, bool using_compression) = 0;
     virtual void set_cow_file_size(uint64_t cow_file_size) = 0;
+    virtual void set_total_cow_size_bytes(uint64_t bytes) = 0;
+    virtual void set_estimated_cow_size_bytes(uint64_t bytes) = 0;
+    virtual void set_boot_complete_time_ms(uint32_t ms) = 0;
+    virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0;
     virtual uint64_t cow_file_size() = 0;
+    virtual uint64_t total_cow_size_bytes() = 0;
+    virtual uint64_t estimated_cow_size_bytes() = 0;
+    virtual uint32_t boot_complete_time_ms() = 0;
+    virtual uint32_t boot_complete_to_merge_start_time_ms() = 0;
 
     // Called when merge ends. Properly clean up permanent storage.
     class Result {
@@ -54,6 +62,14 @@
     void set_state(android::snapshot::UpdateState state, bool using_compression) override;
     void set_cow_file_size(uint64_t cow_file_size) override;
     uint64_t cow_file_size() override;
+    void set_total_cow_size_bytes(uint64_t bytes) override;
+    void set_estimated_cow_size_bytes(uint64_t bytes) override;
+    uint64_t total_cow_size_bytes() override;
+    uint64_t estimated_cow_size_bytes() override;
+    void set_boot_complete_time_ms(uint32_t ms) override;
+    uint32_t boot_complete_time_ms() override;
+    void set_boot_complete_to_merge_start_time_ms(uint32_t ms) override;
+    uint32_t boot_complete_to_merge_start_time_ms() override;
     std::unique_ptr<Result> Finish() override;
 
   private:
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index 3365ceb..cc75db8 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -28,7 +28,8 @@
     bool BeginUpdate() override;
     bool CancelUpdate() override;
     bool FinishedSnapshotWrites(bool wipe) override;
-    bool InitiateMerge(uint64_t* cow_file_size = nullptr) override;
+    void UpdateCowStats(ISnapshotMergeStats* stats) override;
+    bool InitiateMerge() override;
     UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 6002043..5569da0 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -202,6 +202,10 @@
     ret.snapshot_status.set_device_size(target_partition->size());
     ret.snapshot_status.set_snapshot_size(target_partition->size());
 
+    if (update && update->has_estimate_cow_size()) {
+        ret.snapshot_status.set_estimated_cow_size(update->estimate_cow_size());
+    }
+
     if (ret.snapshot_status.snapshot_size() == 0) {
         LOG(INFO) << "Not creating snapshot for partition " << ret.snapshot_status.name();
         ret.snapshot_status.set_cow_partition_size(0);
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index ca4c265..bd1e284 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -230,6 +230,15 @@
     return Slot::Target;
 }
 
+std::string SnapshotManager::GetSnapshotSlotSuffix() {
+    switch (GetCurrentSlot()) {
+        case Slot::Target:
+            return device_->GetSlotSuffix();
+        default:
+            return device_->GetOtherSlotSuffix();
+    }
+}
+
 static bool RemoveFileIfExists(const std::string& path) {
     std::string message;
     if (!android::base::RemoveFileIfExists(path, &message)) {
@@ -624,7 +633,7 @@
     return true;
 }
 
-bool SnapshotManager::InitiateMerge(uint64_t* cow_file_size) {
+bool SnapshotManager::InitiateMerge() {
     auto lock = LockExclusive();
     if (!lock) return false;
 
@@ -691,7 +700,6 @@
 
     std::vector<std::string> first_merge_group;
 
-    uint64_t total_cow_file_size = 0;
     DmTargetSnapshot::Status initial_target_values = {};
     for (const auto& snapshot : snapshots) {
         DmTargetSnapshot::Status current_status;
@@ -706,7 +714,6 @@
         if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
             return false;
         }
-        total_cow_file_size += snapshot_status.cow_file_size();
 
         compression_enabled |= snapshot_status.compression_enabled();
         if (DecideMergePhase(snapshot_status) == MergePhase::FIRST_PHASE) {
@@ -714,10 +721,6 @@
         }
     }
 
-    if (cow_file_size) {
-        *cow_file_size = total_cow_file_size;
-    }
-
     SnapshotUpdateStatus initial_status;
     initial_status.set_state(UpdateState::Merging);
     initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
@@ -1262,7 +1265,7 @@
             LOG(ERROR) << "DeleteDevice timeout: " << name;
             return false;
         }
-        std::this_thread::sleep_for(250ms);
+        std::this_thread::sleep_for(400ms);
     }
 
     return true;
@@ -1732,7 +1735,8 @@
     return update_status.compression_enabled();
 }
 
-bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots) {
+bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
+                                    const std::string& suffix) {
     CHECK(lock);
 
     auto dir_path = metadata_dir_ + "/snapshots"s;
@@ -1745,7 +1749,12 @@
     struct dirent* dp;
     while ((dp = readdir(dir.get())) != nullptr) {
         if (dp->d_type != DT_REG) continue;
-        snapshots->emplace_back(dp->d_name);
+
+        std::string name(dp->d_name);
+        if (!suffix.empty() && !android::base::EndsWith(name, suffix)) {
+            continue;
+        }
+        snapshots->emplace_back(std::move(name));
     }
     return true;
 }
@@ -3565,5 +3574,34 @@
     return MergePhase::SECOND_PHASE;
 }
 
+void SnapshotManager::UpdateCowStats(ISnapshotMergeStats* stats) {
+    auto lock = LockExclusive();
+    if (!lock) return;
+
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock.get(), &snapshots, GetSnapshotSlotSuffix())) {
+        LOG(ERROR) << "Could not list snapshots";
+        return;
+    }
+
+    uint64_t cow_file_size = 0;
+    uint64_t total_cow_size = 0;
+    uint64_t estimated_cow_size = 0;
+    for (const auto& snapshot : snapshots) {
+        SnapshotStatus status;
+        if (!ReadSnapshotStatus(lock.get(), snapshot, &status)) {
+            return;
+        }
+
+        cow_file_size += status.cow_file_size();
+        total_cow_size += status.cow_file_size() + status.cow_partition_size();
+        estimated_cow_size += status.estimated_cow_size();
+    }
+
+    stats->set_cow_file_size(cow_file_size);
+    stats->set_total_cow_size_bytes(total_cow_size);
+    stats->set_estimated_cow_size_bytes(estimated_cow_size);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 513700d..7fcfcea 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -98,6 +98,38 @@
     return report_.cow_file_size();
 }
 
+void SnapshotMergeStats::set_total_cow_size_bytes(uint64_t bytes) {
+    report_.set_total_cow_size_bytes(bytes);
+}
+
+void SnapshotMergeStats::set_estimated_cow_size_bytes(uint64_t bytes) {
+    report_.set_estimated_cow_size_bytes(bytes);
+}
+
+uint64_t SnapshotMergeStats::total_cow_size_bytes() {
+    return report_.total_cow_size_bytes();
+}
+
+uint64_t SnapshotMergeStats::estimated_cow_size_bytes() {
+    return report_.estimated_cow_size_bytes();
+}
+
+void SnapshotMergeStats::set_boot_complete_time_ms(uint32_t ms) {
+    report_.set_boot_complete_time_ms(ms);
+}
+
+uint32_t SnapshotMergeStats::boot_complete_time_ms() {
+    return report_.boot_complete_time_ms();
+}
+
+void SnapshotMergeStats::set_boot_complete_to_merge_start_time_ms(uint32_t ms) {
+    report_.set_boot_complete_to_merge_start_time_ms(ms);
+}
+
+uint32_t SnapshotMergeStats::boot_complete_to_merge_start_time_ms() {
+    return report_.boot_complete_to_merge_start_time_ms();
+}
+
 class SnapshotMergeStatsResultImpl : public SnapshotMergeStats::Result {
   public:
     SnapshotMergeStatsResultImpl(const SnapshotMergeReport& report,
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 8a254c9..43825cc 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -43,7 +43,7 @@
     return false;
 }
 
-bool SnapshotManagerStub::InitiateMerge(uint64_t*) {
+bool SnapshotManagerStub::InitiateMerge() {
     LOG(ERROR) << __FUNCTION__ << " should never be called.";
     return false;
 }
@@ -127,6 +127,14 @@
     void set_cow_file_size(uint64_t) override {}
     uint64_t cow_file_size() override { return 0; }
     std::unique_ptr<Result> Finish() override { return nullptr; }
+    void set_total_cow_size_bytes(uint64_t) override {}
+    void set_estimated_cow_size_bytes(uint64_t) override {}
+    uint64_t total_cow_size_bytes() override { return 0; }
+    uint64_t estimated_cow_size_bytes() override { return 0; }
+    void set_boot_complete_time_ms(uint32_t) override {}
+    uint32_t boot_complete_time_ms() override { return 0; }
+    void set_boot_complete_to_merge_start_time_ms(uint32_t) override {}
+    uint32_t boot_complete_to_merge_start_time_ms() override { return 0; }
 };
 
 ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -151,4 +159,8 @@
     return false;
 }
 
+void SnapshotManagerStub::UpdateCowStats(ISnapshotMergeStats*) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+}
+
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 4c4a342..f1fcb70 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -32,41 +32,6 @@
 #define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
 #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
 
-static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
-
-static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
-
-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() {
-    CHECK(sizeof(struct dm_user_header) <= buffer_size_);
-    char* buf = reinterpret_cast<char*>(GetBufPtr());
-    struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
-    return header;
-}
-
 Snapuserd::Snapuserd(const std::string& misc_name, const std::string& cow_device,
                      const std::string& backing_device) {
     misc_name_ = misc_name;
@@ -75,356 +40,32 @@
     control_device_ = "/dev/dm-user/" + misc_name;
 }
 
-// Construct kernel COW header in memory
-// This header will be in sector 0. The IO
-// request will always be 4k. After constructing
-// the header, zero out the remaining block.
-void Snapuserd::ConstructKernelCowHeader() {
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-    CHECK(buffer != nullptr);
+bool Snapuserd::InitializeWorkers() {
+    for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
+        std::unique_ptr<WorkerThread> wt = std::make_unique<WorkerThread>(
+                cow_device_, backing_store_device_, control_device_, misc_name_, GetSharedPtr());
 
-    memset(buffer, 0, BLOCK_SZ);
-
-    struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer);
-
-    dh->magic = SNAP_MAGIC;
-    dh->valid = SNAPSHOT_VALID;
-    dh->version = SNAPSHOT_DISK_VERSION;
-    dh->chunk_size = CHUNK_SIZE;
-}
-
-// Start the replace operation. This will read the
-// internal COW format and if the block is compressed,
-// it will be de-compressed.
-bool Snapuserd::ProcessReplaceOp(const CowOperation* cow_op) {
-    if (!reader_->ReadData(*cow_op, &bufsink_)) {
-        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
-        return false;
+        worker_threads_.push_back(std::move(wt));
     }
-
     return true;
 }
 
-// Start the copy operation. This will read the backing
-// block device which is represented by cow_op->source.
-bool Snapuserd::ProcessCopyOp(const CowOperation* cow_op) {
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-    CHECK(buffer != nullptr);
+bool Snapuserd::CommitMerge(int num_merge_ops) {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        CowHeader header;
 
-    // Issue a single 4K IO. However, this can be optimized
-    // if the successive blocks are contiguous.
-    if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ,
-                                          cow_op->source * BLOCK_SZ)) {
-        SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
-                         << "at block :" << cow_op->source;
-        return false;
-    }
-
-    return true;
-}
-
-bool Snapuserd::ProcessZeroOp() {
-    // Zero out the entire block
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-    CHECK(buffer != nullptr);
-
-    memset(buffer, 0, BLOCK_SZ);
-    return true;
-}
-
-bool Snapuserd::ProcessCowOp(const CowOperation* cow_op) {
-    CHECK(cow_op != nullptr);
-
-    switch (cow_op->type) {
-        case kCowReplaceOp: {
-            return ProcessReplaceOp(cow_op);
+        reader_->GetHeader(&header);
+        header.num_merge_ops += num_merge_ops;
+        reader_->UpdateMergeProgress(num_merge_ops);
+        if (!writer_->CommitMerge(num_merge_ops)) {
+            SNAP_LOG(ERROR) << "CommitMerge failed... merged_ops_cur_iter: " << num_merge_ops
+                            << " Total-merged-ops: " << header.num_merge_ops;
+            return false;
         }
-
-        case kCowZeroOp: {
-            return ProcessZeroOp();
-        }
-
-        case kCowCopyOp: {
-            return ProcessCopyOp(cow_op);
-        }
-
-        default: {
-            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
-        }
-    }
-    return false;
-}
-
-int Snapuserd::ReadUnalignedSector(sector_t sector, size_t size,
-                                   std::map<sector_t, const CowOperation*>::iterator& it) {
-    size_t skip_sector_size = 0;
-
-    SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
-                    << " Aligned sector: " << it->second;
-
-    if (!ProcessCowOp(it->second)) {
-        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
-        return -1;
+        merge_initiated_ = true;
     }
 
-    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]));
-
-        memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
-                (BLOCK_SZ - skip_sector_size));
-    }
-
-    bufsink_.ResetBufferOffset();
-    return std::min(size, (BLOCK_SZ - skip_sector_size));
-}
-
-/*
- * Read the data for a given COW Operation.
- *
- * Kernel can issue IO at a sector granularity.
- * Hence, an IO may end up with reading partial
- * data from a COW operation or we may also
- * end up with interspersed request between
- * two COW operations.
- *
- */
-int Snapuserd::ReadData(sector_t sector, size_t size) {
-    /*
-     * chunk_map stores COW operation at 4k granularity.
-     * If the requested IO with the sector falls on the 4k
-     * boundary, then we can read the COW op directly without
-     * any issue.
-     *
-     * However, if the requested sector is not 4K aligned,
-     * then we will have the find the nearest COW operation
-     * and chop the 4K block to fetch the requested sector.
-     */
-    std::map<sector_t, const CowOperation*>::iterator it = chunk_map_.find(sector);
-    if (it == chunk_map_.end()) {
-        it = chunk_map_.lower_bound(sector);
-        if (it != chunk_map_.begin()) {
-            --it;
-        }
-
-        /*
-         * If the IO is spanned between two COW operations,
-         * split the IO into two parts:
-         *
-         * 1: Read the first part from the single COW op
-         * 2: Read the second part from the next COW op.
-         *
-         * Ex: Let's say we have a 1024 Bytes IO request.
-         *
-         * 0       COW OP-1  4096     COW OP-2  8192
-         * |******************|*******************|
-         *              |*****|*****|
-         *           3584           4608
-         *              <- 1024B - >
-         *
-         * We have two COW operations which are 4k blocks.
-         * The IO is requested for 1024 Bytes which are spanned
-         * between two COW operations. We will split this IO
-         * into two parts:
-         *
-         * 1: IO of size 512B from offset 3584 bytes (COW OP-1)
-         * 2: IO of size 512B from offset 4096 bytes (COW OP-2)
-         */
-        return ReadUnalignedSector(sector, size, it);
-    }
-
-    int num_ops = DIV_ROUND_UP(size, BLOCK_SZ);
-    while (num_ops) {
-        if (!ProcessCowOp(it->second)) {
-            return -1;
-        }
-        num_ops -= 1;
-        it++;
-        // Update the buffer offset
-        bufsink_.UpdateBufferOffset(BLOCK_SZ);
-
-        SNAP_LOG(DEBUG) << "ReadData at sector: " << sector << " size: " << size;
-    }
-
-    // Reset the buffer offset
-    bufsink_.ResetBufferOffset();
-    return size;
-}
-
-/*
- * dm-snap does prefetch reads while reading disk-exceptions.
- * By default, prefetch value is set to 12; this means that
- * dm-snap will issue 12 areas wherein each area is a 4k page
- * of disk-exceptions.
- *
- * If during prefetch, if the chunk-id seen is beyond the
- * actual number of metadata page, fill the buffer with zero.
- * When dm-snap starts parsing the buffer, it will stop
- * reading metadata page once the buffer content is zero.
- */
-bool Snapuserd::ZerofillDiskExceptions(size_t read_size) {
-    size_t size = exceptions_per_area_ * sizeof(struct disk_exception);
-
-    if (read_size > size) {
-        return false;
-    }
-
-    void* buffer = bufsink_.GetPayloadBuffer(size);
-    CHECK(buffer != nullptr);
-
-    memset(buffer, 0, size);
-    return true;
-}
-
-/*
- * A disk exception is a simple mapping of old_chunk to new_chunk.
- * When dm-snapshot device is created, kernel requests these mapping.
- *
- * Each disk exception is of size 16 bytes. Thus a single 4k page can
- * have:
- *
- * exceptions_per_area_ = 4096/16 = 256. This entire 4k page
- * is considered a metadata page and it is represented by chunk ID.
- *
- * Convert the chunk ID to index into the vector which gives us
- * the metadata page.
- */
-bool Snapuserd::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
-    uint32_t stride = exceptions_per_area_ + 1;
-    size_t size;
-
-    // ChunkID to vector index
-    lldiv_t divresult = lldiv(chunk, stride);
-
-    if (divresult.quot < vec_.size()) {
-        size = exceptions_per_area_ * sizeof(struct disk_exception);
-
-        CHECK(read_size == size);
-
-        void* buffer = bufsink_.GetPayloadBuffer(size);
-        CHECK(buffer != nullptr);
-
-        memcpy(buffer, vec_[divresult.quot].get(), size);
-    } else {
-        return ZerofillDiskExceptions(read_size);
-    }
-
-    return true;
-}
-
-loff_t Snapuserd::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
-                                      int* unmerged_exceptions) {
-    loff_t offset = 0;
-    *unmerged_exceptions = 0;
-
-    while (*unmerged_exceptions <= exceptions_per_area_) {
-        struct disk_exception* merged_de =
-                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
-        struct disk_exception* cow_de =
-                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
-
-        // Unmerged op by the kernel
-        if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) {
-            CHECK(merged_de->old_chunk == cow_de->old_chunk);
-            CHECK(merged_de->new_chunk == cow_de->new_chunk);
-
-            offset += sizeof(struct disk_exception);
-            *unmerged_exceptions += 1;
-            continue;
-        }
-
-        break;
-    }
-
-    CHECK(!(*unmerged_exceptions == exceptions_per_area_));
-
-    SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
-    return offset;
-}
-
-int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
-                                    int unmerged_exceptions) {
-    int merged_ops_cur_iter = 0;
-
-    // Find the operations which are merged in this cycle.
-    while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
-        struct disk_exception* merged_de =
-                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
-        struct disk_exception* cow_de =
-                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
-
-        CHECK(merged_de->new_chunk == 0);
-        CHECK(merged_de->old_chunk == 0);
-
-        if (cow_de->new_chunk != 0) {
-            merged_ops_cur_iter += 1;
-            offset += sizeof(struct disk_exception);
-            const CowOperation* cow_op = chunk_map_[ChunkToSector(cow_de->new_chunk)];
-            CHECK(cow_op != nullptr);
-
-            CHECK(cow_op->new_block == cow_de->old_chunk);
-            // zero out to indicate that operation is merged.
-            cow_de->old_chunk = 0;
-            cow_de->new_chunk = 0;
-        } else if (cow_de->old_chunk == 0) {
-            // Already merged op in previous iteration or
-            // This could also represent a partially filled area.
-            //
-            // If the op was merged in previous cycle, we don't have
-            // to count them.
-            CHECK(cow_de->new_chunk == 0);
-            break;
-        } else {
-            SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
-                            << " merged_de-old-chunk: " << merged_de->old_chunk
-                            << " merged_de-new-chunk: " << merged_de->new_chunk
-                            << " cow_de-old-chunk: " << cow_de->old_chunk
-                            << " cow_de-new-chunk: " << cow_de->new_chunk
-                            << " unmerged_exceptions: " << unmerged_exceptions
-                            << " merged_ops_cur_iter: " << merged_ops_cur_iter
-                            << " offset: " << offset;
-            return -1;
-        }
-    }
-    return merged_ops_cur_iter;
-}
-
-bool Snapuserd::ProcessMergeComplete(chunk_t chunk, void* buffer) {
-    uint32_t stride = exceptions_per_area_ + 1;
-    CowHeader header;
-
-    if (!reader_->GetHeader(&header)) {
-        SNAP_LOG(ERROR) << "Failed to get header";
-        return false;
-    }
-
-    // ChunkID to vector index
-    lldiv_t divresult = lldiv(chunk, stride);
-    CHECK(divresult.quot < vec_.size());
-    SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
-                    << " Metadata-Index: " << divresult.quot;
-
-    int unmerged_exceptions = 0;
-    loff_t offset = GetMergeStartOffset(buffer, vec_[divresult.quot].get(), &unmerged_exceptions);
-
-    int merged_ops_cur_iter =
-            GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset, unmerged_exceptions);
-
-    // There should be at least one operation merged in this cycle
-    CHECK(merged_ops_cur_iter > 0);
-
-    header.num_merge_ops += merged_ops_cur_iter;
-    reader_->UpdateMergeProgress(merged_ops_cur_iter);
-    if (!writer_->CommitMerge(merged_ops_cur_iter)) {
-        SNAP_LOG(ERROR) << "CommitMerge failed... merged_ops_cur_iter: " << merged_ops_cur_iter;
-        return false;
-    }
-
-    SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
-    merge_initiated_ = true;
     return true;
 }
 
@@ -836,7 +477,6 @@
 
     // Total number of sectors required for creating dm-user device
     num_sectors_ = ChunkToSector(data_chunk_id);
-    metadata_read_done_ = true;
     merge_initiated_ = false;
     return true;
 }
@@ -850,37 +490,6 @@
     }
 }
 
-// Read Header from dm-user misc device. This gives
-// us the sector number for which IO is issued by dm-snapshot device
-bool Snapuserd::ReadDmUserHeader() {
-    if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
-        SNAP_PLOG(ERROR) << "Control-read failed";
-        return false;
-    }
-
-    return true;
-}
-
-// Send the payload/data back to dm-user misc device.
-bool Snapuserd::WriteDmUserPayload(size_t size) {
-    if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
-                                   sizeof(struct dm_user_header) + size)) {
-        SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << size;
-        return false;
-    }
-
-    return true;
-}
-
-bool Snapuserd::ReadDmUserPayload(void* buffer, size_t size) {
-    if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
-        SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size;
-        return false;
-    }
-
-    return true;
-}
-
 bool Snapuserd::InitCowDevice() {
     cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
     if (cow_fd_ < 0) {
@@ -888,186 +497,26 @@
         return false;
     }
 
-    // Allocate the buffer which is used to communicate between
-    // daemon and dm-user. The buffer comprises of header and a fixed payload.
-    // If the dm-user requests a big IO, the IO will be broken into chunks
-    // of PAYLOAD_SIZE.
-    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
-    bufsink_.Initialize(buf_size);
-
     return ReadMetadata();
 }
 
-bool Snapuserd::InitBackingAndControlDevice() {
-    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;
+/*
+ * Entry point to launch worker threads
+ */
+bool Snapuserd::Start() {
+    std::vector<std::future<bool>> threads;
+
+    for (int i = 0; i < worker_threads_.size(); i++) {
+        threads.emplace_back(
+                std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get()));
     }
 
-    ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
-    if (ctrl_fd_ < 0) {
-        SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
-        return false;
+    bool ret = true;
+    for (auto& t : threads) {
+        ret = t.get() && ret;
     }
 
-    return true;
-}
-
-bool Snapuserd::DmuserWriteRequest() {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
-
-    // device mapper has the capability to allow
-    // targets to flush the cache when writes are completed. This
-    // is controlled by each target by a flag "flush_supported".
-    // This flag is set by dm-user. When flush is supported,
-    // a number of zero-length bio's will be submitted to
-    // the target for the purpose of flushing cache. It is the
-    // responsibility of the target driver - which is dm-user in this
-    // case, to remap these bio's to the underlying device. Since,
-    // there is no underlying device for dm-user, this zero length
-    // bio's gets routed to daemon.
-    //
-    // Flush operations are generated post merge by dm-snap by having
-    // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
-    // to flush per se; hence, just respond back with a success message.
-    if (header->sector == 0) {
-        CHECK(header->len == 0);
-        header->type = DM_USER_RESP_SUCCESS;
-        if (!WriteDmUserPayload(0)) {
-            return false;
-        }
-        return true;
-    }
-
-    size_t remaining_size = header->len;
-    size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
-    CHECK(read_size == BLOCK_SZ);
-
-    CHECK(header->sector > 0);
-    chunk_t chunk = SectorToChunk(header->sector);
-    CHECK(chunk_map_.find(header->sector) == chunk_map_.end());
-
-    void* buffer = bufsink_.GetPayloadBuffer(read_size);
-    CHECK(buffer != nullptr);
-    header->type = DM_USER_RESP_SUCCESS;
-
-    if (!ReadDmUserPayload(buffer, read_size)) {
-        SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
-                        << "Sector: " << header->sector;
-        header->type = DM_USER_RESP_ERROR;
-    }
-
-    if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
-        SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
-                        << "Sector: " << header->sector;
-        header->type = DM_USER_RESP_ERROR;
-    } else {
-        SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk
-                        << "Sector: " << header->sector;
-    }
-
-    if (!WriteDmUserPayload(0)) {
-        return false;
-    }
-
-    return true;
-}
-
-bool Snapuserd::DmuserReadRequest() {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
-    size_t remaining_size = header->len;
-    loff_t offset = 0;
-    sector_t sector = header->sector;
-    do {
-        size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
-
-        int ret = read_size;
-        header->type = DM_USER_RESP_SUCCESS;
-        chunk_t chunk = SectorToChunk(header->sector);
-
-        // Request to sector 0 is always for kernel
-        // representation of COW header. This IO should be only
-        // once during dm-snapshot device creation. We should
-        // never see multiple IO requests. Additionally this IO
-        // will always be a single 4k.
-        if (header->sector == 0) {
-            CHECK(metadata_read_done_ == true);
-            CHECK(read_size == BLOCK_SZ);
-            ConstructKernelCowHeader();
-            SNAP_LOG(DEBUG) << "Kernel header constructed";
-        } else {
-            if (!offset && (read_size == BLOCK_SZ) &&
-                chunk_map_.find(header->sector) == chunk_map_.end()) {
-                if (!ReadDiskExceptions(chunk, read_size)) {
-                    SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
-                                    << "Sector: " << header->sector;
-                    header->type = DM_USER_RESP_ERROR;
-                } else {
-                    SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk
-                                    << "Sector: " << header->sector;
-                }
-            } else {
-                chunk_t num_sectors_read = (offset >> SECTOR_SHIFT);
-                ret = ReadData(sector + num_sectors_read, read_size);
-                if (ret < 0) {
-                    SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
-                                    << " Sector: " << (sector + num_sectors_read)
-                                    << " size: " << read_size << " header-len: " << header->len;
-                    header->type = DM_USER_RESP_ERROR;
-                } else {
-                    SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk
-                                    << "Sector: " << header->sector;
-                }
-            }
-        }
-
-        // Daemon will not be terminated if there is any error. We will
-        // just send the error back to dm-user.
-        if (!WriteDmUserPayload(ret)) {
-            return false;
-        }
-
-        remaining_size -= ret;
-        offset += ret;
-    } while (remaining_size > 0);
-
-    return true;
-}
-
-bool Snapuserd::Run() {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
-
-    bufsink_.Clear();
-
-    if (!ReadDmUserHeader()) {
-        SNAP_LOG(ERROR) << "ReadDmUserHeader failed";
-        return false;
-    }
-
-    SNAP_LOG(DEBUG) << "msg->seq: " << std::hex << header->seq;
-    SNAP_LOG(DEBUG) << "msg->type: " << std::hex << header->type;
-    SNAP_LOG(DEBUG) << "msg->flags: " << std::hex << header->flags;
-    SNAP_LOG(DEBUG) << "msg->sector: " << std::hex << header->sector;
-    SNAP_LOG(DEBUG) << "msg->len: " << std::hex << header->len;
-
-    switch (header->type) {
-        case DM_USER_REQ_MAP_READ: {
-            if (!DmuserReadRequest()) {
-                return false;
-            }
-            break;
-        }
-
-        case DM_USER_REQ_MAP_WRITE: {
-            if (!DmuserWriteRequest()) {
-                return false;
-            }
-            break;
-        }
-    }
-
-    return true;
+    return ret;
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h
index 518d08b..6ce64d8 100644
--- a/fs_mgr/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd.h
@@ -18,13 +18,17 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+#include <bitset>
 #include <csignal>
 #include <cstring>
+#include <future>
 #include <iostream>
 #include <limits>
 #include <map>
+#include <mutex>
 #include <string>
 #include <thread>
+#include <unordered_map>
 #include <vector>
 
 #include <android-base/file.h>
@@ -40,6 +44,17 @@
 namespace snapshot {
 
 using android::base::unique_fd;
+using namespace std::chrono_literals;
+
+static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
+static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
+
+/*
+ * With 4 threads, we get optimal performance
+ * when update_verifier reads the partition during
+ * boot.
+ */
+static constexpr int NUM_THREADS_PER_PARTITION = 4;
 
 class BufferSink : public IByteSink {
   public:
@@ -59,53 +74,106 @@
     size_t buffer_size_;
 };
 
-class Snapuserd final {
+class Snapuserd;
+
+class WorkerThread {
   public:
-    Snapuserd(const std::string& misc_name, const std::string& cow_device,
-              const std::string& backing_device);
-    bool InitBackingAndControlDevice();
-    bool InitCowDevice();
-    bool Run();
-    const std::string& GetControlDevicePath() { return control_device_; }
-    const std::string& GetMiscName() { return misc_name_; }
-    uint64_t GetNumSectors() { return num_sectors_; }
-    bool IsAttached() const { return ctrl_fd_ >= 0; }
-    void CheckMergeCompletionStatus();
-    void CloseFds() {
-        ctrl_fd_ = {};
-        cow_fd_ = {};
-        backing_store_fd_ = {};
-    }
-    size_t GetMetadataAreaSize() { return vec_.size(); }
-    void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
+    WorkerThread(const std::string& cow_device, const std::string& backing_device,
+                 const std::string& control_device, const std::string& misc_name,
+                 std::shared_ptr<Snapuserd> snapuserd);
+    bool RunThread();
 
   private:
+    // Initialization
+    void InitializeBufsink();
+    bool InitializeFds();
+    bool InitReader();
+    void CloseFds() {
+        ctrl_fd_ = {};
+        backing_store_fd_ = {};
+    }
+
+    // Functions interacting with dm-user
+    bool ReadDmUserHeader();
     bool DmuserReadRequest();
     bool DmuserWriteRequest();
-
-    bool ReadDmUserHeader();
     bool ReadDmUserPayload(void* buffer, size_t size);
     bool WriteDmUserPayload(size_t size);
-    void ConstructKernelCowHeader();
-    bool ReadMetadata();
-    bool ZerofillDiskExceptions(size_t read_size);
+
     bool ReadDiskExceptions(chunk_t chunk, size_t size);
+    bool ZerofillDiskExceptions(size_t read_size);
+    void ConstructKernelCowHeader();
+
+    // IO Path
+    bool ProcessIORequest();
+    int ReadData(sector_t sector, size_t size);
     int ReadUnalignedSector(sector_t sector, size_t size,
                             std::map<sector_t, const CowOperation*>::iterator& it);
-    int ReadData(sector_t sector, size_t size);
-    bool IsChunkIdMetadata(chunk_t chunk);
-    chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
 
+    // Processing COW operations
     bool ProcessCowOp(const CowOperation* cow_op);
     bool ProcessReplaceOp(const CowOperation* cow_op);
     bool ProcessCopyOp(const CowOperation* cow_op);
     bool ProcessZeroOp();
 
+    // Merge related functions
+    bool ProcessMergeComplete(chunk_t chunk, void* buffer);
     loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
                                int* unmerged_exceptions);
     int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
                              int unmerged_exceptions);
-    bool ProcessMergeComplete(chunk_t chunk, void* buffer);
+
+    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+
+    std::unique_ptr<CowReader> reader_;
+    BufferSink bufsink_;
+
+    std::string cow_device_;
+    std::string backing_store_device_;
+    std::string control_device_;
+    std::string misc_name_;
+
+    unique_fd cow_fd_;
+    unique_fd backing_store_fd_;
+    unique_fd ctrl_fd_;
+
+    std::shared_ptr<Snapuserd> snapuserd_;
+    uint32_t exceptions_per_area_;
+};
+
+class Snapuserd : public std::enable_shared_from_this<Snapuserd> {
+  public:
+    Snapuserd(const std::string& misc_name, const std::string& cow_device,
+              const std::string& backing_device);
+    bool InitCowDevice();
+    bool Start();
+    const std::string& GetControlDevicePath() { return control_device_; }
+    const std::string& GetMiscName() { return misc_name_; }
+    uint64_t GetNumSectors() { return num_sectors_; }
+    bool IsAttached() const { return attached_; }
+    void AttachControlDevice() { attached_ = true; }
+
+    void CheckMergeCompletionStatus();
+    bool CommitMerge(int num_merge_ops);
+
+    void CloseFds() { cow_fd_ = {}; }
+    size_t GetMetadataAreaSize() { return vec_.size(); }
+    void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
+
+    bool InitializeWorkers();
+    std::shared_ptr<Snapuserd> GetSharedPtr() { return shared_from_this(); }
+
+    std::map<sector_t, const CowOperation*>& GetChunkMap() { return chunk_map_; }
+    const std::vector<std::unique_ptr<uint8_t[]>>& GetMetadataVec() const { return vec_; }
+
+  private:
+    std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+
+    bool ReadMetadata();
+    bool IsChunkIdMetadata(chunk_t chunk);
+    chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
+
     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); }
@@ -116,8 +184,6 @@
     std::string misc_name_;
 
     unique_fd cow_fd_;
-    unique_fd backing_store_fd_;
-    unique_fd ctrl_fd_;
 
     uint32_t exceptions_per_area_;
     uint64_t num_sectors_;
@@ -141,9 +207,10 @@
     // in the chunk_map to find the nearest COW op.
     std::map<sector_t, const CowOperation*> chunk_map_;
 
-    bool metadata_read_done_ = false;
+    std::mutex lock_;
+
     bool merge_initiated_ = false;
-    BufferSink bufsink_;
+    bool attached_ = false;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 017de3b..167895e 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -77,8 +77,8 @@
     JoinAllThreads();
 }
 
-DmUserHandler::DmUserHandler(std::unique_ptr<Snapuserd>&& snapuserd)
-    : snapuserd_(std::move(snapuserd)), misc_name_(snapuserd_->GetMiscName()) {}
+DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd)
+    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
 
 bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
     ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), 0));
@@ -204,10 +204,8 @@
 void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
     LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
-    while (!StopRequested()) {
-        if (!handler->snapuserd()->Run()) {
-            break;
-        }
+    if (!handler->snapuserd()->Start()) {
+        LOG(ERROR) << " Failed to launch all worker threads";
     }
 
     handler->snapuserd()->CloseFds();
@@ -349,13 +347,18 @@
 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_unique<Snapuserd>(misc_name, cow_device_path, backing_device);
+    auto snapuserd = std::make_shared<Snapuserd>(misc_name, cow_device_path, backing_device);
     if (!snapuserd->InitCowDevice()) {
         LOG(ERROR) << "Failed to initialize Snapuserd";
         return nullptr;
     }
 
-    auto handler = std::make_shared<DmUserHandler>(std::move(snapuserd));
+    if (!snapuserd->InitializeWorkers()) {
+        LOG(ERROR) << "Failed to initialize workers";
+        return nullptr;
+    }
+
+    auto handler = std::make_shared<DmUserHandler>(snapuserd);
     {
         std::lock_guard<std::mutex> lock(lock_);
         if (FindHandler(&lock, misc_name) != dm_users_.end()) {
@@ -370,10 +373,7 @@
 bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
     CHECK(!handler->snapuserd()->IsAttached());
 
-    if (!handler->snapuserd()->InitBackingAndControlDevice()) {
-        LOG(ERROR) << "Failed to initialize control device: " << handler->misc_name();
-        return false;
-    }
+    handler->snapuserd()->AttachControlDevice();
 
     handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler));
     return true;
diff --git a/fs_mgr/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd_server.h
index 7cbc2de..e9d575d 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd_server.h
@@ -47,17 +47,17 @@
 
 class DmUserHandler {
   public:
-    explicit DmUserHandler(std::unique_ptr<Snapuserd>&& snapuserd);
+    explicit DmUserHandler(std::shared_ptr<Snapuserd> snapuserd);
 
     void FreeResources() { snapuserd_ = nullptr; }
-    const std::unique_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
+    const std::shared_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
     std::thread& thread() { return thread_; }
 
     const std::string& misc_name() const { return misc_name_; }
 
   private:
     std::thread thread_;
-    std::unique_ptr<Snapuserd> snapuserd_;
+    std::shared_ptr<Snapuserd> snapuserd_;
     std::string misc_name_;
 };
 
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
new file mode 100644
index 0000000..16f47fe
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -0,0 +1,675 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd.h"
+
+#include <csignal>
+#include <optional>
+#include <set>
+
+#include <libsnapshot/snapuserd_client.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
+
+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() {
+    CHECK(sizeof(struct dm_user_header) <= buffer_size_);
+    char* buf = reinterpret_cast<char*>(GetBufPtr());
+    struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
+    return header;
+}
+
+WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device,
+                           const std::string& control_device, const std::string& misc_name,
+                           std::shared_ptr<Snapuserd> snapuserd) {
+    cow_device_ = cow_device;
+    backing_store_device_ = backing_device;
+    control_device_ = control_device;
+    misc_name_ = misc_name;
+    snapuserd_ = snapuserd;
+    exceptions_per_area_ = (CHUNK_SIZE << SECTOR_SHIFT) / sizeof(struct disk_exception);
+}
+
+bool WorkerThread::InitializeFds() {
+    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+    if (backing_store_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+        return false;
+    }
+
+    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+    if (cow_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+        return false;
+    }
+
+    ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
+    if (ctrl_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::InitReader() {
+    reader_ = std::make_unique<CowReader>();
+    if (!reader_->InitForMerge(std::move(cow_fd_))) {
+        return false;
+    }
+
+    return true;
+}
+
+// Construct kernel COW header in memory
+// This header will be in sector 0. The IO
+// request will always be 4k. After constructing
+// the header, zero out the remaining block.
+void WorkerThread::ConstructKernelCowHeader() {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    CHECK(buffer != nullptr);
+
+    memset(buffer, 0, BLOCK_SZ);
+
+    struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer);
+
+    dh->magic = SNAP_MAGIC;
+    dh->valid = SNAPSHOT_VALID;
+    dh->version = SNAPSHOT_DISK_VERSION;
+    dh->chunk_size = CHUNK_SIZE;
+}
+
+// Start the replace operation. This will read the
+// internal COW format and if the block is compressed,
+// it will be de-compressed.
+bool WorkerThread::ProcessReplaceOp(const CowOperation* cow_op) {
+    if (!reader_->ReadData(*cow_op, &bufsink_)) {
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+        return false;
+    }
+
+    return true;
+}
+
+// Start the copy operation. This will read the backing
+// block device which is represented by cow_op->source.
+bool WorkerThread::ProcessCopyOp(const CowOperation* cow_op) {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    CHECK(buffer != nullptr);
+
+    // Issue a single 4K IO. However, this can be optimized
+    // if the successive blocks are contiguous.
+    if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ,
+                                          cow_op->source * BLOCK_SZ)) {
+        SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
+                         << "at block :" << cow_op->source;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::ProcessZeroOp() {
+    // Zero out the entire block
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    CHECK(buffer != nullptr);
+
+    memset(buffer, 0, BLOCK_SZ);
+    return true;
+}
+
+bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) {
+    CHECK(cow_op != nullptr);
+
+    switch (cow_op->type) {
+        case kCowReplaceOp: {
+            return ProcessReplaceOp(cow_op);
+        }
+
+        case kCowZeroOp: {
+            return ProcessZeroOp();
+        }
+
+        case kCowCopyOp: {
+            return ProcessCopyOp(cow_op);
+        }
+
+        default: {
+            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+        }
+    }
+    return false;
+}
+
+int WorkerThread::ReadUnalignedSector(sector_t sector, size_t size,
+                                      std::map<sector_t, const CowOperation*>::iterator& it) {
+    size_t skip_sector_size = 0;
+
+    SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
+                    << " Aligned sector: " << it->second;
+
+    if (!ProcessCowOp(it->second)) {
+        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
+        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]));
+
+        memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
+                (BLOCK_SZ - skip_sector_size));
+    }
+
+    bufsink_.ResetBufferOffset();
+    return std::min(size, (BLOCK_SZ - skip_sector_size));
+}
+
+/*
+ * Read the data for a given COW Operation.
+ *
+ * Kernel can issue IO at a sector granularity.
+ * Hence, an IO may end up with reading partial
+ * data from a COW operation or we may also
+ * end up with interspersed request between
+ * two COW operations.
+ *
+ */
+int WorkerThread::ReadData(sector_t sector, size_t size) {
+    std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+    /*
+     * chunk_map stores COW operation at 4k granularity.
+     * If the requested IO with the sector falls on the 4k
+     * boundary, then we can read the COW op directly without
+     * any issue.
+     *
+     * However, if the requested sector is not 4K aligned,
+     * then we will have the find the nearest COW operation
+     * and chop the 4K block to fetch the requested sector.
+     */
+    std::map<sector_t, const CowOperation*>::iterator it = chunk_map.find(sector);
+    if (it == chunk_map.end()) {
+        it = chunk_map.lower_bound(sector);
+        if (it != chunk_map.begin()) {
+            --it;
+        }
+
+        /*
+         * If the IO is spanned between two COW operations,
+         * split the IO into two parts:
+         *
+         * 1: Read the first part from the single COW op
+         * 2: Read the second part from the next COW op.
+         *
+         * Ex: Let's say we have a 1024 Bytes IO request.
+         *
+         * 0       COW OP-1  4096     COW OP-2  8192
+         * |******************|*******************|
+         *              |*****|*****|
+         *           3584           4608
+         *              <- 1024B - >
+         *
+         * We have two COW operations which are 4k blocks.
+         * The IO is requested for 1024 Bytes which are spanned
+         * between two COW operations. We will split this IO
+         * into two parts:
+         *
+         * 1: IO of size 512B from offset 3584 bytes (COW OP-1)
+         * 2: IO of size 512B from offset 4096 bytes (COW OP-2)
+         */
+        return ReadUnalignedSector(sector, size, it);
+    }
+
+    int num_ops = DIV_ROUND_UP(size, BLOCK_SZ);
+    while (num_ops) {
+        if (!ProcessCowOp(it->second)) {
+            return -1;
+        }
+        num_ops -= 1;
+        it++;
+        // Update the buffer offset
+        bufsink_.UpdateBufferOffset(BLOCK_SZ);
+
+        SNAP_LOG(DEBUG) << "ReadData at sector: " << sector << " size: " << size;
+    }
+
+    // Reset the buffer offset
+    bufsink_.ResetBufferOffset();
+    return size;
+}
+
+/*
+ * dm-snap does prefetch reads while reading disk-exceptions.
+ * By default, prefetch value is set to 12; this means that
+ * dm-snap will issue 12 areas wherein each area is a 4k page
+ * of disk-exceptions.
+ *
+ * If during prefetch, if the chunk-id seen is beyond the
+ * actual number of metadata page, fill the buffer with zero.
+ * When dm-snap starts parsing the buffer, it will stop
+ * reading metadata page once the buffer content is zero.
+ */
+bool WorkerThread::ZerofillDiskExceptions(size_t read_size) {
+    size_t size = exceptions_per_area_ * sizeof(struct disk_exception);
+
+    if (read_size > size) {
+        return false;
+    }
+
+    void* buffer = bufsink_.GetPayloadBuffer(size);
+    CHECK(buffer != nullptr);
+
+    memset(buffer, 0, size);
+    return true;
+}
+
+/*
+ * A disk exception is a simple mapping of old_chunk to new_chunk.
+ * When dm-snapshot device is created, kernel requests these mapping.
+ *
+ * Each disk exception is of size 16 bytes. Thus a single 4k page can
+ * have:
+ *
+ * exceptions_per_area_ = 4096/16 = 256. This entire 4k page
+ * is considered a metadata page and it is represented by chunk ID.
+ *
+ * Convert the chunk ID to index into the vector which gives us
+ * the metadata page.
+ */
+bool WorkerThread::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
+    uint32_t stride = exceptions_per_area_ + 1;
+    size_t size;
+    const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
+
+    // ChunkID to vector index
+    lldiv_t divresult = lldiv(chunk, stride);
+
+    if (divresult.quot < vec.size()) {
+        size = exceptions_per_area_ * sizeof(struct disk_exception);
+
+        CHECK(read_size == size);
+
+        void* buffer = bufsink_.GetPayloadBuffer(size);
+        CHECK(buffer != nullptr);
+
+        memcpy(buffer, vec[divresult.quot].get(), size);
+    } else {
+        return ZerofillDiskExceptions(read_size);
+    }
+
+    return true;
+}
+
+loff_t WorkerThread::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
+                                         int* unmerged_exceptions) {
+    loff_t offset = 0;
+    *unmerged_exceptions = 0;
+
+    while (*unmerged_exceptions <= exceptions_per_area_) {
+        struct disk_exception* merged_de =
+                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
+        struct disk_exception* cow_de =
+                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
+
+        // Unmerged op by the kernel
+        if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) {
+            CHECK(merged_de->old_chunk == cow_de->old_chunk);
+            CHECK(merged_de->new_chunk == cow_de->new_chunk);
+
+            offset += sizeof(struct disk_exception);
+            *unmerged_exceptions += 1;
+            continue;
+        }
+
+        break;
+    }
+
+    CHECK(!(*unmerged_exceptions == exceptions_per_area_));
+
+    SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
+    return offset;
+}
+
+int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
+                                       int unmerged_exceptions) {
+    int merged_ops_cur_iter = 0;
+    std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+
+    // Find the operations which are merged in this cycle.
+    while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
+        struct disk_exception* merged_de =
+                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
+        struct disk_exception* cow_de =
+                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
+
+        CHECK(merged_de->new_chunk == 0);
+        CHECK(merged_de->old_chunk == 0);
+
+        if (cow_de->new_chunk != 0) {
+            merged_ops_cur_iter += 1;
+            offset += sizeof(struct disk_exception);
+            const CowOperation* cow_op = chunk_map[ChunkToSector(cow_de->new_chunk)];
+            CHECK(cow_op != nullptr);
+
+            CHECK(cow_op->new_block == cow_de->old_chunk);
+            // zero out to indicate that operation is merged.
+            cow_de->old_chunk = 0;
+            cow_de->new_chunk = 0;
+        } else if (cow_de->old_chunk == 0) {
+            // Already merged op in previous iteration or
+            // This could also represent a partially filled area.
+            //
+            // If the op was merged in previous cycle, we don't have
+            // to count them.
+            CHECK(cow_de->new_chunk == 0);
+            break;
+        } else {
+            SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
+                            << " merged_de-old-chunk: " << merged_de->old_chunk
+                            << " merged_de-new-chunk: " << merged_de->new_chunk
+                            << " cow_de-old-chunk: " << cow_de->old_chunk
+                            << " cow_de-new-chunk: " << cow_de->new_chunk
+                            << " unmerged_exceptions: " << unmerged_exceptions
+                            << " merged_ops_cur_iter: " << merged_ops_cur_iter
+                            << " offset: " << offset;
+            return -1;
+        }
+    }
+    return merged_ops_cur_iter;
+}
+
+bool WorkerThread::ProcessMergeComplete(chunk_t chunk, void* buffer) {
+    uint32_t stride = exceptions_per_area_ + 1;
+    const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
+
+    // ChunkID to vector index
+    lldiv_t divresult = lldiv(chunk, stride);
+    CHECK(divresult.quot < vec.size());
+    SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
+                    << " Metadata-Index: " << divresult.quot;
+
+    int unmerged_exceptions = 0;
+    loff_t offset = GetMergeStartOffset(buffer, vec[divresult.quot].get(), &unmerged_exceptions);
+
+    int merged_ops_cur_iter =
+            GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset, unmerged_exceptions);
+
+    // There should be at least one operation merged in this cycle
+    CHECK(merged_ops_cur_iter > 0);
+    if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
+        return false;
+    }
+
+    SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
+    return true;
+}
+
+// Read Header from dm-user misc device. This gives
+// us the sector number for which IO is issued by dm-snapshot device
+bool WorkerThread::ReadDmUserHeader() {
+    if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
+        if (errno != ENOTBLK) {
+            SNAP_PLOG(ERROR) << "Control-read failed";
+        }
+        return false;
+    }
+
+    return true;
+}
+
+// Send the payload/data back to dm-user misc device.
+bool WorkerThread::WriteDmUserPayload(size_t size) {
+    if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
+                                   sizeof(struct dm_user_header) + size)) {
+        SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << size;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::ReadDmUserPayload(void* buffer, size_t size) {
+    if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
+        SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::DmuserWriteRequest() {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+    // device mapper has the capability to allow
+    // targets to flush the cache when writes are completed. This
+    // is controlled by each target by a flag "flush_supported".
+    // This flag is set by dm-user. When flush is supported,
+    // a number of zero-length bio's will be submitted to
+    // the target for the purpose of flushing cache. It is the
+    // responsibility of the target driver - which is dm-user in this
+    // case, to remap these bio's to the underlying device. Since,
+    // there is no underlying device for dm-user, this zero length
+    // bio's gets routed to daemon.
+    //
+    // Flush operations are generated post merge by dm-snap by having
+    // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
+    // to flush per se; hence, just respond back with a success message.
+    if (header->sector == 0) {
+        CHECK(header->len == 0);
+        header->type = DM_USER_RESP_SUCCESS;
+        if (!WriteDmUserPayload(0)) {
+            return false;
+        }
+        return true;
+    }
+
+    std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+    size_t remaining_size = header->len;
+    size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+    CHECK(read_size == BLOCK_SZ) << "DmuserWriteRequest: read_size: " << read_size;
+
+    CHECK(header->sector > 0);
+    chunk_t chunk = SectorToChunk(header->sector);
+    CHECK(chunk_map.find(header->sector) == chunk_map.end());
+
+    void* buffer = bufsink_.GetPayloadBuffer(read_size);
+    CHECK(buffer != nullptr);
+    header->type = DM_USER_RESP_SUCCESS;
+
+    if (!ReadDmUserPayload(buffer, read_size)) {
+        SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
+                        << "Sector: " << header->sector;
+        header->type = DM_USER_RESP_ERROR;
+    }
+
+    if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
+        SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
+                        << "Sector: " << header->sector;
+        header->type = DM_USER_RESP_ERROR;
+    } else {
+        SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk
+                        << "Sector: " << header->sector;
+    }
+
+    if (!WriteDmUserPayload(0)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::DmuserReadRequest() {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+    size_t remaining_size = header->len;
+    loff_t offset = 0;
+    sector_t sector = header->sector;
+    std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+    do {
+        size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+
+        int ret = read_size;
+        header->type = DM_USER_RESP_SUCCESS;
+        chunk_t chunk = SectorToChunk(header->sector);
+
+        // Request to sector 0 is always for kernel
+        // representation of COW header. This IO should be only
+        // once during dm-snapshot device creation. We should
+        // never see multiple IO requests. Additionally this IO
+        // will always be a single 4k.
+        if (header->sector == 0) {
+            CHECK(read_size == BLOCK_SZ) << " Sector 0 read request of size: " << read_size;
+            ConstructKernelCowHeader();
+            SNAP_LOG(DEBUG) << "Kernel header constructed";
+        } else {
+            if (!offset && (read_size == BLOCK_SZ) &&
+                chunk_map.find(header->sector) == chunk_map.end()) {
+                if (!ReadDiskExceptions(chunk, read_size)) {
+                    SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
+                                    << "Sector: " << header->sector;
+                    header->type = DM_USER_RESP_ERROR;
+                } else {
+                    SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk
+                                    << "Sector: " << header->sector;
+                }
+            } else {
+                chunk_t num_sectors_read = (offset >> SECTOR_SHIFT);
+                ret = ReadData(sector + num_sectors_read, read_size);
+                if (ret < 0) {
+                    SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
+                                    << " Sector: " << (sector + num_sectors_read)
+                                    << " size: " << read_size << " header-len: " << header->len;
+                    header->type = DM_USER_RESP_ERROR;
+                } else {
+                    SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk
+                                    << "Sector: " << header->sector;
+                }
+            }
+        }
+
+        // Daemon will not be terminated if there is any error. We will
+        // just send the error back to dm-user.
+        if (!WriteDmUserPayload(ret)) {
+            return false;
+        }
+
+        remaining_size -= ret;
+        offset += ret;
+    } while (remaining_size > 0);
+
+    return true;
+}
+
+void WorkerThread::InitializeBufsink() {
+    // Allocate the buffer which is used to communicate between
+    // daemon and dm-user. The buffer comprises of header and a fixed payload.
+    // If the dm-user requests a big IO, the IO will be broken into chunks
+    // of PAYLOAD_SIZE.
+    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
+    bufsink_.Initialize(buf_size);
+}
+
+bool WorkerThread::RunThread() {
+    InitializeBufsink();
+
+    if (!InitializeFds()) {
+        return false;
+    }
+
+    if (!InitReader()) {
+        return false;
+    }
+
+    // Start serving IO
+    while (true) {
+        if (!ProcessIORequest()) {
+            break;
+        }
+    }
+
+    CloseFds();
+    reader_->CloseCowFd();
+
+    return true;
+}
+
+bool WorkerThread::ProcessIORequest() {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+    if (!ReadDmUserHeader()) {
+        return false;
+    }
+
+    SNAP_LOG(DEBUG) << "msg->seq: " << std::hex << header->seq;
+    SNAP_LOG(DEBUG) << "msg->type: " << std::hex << header->type;
+    SNAP_LOG(DEBUG) << "msg->flags: " << std::hex << header->flags;
+    SNAP_LOG(DEBUG) << "msg->sector: " << std::hex << header->sector;
+    SNAP_LOG(DEBUG) << "msg->len: " << std::hex << header->len;
+
+    switch (header->type) {
+        case DM_USER_REQ_MAP_READ: {
+            if (!DmuserReadRequest()) {
+                return false;
+            }
+            break;
+        }
+
+        case DM_USER_REQ_MAP_WRITE: {
+            if (!DmuserWriteRequest()) {
+                return false;
+            }
+            break;
+        }
+    }
+
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index bcd5180..5887641 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -127,7 +127,7 @@
         "androidboot.serialno = \"BLAHBLAHBLAH\"\n"
         "androidboot.slot_suffix = \"_a\"\n"
         "androidboot.hardware.platform = \"sdw813\"\n"
-        "androidboot.hardware = \"foo\"\n"
+        "hardware = \"foo\"\n"
         "androidboot.revision = \"EVT1.0\"\n"
         "androidboot.bootloader = \"burp-0.1-7521\"\n"
         "androidboot.hardware.sku = \"mary\"\n"
@@ -159,7 +159,7 @@
         {"androidboot.serialno", "BLAHBLAHBLAH"},
         {"androidboot.slot_suffix", "_a"},
         {"androidboot.hardware.platform", "sdw813"},
-        {"androidboot.hardware", "foo"},
+        {"hardware", "foo"},
         {"androidboot.revision", "EVT1.0"},
         {"androidboot.bootloader", "burp-0.1-7521"},
         {"androidboot.hardware.sku", "mary"},
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index b2ab550..5cde167 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -337,12 +337,21 @@
         SwitchRoot("/first_stage_ramdisk");
     }
 
+    std::string force_debuggable("/force_debuggable");
+    std::string adb_debug_prop("/adb_debug.prop");
+    std::string userdebug_sepolicy("/userdebug_plat_sepolicy.cil");
+    if (IsRecoveryMode()) {
+        // Update these file paths since we didn't switch root
+        force_debuggable.insert(0, "/first_stage_ramdisk");
+        adb_debug_prop.insert(0, "/first_stage_ramdisk");
+        userdebug_sepolicy.insert(0, "/first_stage_ramdisk");
+    }
     // If this file 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) {
+    if (access(force_debuggable.c_str(), F_OK) == 0) {
         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)) {
+        if (!fs::copy_file(adb_debug_prop, kDebugRamdiskProp, ec) ||
+            !fs::copy_file(userdebug_sepolicy, kDebugRamdiskSEPolicy, ec)) {
             LOG(ERROR) << "Failed to setup debug ramdisk";
         } else {
             // setenv for second-stage init to read above kDebugRamdisk* files.
diff --git a/init/init.cpp b/init/init.cpp
index 70d6809..7264b22 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -518,11 +518,9 @@
     if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
         return;
     }
-    ImportKernelCmdline([](const std::string& key, const std::string& value) {
-        if (key == "androidboot.verifiedbootstate") {
-            SetProperty("ro.boot.flash.locked", value == "orange" ? "0" : "1");
-        }
-    });
+    SetProperty(
+            "ro.boot.flash.locked",
+            android::base::GetProperty("ro.boot.verifiedbootstate", "") == "orange" ? "0" : "1");
 }
 
 static Result<void> property_enable_triggers_action(const BuiltinArguments& args) {
diff --git a/init/property_service.cpp b/init/property_service.cpp
index b722702..382f430 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -44,6 +44,7 @@
 #include <mutex>
 #include <optional>
 #include <queue>
+#include <string_view>
 #include <thread>
 #include <vector>
 
@@ -1162,28 +1163,29 @@
     }
 }
 
+constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv;
+
 static void ProcessKernelCmdline() {
-    bool for_emulator = false;
     ImportKernelCmdline([&](const std::string& key, const std::string& value) {
-        if (key == "qemu") {
-            for_emulator = true;
-        } else if (StartsWith(key, "androidboot.")) {
-            InitPropertySet("ro.boot." + key.substr(12), value);
+        if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
+            InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
+        } else if (StartsWith(key, "qemu."sv)) {
+            InitPropertySet("ro.kernel." + key, value);
+        } else if (key == "qemu") {
+            InitPropertySet("ro.kernel." + key, value);  // emulator specific, deprecated
+            InitPropertySet("ro.boot." + key, value);
         }
     });
-
-    if (for_emulator) {
-        ImportKernelCmdline([&](const std::string& key, const std::string& value) {
-            // In the emulator, export any kernel option with the "ro.kernel." prefix.
-            InitPropertySet("ro.kernel." + key, value);
-        });
-    }
 }
 
 static void ProcessBootconfig() {
     ImportBootconfig([&](const std::string& key, const std::string& value) {
-        if (StartsWith(key, "androidboot.")) {
-            InitPropertySet("ro.boot." + key.substr(12), value);
+        if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
+            InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
+        } else if (key == "hardware") {
+            // "hardware" in bootconfig replaces "androidboot.hardware" kernel
+            // cmdline parameter
+            InitPropertySet("ro.boot." + key, value);
         }
     });
 }
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 0336936..62c4586 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -63,6 +63,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/result.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <fs_avb/fs_avb.h>
@@ -92,7 +93,7 @@
 
 enum EnforcingStatus { SELINUX_PERMISSIVE, SELINUX_ENFORCING };
 
-EnforcingStatus StatusFromCmdline() {
+EnforcingStatus StatusFromProperty() {
     EnforcingStatus status = SELINUX_ENFORCING;
 
     ImportKernelCmdline([&](const std::string& key, const std::string& value) {
@@ -101,12 +102,20 @@
         }
     });
 
+    if (status == SELINUX_ENFORCING) {
+        ImportBootconfig([&](const std::string& key, const std::string& value) {
+            if (key == "androidboot.selinux" && value == "permissive") {
+                status = SELINUX_PERMISSIVE;
+            }
+        });
+    }
+
     return status;
 }
 
 bool IsEnforcing() {
     if (ALLOW_PERMISSIVE_SELINUX) {
-        return StatusFromCmdline() == SELINUX_ENFORCING;
+        return StatusFromProperty() == SELINUX_ENFORCING;
     }
     return true;
 }
@@ -214,8 +223,8 @@
     return true;
 }
 
-bool FindPrecompiledSplitPolicy(std::string* file) {
-    file->clear();
+Result<std::string> FindPrecompiledSplitPolicy() {
+    std::string precompiled_sepolicy;
     // If there is an odm partition, precompiled_sepolicy will be in
     // odm/etc/selinux. Otherwise it will be in vendor/etc/selinux.
     static constexpr const char vendor_precompiled_sepolicy[] =
@@ -223,62 +232,49 @@
     static constexpr const char odm_precompiled_sepolicy[] =
         "/odm/etc/selinux/precompiled_sepolicy";
     if (access(odm_precompiled_sepolicy, R_OK) == 0) {
-        *file = odm_precompiled_sepolicy;
+        precompiled_sepolicy = odm_precompiled_sepolicy;
     } else if (access(vendor_precompiled_sepolicy, R_OK) == 0) {
-        *file = vendor_precompiled_sepolicy;
+        precompiled_sepolicy = vendor_precompiled_sepolicy;
     } else {
-        PLOG(INFO) << "No precompiled sepolicy";
-        return false;
-    }
-    std::string actual_plat_id;
-    if (!ReadFirstLine("/system/etc/selinux/plat_sepolicy_and_mapping.sha256", &actual_plat_id)) {
-        PLOG(INFO) << "Failed to read "
-                      "/system/etc/selinux/plat_sepolicy_and_mapping.sha256";
-        return false;
-    }
-    std::string actual_system_ext_id;
-    if (!ReadFirstLine("/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
-                       &actual_system_ext_id)) {
-        PLOG(INFO) << "Failed to read "
-                      "/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256";
-        return false;
-    }
-    std::string actual_product_id;
-    if (!ReadFirstLine("/product/etc/selinux/product_sepolicy_and_mapping.sha256",
-                       &actual_product_id)) {
-        PLOG(INFO) << "Failed to read "
-                      "/product/etc/selinux/product_sepolicy_and_mapping.sha256";
-        return false;
+        return ErrnoError() << "No precompiled sepolicy at " << vendor_precompiled_sepolicy;
     }
 
-    std::string precompiled_plat_id;
-    std::string precompiled_plat_sha256 = *file + ".plat_sepolicy_and_mapping.sha256";
-    if (!ReadFirstLine(precompiled_plat_sha256.c_str(), &precompiled_plat_id)) {
-        PLOG(INFO) << "Failed to read " << precompiled_plat_sha256;
-        file->clear();
-        return false;
+    // Use precompiled sepolicy only when all corresponding hashes are equal.
+    // plat_sepolicy is always checked, while system_ext and product are checked only when they
+    // exist.
+    std::vector<std::pair<std::string, std::string>> sepolicy_hashes{
+            {"/system/etc/selinux/plat_sepolicy_and_mapping.sha256",
+             precompiled_sepolicy + ".plat_sepolicy_and_mapping.sha256"},
+    };
+
+    if (access("/system_ext/etc/selinux/system_ext_sepolicy.cil", F_OK) == 0) {
+        sepolicy_hashes.emplace_back(
+                "/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
+                precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256");
     }
-    std::string precompiled_system_ext_id;
-    std::string precompiled_system_ext_sha256 = *file + ".system_ext_sepolicy_and_mapping.sha256";
-    if (!ReadFirstLine(precompiled_system_ext_sha256.c_str(), &precompiled_system_ext_id)) {
-        PLOG(INFO) << "Failed to read " << precompiled_system_ext_sha256;
-        file->clear();
-        return false;
+
+    if (access("/product/etc/selinux/product_sepolicy.cil", F_OK) == 0) {
+        sepolicy_hashes.emplace_back("/product/etc/selinux/product_sepolicy_and_mapping.sha256",
+                                     precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256");
     }
-    std::string precompiled_product_id;
-    std::string precompiled_product_sha256 = *file + ".product_sepolicy_and_mapping.sha256";
-    if (!ReadFirstLine(precompiled_product_sha256.c_str(), &precompiled_product_id)) {
-        PLOG(INFO) << "Failed to read " << precompiled_product_sha256;
-        file->clear();
-        return false;
+
+    for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) {
+        std::string actual_id;
+        if (!ReadFirstLine(actual_id_path.c_str(), &actual_id)) {
+            return ErrnoError() << "Failed to read " << actual_id_path;
+        }
+
+        std::string precompiled_id;
+        if (!ReadFirstLine(precompiled_id_path.c_str(), &precompiled_id)) {
+            return ErrnoError() << "Failed to read " << precompiled_id_path;
+        }
+
+        if (actual_id.empty() || actual_id != precompiled_id) {
+            return Error() << actual_id_path << " and " << precompiled_id_path << " differ";
+        }
     }
-    if (actual_plat_id.empty() || actual_plat_id != precompiled_plat_id ||
-        actual_system_ext_id.empty() || actual_system_ext_id != precompiled_system_ext_id ||
-        actual_product_id.empty() || actual_product_id != precompiled_product_id) {
-        file->clear();
-        return false;
-    }
-    return true;
+
+    return precompiled_sepolicy;
 }
 
 bool GetVendorMappingVersion(std::string* plat_vers) {
@@ -325,15 +321,18 @@
 
     // Load precompiled policy from vendor image, if a matching policy is found there. The policy
     // must match the platform policy on the system image.
-    std::string precompiled_sepolicy_file;
     // use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil.
     // Thus it cannot use the precompiled policy from vendor image.
-    if (!use_userdebug_policy && FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) {
-        unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
-        if (fd != -1) {
-            policy_file->fd = std::move(fd);
-            policy_file->path = std::move(precompiled_sepolicy_file);
-            return true;
+    if (!use_userdebug_policy) {
+        if (auto res = FindPrecompiledSplitPolicy(); res.ok()) {
+            unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+            if (fd != -1) {
+                policy_file->fd = std::move(fd);
+                policy_file->path = std::move(*res);
+                return true;
+            }
+        } else {
+            LOG(INFO) << res.error();
         }
     }
     // No suitable precompiled policy could be loaded
diff --git a/init/util.cpp b/init/util.cpp
index eab99d4..a40d104 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -376,6 +376,15 @@
             android_dt_dir = value;
         }
     });
+    // ..Or bootconfig
+    if (android_dt_dir == kDefaultAndroidDtDir) {
+        ImportBootconfig([&](const std::string& key, const std::string& value) {
+            if (key == "androidboot.android_dt_dir") {
+                android_dt_dir = value;
+            }
+        });
+    }
+
     LOG(INFO) << "Using Android DT directory " << android_dt_dir;
     return android_dt_dir;
 }
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index b38818a..0d9f2c7 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -170,14 +170,10 @@
         linux_bionic: {
             enabled: true,
         },
-        linux: {
-            srcs: [
-                "fs_config.cpp",
-            ],
-        },
         not_windows: {
             srcs: libcutils_nonwindows_sources + [
                 "ashmem-host.cpp",
+                "fs_config.cpp",
                 "trace-host.cpp",
             ],
         },
@@ -197,6 +193,7 @@
             srcs: libcutils_nonwindows_sources + [
                 "android_reboot.cpp",
                 "ashmem-dev.cpp",
+                "fs_config.cpp",
                 "klog.cpp",
                 "partition_utils.cpp",
                 "qtaguid.cpp",
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 54eeeac..d69c038 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -35,6 +35,7 @@
 #include <string>
 
 #include <android-base/strings.h>
+#include <cutils/fs.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 
diff --git a/libkeyutils/Android.bp b/libkeyutils/Android.bp
index 9848cd8..86f68fb 100644
--- a/libkeyutils/Android.bp
+++ b/libkeyutils/Android.bp
@@ -2,25 +2,10 @@
     default_applicable_licenses: ["system_core_libkeyutils_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_libkeyutils_license",
     visibility: [":__subpackages__"],
     license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
         "SPDX-license-identifier-BSD",
     ],
     // large-scale-change unable to identify any license_text files
diff --git a/libprocessgroup/cgrouprc/include/android/cgrouprc.h b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
index 9a79954..100d60e 100644
--- a/libprocessgroup/cgrouprc/include/android/cgrouprc.h
+++ b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
@@ -68,6 +68,7 @@
  */
 #define CGROUPRC_CONTROLLER_FLAG_MOUNTED 0x1
 #define CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION 0x2
+#define CGROUPRC_CONTROLLER_FLAG_OPTIONAL 0x4
 
 /**
  * Returns the flags bitmask of the given controller.
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 962d2ba..0634220 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -26,7 +26,8 @@
       "Path": "/dev/memcg",
       "Mode": "0700",
       "UID": "root",
-      "GID": "system"
+      "GID": "system",
+      "Optional": true
     }
   ],
   "Cgroups2": {
diff --git a/libprocessgroup/profiles/cgroups.proto b/libprocessgroup/profiles/cgroups.proto
index 13adcae..f2de345 100644
--- a/libprocessgroup/profiles/cgroups.proto
+++ b/libprocessgroup/profiles/cgroups.proto
@@ -24,7 +24,7 @@
     Cgroups2 cgroups2 = 2 [json_name = "Cgroups2"];
 }
 
-// Next: 7
+// Next: 8
 message Cgroup {
     string controller = 1 [json_name = "Controller"];
     string path = 2 [json_name = "Path"];
@@ -35,6 +35,7 @@
 // when a boolean is specified as false, so leave unspecified in that case
 // https://developers.google.com/protocol-buffers/docs/proto3#default
     bool needs_activation = 6 [json_name = "NeedsActivation"];
+    bool is_optional = 7 [json_name = "Optional"];
 }
 
 // Next: 6
diff --git a/libprocessgroup/profiles/cgroups_30.json b/libprocessgroup/profiles/cgroups_30.json
index 17d4929..80a074b 100644
--- a/libprocessgroup/profiles/cgroups_30.json
+++ b/libprocessgroup/profiles/cgroups_30.json
@@ -5,7 +5,8 @@
       "Path": "/dev/stune",
       "Mode": "0755",
       "UID": "system",
-      "GID": "system"
+      "GID": "system",
+      "Optional": true
     }
   ]
 }
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index aa41acb..3121d24 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -161,6 +161,10 @@
         controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION;
     }
 
+    if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) {
+        controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+    }
+
     CgroupDescriptor descriptor(
             cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8),
             cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags);
@@ -267,8 +271,6 @@
                                        descriptor.gid())) {
                 LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
                 result = -1;
-            } else {
-                LOG(ERROR) << "restored ownership for " << controller->name() << " cgroup";
             }
         } else {
             if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
@@ -310,8 +312,15 @@
     }
 
     if (result < 0) {
-        PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
-        return false;
+        bool optional = controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+
+        if (optional && errno == EINVAL) {
+            // Optional controllers are allowed to fail to mount if kernel does not support them
+            LOG(INFO) << "Optional " << controller->name() << " cgroup controller is not mounted";
+        } else {
+            PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
+            return false;
+        }
     }
 
     return true;
diff --git a/libstats/OWNERS b/libstats/OWNERS
index 7855774..d391679 100644
--- a/libstats/OWNERS
+++ b/libstats/OWNERS
@@ -1,6 +1,7 @@
-joeo@google.com
+jeffreyhuang@google.com
+jtnguyen@google.com
 muhammadq@google.com
-ruchirr@google.com
+sharaienko@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yaochen@google.com
diff --git a/libstats/pull_lazy/Android.bp b/libstats/pull_lazy/Android.bp
new file mode 100644
index 0000000..65dce26
--- /dev/null
+++ b/libstats/pull_lazy/Android.bp
@@ -0,0 +1,48 @@
+// Lazy loading version of libstatspull that can be used by code
+// that is running before the statsd APEX is mounted and
+// libstatspull.so is available.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libstatspull_lazy",
+    header_libs: [
+        "libstatspull_headers",
+        "libstatssocket_headers",
+    ],
+    export_header_lib_headers: [
+        "libstatspull_headers",
+    ],
+    apex_available: ["//apex_available:platform"],
+    srcs: ["libstatspull_lazy.cpp"],
+}
+
+cc_test {
+    name: "libstatspull_lazy_test",
+    srcs: [
+        "tests/libstatspull_lazy_test.cpp",
+    ],
+    static_libs: [
+        "libstatspull_lazy",
+        "libstatssocket_lazy",
+    ],
+    shared_libs: ["liblog"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    test_suites: ["device-tests", "mts-statsd"],
+    test_config: "libstatspull_lazy_test.xml",
+    // TODO(b/153588990): Remove when the build system properly separates.
+    // 32bit and 64bit architectures.
+    compile_multilib: "both",
+    multilib: {
+        lib64: {
+            suffix: "64",
+        },
+        lib32: {
+            suffix: "32",
+        },
+    },
+}
diff --git a/libstats/pull_lazy/TEST_MAPPING b/libstats/pull_lazy/TEST_MAPPING
new file mode 100644
index 0000000..89b8c2a
--- /dev/null
+++ b/libstats/pull_lazy/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "libstatspull_lazy_test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/libstats/pull_lazy/libstatspull_lazy.cpp b/libstats/pull_lazy/libstatspull_lazy.cpp
new file mode 100644
index 0000000..b11fcee
--- /dev/null
+++ b/libstats/pull_lazy/libstatspull_lazy.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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 "libstatspull_lazy.h"
+
+#include <mutex>
+
+#include <dlfcn.h>
+#include <stdatomic.h>
+
+#include "log/log.h"
+
+#include "stats_pull_atom_callback.h"
+
+// This file provides a lazy interface to libstatspull.so to address early boot dependencies.
+// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and
+// libstatspull.so is in the statsd APEX.
+
+// Method pointers to libstatspull methods are held in an array which simplifies checking
+// all pointers are initialized.
+enum MethodIndex {
+    // PullAtomMetadata APIs in stats_pull_atom_callback.h.
+    k_AStatsManager_PullAtomMetadata_obtain,
+    k_AStatsManager_PullAtomMetadata_release,
+    k_AStatsManager_PullAtomMetadata_setCoolDownMillis,
+    k_AStatsManager_PullAtomMetadata_getCoolDownMillis,
+    k_AStatsManager_PullAtomMetadata_setTimeoutMillis,
+    k_AStatsManager_PullAtomMetadata_getTimeoutMillis,
+    k_AStatsManager_PullAtomMetadata_setAdditiveFields,
+    k_AStatsManager_PullAtomMetadata_getNumAdditiveFields,
+    k_AStatsManager_PullAtomMetadata_getAdditiveFields,
+
+    // AStatsEventList APIs in stats_pull_atom_callback.h
+    k_AStatsEventList_addStatsEvent,
+
+    // PullAtomCallback APIs in stats_pull_atom_callback.h
+    k_AStatsManager_setPullAtomCallback,
+    k_AStatsManager_clearPullAtomCallback,
+
+    // Marker for count of methods
+    k_MethodCount
+};
+
+// Table of methods pointers in libstatspull APIs.
+static void* g_Methods[k_MethodCount];
+
+//
+// Libstatspull lazy loading.
+//
+
+static atomic_bool gPreventLibstatspullLoading = false;  // Allows tests to block loading.
+
+void PreventLibstatspullLazyLoadingForTests() {
+    gPreventLibstatspullLoading.store(true);
+}
+
+static void* LoadLibstatspull(int dlopen_flags) {
+    if (gPreventLibstatspullLoading.load()) {
+        return nullptr;
+    }
+    return dlopen("libstatspull.so", dlopen_flags);
+}
+
+//
+// Initialization and symbol binding.
+
+static void BindSymbol(void* handle, const char* name, enum MethodIndex index) {
+    void* symbol = dlsym(handle, name);
+    LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatspull.so: %s",
+                        name, dlerror());
+    g_Methods[index] = symbol;
+}
+
+static void InitializeOnce() {
+    void* handle = LoadLibstatspull(RTLD_NOW);
+    LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatspull.so: %s", dlerror());
+
+#undef BIND_SYMBOL
+#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name);
+    // PullAtomMetadata APIs in stats_pull_atom_callback.h.
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_obtain);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_release);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_setCoolDownMillis);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_getCoolDownMillis);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_setTimeoutMillis);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_getTimeoutMillis);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_setAdditiveFields);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_getNumAdditiveFields);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_getAdditiveFields);
+
+    // AStatsEventList APIs in stats_pull_atom_callback.h
+    BIND_SYMBOL(AStatsEventList_addStatsEvent);
+
+    // PullAtomCallback APIs in stats_pull_atom_callback.h
+    BIND_SYMBOL(AStatsManager_setPullAtomCallback);
+    BIND_SYMBOL(AStatsManager_clearPullAtomCallback);
+
+#undef BIND_SYMBOL
+
+    // Check every symbol is bound.
+    for (int i = 0; i < k_MethodCount; ++i) {
+        LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr,
+                            "Uninitialized method in libstatspull_lazy at index: %d", i);
+    }
+}
+
+static void EnsureInitialized() {
+    static std::once_flag initialize_flag;
+    std::call_once(initialize_flag, InitializeOnce);
+}
+
+#define INVOKE_METHOD(name, args...)                            \
+    do {                                                        \
+        EnsureInitialized();                                    \
+        void* method = g_Methods[k_##name];                     \
+        return reinterpret_cast<decltype(&name)>(method)(args); \
+    } while (0)
+
+//
+// Forwarding for methods in stats_pull_atom_callback.h.
+//
+
+AStatsManager_PullAtomMetadata* AStatsManager_PullAtomMetadata_obtain() {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_obtain);
+}
+
+void AStatsManager_PullAtomMetadata_release(AStatsManager_PullAtomMetadata* metadata) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_release, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setCoolDownMillis(AStatsManager_PullAtomMetadata* metadata,
+                                                      int64_t cool_down_millis) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_setCoolDownMillis, metadata, cool_down_millis);
+}
+
+int64_t AStatsManager_PullAtomMetadata_getCoolDownMillis(AStatsManager_PullAtomMetadata* metadata) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_getCoolDownMillis, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setTimeoutMillis(AStatsManager_PullAtomMetadata* metadata,
+                                                     int64_t timeout_millis) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_setTimeoutMillis, metadata, timeout_millis);
+}
+
+int64_t AStatsManager_PullAtomMetadata_getTimeoutMillis(AStatsManager_PullAtomMetadata* metadata) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_getTimeoutMillis, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
+                                                      int32_t* additive_fields,
+                                                      int32_t num_fields) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_setAdditiveFields, metadata, additive_fields,
+                  num_fields);
+}
+
+int32_t AStatsManager_PullAtomMetadata_getNumAdditiveFields(
+        AStatsManager_PullAtomMetadata* metadata) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_getNumAdditiveFields, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_getAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
+                                                      int32_t* fields) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_getAdditiveFields, metadata, fields);
+}
+
+AStatsEvent* AStatsEventList_addStatsEvent(AStatsEventList* pull_data) {
+    INVOKE_METHOD(AStatsEventList_addStatsEvent, pull_data);
+}
+
+void AStatsManager_setPullAtomCallback(int32_t atom_tag, AStatsManager_PullAtomMetadata* metadata,
+                                       AStatsManager_PullAtomCallback callback, void* cookie) {
+    INVOKE_METHOD(AStatsManager_setPullAtomCallback, atom_tag, metadata, callback, cookie);
+}
+
+void AStatsManager_clearPullAtomCallback(int32_t atom_tag) {
+    INVOKE_METHOD(AStatsManager_clearPullAtomCallback, atom_tag);
+}
diff --git a/libstats/pull_lazy/libstatspull_lazy.h b/libstats/pull_lazy/libstatspull_lazy.h
new file mode 100644
index 0000000..2edddc7
--- /dev/null
+++ b/libstats/pull_lazy/libstatspull_lazy.h
@@ -0,0 +1,19 @@
+/*
+ * 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
+
+extern "C" void PreventLibstatspullLazyLoadingForTests();
\ No newline at end of file
diff --git a/libstats/pull_lazy/libstatspull_lazy_test.xml b/libstats/pull_lazy/libstatspull_lazy_test.xml
new file mode 100644
index 0000000..1b619af
--- /dev/null
+++ b/libstats/pull_lazy/libstatspull_lazy_test.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs libstatspull_lazy_test.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+    <option name="test-suite-tag" value="mts" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="libstatspull_lazy_test->/data/local/tmp/libstatspull_lazy_test" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="libstatspull_lazy_test" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+    </object>
+</configuration>
\ No newline at end of file
diff --git a/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp b/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp
new file mode 100644
index 0000000..41f82d0
--- /dev/null
+++ b/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "../libstatspull_lazy.h"
+
+#include <gtest/gtest.h>
+
+#include "stats_pull_atom_callback.h"
+//#include "stats_event.h"
+
+// The tests here are just for the case when libstatspull.so cannot be loaded by
+// libstatspull_lazy.
+class LibstatspullLazyTest : public ::testing::Test {
+  protected:
+    virtual void SetUp() {
+        ::testing::Test::SetUp();
+        PreventLibstatspullLazyLoadingForTests();
+    }
+};
+
+static const char* kLoadFailed = "Failed to load libstatspull.so";
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForPullAtomMetadata) {
+    AStatsManager_PullAtomMetadata* metadata = NULL;
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_obtain(), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_release(metadata), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_setCoolDownMillis(metadata, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_setTimeoutMillis(metadata, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_setAdditiveFields(metadata, NULL, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_getAdditiveFields(metadata, NULL), kLoadFailed);
+}
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForAStatsEventList) {
+    AStatsEventList* event_list = NULL;
+    EXPECT_DEATH(AStatsEventList_addStatsEvent(event_list), kLoadFailed);
+}
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForPullAtomCallback) {
+    AStatsManager_PullAtomCallback callback = NULL;
+    EXPECT_DEATH(AStatsManager_setPullAtomCallback(0, NULL, callback, NULL), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_clearPullAtomCallback(0), kLoadFailed);
+}
\ No newline at end of file
diff --git a/libstats/socket_lazy/Android.bp b/libstats/socket_lazy/Android.bp
new file mode 100644
index 0000000..b2cd7b2
--- /dev/null
+++ b/libstats/socket_lazy/Android.bp
@@ -0,0 +1,44 @@
+// Lazy loading version of libstatssocket that can be used by code
+// that is running before the statsd APEX is mounted and
+// libstatssocket.so is available.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libstatssocket_lazy",
+    header_libs: [
+        "libstatssocket_headers",
+    ],
+    export_header_lib_headers: [
+        "libstatssocket_headers",
+    ],
+    apex_available: ["//apex_available:platform"],
+    srcs: ["libstatssocket_lazy.cpp"],
+}
+
+cc_test {
+    name: "libstatssocket_lazy_test",
+    srcs: [
+        "tests/libstatssocket_lazy_test.cpp",
+    ],
+    static_libs: ["libstatssocket_lazy"],
+    shared_libs: ["liblog"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    test_suites: ["device-tests", "mts-statsd"],
+    test_config: "libstatssocket_lazy_test.xml",
+    // TODO(b/153588990): Remove when the build system properly separates.
+    // 32bit and 64bit architectures.
+    compile_multilib: "both",
+    multilib: {
+        lib64: {
+            suffix: "64",
+        },
+        lib32: {
+            suffix: "32",
+        },
+    },
+}
diff --git a/libstats/socket_lazy/TEST_MAPPING b/libstats/socket_lazy/TEST_MAPPING
new file mode 100644
index 0000000..13afc00
--- /dev/null
+++ b/libstats/socket_lazy/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "libstatssocket_lazy_test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/libstats/socket_lazy/libstatssocket_lazy.cpp b/libstats/socket_lazy/libstatssocket_lazy.cpp
new file mode 100644
index 0000000..dd93eeb
--- /dev/null
+++ b/libstats/socket_lazy/libstatssocket_lazy.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 "libstatssocket_lazy.h"
+
+#include <mutex>
+
+#include <dlfcn.h>
+#include <stdatomic.h>
+
+#include "log/log.h"
+
+#include "stats_event.h"
+#include "stats_socket.h"
+
+// This file provides a lazy interface to libstatssocket.so to address early boot dependencies.
+// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and
+// libstatssocket.so is in the statsd APEX.
+
+// Method pointers to libstatssocket methods are held in an array which simplifies checking
+// all pointers are initialized.
+enum MethodIndex {
+    // Stats Event APIs in stats_event.h.
+    k_AStatsEvent_obtain,
+    k_AStatsEvent_build,
+    k_AStatsEvent_write,
+    k_AStatsEvent_release,
+    k_AStatsEvent_setAtomId,
+    k_AStatsEvent_writeInt32,
+    k_AStatsEvent_writeInt64,
+    k_AStatsEvent_writeFloat,
+    k_AStatsEvent_writeBool,
+    k_AStatsEvent_writeByteArray,
+    k_AStatsEvent_writeString,
+    k_AStatsEvent_writeAttributionChain,
+    k_AStatsEvent_addBoolAnnotation,
+    k_AStatsEvent_addInt32Annotation,
+
+    // Stats Socket APIs in stats_socket.h.
+    k_AStatsSocket_close,
+
+    // Marker for count of methods
+    k_MethodCount
+};
+
+// Table of methods pointers in libstatssocket APIs.
+static void* g_Methods[k_MethodCount];
+
+//
+// Libstatssocket lazy loading.
+//
+
+static atomic_bool gPreventLibstatssocketLoading = false;  // Allows tests to block loading.
+
+void PreventLibstatssocketLazyLoadingForTests() {
+    gPreventLibstatssocketLoading.store(true);
+}
+
+static void* LoadLibstatssocket(int dlopen_flags) {
+    if (gPreventLibstatssocketLoading.load()) {
+        return nullptr;
+    }
+    return dlopen("libstatssocket.so", dlopen_flags);
+}
+
+//
+// Initialization and symbol binding.
+
+static void BindSymbol(void* handle, const char* name, enum MethodIndex index) {
+    void* symbol = dlsym(handle, name);
+    LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatssocket.so: %s",
+                        name, dlerror());
+    g_Methods[index] = symbol;
+}
+
+static void InitializeOnce() {
+    void* handle = LoadLibstatssocket(RTLD_NOW);
+    LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatssocket.so: %s", dlerror());
+
+#undef BIND_SYMBOL
+#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name);
+    // Methods in stats_event.h.
+    BIND_SYMBOL(AStatsEvent_obtain);
+    BIND_SYMBOL(AStatsEvent_build);
+    BIND_SYMBOL(AStatsEvent_write);
+    BIND_SYMBOL(AStatsEvent_release);
+    BIND_SYMBOL(AStatsEvent_setAtomId);
+    BIND_SYMBOL(AStatsEvent_writeInt32);
+    BIND_SYMBOL(AStatsEvent_writeInt64);
+    BIND_SYMBOL(AStatsEvent_writeFloat);
+    BIND_SYMBOL(AStatsEvent_writeBool);
+    BIND_SYMBOL(AStatsEvent_writeByteArray);
+    BIND_SYMBOL(AStatsEvent_writeString);
+    BIND_SYMBOL(AStatsEvent_writeAttributionChain);
+    BIND_SYMBOL(AStatsEvent_addBoolAnnotation);
+    BIND_SYMBOL(AStatsEvent_addInt32Annotation);
+
+    // Methods in stats_socket.h.
+    BIND_SYMBOL(AStatsSocket_close);
+#undef BIND_SYMBOL
+
+    // Check every symbol is bound.
+    for (int i = 0; i < k_MethodCount; ++i) {
+        LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr,
+                            "Uninitialized method in libstatssocket_lazy at index: %d", i);
+    }
+}
+
+static void EnsureInitialized() {
+    static std::once_flag initialize_flag;
+    std::call_once(initialize_flag, InitializeOnce);
+}
+
+#define INVOKE_METHOD(name, args...)                            \
+    do {                                                        \
+        EnsureInitialized();                                    \
+        void* method = g_Methods[k_##name];                     \
+        return reinterpret_cast<decltype(&name)>(method)(args); \
+    } while (0)
+
+//
+// Forwarding for methods in stats_event.h.
+//
+
+AStatsEvent* AStatsEvent_obtain() {
+    INVOKE_METHOD(AStatsEvent_obtain);
+}
+
+void AStatsEvent_build(AStatsEvent* event) {
+    INVOKE_METHOD(AStatsEvent_build, event);
+}
+
+int AStatsEvent_write(AStatsEvent* event) {
+    INVOKE_METHOD(AStatsEvent_write, event);
+}
+
+void AStatsEvent_release(AStatsEvent* event) {
+    INVOKE_METHOD(AStatsEvent_release, event);
+}
+
+void AStatsEvent_setAtomId(AStatsEvent* event, uint32_t atomId) {
+    INVOKE_METHOD(AStatsEvent_setAtomId, event, atomId);
+}
+
+void AStatsEvent_writeInt32(AStatsEvent* event, int32_t value) {
+    INVOKE_METHOD(AStatsEvent_writeInt32, event, value);
+}
+
+void AStatsEvent_writeInt64(AStatsEvent* event, int64_t value) {
+    INVOKE_METHOD(AStatsEvent_writeInt64, event, value);
+}
+
+void AStatsEvent_writeFloat(AStatsEvent* event, float value) {
+    INVOKE_METHOD(AStatsEvent_writeFloat, event, value);
+}
+
+void AStatsEvent_writeBool(AStatsEvent* event, bool value) {
+    INVOKE_METHOD(AStatsEvent_writeBool, event, value);
+}
+
+void AStatsEvent_writeByteArray(AStatsEvent* event, const uint8_t* buf, size_t numBytes) {
+    INVOKE_METHOD(AStatsEvent_writeByteArray, event, buf, numBytes);
+}
+
+void AStatsEvent_writeString(AStatsEvent* event, const char* value) {
+    INVOKE_METHOD(AStatsEvent_writeString, event, value);
+}
+
+void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids,
+                                       const char* const* tags, uint8_t numNodes) {
+    INVOKE_METHOD(AStatsEvent_writeAttributionChain, event, uids, tags, numNodes);
+}
+
+void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value) {
+    INVOKE_METHOD(AStatsEvent_addBoolAnnotation, event, annotationId, value);
+}
+
+void AStatsEvent_addInt32Annotation(AStatsEvent* event, uint8_t annotationId, int32_t value) {
+    INVOKE_METHOD(AStatsEvent_addInt32Annotation, event, annotationId, value);
+}
+
+//
+// Forwarding for methods in stats_socket.h.
+//
+
+void AStatsSocket_close() {
+    INVOKE_METHOD(AStatsSocket_close);
+}
\ No newline at end of file
diff --git a/libstats/socket_lazy/libstatssocket_lazy.h b/libstats/socket_lazy/libstatssocket_lazy.h
new file mode 100644
index 0000000..3ff87cb
--- /dev/null
+++ b/libstats/socket_lazy/libstatssocket_lazy.h
@@ -0,0 +1,19 @@
+/*
+ * 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
+
+extern "C" void PreventLibstatssocketLazyLoadingForTests();
\ No newline at end of file
diff --git a/libstats/socket_lazy/libstatssocket_lazy_test.xml b/libstats/socket_lazy/libstatssocket_lazy_test.xml
new file mode 100644
index 0000000..ca6339b
--- /dev/null
+++ b/libstats/socket_lazy/libstatssocket_lazy_test.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs libstatssocket_lazy_test.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+    <option name="test-suite-tag" value="mts" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="libstatssocket_lazy_test->/data/local/tmp/libstatssocket_lazy_test" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="libstatssocket_lazy_test" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+    </object>
+</configuration>
\ No newline at end of file
diff --git a/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
new file mode 100644
index 0000000..fe13598
--- /dev/null
+++ b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "../libstatssocket_lazy.h"
+
+#include <gtest/gtest.h>
+
+#include "stats_event.h"
+#include "stats_socket.h"
+
+// The tests here are just for the case when libstatssocket.so cannot be loaded by
+// libstatssocket_lazy.
+class LibstatssocketLazyTest : public ::testing::Test {
+  protected:
+    virtual void SetUp() {
+        ::testing::Test::SetUp();
+        PreventLibstatssocketLazyLoadingForTests();
+    }
+};
+
+static const char* kLoadFailed = "Failed to load libstatssocket.so";
+
+TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsEvent) {
+    AStatsEvent* event = NULL;
+    EXPECT_DEATH(AStatsEvent_obtain(), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_build(event), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_write(event), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_release(event), kLoadFailed);
+
+    EXPECT_DEATH(AStatsEvent_setAtomId(event, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeInt32(event, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeInt64(event, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeFloat(event, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeBool(event, false), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeByteArray(event, NULL, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeString(event, NULL), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeAttributionChain(event, NULL, NULL, 0), kLoadFailed);
+
+    EXPECT_DEATH(AStatsEvent_addBoolAnnotation(event, 0, false), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_addInt32Annotation(event, 0, 0), kLoadFailed);
+}
+
+TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsSocket) {
+    EXPECT_DEATH(AStatsSocket_close(), kLoadFailed);
+}
\ No newline at end of file
diff --git a/llkd/README.md b/llkd/README.md
index 6f92f14..9bcf806 100644
--- a/llkd/README.md
+++ b/llkd/README.md
@@ -207,7 +207,7 @@
 
 The `llkd` does not monitor the specified subset of processes for live lock stack
 signatures. Default is process names
-`init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd`. Prevents the sepolicy
+`init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd,logd`. Prevents the sepolicy
 violation associated with processes that block `ptrace` (as these can't be
 checked). **Active only on userdebug and eng builds**. For details on build
 types, refer to [Building Android](/setup/build/building#choose-a-target).
diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h
index 4b20a56..0822a3e 100644
--- a/llkd/include/llkd.h
+++ b/llkd/include/llkd.h
@@ -60,7 +60,7 @@
 #define LLK_IGNORELIST_UID_PROPERTY     "ro.llk.ignorelist.uid"
 #define LLK_IGNORELIST_UID_DEFAULT      ""
 #define LLK_IGNORELIST_STACK_PROPERTY   "ro.llk.ignorelist.process.stack"
-#define LLK_IGNORELIST_STACK_DEFAULT    "init,lmkd.llkd,llkd,keystore,ueventd,apexd"
+#define LLK_IGNORELIST_STACK_DEFAULT    "init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd"
 /* clang-format on */
 
 __END_DECLS
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index 9f3e218..c4c58ee 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -115,8 +115,8 @@
 // list of uids, and uid names, to skip, default nothing
 std::unordered_set<std::string> llkIgnorelistUid;
 #ifdef __PTRACE_ENABLED__
-// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore" or
-// "logd" (if not userdebug).
+// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore",
+// "keystore2", or "logd" (if not userdebug).
 std::unordered_set<std::string> llkIgnorelistStack;
 #endif
 
@@ -962,7 +962,8 @@
     //
     // This alarm is effectively the live lock detection of llkd, as
     // we understandably can not monitor ourselves otherwise.
-    ::alarm(duration_cast<seconds>(llkTimeoutMs * 2 * android::base::TimeoutMultiplier()).count());
+    ::alarm(duration_cast<seconds>(llkTimeoutMs * 2 * android::base::HwTimeoutMultiplier())
+                    .count());
 
     // kernel jiffy precision fastest acquisition
     static timespec last;
diff --git a/libkeyutils/mini_keyctl/Android.bp b/mini_keyctl/Android.bp
similarity index 100%
rename from libkeyutils/mini_keyctl/Android.bp
rename to mini_keyctl/Android.bp
diff --git a/libkeyutils/mini_keyctl/mini_keyctl.cpp b/mini_keyctl/mini_keyctl.cpp
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl.cpp
rename to mini_keyctl/mini_keyctl.cpp
diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.cpp b/mini_keyctl/mini_keyctl_utils.cpp
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl_utils.cpp
rename to mini_keyctl/mini_keyctl_utils.cpp
diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.h b/mini_keyctl/mini_keyctl_utils.h
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl_utils.h
rename to mini_keyctl/mini_keyctl_utils.h
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
index 2faf608..83cb6ff 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -6,6 +6,7 @@
     "libnativebridge.so",
     "libnativehelper.so",
     "libnativeloader.so",
+    "libsigchain.so",
     "libandroidicu.so",
     "libicu.so",
     // TODO(b/122876336): Remove libpac.so once it's migrated to Webview
@@ -26,4 +27,4 @@
     "libadb_pairing_connection.so",
     "libadb_pairing_server.so"
   ]
-}
\ No newline at end of file
+}
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 0e1e98b..11b3da7 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -630,6 +630,9 @@
     write /sys/kernel/tracing/instances/bootreceiver/events/error_report/error_report_end/enable 1
 
 on post-fs-data
+    # Boot level 30 - at this point daemons like apexd and odsign run
+    setprop keystore.boot_level 30
+
     mark_post_data
 
     # Start checkpoint before we touch data
@@ -908,6 +911,8 @@
     # Lock the fs-verity keyring, so no more keys can be added
     exec -- /system/bin/fsverity_init --lock
 
+    setprop keystore.boot_level 40
+
     # Allow apexd to snapshot and restore device encrypted apex data in the case
     # of a rollback. This should be done immediately after DE_user data keys
     # are loaded. APEXes should not access this data until this has been
@@ -1069,7 +1074,7 @@
     chown root radio /proc/cmdline
 
     # Define default initial receive window size in segments.
-    setprop net.tcp.default_init_rwnd 60
+    setprop net.tcp_def_init_rwnd 60
 
     # Start standard binderized HAL daemons
     class_start hal
diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp
index d147767..5d0ff79 100644
--- a/trusty/fuzz/Android.bp
+++ b/trusty/fuzz/Android.bp
@@ -30,7 +30,6 @@
         "-Werror",
     ],
     fuzz_config: {
-        fuzz_on_haiku_device: false,
         fuzz_on_haiku_host: false,
     },
 }
diff --git a/trusty/fuzz/test/Android.bp b/trusty/fuzz/test/Android.bp
index 7d74913..e0bca55 100644
--- a/trusty/fuzz/test/Android.bp
+++ b/trusty/fuzz/test/Android.bp
@@ -24,5 +24,8 @@
         "-DTRUSTY_APP_PORT=\"com.android.trusty.sancov.test.srv\"",
         "-DTRUSTY_APP_UUID=\"77f68803-c514-43ba-bdce-3254531c3d24\"",
         "-DTRUSTY_APP_FILENAME=\"srv.syms.elf\"",
-    ]
+    ],
+    fuzz_config: {
+        fuzz_on_haiku_device: false,
+    },
 }
diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp
index 24b0f98..3258944 100644
--- a/trusty/fuzz/tipc_fuzzer.cpp
+++ b/trusty/fuzz/tipc_fuzzer.cpp
@@ -51,13 +51,21 @@
         exit(-1);
     }
 
+    /* Make sure lazy-loaded TAs have started and connected to coverage service. */
+    TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
+    auto ret = ta.Connect();
+    if (!ret.ok()) {
+        std::cerr << ret.error() << std::endl;
+        exit(-1);
+    }
+
     record = std::make_unique<CoverageRecord>(TIPC_DEV, &module_uuid, TRUSTY_APP_FILENAME);
     if (!record) {
         std::cerr << "Failed to allocate coverage record" << std::endl;
         exit(-1);
     }
 
-    auto ret = record->Open();
+    ret = record->Open();
     if (!ret.ok()) {
         std::cerr << ret.error() << std::endl;
         exit(-1);
diff --git a/trusty/utils/acvp/Android.bp b/trusty/utils/acvp/Android.bp
new file mode 100644
index 0000000..b851e39
--- /dev/null
+++ b/trusty/utils/acvp/Android.bp
@@ -0,0 +1,40 @@
+// 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: "trusty_acvp_modulewrapper",
+    vendor: true,
+
+    srcs: [
+        "trusty_modulewrapper.cpp",
+    ],
+    static_libs: [
+        "libacvp_modulewrapper",
+    ],
+    shared_libs: [
+        "libbase",
+        "libc",
+        "libdmabufheap",
+        "liblog",
+        "libtrusty",
+        "libssl",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
diff --git a/trusty/utils/acvp/acvp_ipc.h b/trusty/utils/acvp/acvp_ipc.h
new file mode 100644
index 0000000..8b48ae3
--- /dev/null
+++ b/trusty/utils/acvp/acvp_ipc.h
@@ -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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ACVP_PORT "com.android.trusty.acvp"
+
+/*
+ * Maximum number of arguments
+ */
+#define ACVP_MAX_NUM_ARGUMENTS 8
+
+/*
+ * Maximum length of an algorithm name
+ */
+#define ACVP_MAX_NAME_LENGTH 30
+
+/*
+ * Maximum length of an ACVP request message
+ */
+#define ACVP_MAX_MESSAGE_LENGTH sizeof(struct acvp_req)
+
+/*
+ * Minimum length of the shared memory buffer
+ *
+ * This must be at least as long as the longest reply from the ACVP service
+ * (currently the reply from getConfig()).
+ */
+#define ACVP_MIN_SHARED_MEMORY 16384
+
+/**
+ * acvp_req - Request for the Trusty ACVP app
+ * @num_args: Number of acvp_arg structures following this struct
+ * @buffer_size: Total size of shared memory buffer
+ * @lengths: Length of each argument in the shared memory buffer
+ *
+ * @num_args copies of the acvp_arg struct follow this structure.
+ */
+struct acvp_req {
+    uint32_t num_args;
+    uint32_t buffer_size;
+    uint32_t lengths[ACVP_MAX_NUM_ARGUMENTS];
+};
+
+/**
+ * acvp_resp - Response to a ACVP request
+ *
+ * @num_spans: Number of response sections
+ * @lengths: Length of each response section
+ */
+struct acvp_resp {
+    uint32_t num_spans;
+    uint32_t lengths[ACVP_MAX_NUM_ARGUMENTS];
+};
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
diff --git a/trusty/utils/acvp/trusty_modulewrapper.cpp b/trusty/utils/acvp/trusty_modulewrapper.cpp
new file mode 100644
index 0000000..70ffb52
--- /dev/null
+++ b/trusty/utils/acvp/trusty_modulewrapper.cpp
@@ -0,0 +1,235 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TrustyAcvpModulewrapper"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <errno.h>
+#include <log/log.h>
+#include <modulewrapper.h>
+#include <openssl/span.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+#include <iostream>
+
+#include "acvp_ipc.h"
+
+constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0";
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::unique_fd;
+using android::base::WriteFully;
+
+static inline size_t AlignUpToPage(size_t size) {
+    return (size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+}
+
+namespace {
+
+class ModuleWrapper {
+  private:
+    static const char* kAcvpPort_;
+    static const char* kTrustyDeviceName_;
+
+  public:
+    ModuleWrapper();
+    ~ModuleWrapper();
+
+    Result<void> SendMessage(bssl::Span<const bssl::Span<const uint8_t>>);
+
+    Result<void> ForwardResponse();
+
+  private:
+    // Connection to the Trusty ACVP service
+    int tipc_fd_ = -1;
+
+    // Shared memory DMA buf
+    unique_fd dmabuf_fd_;
+
+    // Size of shared memory mapping
+    size_t shm_size_ = 0;
+
+    // Shared memory mapping
+    uint8_t* shm_buffer_ = nullptr;
+};
+
+}  // namespace
+
+const char* ModuleWrapper::kAcvpPort_ = ACVP_PORT;
+const char* ModuleWrapper::kTrustyDeviceName_ = kTrustyDeviceName;
+
+ModuleWrapper::ModuleWrapper() {
+    tipc_fd_ = tipc_connect(kTrustyDeviceName_, kAcvpPort_);
+    if (tipc_fd_ < 0) {
+        fprintf(stderr, "Failed to connect to Trusty ACVP test app: %s\n", strerror(-tipc_fd_));
+    }
+}
+
+ModuleWrapper::~ModuleWrapper() {
+    if (tipc_fd_ >= 0) {
+        tipc_close(tipc_fd_);
+    }
+
+    if (shm_buffer_) {
+        munmap(shm_buffer_, shm_size_);
+    }
+}
+
+Result<void> ModuleWrapper::SendMessage(bssl::Span<const bssl::Span<const uint8_t>> args) {
+    assert(args.size() < ACVP_MAX_NUM_ARGUMENTS);
+    assert(args[0].size() < ACVP_MAX_NAME_LENGTH);
+
+    struct acvp_req request;
+    request.num_args = args.size();
+
+    size_t total_args_size = 0;
+    for (auto arg : args) {
+        total_args_size += arg.size();
+    }
+
+    shm_size_ = ACVP_MIN_SHARED_MEMORY;
+    if (total_args_size > shm_size_) {
+        shm_size_ = AlignUpToPage(total_args_size);
+    }
+    request.buffer_size = shm_size_;
+
+    struct iovec iov = {
+            .iov_base = &request,
+            .iov_len = sizeof(struct acvp_req),
+    };
+
+    BufferAllocator alloc;
+    dmabuf_fd_.reset(alloc.Alloc(kDmabufSystemHeapName, shm_size_));
+    if (!dmabuf_fd_.ok()) {
+        return ErrnoError() << "Error creating dmabuf";
+    }
+
+    shm_buffer_ = (uint8_t*)mmap(0, shm_size_, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd_, 0);
+    if (shm_buffer_ == MAP_FAILED) {
+        return ErrnoError() << "Failed to map shared memory dmabuf";
+    }
+
+    size_t cur_offset = 0;
+    for (int i = 0; i < args.size(); ++i) {
+        request.lengths[i] = args[i].size();
+        memcpy(shm_buffer_ + cur_offset, args[i].data(), args[i].size());
+        cur_offset += args[i].size();
+    }
+
+    struct trusty_shm shm = {
+            .fd = dmabuf_fd_.get(),
+            .transfer = TRUSTY_SHARE,
+    };
+
+    int rc = tipc_send(tipc_fd_, &iov, 1, &shm, 1);
+    if (rc != sizeof(struct acvp_req)) {
+        return ErrnoError() << "Failed to send request to Trusty ACVP service";
+    }
+
+    return {};
+}
+
+Result<void> ModuleWrapper::ForwardResponse() {
+    struct acvp_resp resp;
+    int bytes_read = read(tipc_fd_, &resp, sizeof(struct acvp_resp));
+    if (bytes_read < 0) {
+        return ErrnoError() << "Failed to read response from Trusty ACVP service";
+    }
+
+    if (bytes_read != sizeof(struct acvp_resp)) {
+        return Error() << "Trusty ACVP response overflowed expected size";
+    }
+
+    size_t total_args_size = 0;
+    for (size_t i = 0; i < resp.num_spans; i++) {
+        total_args_size += resp.lengths[i];
+    }
+
+    iovec iovs[2];
+    iovs[0].iov_base = &resp;
+    iovs[0].iov_len = sizeof(uint32_t) * (1 + resp.num_spans);
+
+    iovs[1].iov_base = shm_buffer_;
+    iovs[1].iov_len = total_args_size;
+
+    size_t iov_done = 0;
+    while (iov_done < 2) {
+        ssize_t r;
+        do {
+            r = writev(STDOUT_FILENO, &iovs[iov_done], 2 - iov_done);
+        } while (r == -1 && errno == EINTR);
+
+        if (r <= 0) {
+            return Error() << "Failed to write ACVP response to standard out";
+        }
+
+        size_t written = r;
+        for (size_t i = iov_done; i < 2 && written > 0; i++) {
+            iovec& iov = iovs[i];
+
+            size_t done = written;
+            if (done > iov.iov_len) {
+                done = iov.iov_len;
+            }
+
+            iov.iov_base = reinterpret_cast<uint8_t*>(iov.iov_base) + done;
+            iov.iov_len -= done;
+            written -= done;
+
+            if (iov.iov_len == 0) {
+                iov_done++;
+            }
+        }
+
+        assert(written == 0);
+    }
+
+    return {};
+}
+
+int main() {
+    for (;;) {
+        auto buffer = bssl::acvp::RequestBuffer::New();
+        auto args = bssl::acvp::ParseArgsFromFd(STDIN_FILENO, buffer.get());
+        if (args.empty()) {
+            ALOGE("Could not parse arguments\n");
+            return EXIT_FAILURE;
+        }
+
+        ModuleWrapper wrapper;
+        auto res = wrapper.SendMessage(args);
+        if (!res.ok()) {
+            std::cerr << res.error() << std::endl;
+            return EXIT_FAILURE;
+        }
+
+        res = wrapper.ForwardResponse();
+        if (!res.ok()) {
+            std::cerr << res.error() << std::endl;
+            return EXIT_FAILURE;
+        }
+    }
+
+    return EXIT_SUCCESS;
+};