Merge "libprocessgroup: remove failure log for aggregate profiles"
diff --git a/bootstat/Android.bp b/bootstat/Android.bp
index 545a06f..ca59ef3 100644
--- a/bootstat/Android.bp
+++ b/bootstat/Android.bp
@@ -35,7 +35,7 @@
         "libcutils",
         "liblog",
     ],
-    static_libs: ["libgtest_prod"],
+    header_libs: ["libgtest_prod_headers"],
 }
 
 // bootstat static library
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index ee1ae31..d6ebb0d 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -438,6 +438,7 @@
     {"reboot,mount_userdata_failed", 190},
     {"reboot,forcedsilent", 191},
     {"reboot,forcednonsilent", 192},
+    {"reboot,thermal,tj", 193},
 };
 
 // Converts a string value representing the reason the system booted to an
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index fb274ec..198e4de 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -238,6 +238,7 @@
         "gwp_asan_crash_handler",
         "libscudo",
         "libtombstone_proto",
+        "libprocinfo",
         "libprotobuf-cpp-lite",
     ],
 
@@ -275,6 +276,12 @@
     ],
 }
 
+cc_test_library {
+    name: "libcrash_test",
+    defaults: ["debuggerd_defaults"],
+    srcs: ["crash_test.cpp"],
+}
+
 cc_test {
     name: "debuggerd_test",
     defaults: ["debuggerd_defaults"],
@@ -340,6 +347,10 @@
         },
     },
 
+    data: [
+        ":libcrash_test",
+    ],
+
     test_suites: ["device-tests"],
 }
 
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index 6bfb5f2..530e0e8 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -96,7 +96,7 @@
 
   if (std::string str = data.str(); !str.empty()) {
     buffer << "\n----- Waiting Channels: pid " << pid << " at " << get_timestamp() << " -----\n"
-           << "Cmd line: " << get_process_name(pid) << "\n";
+           << "Cmd line: " << android::base::Join(get_command_line(pid), " ") << "\n";
     buffer << "\n" << str << "\n";
     buffer << "----- end " << std::to_string(pid) << " -----\n";
     buffer << "\n";
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index a52f719..a152740 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -391,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();
 
@@ -450,9 +450,6 @@
   //       unwind, do not make this too small. b/62828735
   alarm(30 * android::base::HwTimeoutMultiplier());
 
-  // Get the process name (aka cmdline).
-  std::string process_name = get_process_name(g_target_thread);
-
   // Collect the list of open files.
   OpenFilesList open_files;
   {
@@ -489,7 +486,6 @@
       info.pid = target_process;
       info.tid = thread;
       info.uid = getuid();
-      info.process_name = process_name;
       info.thread_name = get_thread_name(thread);
 
       unique_fd attr_fd(openat(target_proc_fd, "attr/current", O_RDONLY | O_CLOEXEC));
@@ -517,6 +513,8 @@
         ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info);
         info.siginfo = &siginfo;
         info.signo = info.siginfo->si_signo;
+
+        info.command_line = get_command_line(g_target_thread);
       } else {
         info.registers.reset(unwindstack::Regs::RemoteGet(thread));
         if (!info.registers) {
@@ -548,15 +546,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;
@@ -641,12 +641,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/crash_test.cpp b/debuggerd/crash_test.cpp
new file mode 100644
index 0000000..c15145f
--- /dev/null
+++ b/debuggerd/crash_test.cpp
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+
+extern "C" void crash() {
+  *reinterpret_cast<volatile char*>(0xdead) = '1';
+}
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index ab95768..4634283 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <dirent.h>
+#include <dlfcn.h>
 #include <err.h>
 #include <fcntl.h>
 #include <malloc.h>
@@ -30,10 +31,12 @@
 
 #include <chrono>
 #include <regex>
+#include <string>
 #include <thread>
 
 #include <android/fdsan.h>
 #include <android/set_abort_message.h>
+#include <bionic/malloc.h>
 #include <bionic/mte.h>
 #include <bionic/reserved_signals.h>
 
@@ -69,7 +72,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)                                     \
   [&]() {                                                          \
@@ -98,6 +101,13 @@
   ASSERT_MATCH(result,                             \
                R"(#\d\d pc [0-9a-f]+\s+ \S+ (\(offset 0x[0-9a-f]+\) )?\()" frame_name R"(\+)");
 
+// Enable GWP-ASan at the start of this process. GWP-ASan is enabled using
+// process sampling, so we need to ensure we force GWP-ASan on.
+__attribute__((constructor)) static void enable_gwp_asan() {
+  bool force = true;
+  android_mallopt(M_INITIALIZE_GWP_ASAN, &force, sizeof(force));
+}
+
 static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd,
                                  InterceptStatus* status, DebuggerdDumpType intercept_type) {
   intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName,
@@ -157,7 +167,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 +188,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 +204,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) {
@@ -261,7 +276,7 @@
   }
 
   if (signo == 0) {
-    ASSERT_TRUE(WIFEXITED(status));
+    ASSERT_TRUE(WIFEXITED(status)) << "Terminated due to unexpected signal " << WTERMSIG(status);
     ASSERT_EQ(0, WEXITSTATUS(signo));
   } else {
     ASSERT_FALSE(WIFEXITED(status));
@@ -392,9 +407,75 @@
 }
 #endif
 
+// Number of iterations required to reliably guarantee a GWP-ASan crash.
+// GWP-ASan's sample rate is not truly nondeterministic, it initialises a
+// thread-local counter at 2*SampleRate, and decrements on each malloc(). Once
+// the counter reaches zero, we provide a sampled allocation. Then, double that
+// figure to allow for left/right allocation alignment, as this is done randomly
+// without bias.
+#define GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH (0x20000)
+
+struct GwpAsanTestParameters {
+  size_t alloc_size;
+  bool free_before_access;
+  int access_offset;
+  std::string cause_needle; // Needle to be found in the "Cause: [GWP-ASan]" line.
+};
+
+struct GwpAsanCrasherTest : CrasherTest, testing::WithParamInterface<GwpAsanTestParameters> {};
+
+GwpAsanTestParameters gwp_asan_tests[] = {
+  {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0, "Use After Free, 0 bytes into a 7-byte allocation"},
+  {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 1, "Use After Free, 1 byte into a 7-byte allocation"},
+  {/* alloc_size */ 7, /* free_before_access */ false, /* access_offset */ 16, "Buffer Overflow, 9 bytes right of a 7-byte allocation"},
+  {/* alloc_size */ 16, /* free_before_access */ false, /* access_offset */ -1, "Buffer Underflow, 1 byte left of a 16-byte allocation"},
+};
+
+INSTANTIATE_TEST_SUITE_P(GwpAsanTests, GwpAsanCrasherTest, testing::ValuesIn(gwp_asan_tests));
+
+TEST_P(GwpAsanCrasherTest, gwp_asan_uaf) {
+  if (mte_supported()) {
+    // Skip this test on MTE hardware, as MTE will reliably catch these errors
+    // instead of GWP-ASan.
+    GTEST_SKIP() << "Skipped on MTE.";
+  }
+
+  GwpAsanTestParameters params = GetParam();
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&params]() {
+    for (unsigned i = 0; i < GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH; ++i) {
+      volatile char* p = reinterpret_cast<volatile char*>(malloc(params.alloc_size));
+      if (params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
+      p[params.access_offset] = 42;
+      if (!params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
+    }
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
+  ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
+  if (params.free_before_access) {
+    ASSERT_MATCH(result, R"(deallocated by thread .*
+      #00 pc)");
+  }
+  ASSERT_MATCH(result, R"(allocated by thread .*
+      #00 pc)");
+}
+
 struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
 
-INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(16, 131072));
+INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(0, 16, 131072));
 
 TEST_P(SizeParamCrasherTest, mte_uaf) {
 #if defined(__aarch64__)
@@ -402,6 +483,11 @@
     GTEST_SKIP() << "Requires MTE";
   }
 
+  // Any UAF on a zero-sized allocation will be out-of-bounds so it won't be reported.
+  if (GetParam() == 0) {
+    return;
+  }
+
   int intercept_result;
   unique_fd output_fd;
   StartProcess([&]() {
@@ -423,12 +509,43 @@
 
   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)");
+                           std::to_string(GetParam()) + R"(-byte allocation)");
   ASSERT_MATCH(result, R"(deallocated by thread .*
       #00 pc)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
+      #00 pc)");
+#else
+  GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_P(SizeParamCrasherTest, mte_oob_uaf) {
+#if defined(__aarch64__)
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&]() {
+    SetTagCheckingLevelSync();
+    volatile int* p = (volatile int*)malloc(GetParam());
+    free((void *)p);
+    p[-1] = 42;
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+  ASSERT_NOT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 4 bytes left)");
 #else
   GTEST_SKIP() << "Requires aarch64";
 #endif
@@ -460,9 +577,8 @@
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
   ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
-                           std::to_string(GetParam()) + R"(-byte allocation.*
-
-allocated by thread .*
+                           std::to_string(GetParam()) + R"(-byte allocation)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
       #00 pc)");
 #else
   GTEST_SKIP() << "Requires aarch64";
@@ -495,9 +611,8 @@
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
   ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
-                           std::to_string(GetParam()) + R"(-byte allocation.*
-
-allocated by thread .*
+                           std::to_string(GetParam()) + R"(-byte allocation)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
       #00 pc)");
 #else
   GTEST_SKIP() << "Requires aarch64";
@@ -734,9 +849,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);
 
@@ -1434,6 +1549,60 @@
   ASSERT_MATCH(result, R"(Cause: stack pointer[^\n]*stack overflow.\n)");
 }
 
+static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) {
+  std::string test_lib(testing::internal::GetArgvs()[0]);
+  auto const value = test_lib.find_last_of('/');
+  if (value == std::string::npos) {
+    test_lib = "./";
+  } else {
+    test_lib = test_lib.substr(0, value + 1) + "./";
+  }
+  test_lib += "libcrash_test.so";
+
+  *tmp_so_name = std::string(tmp_dir) + "/libcrash_test.so";
+  std::string cp_cmd = android::base::StringPrintf("cp %s %s", test_lib.c_str(), tmp_dir);
+
+  // Copy the shared so to a tempory directory.
+  return system(cp_cmd.c_str()) == 0;
+}
+
+TEST_F(CrasherTest, unreadable_elf) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    TemporaryDir td;
+    std::string tmp_so_name;
+    if (!CopySharedLibrary(td.path, &tmp_so_name)) {
+      _exit(1);
+    }
+    void* handle = dlopen(tmp_so_name.c_str(), RTLD_NOW);
+    if (handle == nullptr) {
+      _exit(1);
+    }
+    // Delete the original shared library so that we get the warning
+    // about unreadable elf files.
+    if (unlink(tmp_so_name.c_str()) == -1) {
+      _exit(1);
+    }
+    void (*crash_func)() = reinterpret_cast<void (*)()>(dlsym(handle, "crash"));
+    if (crash_func == nullptr) {
+      _exit(1);
+    }
+    crash_func();
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(NOTE: Function names and BuildId information is missing )");
+}
+
 TEST(tombstoned, proto) {
   const pid_t self = getpid();
   unique_fd tombstoned_socket, text_fd, proto_fd;
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/libdebuggerd/backtrace.cpp b/debuggerd/libdebuggerd/backtrace.cpp
index c543a83..fd91038 100644
--- a/debuggerd/libdebuggerd/backtrace.cpp
+++ b/debuggerd/libdebuggerd/backtrace.cpp
@@ -34,6 +34,7 @@
 #include <memory>
 #include <string>
 
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <log/log.h>
 #include <unwindstack/Unwinder.h>
@@ -42,11 +43,12 @@
 #include "libdebuggerd/utility.h"
 #include "util.h"
 
-static void dump_process_header(log_t* log, pid_t pid, const char* process_name) {
+static void dump_process_header(log_t* log, pid_t pid,
+                                const std::vector<std::string>& command_line) {
   _LOG(log, logtype::BACKTRACE, "\n\n----- pid %d at %s -----\n", pid, get_timestamp().c_str());
 
-  if (process_name) {
-    _LOG(log, logtype::BACKTRACE, "Cmd line: %s\n", process_name);
+  if (!command_line.empty()) {
+    _LOG(log, logtype::BACKTRACE, "Cmd line: %s\n", android::base::Join(command_line, " ").c_str());
   }
   _LOG(log, logtype::BACKTRACE, "ABI: '%s'\n", ABI_STRING);
 }
@@ -89,7 +91,7 @@
     return;
   }
 
-  dump_process_header(&log, target->second.pid, target->second.process_name.c_str());
+  dump_process_header(&log, target->second.pid, target->second.command_line);
 
   dump_backtrace_thread(output_fd.get(), unwinder, target->second);
   for (const auto& [tid, info] : thread_info) {
@@ -107,7 +109,7 @@
   log.amfd_data = nullptr;
 
   pid_t pid = getpid();
-  dump_process_header(&log, pid, get_process_name(pid).c_str());
+  dump_process_header(&log, pid, get_command_line(pid));
 }
 
 void dump_backtrace_footer(int output_fd) {
diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index 9750fc4..3ee309f 100644
--- a/debuggerd/libdebuggerd/gwp_asan.cpp
+++ b/debuggerd/libdebuggerd/gwp_asan.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/tombstone.h"
 #include "libdebuggerd/utility.h"
 
 #include "gwp_asan/common.h"
@@ -25,6 +26,8 @@
 #include <unwindstack/Regs.h>
 #include <unwindstack/Unwinder.h>
 
+#include "tombstone.pb.h"
+
 // Retrieve GWP-ASan state from `state_addr` inside the process at
 // `process_memory`. Place the state into `*state`.
 static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr,
@@ -98,6 +101,67 @@
   return is_gwp_asan_responsible_;
 }
 
+constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
+
+void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
+  if (!CrashIsMine()) {
+    ALOGE("Internal Error: AddCauseProtos() on a non-GWP-ASan crash.");
+    return;
+  }
+
+  Cause* cause = tombstone->add_causes();
+  MemoryError* memory_error = cause->mutable_memory_error();
+  HeapObject* heap_object = memory_error->mutable_heap();
+
+  memory_error->set_tool(MemoryError_Tool_GWP_ASAN);
+  switch (error_) {
+    case gwp_asan::Error::USE_AFTER_FREE:
+      memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
+      break;
+    case gwp_asan::Error::DOUBLE_FREE:
+      memory_error->set_type(MemoryError_Type_DOUBLE_FREE);
+      break;
+    case gwp_asan::Error::INVALID_FREE:
+      memory_error->set_type(MemoryError_Type_INVALID_FREE);
+      break;
+    case gwp_asan::Error::BUFFER_OVERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
+      break;
+    case gwp_asan::Error::BUFFER_UNDERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
+      break;
+    default:
+      memory_error->set_type(MemoryError_Type_UNKNOWN);
+      break;
+  }
+
+  heap_object->set_address(__gwp_asan_get_allocation_address(responsible_allocation_));
+  heap_object->set_size(__gwp_asan_get_allocation_size(responsible_allocation_));
+  unwinder->SetDisplayBuildID(true);
+
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
+
+  heap_object->set_allocation_tid(__gwp_asan_get_allocation_thread_id(responsible_allocation_));
+  size_t num_frames =
+      __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
+  for (size_t i = 0; i != num_frames; ++i) {
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
+    BacktraceFrame* f = heap_object->add_allocation_backtrace();
+    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+  }
+
+  heap_object->set_deallocation_tid(__gwp_asan_get_deallocation_thread_id(responsible_allocation_));
+  num_frames =
+      __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
+  for (size_t i = 0; i != num_frames; ++i) {
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
+    BacktraceFrame* f = heap_object->add_deallocation_backtrace();
+    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+  }
+
+  set_human_readable_cause(cause, crash_address_);
+}
+
 void GwpAsanCrashData::DumpCause(log_t* log) const {
   if (!CrashIsMine()) {
     ALOGE("Internal Error: DumpCause() on a non-GWP-ASan crash.");
@@ -119,13 +183,6 @@
   uintptr_t alloc_address = __gwp_asan_get_allocation_address(responsible_allocation_);
   size_t alloc_size = __gwp_asan_get_allocation_size(responsible_allocation_);
 
-  if (crash_address_ == alloc_address) {
-    // Use After Free on a 41-byte allocation at 0xdeadbeef.
-    _LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: %s on a %zu-byte allocation at 0x%" PRIxPTR "\n",
-         error_string_, alloc_size, alloc_address);
-    return;
-  }
-
   uintptr_t diff;
   const char* location_str;
 
@@ -157,8 +214,6 @@
        error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address);
 }
 
-constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
-
 bool GwpAsanCrashData::HasDeallocationTrace() const {
   assert(CrashIsMine() && "HasDeallocationTrace(): Crash is not mine!");
   if (!responsible_allocation_ || !__gwp_asan_is_deallocated(responsible_allocation_)) {
@@ -171,7 +226,7 @@
   assert(HasDeallocationTrace() && "DumpDeallocationTrace(): No dealloc trace!");
   uint64_t thread_id = __gwp_asan_get_deallocation_thread_id(responsible_allocation_);
 
-  std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
   size_t num_frames =
       __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
 
@@ -183,7 +238,7 @@
 
   unwinder->SetDisplayBuildID(true);
   for (size_t i = 0; i < num_frames; ++i) {
-    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
     frame_data.num = i;
     _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
   }
@@ -198,7 +253,7 @@
   assert(HasAllocationTrace() && "DumpAllocationTrace(): No dealloc trace!");
   uint64_t thread_id = __gwp_asan_get_allocation_thread_id(responsible_allocation_);
 
-  std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
   size_t num_frames =
       __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
 
@@ -210,7 +265,7 @@
 
   unwinder->SetDisplayBuildID(true);
   for (size_t i = 0; i < num_frames; ++i) {
-    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
     frame_data.num = i;
     _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
   }
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
index 6c88733..f9c2481 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
@@ -26,6 +26,9 @@
 #include "types.h"
 #include "utility.h"
 
+class Cause;
+class Tombstone;
+
 class GwpAsanCrashData {
  public:
   GwpAsanCrashData() = delete;
@@ -69,6 +72,8 @@
   // HasAllocationTrace() returns true.
   void DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;
 
+  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
+
  protected:
   // Is GWP-ASan responsible for this crash.
   bool is_gwp_asan_responsible_ = false;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
index 4d00ece..c3b95d6 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
@@ -23,6 +23,9 @@
 
 #include "scudo/interface.h"
 
+class Cause;
+class Tombstone;
+
 class ScudoCrashData {
  public:
   ScudoCrashData() = delete;
@@ -32,6 +35,7 @@
   bool CrashIsMine() const;
 
   void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const;
+  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
 
  private:
   scudo_error_info error_info_ = {};
@@ -39,4 +43,7 @@
 
   void DumpReport(const scudo_error_report* report, log_t* log,
                   unwindstack::Unwinder* unwinder) const;
+
+  void FillInCause(Cause* cause, const scudo_error_report* report,
+                   unwindstack::Unwinder* unwinder) const;
 };
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
index bf2cbb3..2331f1e 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
@@ -31,9 +31,13 @@
 #include "types.h"
 
 // Forward declarations
+class BacktraceFrame;
+class Cause;
 class Tombstone;
 
 namespace unwindstack {
+struct FrameData;
+class Maps;
 class Unwinder;
 }
 
@@ -64,4 +68,8 @@
     const Tombstone& tombstone,
     std::function<void(const std::string& line, bool should_log)> callback);
 
+void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
+                             unwindstack::Maps* maps);
+void set_human_readable_cause(Cause* cause, uint64_t fault_addr);
+
 #endif  // _DEBUGGERD_TOMBSTONE_H
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index dcb52f9..086dc97 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include <unwindstack/Regs.h>
 
@@ -32,13 +33,14 @@
 
   pid_t pid;
 
-  std::string process_name;
+  std::vector<std::string> command_line;
   std::string selinux_label;
 
   int signo = 0;
   siginfo_t* siginfo = nullptr;
 };
 
+// This struct is written into a pipe from inside the crashing process.
 struct ProcessInfo {
   uintptr_t abort_msg_address = 0;
   uintptr_t fdsan_table_address = 0;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index d71b76f..c490fb1 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -81,7 +81,7 @@
 
 void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix);
 
-ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
+ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
                     unwindstack::Memory* memory);
 void dump_memory(log_t* log, unwindstack::Memory* backtrace, uint64_t addr, const std::string&);
 
@@ -93,4 +93,7 @@
 const char* get_signame(const siginfo_t*);
 const char* get_sigcode(const siginfo_t*);
 
+// Number of bytes per MTE granule.
+constexpr size_t kTagGranuleSize = 16;
+
 #endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index 1c3437f..f4690ba 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -15,13 +15,16 @@
  */
 
 #include "libdebuggerd/scudo.h"
-#include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/tombstone.h"
 
 #include "unwindstack/Memory.h"
 #include "unwindstack/Unwinder.h"
 
+#include <android-base/macros.h>
 #include <bionic/macros.h>
 
+#include "tombstone.pb.h"
+
 std::unique_ptr<char[]> AllocAndReadFully(unwindstack::Memory* process_memory, uint64_t addr,
                                           size_t size) {
   auto buf = std::make_unique<char[]>(size);
@@ -31,8 +34,6 @@
   return buf;
 }
 
-static const uintptr_t kTagGranuleSize = 16;
-
 ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory,
                                const ProcessInfo& process_info) {
   if (!process_info.has_fault_address) {
@@ -78,6 +79,58 @@
   return error_info_.reports[0].error_type != UNKNOWN;
 }
 
+void ScudoCrashData::FillInCause(Cause* cause, const scudo_error_report* report,
+                                 unwindstack::Unwinder* unwinder) const {
+  MemoryError* memory_error = cause->mutable_memory_error();
+  HeapObject* heap_object = memory_error->mutable_heap();
+
+  memory_error->set_tool(MemoryError_Tool_SCUDO);
+  switch (report->error_type) {
+    case USE_AFTER_FREE:
+      memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
+      break;
+    case BUFFER_OVERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
+      break;
+    case BUFFER_UNDERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
+      break;
+    default:
+      memory_error->set_type(MemoryError_Type_UNKNOWN);
+      break;
+  }
+
+  heap_object->set_address(report->allocation_address);
+  heap_object->set_size(report->allocation_size);
+  unwinder->SetDisplayBuildID(true);
+
+  heap_object->set_allocation_tid(report->allocation_tid);
+  for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i]; ++i) {
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]);
+    BacktraceFrame* f = heap_object->add_allocation_backtrace();
+    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+  }
+
+  heap_object->set_deallocation_tid(report->deallocation_tid);
+  for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i];
+       ++i) {
+    unwindstack::FrameData frame_data =
+        unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
+    BacktraceFrame* f = heap_object->add_deallocation_backtrace();
+    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+  }
+
+  set_human_readable_cause(cause, untagged_fault_addr_);
+}
+
+void ScudoCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
+  size_t report_num = 0;
+  while (report_num < sizeof(error_info_.reports) / sizeof(error_info_.reports[0]) &&
+         error_info_.reports[report_num].error_type != UNKNOWN) {
+    FillInCause(tombstone->add_causes(), &error_info_.reports[report_num++], unwinder);
+  }
+}
+
 void ScudoCrashData::DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const {
   if (error_info_.reports[1].error_type != UNKNOWN) {
     _LOG(log, logtype::HEADER,
@@ -140,7 +193,8 @@
   if (report->allocation_trace[0]) {
     _LOG(log, logtype::BACKTRACE, "\nallocated by thread %u:\n", report->allocation_tid);
     unwinder->SetDisplayBuildID(true);
-    for (size_t i = 0; i < 64 && report->allocation_trace[i]; ++i) {
+    for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i];
+         ++i) {
       unwindstack::FrameData frame_data =
           unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]);
       frame_data.num = i;
@@ -151,7 +205,8 @@
   if (report->deallocation_trace[0]) {
     _LOG(log, logtype::BACKTRACE, "\ndeallocated by thread %u:\n", report->deallocation_tid);
     unwinder->SetDisplayBuildID(true);
-    for (size_t i = 0; i < 64 && report->deallocation_trace[i]; ++i) {
+    for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i];
+         ++i) {
       unwindstack::FrameData frame_data =
           unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
       frame_data.num = i;
diff --git a/debuggerd/libdebuggerd/test/UnwinderMock.h b/debuggerd/libdebuggerd/test/UnwinderMock.h
index 44a9214..8f84346 100644
--- a/debuggerd/libdebuggerd/test/UnwinderMock.h
+++ b/debuggerd/libdebuggerd/test/UnwinderMock.h
@@ -33,8 +33,7 @@
   void MockSetBuildID(uint64_t offset, const std::string& build_id) {
     unwindstack::MapInfo* map_info = GetMaps()->Find(offset);
     if (map_info != nullptr) {
-      std::string* new_build_id = new std::string(build_id);
-      map_info->build_id = new_build_id;
+      map_info->SetBuildID(std::string(build_id));
     }
   }
 };
diff --git a/debuggerd/libdebuggerd/test/dump_memory_test.cpp b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
index f16f578..5be145a 100644
--- a/debuggerd/libdebuggerd/test/dump_memory_test.cpp
+++ b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
@@ -73,34 +73,14 @@
 "    0000000012345600 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
 "    0000000012345610 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
 "    0000000012345620 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    0000000012345630 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
-"    0000000012345640 6766656463626160 ----------------  `abcdefg........\n"
-"    0000000012345650 ---------------- ----------------  ................\n"
-"    0000000012345660 ---------------- ----------------  ................\n"
-"    0000000012345670 ---------------- ----------------  ................\n"
-"    0000000012345680 ---------------- ----------------  ................\n"
-"    0000000012345690 ---------------- ----------------  ................\n"
-"    00000000123456a0 ---------------- ----------------  ................\n"
-"    00000000123456b0 ---------------- ----------------  ................\n"
-"    00000000123456c0 ---------------- ----------------  ................\n"
-"    00000000123456d0 ---------------- ----------------  ................\n";
+"    0000000012345630 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n";
 #else
 "    123455e0 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
 "    123455f0 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
 "    12345600 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
 "    12345610 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
 "    12345620 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
-"    12345630 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
-"    12345640 63626160 67666564 -------- --------  `abcdefg........\n"
-"    12345650 -------- -------- -------- --------  ................\n"
-"    12345660 -------- -------- -------- --------  ................\n"
-"    12345670 -------- -------- -------- --------  ................\n"
-"    12345680 -------- -------- -------- --------  ................\n"
-"    12345690 -------- -------- -------- --------  ................\n"
-"    123456a0 -------- -------- -------- --------  ................\n"
-"    123456b0 -------- -------- -------- --------  ................\n"
-"    123456c0 -------- -------- -------- --------  ................\n"
-"    123456d0 -------- -------- -------- --------  ................\n";
+"    12345630 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n";
 #endif
 
 class MemoryMock : public unwindstack::Memory {
@@ -513,15 +493,7 @@
   const char* expected_dump = \
 "\nmemory near r4:\n"
 #if defined(__LP64__)
-R"(    0000000010000f80 ---------------- ----------------  ................
-    0000000010000f90 ---------------- ----------------  ................
-    0000000010000fa0 ---------------- ----------------  ................
-    0000000010000fb0 ---------------- ----------------  ................
-    0000000010000fc0 ---------------- ----------------  ................
-    0000000010000fd0 ---------------- ----------------  ................
-    0000000010000fe0 ---------------- ----------------  ................
-    0000000010000ff0 ---------------- ----------------  ................
-    0000000010001000 8786858483828180 8f8e8d8c8b8a8988  ................
+R"(    0000000010001000 8786858483828180 8f8e8d8c8b8a8988  ................
     0000000010001010 9796959493929190 9f9e9d9c9b9a9998  ................
     0000000010001020 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................
     0000000010001030 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................
@@ -531,15 +503,7 @@
     0000000010001070 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................
 )";
 #else
-R"(    10000f80 -------- -------- -------- --------  ................
-    10000f90 -------- -------- -------- --------  ................
-    10000fa0 -------- -------- -------- --------  ................
-    10000fb0 -------- -------- -------- --------  ................
-    10000fc0 -------- -------- -------- --------  ................
-    10000fd0 -------- -------- -------- --------  ................
-    10000fe0 -------- -------- -------- --------  ................
-    10000ff0 -------- -------- -------- --------  ................
-    10001000 83828180 87868584 8b8a8988 8f8e8d8c  ................
+R"(    10001000 83828180 87868584 8b8a8988 8f8e8d8c  ................
     10001010 93929190 97969594 9b9a9998 9f9e9d9c  ................
     10001020 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................
     10001030 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................
@@ -574,39 +538,11 @@
   const char* expected_dump = \
 "\nmemory near r4:\n"
 #if defined(__LP64__)
-"    0000000010000f40 ---------------- ----------------  ................\n"
-"    0000000010000f50 ---------------- ----------------  ................\n"
-"    0000000010000f60 ---------------- ----------------  ................\n"
-"    0000000010000f70 ---------------- ----------------  ................\n"
-"    0000000010000f80 ---------------- ----------------  ................\n"
-"    0000000010000f90 ---------------- ----------------  ................\n"
-"    0000000010000fa0 ---------------- ----------------  ................\n"
-"    0000000010000fb0 ---------------- ----------------  ................\n"
-"    0000000010000fc0 ---------------- ----------------  ................\n"
-"    0000000010000fd0 ---------------- ----------------  ................\n"
-"    0000000010000fe0 ---------------- ----------------  ................\n"
-"    0000000010000ff0 ---------------- ----------------  ................\n"
 "    0000000010001000 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    0000000010001020 ---------------- ----------------  ................\n"
-"    0000000010001030 ---------------- ----------------  ................\n";
+"    0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n";
 #else
-"    10000f40 -------- -------- -------- --------  ................\n"
-"    10000f50 -------- -------- -------- --------  ................\n"
-"    10000f60 -------- -------- -------- --------  ................\n"
-"    10000f70 -------- -------- -------- --------  ................\n"
-"    10000f80 -------- -------- -------- --------  ................\n"
-"    10000f90 -------- -------- -------- --------  ................\n"
-"    10000fa0 -------- -------- -------- --------  ................\n"
-"    10000fb0 -------- -------- -------- --------  ................\n"
-"    10000fc0 -------- -------- -------- --------  ................\n"
-"    10000fd0 -------- -------- -------- --------  ................\n"
-"    10000fe0 -------- -------- -------- --------  ................\n"
-"    10000ff0 -------- -------- -------- --------  ................\n"
 "    10001000 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
-"    10001020 -------- -------- -------- --------  ................\n"
-"    10001030 -------- -------- -------- --------  ................\n";
+"    10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n";
 #endif
   ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
 
diff --git a/debuggerd/libdebuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp
index 7fe8f82..a14dcb0 100644
--- a/debuggerd/libdebuggerd/test/tombstone_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp
@@ -350,11 +350,11 @@
 }
 
 TEST_F(TombstoneTest, dump_thread_info_uid) {
-  dump_thread_info(&log_, ThreadInfo{.uid = 1,
-                                     .tid = 3,
-                                     .thread_name = "some_thread",
-                                     .pid = 2,
-                                     .process_name = "some_process"});
+  std::vector<std::string> cmdline = {"some_process"};
+  dump_thread_info(
+      &log_,
+      ThreadInfo{
+          .uid = 1, .tid = 3, .thread_name = "some_thread", .pid = 2, .command_line = cmdline});
   std::string expected = "pid: 2, tid: 3, name: some_thread  >>> some_process <<<\nuid: 1\n";
   ASSERT_STREQ(expected.c_str(), amfd_data_.c_str());
 }
@@ -388,9 +388,8 @@
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   std::string tombstone_contents;
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_THAT(tombstone_contents,
-              MatchesRegex("Cause: \\[GWP-ASan\\]: Use After Free on a 32-byte "
-                           "allocation at 0x[a-fA-F0-9]+\n"));
+  ASSERT_THAT(tombstone_contents, MatchesRegex("Cause: \\[GWP-ASan\\]: Use After Free, 0 bytes "
+                                               "into a 32-byte allocation at 0x[a-fA-F0-9]+\n"));
 }
 
 TEST_F(TombstoneTest, gwp_asan_cause_double_free) {
@@ -405,9 +404,8 @@
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   std::string tombstone_contents;
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_THAT(tombstone_contents,
-              MatchesRegex("Cause: \\[GWP-ASan\\]: Double Free on a 32-byte "
-                           "allocation at 0x[a-fA-F0-9]+\n"));
+  ASSERT_THAT(tombstone_contents, MatchesRegex("Cause: \\[GWP-ASan\\]: Double Free, 0 bytes into a "
+                                               "32-byte allocation at 0x[a-fA-F0-9]+\n"));
 }
 
 TEST_F(TombstoneTest, gwp_asan_cause_overflow) {
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 4f75ff1..ad903ce 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -106,9 +106,9 @@
     unwindstack::MapInfo* map_info = maps->Find(sp);
     if (map_info == nullptr) {
       return "stack pointer is in a non-existent map; likely due to stack overflow.";
-    } else if ((map_info->flags & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
+    } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
       return "stack pointer is not in a rw map; likely due to stack overflow.";
-    } else if ((sp - map_info->start) <= kMaxDifferenceBytes) {
+    } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) {
       return "stack pointer is close to top of stack; likely stack overflow.";
     }
   }
@@ -137,7 +137,7 @@
   } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
     uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
     unwindstack::MapInfo* map_info = maps->Find(fault_addr);
-    if (map_info != nullptr && map_info->flags == PROT_EXEC) {
+    if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
       cause = "execute-only (no-read) memory access error; likely due to data in .text.";
     } else {
       cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps);
@@ -182,8 +182,13 @@
   // Don't try to collect logs from the threads that implement the logging system itself.
   if (thread_info.uid == AID_LOGD) log->should_retrieve_logcat = false;
 
+  const char* process_name = "<unknown>";
+  if (!thread_info.command_line.empty()) {
+    process_name = thread_info.command_line[0].c_str();
+  }
+
   _LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s  >>> %s <<<\n", thread_info.pid,
-       thread_info.tid, thread_info.thread_name.c_str(), thread_info.process_name.c_str());
+       thread_info.tid, thread_info.thread_name.c_str(), process_name);
   _LOG(log, logtype::HEADER, "uid: %d\n", thread_info.uid);
   if (thread_info.tagged_addr_ctrl != -1) {
     _LOG(log, logtype::HEADER, "tagged_addr_ctrl: %016lx\n", thread_info.tagged_addr_ctrl);
@@ -239,7 +244,7 @@
        "memory map (%zu entr%s):",
        maps->Total(), maps->Total() == 1 ? "y" : "ies");
   if (print_fault_address_marker) {
-    if (maps->Total() != 0 && addr < maps->Get(0)->start) {
+    if (maps->Total() != 0 && addr < maps->Get(0)->start()) {
       _LOG(log, logtype::MAPS, "\n--->Fault address falls at %s before any mapped regions\n",
            get_addr_string(addr).c_str());
       print_fault_address_marker = false;
@@ -256,37 +261,37 @@
   for (auto const& map_info : *maps) {
     line = "    ";
     if (print_fault_address_marker) {
-      if (addr < map_info->start) {
+      if (addr < map_info->start()) {
         _LOG(log, logtype::MAPS, "--->Fault address falls at %s between mapped regions\n",
              get_addr_string(addr).c_str());
         print_fault_address_marker = false;
-      } else if (addr >= map_info->start && addr < map_info->end) {
+      } else if (addr >= map_info->start() && addr < map_info->end()) {
         line = "--->";
         print_fault_address_marker = false;
       }
     }
-    line += get_addr_string(map_info->start) + '-' + get_addr_string(map_info->end - 1) + ' ';
-    if (map_info->flags & PROT_READ) {
+    line += get_addr_string(map_info->start()) + '-' + get_addr_string(map_info->end() - 1) + ' ';
+    if (map_info->flags() & PROT_READ) {
       line += 'r';
     } else {
       line += '-';
     }
-    if (map_info->flags & PROT_WRITE) {
+    if (map_info->flags() & PROT_WRITE) {
       line += 'w';
     } else {
       line += '-';
     }
-    if (map_info->flags & PROT_EXEC) {
+    if (map_info->flags() & PROT_EXEC) {
       line += 'x';
     } else {
       line += '-';
     }
-    line += StringPrintf("  %8" PRIx64 "  %8" PRIx64, map_info->offset,
-                         map_info->end - map_info->start);
+    line += StringPrintf("  %8" PRIx64 "  %8" PRIx64, map_info->offset(),
+                         map_info->end() - map_info->start());
     bool space_needed = true;
-    if (!map_info->name.empty()) {
+    if (!map_info->name().empty()) {
       space_needed = false;
-      line += "  " + map_info->name;
+      line += "  " + map_info->name();
       std::string build_id = map_info->GetPrintableBuildID();
       if (!build_id.empty()) {
         line += " (BuildId: " + build_id + ")";
@@ -364,8 +369,8 @@
     std::string label{"memory near "s + reg_name};
     if (maps) {
       unwindstack::MapInfo* map_info = maps->Find(untag_address(reg_value));
-      if (map_info != nullptr && !map_info->name.empty()) {
-        label += " (" + map_info->name + ")";
+      if (map_info != nullptr && !map_info->name().empty()) {
+        label += " (" + map_info->name() + ")";
       }
     }
     dump_memory(log, memory, reg_value, label);
@@ -567,7 +572,7 @@
   log.amfd_data = nullptr;
 
   std::string thread_name = get_thread_name(tid);
-  std::string process_name = get_process_name(pid);
+  std::vector<std::string> command_line = get_command_line(pid);
 
   std::unique_ptr<unwindstack::Regs> regs(
       unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext));
@@ -582,7 +587,7 @@
       .tid = tid,
       .thread_name = std::move(thread_name),
       .pid = pid,
-      .process_name = std::move(process_name),
+      .command_line = std::move(command_line),
       .selinux_label = std::move(selinux_label),
       .siginfo = siginfo,
   };
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 23ca070..abd1f12 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "DEBUG"
 
 #include "libdebuggerd/tombstone.h"
+#include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/scudo.h"
 
 #include <errno.h>
 #include <fcntl.h>
@@ -29,10 +31,12 @@
 #include <time.h>
 
 #include <memory>
+#include <optional>
 #include <string>
 
 #include <async_safe/log.h>
 
+#include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -45,6 +49,7 @@
 #include <log/logprint.h>
 #include <private/android_filesystem_config.h>
 
+#include <procinfo/process.h>
 #include <unwindstack/Maps.h>
 #include <unwindstack/Memory.h>
 #include <unwindstack/Regs.h>
@@ -97,41 +102,129 @@
     unwindstack::MapInfo* map_info = maps->Find(sp);
     if (map_info == nullptr) {
       return "stack pointer is in a non-existent map; likely due to stack overflow.";
-    } else if ((map_info->flags & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
+    } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
       return "stack pointer is not in a rw map; likely due to stack overflow.";
-    } else if ((sp - map_info->start) <= kMaxDifferenceBytes) {
+    } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) {
       return "stack pointer is close to top of stack; likely stack overflow.";
     }
   }
   return {};
 }
 
-static void dump_probable_cause(Tombstone* tombstone, const siginfo_t* si, unwindstack::Maps* maps,
-                                unwindstack::Regs* regs) {
+void set_human_readable_cause(Cause* cause, uint64_t fault_addr) {
+  if (!cause->has_memory_error() || !cause->memory_error().has_heap()) {
+    return;
+  }
+
+  const MemoryError& memory_error = cause->memory_error();
+  const HeapObject& heap_object = memory_error.heap();
+
+  const char *tool_str;
+  switch (memory_error.tool()) {
+    case MemoryError_Tool_GWP_ASAN:
+      tool_str = "GWP-ASan";
+      break;
+    case MemoryError_Tool_SCUDO:
+      tool_str = "MTE";
+      break;
+    default:
+      tool_str = "Unknown";
+      break;
+  }
+
+  const char *error_type_str;
+  switch (memory_error.type()) {
+    case MemoryError_Type_USE_AFTER_FREE:
+      error_type_str = "Use After Free";
+      break;
+    case MemoryError_Type_DOUBLE_FREE:
+      error_type_str = "Double Free";
+      break;
+    case MemoryError_Type_INVALID_FREE:
+      error_type_str = "Invalid (Wild) Free";
+      break;
+    case MemoryError_Type_BUFFER_OVERFLOW:
+      error_type_str = "Buffer Overflow";
+      break;
+    case MemoryError_Type_BUFFER_UNDERFLOW:
+      error_type_str = "Buffer Underflow";
+      break;
+    default:
+      cause->set_human_readable(
+          StringPrintf("[%s]: Unknown error occurred at 0x%" PRIx64 ".", tool_str, fault_addr));
+      return;
+  }
+
+  uint64_t diff;
+  const char* location_str;
+
+  if (fault_addr < heap_object.address()) {
+    // Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef.
+    location_str = "left of";
+    diff = heap_object.address() - fault_addr;
+  } else if (fault_addr - heap_object.address() < heap_object.size()) {
+    // Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef.
+    location_str = "into";
+    diff = fault_addr - heap_object.address();
+  } else {
+    // Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef.
+    location_str = "right of";
+    diff = fault_addr - heap_object.address() - heap_object.size();
+  }
+
+  // Suffix of 'bytes', i.e. 4 bytes' vs. '1 byte'.
+  const char* byte_suffix = "s";
+  if (diff == 1) {
+    byte_suffix = "";
+  }
+
+  cause->set_human_readable(StringPrintf(
+      "[%s]: %s, %" PRIu64 " byte%s %s a %" PRIu64 "-byte allocation at 0x%" PRIx64, tool_str,
+      error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address()));
+}
+
+static void dump_probable_cause(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+                                const ProcessInfo& process_info, const ThreadInfo& main_thread) {
+  ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
+  if (scudo_crash_data.CrashIsMine()) {
+    scudo_crash_data.AddCauseProtos(tombstone, unwinder);
+    return;
+  }
+
+  GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
+                                       main_thread);
+  if (gwp_asan_crash_data.CrashIsMine()) {
+    gwp_asan_crash_data.AddCauseProtos(tombstone, unwinder);
+    return;
+  }
+
+  const siginfo *si = main_thread.siginfo;
+  auto fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
+  unwindstack::Maps* maps = unwinder->GetMaps();
+
   std::optional<std::string> cause;
   if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) {
-    if (si->si_addr < reinterpret_cast<void*>(4096)) {
+    if (fault_addr < 4096) {
       cause = "null pointer dereference";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0ffc)) {
+    } else if (fault_addr == 0xffff0ffc) {
       cause = "call to kuser_helper_version";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fe0)) {
+    } else if (fault_addr == 0xffff0fe0) {
       cause = "call to kuser_get_tls";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fc0)) {
+    } else if (fault_addr == 0xffff0fc0) {
       cause = "call to kuser_cmpxchg";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fa0)) {
+    } else if (fault_addr == 0xffff0fa0) {
       cause = "call to kuser_memory_barrier";
-    } else if (si->si_addr == reinterpret_cast<void*>(0xffff0f60)) {
+    } else if (fault_addr == 0xffff0f60) {
       cause = "call to kuser_cmpxchg64";
     } else {
-      cause = get_stack_overflow_cause(reinterpret_cast<uint64_t>(si->si_addr), regs->sp(), maps);
+      cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
     }
   } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
-    uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
     unwindstack::MapInfo* map_info = maps->Find(fault_addr);
-    if (map_info != nullptr && map_info->flags == PROT_EXEC) {
+    if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
       cause = "execute-only (no-read) memory access error; likely due to data in .text.";
     } else {
-      cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps);
+      cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
     }
   } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
     cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
@@ -139,7 +232,8 @@
   }
 
   if (cause) {
-    tombstone->mutable_cause()->set_human_readable(*cause);
+    Cause *cause_proto = tombstone->add_causes();
+    cause_proto->set_human_readable(*cause);
   }
 }
 
@@ -205,12 +299,49 @@
   }
 }
 
+void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
+                             unwindstack::Maps* maps) {
+  f->set_rel_pc(frame.rel_pc);
+  f->set_pc(frame.pc);
+  f->set_sp(frame.sp);
+
+  if (!frame.function_name.empty()) {
+    // TODO: Should this happen here, or on the display side?
+    char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr);
+    if (demangled_name) {
+      f->set_function_name(demangled_name);
+      free(demangled_name);
+    } else {
+      f->set_function_name(frame.function_name);
+    }
+  }
+
+  f->set_function_offset(frame.function_offset);
+
+  if (frame.map_start == frame.map_end) {
+    // No valid map associated with this frame.
+    f->set_file_name("<unknown>");
+  } else if (!frame.map_name.empty()) {
+    f->set_file_name(frame.map_name);
+  } else {
+    f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start));
+  }
+
+  f->set_file_map_offset(frame.map_elf_start_offset);
+
+  unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
+  if (map_info) {
+    f->set_build_id(map_info->GetPrintableBuildID());
+  }
+}
+
 static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
                         const ThreadInfo& thread_info, bool memory_dump = false) {
   Thread thread;
 
   thread.set_id(thread_info.tid);
   thread.set_name(thread_info.thread_name);
+  thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl);
 
   unwindstack::Maps* maps = unwinder->GetMaps();
   unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
@@ -225,20 +356,19 @@
         if (memory_dump) {
           MemoryDump dump;
 
+          dump.set_register_name(name);
+          unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
+          if (map_info) {
+            dump.set_mapping_name(map_info->name());
+          }
+
           char buf[256];
+          uint8_t tags[256 / kTagGranuleSize];
           size_t start_offset = 0;
-          ssize_t bytes = dump_memory(buf, sizeof(buf), &start_offset, &value, memory);
+          ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
           if (bytes == -1) {
             return;
           }
-
-          dump.set_register_name(name);
-
-          unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
-          if (map_info) {
-            dump.set_mapping_name(map_info->name);
-          }
-
           dump.set_begin_address(value);
 
           if (start_offset + bytes > sizeof(buf)) {
@@ -246,7 +376,8 @@
                              start_offset, bytes);
           }
 
-          dump.set_memory(buf, start_offset + bytes);
+          dump.set_memory(buf, bytes);
+          dump.set_tags(tags, bytes / kTagGranuleSize);
 
           *thread.add_memory_dump() = std::move(dump);
         }
@@ -264,42 +395,19 @@
                             unwinder->LastErrorAddress());
     }
   } else {
+    if (unwinder->elf_from_memory_not_file()) {
+      auto backtrace_note = thread.mutable_backtrace_note();
+      *backtrace_note->Add() =
+          "Function names and BuildId information is missing for some frames due";
+      *backtrace_note->Add() =
+          "to unreadable libraries. For unwinds of apps, only shared libraries";
+      *backtrace_note->Add() = "found under the lib/ directory are readable.";
+      *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable.";
+    }
     unwinder->SetDisplayBuildID(true);
     for (const auto& frame : unwinder->frames()) {
       BacktraceFrame* f = thread.add_current_backtrace();
-      f->set_rel_pc(frame.rel_pc);
-      f->set_pc(frame.pc);
-      f->set_sp(frame.sp);
-
-      if (!frame.function_name.empty()) {
-        // TODO: Should this happen here, or on the display side?
-        char* demangled_name =
-            __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr);
-        if (demangled_name) {
-          f->set_function_name(demangled_name);
-          free(demangled_name);
-        } else {
-          f->set_function_name(frame.function_name);
-        }
-      }
-
-      f->set_function_offset(frame.function_offset);
-
-      if (frame.map_start == frame.map_end) {
-        // No valid map associated with this frame.
-        f->set_file_name("<unknown>");
-      } else if (!frame.map_name.empty()) {
-        f->set_file_name(frame.map_name);
-      } else {
-        f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start));
-      }
-
-      f->set_file_map_offset(frame.map_elf_start_offset);
-
-      unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
-      if (map_info) {
-        f->set_build_id(map_info->GetPrintableBuildID());
-      }
+      fill_in_backtrace_frame(f, frame, maps);
     }
   }
 
@@ -318,21 +426,21 @@
 
   for (const auto& map_info : *maps) {
     auto* map = tombstone->add_memory_mappings();
-    map->set_begin_address(map_info->start);
-    map->set_end_address(map_info->end);
-    map->set_offset(map_info->offset);
+    map->set_begin_address(map_info->start());
+    map->set_end_address(map_info->end());
+    map->set_offset(map_info->offset());
 
-    if (map_info->flags & PROT_READ) {
+    if (map_info->flags() & PROT_READ) {
       map->set_read(true);
     }
-    if (map_info->flags & PROT_WRITE) {
+    if (map_info->flags() & PROT_WRITE) {
       map->set_write(true);
     }
-    if (map_info->flags & PROT_EXEC) {
+    if (map_info->flags() & PROT_EXEC) {
       map->set_execute(true);
     }
 
-    map->set_mapping_name(map_info->name);
+    map->set_mapping_name(map_info->name());
 
     std::string build_id = map_info->GetPrintableBuildID();
     if (!build_id.empty()) {
@@ -423,6 +531,14 @@
   dump_log_file(tombstone, "main", pid);
 }
 
+static std::optional<uint64_t> read_uptime_secs() {
+  std::string uptime;
+  if (!android::base::ReadFileToString("/proc/uptime", &uptime)) {
+    return {};
+  }
+  return strtoll(uptime.c_str(), nullptr, 10);
+}
+
 void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
                              const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
                              const ProcessInfo& process_info, const OpenFilesList* open_files) {
@@ -433,13 +549,33 @@
   result.set_revision(android::base::GetProperty("ro.revision", "unknown"));
   result.set_timestamp(get_timestamp());
 
+  std::optional<uint64_t> system_uptime = read_uptime_secs();
+  if (system_uptime) {
+    android::procinfo::ProcessInfo proc_info;
+    std::string error;
+    if (android::procinfo::GetProcessInfo(target_thread, &proc_info, &error)) {
+      uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK);
+      result.set_process_uptime(*system_uptime - starttime);
+    } else {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read process info: %s",
+                            error.c_str());
+    }
+  } else {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read /proc/uptime: %s",
+                          strerror(errno));
+  }
+
   const ThreadInfo& main_thread = threads.at(target_thread);
   result.set_pid(main_thread.pid);
   result.set_tid(main_thread.tid);
   result.set_uid(main_thread.uid);
   result.set_selinux_label(main_thread.selinux_label);
 
-  result.set_process_name(main_thread.process_name);
+  auto cmd_line = result.mutable_command_line();
+  for (const auto& arg : main_thread.command_line) {
+    *cmd_line->Add() = arg;
+  }
+
   if (!main_thread.siginfo) {
     async_safe_fatal("siginfo missing");
   }
@@ -458,7 +594,7 @@
 
   if (process_info.has_fault_address) {
     sig.set_has_fault_address(true);
-    sig.set_fault_address(process_info.untagged_fault_address);
+    sig.set_fault_address(process_info.maybe_tagged_fault_address);
   }
 
   *result.mutable_signal_info() = sig;
@@ -473,8 +609,7 @@
     }
   }
 
-  dump_probable_cause(&result, main_thread.siginfo, unwinder->GetMaps(),
-                      main_thread.registers.get());
+  dump_probable_cause(&result, unwinder, process_info, main_thread);
 
   dump_mappings(&result, unwinder);
 
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 187379d..b780b22 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <async_safe/log.h>
 
@@ -71,9 +72,17 @@
 
 static void print_thread_header(CallbackType callback, const Tombstone& tombstone,
                                 const Thread& thread, bool should_log) {
+  const char* process_name = "<unknown>";
+  if (!tombstone.command_line().empty()) {
+    process_name = tombstone.command_line()[0].c_str();
+    CB(should_log, "Cmdline: %s", android::base::Join(tombstone.command_line(), " ").c_str());
+  }
   CB(should_log, "pid: %d, tid: %d, name: %s  >>> %s <<<", tombstone.pid(), thread.id(),
-     thread.name().c_str(), tombstone.process_name().c_str());
+     thread.name().c_str(), process_name);
   CB(should_log, "uid: %d", tombstone.uid());
+  if (thread.tagged_addr_ctrl() != -1) {
+    CB(should_log, "tagged_addr_ctrl: %016" PRIx64, thread.tagged_addr_ctrl());
+  }
 }
 
 static void print_register_row(CallbackType callback, int word_size,
@@ -136,12 +145,11 @@
   print_register_row(callback, word_size, special_row, should_log);
 }
 
-static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
-                                   const Thread& thread, bool should_log) {
-  CBS("");
-  CB(should_log, "backtrace:");
+static void print_backtrace(CallbackType callback, const Tombstone& tombstone,
+                            const google::protobuf::RepeatedPtrField<BacktraceFrame>& backtrace,
+                            bool should_log) {
   int index = 0;
-  for (const auto& frame : thread.current_backtrace()) {
+  for (const auto& frame : backtrace) {
     std::string function;
 
     if (!frame.function_name().empty()) {
@@ -159,16 +167,36 @@
   }
 }
 
+static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
+                                   const Thread& thread, bool should_log) {
+  CBS("");
+  CB(should_log, "backtrace:");
+  if (!thread.backtrace_note().empty()) {
+    CB(should_log, "  NOTE: %s",
+       android::base::Join(thread.backtrace_note(), "\n  NOTE: ").c_str());
+  }
+  print_backtrace(callback, tombstone, thread.current_backtrace(), should_log);
+}
+
 static void print_thread_memory_dump(CallbackType callback, const Tombstone& tombstone,
                                      const Thread& thread) {
   static constexpr size_t bytes_per_line = 16;
+  static_assert(bytes_per_line == kTagGranuleSize);
   int word_size = pointer_width(tombstone);
   for (const auto& mem : thread.memory_dump()) {
     CBS("");
-    CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str());
+    if (mem.mapping_name().empty()) {
+      CBS("memory near %s:", mem.register_name().c_str());
+    } else {
+      CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str());
+    }
     uint64_t addr = mem.begin_address();
     for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) {
-      std::string line = StringPrintf("    %0*" PRIx64, word_size * 2, addr + offset);
+      uint64_t tagged_addr = addr;
+      if (mem.tags().size() > offset / kTagGranuleSize) {
+        tagged_addr |= static_cast<uint64_t>(mem.tags()[offset / kTagGranuleSize]) << 56;
+      }
+      std::string line = StringPrintf("    %0*" PRIx64, word_size * 2, tagged_addr + offset);
 
       size_t bytes = std::min(bytes_per_line, mem.memory().size() - offset);
       for (size_t i = 0; i < bytes; i += word_size) {
@@ -231,9 +259,8 @@
         sender_desc.c_str(), fault_addr_desc.c_str());
   }
 
-  if (tombstone.has_cause()) {
-    const Cause& cause = tombstone.cause();
-    CBL("Cause: %s", cause.human_readable().c_str());
+  if (tombstone.causes_size() == 1) {
+    CBL("Cause: %s", tombstone.causes(0).human_readable().c_str());
   }
 
   if (!tombstone.abort_message().empty()) {
@@ -242,6 +269,36 @@
 
   print_thread_registers(callback, tombstone, thread, true);
   print_thread_backtrace(callback, tombstone, thread, true);
+
+  if (tombstone.causes_size() > 1) {
+    CBS("");
+    CBS("Note: multiple potential causes for this crash were detected, listing them in decreasing "
+        "order of probability.");
+  }
+
+  for (const Cause& cause : tombstone.causes()) {
+    if (tombstone.causes_size() > 1) {
+      CBS("");
+      CBS("Cause: %s", cause.human_readable().c_str());
+    }
+
+    if (cause.has_memory_error() && cause.memory_error().has_heap()) {
+      const HeapObject& heap_object = cause.memory_error().heap();
+
+      if (heap_object.deallocation_backtrace_size() != 0) {
+        CBS("");
+        CBS("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
+        print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), false);
+      }
+
+      if (heap_object.allocation_backtrace_size() != 0) {
+        CBS("");
+        CBS("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
+        print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), false);
+      }
+    }
+  }
+
   print_thread_memory_dump(callback, tombstone, thread);
 
   CBS("");
@@ -313,6 +370,7 @@
   CBL("Revision: '%s'", tombstone.revision().c_str());
   CBL("ABI: '%s'", abi_string(tombstone));
   CBL("Timestamp: %s", tombstone.timestamp().c_str());
+  CBL("Process uptime: %ds", tombstone.process_uptime());
 
   // Process header
   const auto& threads = tombstone.threads();
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 6f13ed4..2c645b5 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -125,8 +125,9 @@
 
 #define MEMORY_BYTES_TO_DUMP 256
 #define MEMORY_BYTES_PER_LINE 16
+static_assert(MEMORY_BYTES_PER_LINE == kTagGranuleSize);
 
-ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr,
+ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
                     unwindstack::Memory* memory) {
   // Align the address to the number of bytes per line to avoid confusing memory tag output if
   // memory is tagged and we start from a misaligned address. Start 32 bytes before the address.
@@ -154,17 +155,17 @@
     bytes &= ~(sizeof(uintptr_t) - 1);
   }
 
-  *start_offset = 0;
   bool skip_2nd_read = false;
   if (bytes == 0) {
     // In this case, we might want to try another read at the beginning of
     // the next page only if it's within the amount of memory we would have
     // read.
     size_t page_size = sysconf(_SC_PAGE_SIZE);
-    *start_offset = ((*addr + (page_size - 1)) & ~(page_size - 1)) - *addr;
-    if (*start_offset == 0 || *start_offset >= len) {
+    uint64_t next_page = (*addr + (page_size - 1)) & ~(page_size - 1);
+    if (next_page == *addr || next_page >= *addr + len) {
       skip_2nd_read = true;
     }
+    *addr = next_page;
   }
 
   if (bytes < len && !skip_2nd_read) {
@@ -174,8 +175,7 @@
     // into a readable map. Only requires one extra read because a map has
     // to contain at least one page, and the total number of bytes to dump
     // is smaller than a page.
-    size_t bytes2 = memory->Read(*addr + *start_offset + bytes, static_cast<uint8_t*>(out) + bytes,
-                                 len - bytes - *start_offset);
+    size_t bytes2 = memory->Read(*addr + bytes, static_cast<uint8_t*>(out) + bytes, len - bytes);
     bytes += bytes2;
     if (bytes2 > 0 && bytes % sizeof(uintptr_t) != 0) {
       // This should never happen, but we'll try and continue any way.
@@ -190,15 +190,24 @@
     return -1;
   }
 
+  for (uint64_t tag_granule = 0; tag_granule < bytes / kTagGranuleSize; ++tag_granule) {
+    long tag = memory->ReadTag(*addr + kTagGranuleSize * tag_granule);
+    if (tag_granule < tags_len) {
+      tags[tag_granule] = tag >= 0 ? tag : 0;
+    } else {
+      ALOGE("Insufficient space for tags");
+    }
+  }
+
   return bytes;
 }
 
 void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const std::string& label) {
   // Dump 256 bytes
   uintptr_t data[MEMORY_BYTES_TO_DUMP / sizeof(uintptr_t)];
-  size_t start_offset = 0;
+  uint8_t tags[MEMORY_BYTES_TO_DUMP / kTagGranuleSize];
 
-  ssize_t bytes = dump_memory(data, sizeof(data), &start_offset, &addr, memory);
+  ssize_t bytes = dump_memory(data, sizeof(data), tags, sizeof(tags), &addr, memory);
   if (bytes == -1) {
     return;
   }
@@ -212,38 +221,27 @@
   // On 32-bit machines, there are still 16 bytes per line but addresses and
   // words are of course presented differently.
   uintptr_t* data_ptr = data;
-  size_t current = 0;
-  size_t total_bytes = start_offset + bytes;
-  for (size_t line = 0; line < MEMORY_BYTES_TO_DUMP / MEMORY_BYTES_PER_LINE; line++) {
-    uint64_t tagged_addr = addr;
-    long tag = memory->ReadTag(addr);
-    if (tag >= 0) {
-      tagged_addr |= static_cast<uint64_t>(tag) << 56;
-    }
+  uint8_t* tags_ptr = tags;
+  for (size_t line = 0; line < static_cast<size_t>(bytes) / MEMORY_BYTES_PER_LINE; line++) {
+    uint64_t tagged_addr = addr | static_cast<uint64_t>(*tags_ptr++) << 56;
     std::string logline;
     android::base::StringAppendF(&logline, "    %" PRIPTR, tagged_addr);
 
     addr += MEMORY_BYTES_PER_LINE;
     std::string ascii;
     for (size_t i = 0; i < MEMORY_BYTES_PER_LINE / sizeof(uintptr_t); i++) {
-      if (current >= start_offset && current + sizeof(uintptr_t) <= total_bytes) {
-        android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr));
+      android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr));
 
-        // Fill out the ascii string from the data.
-        uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr);
-        for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) {
-          if (*ptr >= 0x20 && *ptr < 0x7f) {
-            ascii += *ptr;
-          } else {
-            ascii += '.';
-          }
+      // Fill out the ascii string from the data.
+      uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr);
+      for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) {
+        if (*ptr >= 0x20 && *ptr < 0x7f) {
+          ascii += *ptr;
+        } else {
+          ascii += '.';
         }
-        data_ptr++;
-      } else {
-        logline += ' ' + std::string(sizeof(uintptr_t) * 2, '-');
-        ascii += std::string(sizeof(uintptr_t), '.');
       }
-      current += sizeof(uintptr_t);
+      data_ptr++;
     }
     _LOG(log, logtype::MEMORY, "%s  %s\n", logline.c_str(), ascii.c_str());
   }
diff --git a/debuggerd/proto/Android.bp b/debuggerd/proto/Android.bp
index b78224b..73cf573 100644
--- a/debuggerd/proto/Android.bp
+++ b/debuggerd/proto/Android.bp
@@ -31,6 +31,7 @@
 
     stl: "libc++_static",
     apex_available: [
+        "//apex_available:platform",
         "com.android.runtime",
     ],
 
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index 2c7156b..22fc30e 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -17,18 +17,21 @@
   uint32 uid = 7;
   string selinux_label = 8;
 
-  string process_name = 9;
+  repeated string command_line = 9;
+
+  // Process uptime in seconds.
+  uint32 process_uptime = 20;
 
   Signal signal_info = 10;
   string abort_message = 14;
-  Cause cause = 15;
+  repeated Cause causes = 15;
 
   map<uint32, Thread> threads = 16;
   repeated MemoryMapping memory_mappings = 17;
   repeated LogBuffer log_buffers = 18;
   repeated FD open_fds = 19;
 
-  reserved 20 to 999;
+  reserved 21 to 999;
 }
 
 enum Architecture {
@@ -57,10 +60,52 @@
   reserved 10 to 999;
 }
 
+message HeapObject {
+  uint64 address = 1;
+  uint64 size = 2;
+
+  uint64 allocation_tid = 3;
+  repeated BacktraceFrame allocation_backtrace = 4;
+
+  uint64 deallocation_tid = 5;
+  repeated BacktraceFrame deallocation_backtrace = 6;
+}
+
+message MemoryError {
+  enum Tool {
+    GWP_ASAN = 0;
+    SCUDO = 1;
+
+    reserved 2 to 999;
+  }
+  Tool tool = 1;
+
+  enum Type {
+    UNKNOWN = 0;
+    USE_AFTER_FREE = 1;
+    DOUBLE_FREE = 2;
+    INVALID_FREE = 3;
+    BUFFER_OVERFLOW = 4;
+    BUFFER_UNDERFLOW = 5;
+
+    reserved 6 to 999;
+  }
+  Type type = 2;
+
+  oneof location {
+    HeapObject heap = 3;
+  }
+
+  reserved 4 to 999;
+}
+
 message Cause {
   string human_readable = 1;
+  oneof details {
+    MemoryError memory_error = 2;
+  }
 
-  reserved 2 to 999;
+  reserved 3 to 999;
 }
 
 message Register {
@@ -74,10 +119,12 @@
   int32 id = 1;
   string name = 2;
   repeated Register registers = 3;
+  repeated string backtrace_note = 7;
   repeated BacktraceFrame current_backtrace = 4;
   repeated MemoryDump memory_dump = 5;
+  int64 tagged_addr_ctrl = 6;
 
-  reserved 6 to 999;
+  reserved 8 to 999;
 }
 
 message BacktraceFrame {
@@ -100,8 +147,9 @@
   string mapping_name = 2;
   uint64 begin_address = 3;
   bytes memory = 4;
+  bytes tags = 5;
 
-  reserved 5 to 999;
+  reserved 6 to 999;
 }
 
 message MemoryMapping {
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index 1585cc6..21887ab 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -24,7 +24,7 @@
 rt_sigprocmask: 1
 rt_sigaction: 1
 rt_tgsigqueueinfo: 1
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS
 madvise: 1
 mprotect: arg2 in 0x1|0x2
 munmap: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index cd5aad4..90843fc 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -34,7 +34,12 @@
 rt_tgsigqueueinfo: 1
 
 #define PR_SET_VMA 0x53564d41
+#if defined(__aarch64__)
+// PR_PAC_RESET_KEYS happens on aarch64 in pthread_create path.
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS
+#else
 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA
+#endif
 
 #if 0
 libminijail on vendor partitions older than P does not have constants from <sys/mman.h>.
diff --git a/debuggerd/util.cpp b/debuggerd/util.cpp
index f3bff8c..ce0fd30 100644
--- a/debuggerd/util.cpp
+++ b/debuggerd/util.cpp
@@ -26,10 +26,31 @@
 #include <android-base/strings.h>
 #include "protocol.h"
 
+std::vector<std::string> get_command_line(pid_t pid) {
+  std::vector<std::string> result;
+
+  std::string cmdline;
+  android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &cmdline);
+
+  auto it = cmdline.cbegin();
+  while (it != cmdline.cend()) {
+    // string::iterator is a wrapped type, not a raw char*.
+    auto terminator = std::find(it, cmdline.cend(), '\0');
+    result.emplace_back(it, terminator);
+    it = std::find_if(terminator, cmdline.cend(), [](char c) { return c != '\0'; });
+  }
+  if (result.empty()) {
+    result.emplace_back("<unknown>");
+  }
+
+  return result;
+}
+
 std::string get_process_name(pid_t pid) {
   std::string result = "<unknown>";
   android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &result);
-  return result;
+  // We only want the name, not the whole command line, so truncate at the first NUL.
+  return result.c_str();
 }
 
 std::string get_thread_name(pid_t tid) {
diff --git a/debuggerd/util.h b/debuggerd/util.h
index 07e7e99..ec2862a 100644
--- a/debuggerd/util.h
+++ b/debuggerd/util.h
@@ -17,10 +17,12 @@
 #pragma once
 
 #include <string>
+#include <vector>
 
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
+std::vector<std::string> get_command_line(pid_t pid);
 std::string get_process_name(pid_t pid);
 std::string get_thread_name(pid_t tid);
 
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index a1f1c17..2c70778 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -55,6 +55,7 @@
         "tcp.cpp",
         "udp.cpp",
         "util.cpp",
+        "vendor_boot_img_utils.cpp",
         "fastboot_driver.cpp",
     ],
 
@@ -75,7 +76,9 @@
     ],
 
     header_libs: [
+        "avb_headers",
         "bootimg_headers",
+        "libstorage_literals_headers",
     ],
 
     export_header_lib_headers: [
@@ -124,6 +127,7 @@
         "-Wextra",
         "-Werror",
         "-Wvla",
+        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
     ],
     rtti: true,
 
@@ -138,6 +142,12 @@
 
     recovery: true,
 
+    product_variables: {
+        debuggable: {
+            cppflags: ["-DFB_ENABLE_FETCH"],
+        },
+    },
+
     srcs: [
         "device/commands.cpp",
         "device/fastboot_device.cpp",
@@ -173,7 +183,7 @@
     ],
 
     static_libs: [
-        "libgtest_prod",
+        "libc++fs",
         "libhealthhalutils",
         "libsnapshot_cow",
         "libsnapshot_nobinder",
@@ -182,8 +192,10 @@
 
     header_libs: [
         "avb_headers",
+        "libgtest_prod_headers",
         "libsnapshot_headers",
-    ]
+        "libstorage_literals_headers",
+    ],
 }
 
 cc_defaults {
@@ -196,6 +208,8 @@
         "-Wextra",
         "-Werror",
         "-Wunreachable-code",
+        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+        "-D_FILE_OFFSET_BITS=64"
     ],
 
     target: {
@@ -262,12 +276,17 @@
         "tcp.cpp",
         "udp.cpp",
         "util.cpp",
+        "vendor_boot_img_utils.cpp",
         "fastboot_driver.cpp",
     ],
 
     // Only version the final binaries
     use_version_lib: false,
     static_libs: ["libbuildversion"],
+    header_libs: [
+        "avb_headers",
+        "libstorage_literals_headers",
+    ],
 
     generated_headers: ["platform_tools_version"],
 
@@ -359,3 +378,33 @@
         },
     },
 }
+
+cc_test_host {
+    name: "fastboot_vendor_boot_img_utils_test",
+    srcs: ["vendor_boot_img_utils_test.cpp"],
+    static_libs: [
+        "libbase",
+        "libc++fs",
+        "libfastboot",
+        "libgmock",
+        "liblog",
+    ],
+    header_libs: [
+        "avb_headers",
+        "bootimg_headers",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    data: [
+        ":fastboot_test_dtb",
+        ":fastboot_test_bootconfig",
+        ":fastboot_test_vendor_ramdisk_none",
+        ":fastboot_test_vendor_ramdisk_platform",
+        ":fastboot_test_vendor_ramdisk_replace",
+        ":fastboot_test_vendor_boot_v3",
+        ":fastboot_test_vendor_boot_v4_without_frag",
+        ":fastboot_test_vendor_boot_v4_with_frag"
+    ],
+}
diff --git a/fastboot/bootimg_utils.cpp b/fastboot/bootimg_utils.cpp
index 2c0989e..d2056aa 100644
--- a/fastboot/bootimg_utils.cpp
+++ b/fastboot/bootimg_utils.cpp
@@ -34,22 +34,22 @@
 #include <stdlib.h>
 #include <string.h>
 
-static void bootimg_set_cmdline_v3(boot_img_hdr_v3* h, const std::string& cmdline) {
+static void bootimg_set_cmdline_v3_and_above(boot_img_hdr_v3* h, const std::string& cmdline) {
     if (cmdline.size() >= sizeof(h->cmdline)) die("command line too large: %zu", cmdline.size());
     strcpy(reinterpret_cast<char*>(h->cmdline), cmdline.c_str());
 }
 
 void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline) {
-    if (h->header_version == 3) {
-        return bootimg_set_cmdline_v3(reinterpret_cast<boot_img_hdr_v3*>(h), cmdline);
+    if (h->header_version >= 3) {
+        return bootimg_set_cmdline_v3_and_above(reinterpret_cast<boot_img_hdr_v3*>(h), cmdline);
     }
     if (cmdline.size() >= sizeof(h->cmdline)) die("command line too large: %zu", cmdline.size());
     strcpy(reinterpret_cast<char*>(h->cmdline), cmdline.c_str());
 }
 
-static boot_img_hdr_v3* mkbootimg_v3(const std::vector<char>& kernel,
-                                     const std::vector<char>& ramdisk, const boot_img_hdr_v2& src,
-                                     std::vector<char>* out) {
+static void mkbootimg_v3_and_above(const std::vector<char>& kernel,
+                                   const std::vector<char>& ramdisk, const boot_img_hdr_v2& src,
+                                   std::vector<char>* out) {
 #define V3_PAGE_SIZE 4096
     const size_t page_mask = V3_PAGE_SIZE - 1;
     int64_t kernel_actual = (kernel.size() + page_mask) & (~page_mask);
@@ -65,22 +65,27 @@
     hdr->ramdisk_size = ramdisk.size();
     hdr->os_version = src.os_version;
     hdr->header_size = sizeof(boot_img_hdr_v3);
-    hdr->header_version = 3;
+    hdr->header_version = src.header_version;
+
+    if (src.header_version >= 4) {
+        auto hdr_v4 = reinterpret_cast<boot_img_hdr_v4*>(hdr);
+        hdr_v4->signature_size = 0;
+    }
 
     memcpy(hdr->magic + V3_PAGE_SIZE, kernel.data(), kernel.size());
     memcpy(hdr->magic + V3_PAGE_SIZE + kernel_actual, ramdisk.data(), ramdisk.size());
-
-    return hdr;
 }
 
-boot_img_hdr_v2* mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk,
-                           const std::vector<char>& second, const std::vector<char>& dtb,
-                           size_t base, const boot_img_hdr_v2& src, std::vector<char>* out) {
-    if (src.header_version == 3) {
+void mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk,
+               const std::vector<char>& second, const std::vector<char>& dtb, size_t base,
+               const boot_img_hdr_v2& src, std::vector<char>* out) {
+    if (src.header_version >= 3) {
         if (!second.empty() || !dtb.empty()) {
-            die("Second stage bootloader and dtb not supported in v3 boot image\n");
+            die("Second stage bootloader and dtb not supported in v%d boot image\n",
+                src.header_version);
         }
-        return reinterpret_cast<boot_img_hdr_v2*>(mkbootimg_v3(kernel, ramdisk, src, out));
+        mkbootimg_v3_and_above(kernel, ramdisk, src, out);
+        return;
     }
     const size_t page_mask = src.page_size - 1;
 
@@ -122,5 +127,4 @@
            second.size());
     memcpy(hdr->magic + hdr->page_size + kernel_actual + ramdisk_actual + second_actual, dtb.data(),
            dtb.size());
-    return hdr;
 }
diff --git a/fastboot/bootimg_utils.h b/fastboot/bootimg_utils.h
index b7cf9bd..0eb003d 100644
--- a/fastboot/bootimg_utils.h
+++ b/fastboot/bootimg_utils.h
@@ -35,7 +35,8 @@
 #include <string>
 #include <vector>
 
-boot_img_hdr_v2* mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk,
-                           const std::vector<char>& second, const std::vector<char>& dtb,
-                           size_t base, const boot_img_hdr_v2& src, std::vector<char>* out);
+void mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk,
+               const std::vector<char>& second, const std::vector<char>& dtb, size_t base,
+               const boot_img_hdr_v2& src, std::vector<char>* out);
+
 void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline);
diff --git a/fastboot/constants.h b/fastboot/constants.h
index ba43ca5..4ea68da 100644
--- a/fastboot/constants.h
+++ b/fastboot/constants.h
@@ -35,6 +35,7 @@
 #define FB_CMD_OEM "oem"
 #define FB_CMD_GSI "gsi"
 #define FB_CMD_SNAPSHOT_UPDATE "snapshot-update"
+#define FB_CMD_FETCH "fetch"
 
 #define RESPONSE_OKAY "OKAY"
 #define RESPONSE_FAIL "FAIL"
@@ -77,3 +78,4 @@
 #define FB_VAR_FIRST_API_LEVEL "first-api-level"
 #define FB_VAR_SECURITY_PATCH_LEVEL "security-patch-level"
 #define FB_VAR_TREBLE_ENABLED "treble-enabled"
+#define FB_VAR_MAX_FETCH_SIZE "max-fetch-size"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index b2b6a9e..0a72812 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -16,6 +16,7 @@
 
 #include "commands.h"
 
+#include <inttypes.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 
@@ -36,6 +37,7 @@
 #include <liblp/builder.h>
 #include <liblp/liblp.h>
 #include <libsnapshot/snapshot.h>
+#include <storage_literals/storage_literals.h>
 #include <uuid/uuid.h>
 
 #include "constants.h"
@@ -43,6 +45,12 @@
 #include "flashing.h"
 #include "utility.h"
 
+#ifdef FB_ENABLE_FETCH
+static constexpr bool kEnableFetch = true;
+#else
+static constexpr bool kEnableFetch = false;
+#endif
+
 using android::fs_mgr::MetadataBuilder;
 using ::android::hardware::hidl_string;
 using ::android::hardware::boot::V1_0::BoolResult;
@@ -54,6 +62,8 @@
 using android::snapshot::SnapshotManager;
 using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl;
 
+using namespace android::storage_literals;
+
 struct VariableHandlers {
     // Callback to retrieve the value of a single variable.
     std::function<bool(FastbootDevice*, const std::vector<std::string>&, std::string*)> get;
@@ -136,7 +146,9 @@
             {FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}},
             {FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}},
             {FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}},
-            {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}};
+            {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}},
+            {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}},
+    };
 
     if (args.size() < 2) {
         return device->WriteFail("Missing argument");
@@ -380,13 +392,13 @@
 
     struct sockaddr_un addr = {.sun_family = AF_UNIX};
     strncpy(addr.sun_path, "/dev/socket/recovery", sizeof(addr.sun_path) - 1);
-    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+    if (connect(sock.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) {
         PLOG(ERROR) << "Couldn't connect to recovery";
         return false;
     }
     // Switch to recovery will not update the boot reason since it does not
     // require a reboot.
-    auto ret = write(sock, &msg_switch_to_recovery, sizeof(msg_switch_to_recovery));
+    auto ret = write(sock.get(), &msg_switch_to_recovery, sizeof(msg_switch_to_recovery));
     if (ret != sizeof(msg_switch_to_recovery)) {
         PLOG(ERROR) << "Couldn't write message to switch to recovery";
         return false;
@@ -671,3 +683,175 @@
     }
     return device->WriteStatus(FastbootResult::OKAY, "Success");
 }
+
+namespace {
+// Helper of FetchHandler.
+class PartitionFetcher {
+  public:
+    static bool Fetch(FastbootDevice* device, const std::vector<std::string>& args) {
+        if constexpr (!kEnableFetch) {
+            return device->WriteFail("Fetch is not allowed on user build");
+        }
+
+        if (GetDeviceLockStatus()) {
+            return device->WriteFail("Fetch is not allowed on locked devices");
+        }
+
+        PartitionFetcher fetcher(device, args);
+        if (fetcher.Open()) {
+            fetcher.Fetch();
+        }
+        CHECK(fetcher.ret_.has_value());
+        return *fetcher.ret_;
+    }
+
+  private:
+    PartitionFetcher(FastbootDevice* device, const std::vector<std::string>& args)
+        : device_(device), args_(&args) {}
+    // Return whether the partition is successfully opened.
+    // If successfully opened, ret_ is left untouched. Otherwise, ret_ is set to the value
+    // that FetchHandler should return.
+    bool Open() {
+        if (args_->size() < 2) {
+            ret_ = device_->WriteFail("Missing partition arg");
+            return false;
+        }
+
+        partition_name_ = args_->at(1);
+        if (std::find(kAllowedPartitions.begin(), kAllowedPartitions.end(), partition_name_) ==
+            kAllowedPartitions.end()) {
+            ret_ = device_->WriteFail("Fetch is only allowed on [" +
+                                      android::base::Join(kAllowedPartitions, ", ") + "]");
+            return false;
+        }
+
+        if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) {
+            ret_ = device_->WriteFail(
+                    android::base::StringPrintf("Cannot open %s", partition_name_.c_str()));
+            return false;
+        }
+
+        partition_size_ = get_block_device_size(handle_.fd());
+        if (partition_size_ == 0) {
+            ret_ = device_->WriteOkay(android::base::StringPrintf("Partition %s has size 0",
+                                                                  partition_name_.c_str()));
+            return false;
+        }
+
+        start_offset_ = 0;
+        if (args_->size() >= 3) {
+            if (!android::base::ParseUint(args_->at(2), &start_offset_)) {
+                ret_ = device_->WriteFail("Invalid offset, must be integer");
+                return false;
+            }
+            if (start_offset_ > std::numeric_limits<off64_t>::max()) {
+                ret_ = device_->WriteFail(
+                        android::base::StringPrintf("Offset overflows: %" PRIx64, start_offset_));
+                return false;
+            }
+        }
+        if (start_offset_ > partition_size_) {
+            ret_ = device_->WriteFail(android::base::StringPrintf(
+                    "Invalid offset 0x%" PRIx64 ", partition %s has size 0x%" PRIx64, start_offset_,
+                    partition_name_.c_str(), partition_size_));
+            return false;
+        }
+        uint64_t maximum_total_size_to_read = partition_size_ - start_offset_;
+        total_size_to_read_ = maximum_total_size_to_read;
+        if (args_->size() >= 4) {
+            if (!android::base::ParseUint(args_->at(3), &total_size_to_read_)) {
+                ret_ = device_->WriteStatus(FastbootResult::FAIL, "Invalid size, must be integer");
+                return false;
+            }
+        }
+        if (total_size_to_read_ == 0) {
+            ret_ = device_->WriteOkay("Read 0 bytes");
+            return false;
+        }
+        if (total_size_to_read_ > maximum_total_size_to_read) {
+            ret_ = device_->WriteFail(android::base::StringPrintf(
+                    "Invalid size to read 0x%" PRIx64 ", partition %s has size 0x%" PRIx64
+                    " and fetching from offset 0x%" PRIx64,
+                    total_size_to_read_, partition_name_.c_str(), partition_size_, start_offset_));
+            return false;
+        }
+
+        if (total_size_to_read_ > kMaxFetchSizeDefault) {
+            ret_ = device_->WriteFail(android::base::StringPrintf(
+                    "Cannot fetch 0x%" PRIx64
+                    " bytes because it exceeds maximum transport size 0x%x",
+                    partition_size_, kMaxDownloadSizeDefault));
+            return false;
+        }
+
+        return true;
+    }
+
+    // Assume Open() returns true.
+    // After execution, ret_ is set to the value that FetchHandler should return.
+    void Fetch() {
+        CHECK(start_offset_ <= std::numeric_limits<off64_t>::max());
+        if (lseek64(handle_.fd(), start_offset_, SEEK_SET) != static_cast<off64_t>(start_offset_)) {
+            ret_ = device_->WriteFail(android::base::StringPrintf(
+                    "On partition %s, unable to lseek(0x%" PRIx64 ": %s", partition_name_.c_str(),
+                    start_offset_, strerror(errno)));
+            return;
+        }
+
+        if (!device_->WriteStatus(FastbootResult::DATA,
+                                  android::base::StringPrintf(
+                                          "%08x", static_cast<uint32_t>(total_size_to_read_)))) {
+            ret_ = false;
+            return;
+        }
+        uint64_t end_offset = start_offset_ + total_size_to_read_;
+        std::vector<char> buf(1_MiB);
+        uint64_t current_offset = start_offset_;
+        while (current_offset < end_offset) {
+            // On any error, exit. We can't return a status message to the driver because
+            // we are in the middle of writing data, so just let the driver guess what's wrong
+            // by ending the data stream prematurely.
+            uint64_t remaining = end_offset - current_offset;
+            uint64_t chunk_size = std::min<uint64_t>(buf.size(), remaining);
+            if (!android::base::ReadFully(handle_.fd(), buf.data(), chunk_size)) {
+                PLOG(ERROR) << std::hex << "Unable to read 0x" << chunk_size << " bytes from "
+                            << partition_name_ << " @ offset 0x" << current_offset;
+                ret_ = false;
+                return;
+            }
+            if (!device_->HandleData(false /* is read */, buf.data(), chunk_size)) {
+                PLOG(ERROR) << std::hex << "Unable to send 0x" << chunk_size << " bytes of "
+                            << partition_name_ << " @ offset 0x" << current_offset;
+                ret_ = false;
+                return;
+            }
+            current_offset += chunk_size;
+        }
+
+        ret_ = device_->WriteOkay(android::base::StringPrintf(
+                "Fetched %s (offset=0x%" PRIx64 ", size=0x%" PRIx64, partition_name_.c_str(),
+                start_offset_, total_size_to_read_));
+    }
+
+    static constexpr std::array<const char*, 3> kAllowedPartitions{
+            "vendor_boot",
+            "vendor_boot_a",
+            "vendor_boot_b",
+    };
+
+    FastbootDevice* device_;
+    const std::vector<std::string>* args_ = nullptr;
+    std::string partition_name_;
+    PartitionHandle handle_;
+    uint64_t partition_size_ = 0;
+    uint64_t start_offset_ = 0;
+    uint64_t total_size_to_read_ = 0;
+
+    // What FetchHandler should return.
+    std::optional<bool> ret_ = std::nullopt;
+};
+}  // namespace
+
+bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args) {
+    return PartitionFetcher::Fetch(device, args);
+}
diff --git a/fastboot/device/commands.h b/fastboot/device/commands.h
index c1324bc..345ae1a 100644
--- a/fastboot/device/commands.h
+++ b/fastboot/device/commands.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 constexpr unsigned int kMaxDownloadSizeDefault = 0x10000000;
+constexpr unsigned int kMaxFetchSizeDefault = 0x10000000;
 
 class FastbootDevice;
 
@@ -50,3 +51,4 @@
 bool OemCmdHandler(FastbootDevice* device, const std::vector<std::string>& args);
 bool GsiHandler(FastbootDevice* device, const std::vector<std::string>& args);
 bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string>& args);
+bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args);
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 35f3de0..64a934d 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -61,6 +61,7 @@
               {FB_CMD_OEM, OemCmdHandler},
               {FB_CMD_GSI, GsiHandler},
               {FB_CMD_SNAPSHOT_UPDATE, SnapshotUpdateHandler},
+              {FB_CMD_FETCH, FetchHandler},
       }),
       boot_control_hal_(IBootControl::getService()),
       health_hal_(get_health_service()),
@@ -137,14 +138,18 @@
 }
 
 bool FastbootDevice::HandleData(bool read, std::vector<char>* data) {
-    auto read_write_data_size = read ? this->get_transport()->Read(data->data(), data->size())
-                                     : this->get_transport()->Write(data->data(), data->size());
+    return HandleData(read, data->data(), data->size());
+}
+
+bool FastbootDevice::HandleData(bool read, char* data, uint64_t size) {
+    auto read_write_data_size = read ? this->get_transport()->Read(data, size)
+                                     : this->get_transport()->Write(data, size);
     if (read_write_data_size == -1) {
         LOG(ERROR) << (read ? "read from" : "write to") << " transport failed";
         return false;
     }
-    if (static_cast<size_t>(read_write_data_size) != data->size()) {
-        LOG(ERROR) << (read ? "read" : "write") << " expected " << data->size() << " bytes, got "
+    if (static_cast<size_t>(read_write_data_size) != size) {
+        LOG(ERROR) << (read ? "read" : "write") << " expected " << size << " bytes, got "
                    << read_write_data_size;
         return false;
     }
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index 23be721..3536136 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -40,6 +40,7 @@
     void ExecuteCommands();
     bool WriteStatus(FastbootResult result, const std::string& message);
     bool HandleData(bool read, std::vector<char>* data);
+    bool HandleData(bool read, char* data, uint64_t size);
     std::string GetCurrentSlot();
 
     // Shortcuts for writing status results.
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 333ca50..ee0aa58 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -27,6 +27,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <ext4_utils/ext4_utils.h>
 #include <fs_mgr_overlayfs.h>
@@ -162,7 +163,9 @@
                 partition_name == "boot_b")) {
         CopyAVBFooter(&data, block_device_size);
     }
-    WipeOverlayfsForPartition(device, partition_name);
+    if (android::base::GetProperty("ro.system.build.type", "") != "user") {
+        WipeOverlayfsForPartition(device, partition_name);
+    }
     int result = FlashBlockDevice(handle.fd(), data);
     sync();
     return result;
diff --git a/fastboot/device/usb.cpp b/fastboot/device/usb.cpp
index 4bee7b2..4115a6d 100644
--- a/fastboot/device/usb.cpp
+++ b/fastboot/device/usb.cpp
@@ -82,7 +82,7 @@
     int orig_len = len;
     while (len > 0) {
         int write_len = std::min(USB_FFS_BULK_SIZE, len);
-        int n = write(h->bulk_in, buf, write_len);
+        int n = write(h->bulk_in.get(), buf, write_len);
         if (n < 0) {
             D("ERROR: fd = %d, n = %d: %s", h->bulk_in.get(), n, strerror(errno));
             return -1;
@@ -103,7 +103,7 @@
     unsigned count = 0;
     while (len > 0) {
         int read_len = std::min(USB_FFS_BULK_SIZE, len);
-        int n = read(h->bulk_out, buf, read_len);
+        int n = read(h->bulk_out.get(), buf, read_len);
         if (n < 0) {
             D("ERROR: fd = %d, n = %d: %s", h->bulk_out.get(), n, strerror(errno));
             return -1;
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 7c6ac89..07ad902 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -77,7 +77,8 @@
 
 }  // namespace
 
-bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle) {
+bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
+                   bool read) {
     // We prioritize logical partitions over physical ones, and do this
     // consistently for other partition operations (like getvar:partition-size).
     if (LogicalPartitionExists(device, name)) {
@@ -89,7 +90,9 @@
         return false;
     }
 
-    unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), O_WRONLY | O_EXCL)));
+    int flags = (read ? O_RDONLY : O_WRONLY);
+    flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
+    unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
     if (fd < 0) {
         PLOG(ERROR) << "Failed to open block device: " << handle->path();
         return false;
@@ -201,12 +204,7 @@
 }
 
 bool GetDeviceLockStatus() {
-    std::string cmdline;
-    // Return lock status true if unable to read kernel command line.
-    if (!android::base::ReadFileToString("/proc/cmdline", &cmdline)) {
-        return true;
-    }
-    return cmdline.find("androidboot.verifiedbootstate=orange") == std::string::npos;
+    return android::base::GetProperty("ro.boot.verifiedbootstate", "") != "orange";
 }
 
 bool UpdateAllPartitionMetadata(FastbootDevice* device, const std::string& super_name,
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index 3b71ef0..c2646d7 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -75,7 +75,11 @@
 std::optional<std::string> FindPhysicalPartition(const std::string& name);
 bool LogicalPartitionExists(FastbootDevice* device, const std::string& name,
                             bool* is_zero_length = nullptr);
-bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle);
+
+// If read, partition is readonly. Else it is write only.
+bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
+                   bool read = false);
+
 bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
 std::vector<std::string> ListPartitions(FastbootDevice* device);
 bool GetDeviceLockStatus();
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index e7d8bc3..ee1eed8 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -33,6 +33,12 @@
 #include "flashing.h"
 #include "utility.h"
 
+#ifdef FB_ENABLE_FETCH
+static constexpr bool kEnableFetch = true;
+#else
+static constexpr bool kEnableFetch = false;
+#endif
+
 using ::android::hardware::boot::V1_0::BoolResult;
 using ::android::hardware::boot::V1_0::Slot;
 using ::android::hardware::boot::V1_1::MergeStatus;
@@ -509,3 +515,13 @@
     *message = android::base::GetProperty("ro.treble.enabled", "");
     return true;
 }
+
+bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                     std::string* message) {
+    if (!kEnableFetch) {
+        *message = "fetch not supported on user builds";
+        return false;
+    }
+    *message = android::base::StringPrintf("0x%X", kMaxFetchSizeDefault);
+    return true;
+}
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index c11e472..f40a025 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -80,6 +80,8 @@
                            std::string* message);
 bool GetTrebleEnabled(FastbootDevice* device, const std::vector<std::string>& args,
                       std::string* message);
+bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                     std::string* message);
 
 // Helpers for getvar all.
 std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device);
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 38be934..e5319a5 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -76,12 +76,15 @@
 #include "udp.h"
 #include "usb.h"
 #include "util.h"
+#include "vendor_boot_img_utils.h"
 
+using android::base::borrowed_fd;
 using android::base::ReadFully;
 using android::base::Split;
 using android::base::Trim;
 using android::base::unique_fd;
 using namespace std::string_literals;
+using namespace std::placeholders;
 
 static const char* serial = nullptr;
 
@@ -114,7 +117,7 @@
     enum fb_buffer_type type;
     void* data;
     int64_t sz;
-    int fd;
+    unique_fd fd;
     int64_t image_size;
 };
 
@@ -227,9 +230,9 @@
     fprintf(stderr, "(bootloader) %s\n", info.c_str());
 }
 
-static int64_t get_file_size(int fd) {
+static int64_t get_file_size(borrowed_fd fd) {
     struct stat sb;
-    if (fstat(fd, &sb) == -1) {
+    if (fstat(fd.get(), &sb) == -1) {
         die("could not get file size");
     }
     return sb.st_size;
@@ -408,12 +411,20 @@
             " gsi wipe|disable           Wipe or disable a GSI installation (fastbootd only).\n"
             " wipe-super [SUPER_EMPTY]   Wipe the super partition. This will reset it to\n"
             "                            contain an empty set of default dynamic partitions.\n"
+            " create-logical-partition NAME SIZE\n"
+            "                            Create a logical partition with the given name and\n"
+            "                            size, in the super partition.\n"
+            " delete-logical-partition NAME\n"
+            "                            Delete a logical partition with the given name.\n"
+            " resize-logical-partition NAME SIZE\n"
+            "                            Change the size of the named logical partition.\n"
             " snapshot-update cancel     On devices that support snapshot-based updates, cancel\n"
             "                            an in-progress update. This may make the device\n"
             "                            unbootable until it is reflashed.\n"
             " snapshot-update merge      On devices that support snapshot-based updates, finish\n"
             "                            an in-progress update if it is in the \"merging\"\n"
             "                            phase.\n"
+            " fetch PARTITION            Fetch a partition image from the device."
             "\n"
             "boot image:\n"
             " boot KERNEL [RAMDISK [SECOND]]\n"
@@ -466,7 +477,7 @@
             " --version                  Display version.\n"
             " --help, -h                 Show this message.\n"
         );
-    // clang-format off
+    // clang-format on
     return 0;
 }
 
@@ -519,10 +530,12 @@
     fprintf(stderr,"creating boot image...\n");
 
     std::vector<char> out;
-    boot_img_hdr_v2* boot_image_data = mkbootimg(kernel_data, ramdisk_data, second_stage_data,
-                                                 dtb_data, g_base_addr, g_boot_img_hdr, &out);
+    mkbootimg(kernel_data, ramdisk_data, second_stage_data, dtb_data, g_base_addr, g_boot_img_hdr,
+              &out);
 
-    if (!g_cmdline.empty()) bootimg_set_cmdline(boot_image_data, g_cmdline);
+    if (!g_cmdline.empty()) {
+        bootimg_set_cmdline(reinterpret_cast<boot_img_hdr_v2*>(out.data()), g_cmdline);
+    }
     fprintf(stderr, "creating boot image - %zu bytes\n", out.size());
     return out;
 }
@@ -640,34 +653,34 @@
     }
 }
 
-static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
+static unique_fd unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
     unique_fd fd(make_temporary_fd(entry_name));
 
     ZipEntry64 zip_entry;
     if (FindEntry(zip, entry_name, &zip_entry) != 0) {
         fprintf(stderr, "archive does not contain '%s'\n", entry_name);
         errno = ENOENT;
-        return -1;
+        return unique_fd();
     }
 
     fprintf(stderr, "extracting %s (%" PRIu64 " MB) to disk...", entry_name,
             zip_entry.uncompressed_length / 1024 / 1024);
     double start = now();
-    int error = ExtractEntryToFile(zip, &zip_entry, fd);
+    int error = ExtractEntryToFile(zip, &zip_entry, fd.get());
     if (error != 0) {
         die("\nfailed to extract '%s': %s", entry_name, ErrorCodeString(error));
     }
 
-    if (lseek(fd, 0, SEEK_SET) != 0) {
+    if (lseek(fd.get(), 0, SEEK_SET) != 0) {
         die("\nlseek on extracted file '%s' failed: %s", entry_name, strerror(errno));
     }
 
     fprintf(stderr, " took %.3fs\n", now() - start);
 
-    return fd.release();
+    return fd;
 }
 
-static void CheckRequirement(const std::string& cur_product, const std::string& var,
+static bool CheckRequirement(const std::string& cur_product, const std::string& var,
                              const std::string& product, bool invert,
                              const std::vector<std::string>& options) {
     Status("Checking '" + var + "'");
@@ -679,7 +692,7 @@
             double split = now();
             fprintf(stderr, "IGNORE, product is %s required only for %s [%7.3fs]\n",
                     cur_product.c_str(), product.c_str(), (split - start));
-            return;
+            return true;
         }
     }
 
@@ -688,7 +701,7 @@
         fprintf(stderr, "FAILED\n\n");
         fprintf(stderr, "Could not getvar for '%s' (%s)\n\n", var.c_str(),
                 fb->Error().c_str());
-        die("requirements not met!");
+        return false;
     }
 
     bool match = false;
@@ -708,7 +721,7 @@
     if (match) {
         double split = now();
         fprintf(stderr, "OKAY [%7.3fs]\n", (split - start));
-        return;
+        return true;
     }
 
     fprintf(stderr, "FAILED\n\n");
@@ -718,7 +731,7 @@
         fprintf(stderr, " or '%s'", it->c_str());
     }
     fprintf(stderr, ".\n\n");
-    die("requirements not met!");
+    return false;
 }
 
 bool ParseRequirementLine(const std::string& line, std::string* name, std::string* product,
@@ -782,7 +795,7 @@
     }
 }
 
-static void CheckRequirements(const std::string& data) {
+static void CheckRequirements(const std::string& data, bool force_flash) {
     std::string cur_product;
     if (fb->GetVar("product", &cur_product) != fastboot::SUCCESS) {
         fprintf(stderr, "getvar:product FAILED (%s)\n", fb->Error().c_str());
@@ -806,7 +819,14 @@
         if (name == "partition-exists") {
             HandlePartitionExists(options);
         } else {
-            CheckRequirement(cur_product, name, product, invert, options);
+            bool met = CheckRequirement(cur_product, name, product, invert, options);
+            if (!met) {
+                if (!force_flash) {
+                  die("requirements not met!");
+                } else {
+                  fprintf(stderr, "requirements not met! but proceeding due to --force\n");
+                }
+            }
         }
     }
 }
@@ -851,24 +871,23 @@
     return out_s;
 }
 
-static int64_t get_target_sparse_limit() {
-    std::string max_download_size;
-    if (fb->GetVar("max-download-size", &max_download_size) != fastboot::SUCCESS ||
-        max_download_size.empty()) {
-        verbose("target didn't report max-download-size");
+static uint64_t get_uint_var(const char* var_name) {
+    std::string value_str;
+    if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) {
+        verbose("target didn't report %s", var_name);
         return 0;
     }
 
     // Some bootloaders (angler, for example) send spurious whitespace too.
-    max_download_size = android::base::Trim(max_download_size);
+    value_str = android::base::Trim(value_str);
 
-    uint64_t limit;
-    if (!android::base::ParseUint(max_download_size, &limit)) {
-        fprintf(stderr, "couldn't parse max-download-size '%s'\n", max_download_size.c_str());
+    uint64_t value;
+    if (!android::base::ParseUint(value_str, &value)) {
+        fprintf(stderr, "couldn't parse %s '%s'\n", var_name, value_str.c_str());
         return 0;
     }
-    if (limit > 0) verbose("target reported max download size of %" PRId64 " bytes", limit);
-    return limit;
+    if (value > 0) verbose("target reported %s of %" PRId64 " bytes", var_name, value);
+    return value;
 }
 
 static int64_t get_sparse_limit(int64_t size) {
@@ -877,7 +896,7 @@
         // Unlimited, so see what the target device's limit is.
         // TODO: shouldn't we apply this limit even if you've used -S?
         if (target_sparse_limit == -1) {
-            target_sparse_limit = get_target_sparse_limit();
+            target_sparse_limit = static_cast<int64_t>(get_uint_var("max-download-size"));
         }
         if (target_sparse_limit > 0) {
             limit = target_sparse_limit;
@@ -893,23 +912,24 @@
     return 0;
 }
 
-static bool load_buf_fd(int fd, struct fastboot_buffer* buf) {
+static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf) {
     int64_t sz = get_file_size(fd);
     if (sz == -1) {
         return false;
     }
 
-    if (sparse_file* s = sparse_file_import(fd, false, false)) {
+    if (sparse_file* s = sparse_file_import(fd.get(), false, false)) {
         buf->image_size = sparse_file_len(s, false, false);
         sparse_file_destroy(s);
     } else {
         buf->image_size = sz;
     }
 
-    lseek(fd, 0, SEEK_SET);
+    lseek(fd.get(), 0, SEEK_SET);
     int64_t limit = get_sparse_limit(sz);
+    buf->fd = std::move(fd);
     if (limit) {
-        sparse_file** s = load_sparse_files(fd, limit);
+        sparse_file** s = load_sparse_files(buf->fd.get(), limit);
         if (s == nullptr) {
             return false;
         }
@@ -918,7 +938,6 @@
     } else {
         buf->type = FB_BUFFER_FD;
         buf->data = nullptr;
-        buf->fd = fd;
         buf->sz = sz;
     }
 
@@ -933,7 +952,7 @@
     }
 
     struct stat s;
-    if (fstat(fd, &s)) {
+    if (fstat(fd.get(), &s)) {
         return false;
     }
     if (!S_ISREG(s.st_mode)) {
@@ -941,7 +960,7 @@
         return false;
     }
 
-    return load_buf_fd(fd.release(), buf);
+    return load_buf_fd(std::move(fd), buf);
 }
 
 static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) {
@@ -987,13 +1006,12 @@
         data[flags_offset] |= 0x02;
     }
 
-    int fd = make_temporary_fd("vbmeta rewriting");
+    unique_fd fd(make_temporary_fd("vbmeta rewriting"));
     if (!android::base::WriteStringToFd(data, fd)) {
         die("Failed writing to modified vbmeta");
     }
-    close(buf->fd);
-    buf->fd = fd;
-    lseek(fd, 0, SEEK_SET);
+    buf->fd = std::move(fd);
+    lseek(buf->fd.get(), 0, SEEK_SET);
 }
 
 static bool has_vbmeta_partition() {
@@ -1003,6 +1021,11 @@
            fb->GetVar("partition-type:vbmeta_b", &partition_type) == fastboot::SUCCESS;
 }
 
+static bool is_logical(const std::string& partition) {
+    std::string value;
+    return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
+}
+
 static std::string fb_fix_numeric_var(std::string var) {
     // Some bootloaders (angler, for example), send spurious leading whitespace.
     var = android::base::Trim(var);
@@ -1012,27 +1035,30 @@
     return var;
 }
 
-static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
-    if (buf->sz < AVB_FOOTER_SIZE) {
-        return;
-    }
-
+static uint64_t get_partition_size(const std::string& partition) {
     std::string partition_size_str;
     if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) {
-        die("cannot get boot partition size");
+        if (!is_logical(partition)) {
+            return 0;
+        }
+        die("cannot get partition size for %s", partition.c_str());
     }
 
     partition_size_str = fb_fix_numeric_var(partition_size_str);
-    int64_t partition_size;
-    if (!android::base::ParseInt(partition_size_str, &partition_size)) {
+    uint64_t partition_size;
+    if (!android::base::ParseUint(partition_size_str, &partition_size)) {
+        if (!is_logical(partition)) {
+            return 0;
+        }
         die("Couldn't parse partition size '%s'.", partition_size_str.c_str());
     }
-    if (partition_size == buf->sz) {
+    return partition_size;
+}
+
+static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
+    if (buf->sz < AVB_FOOTER_SIZE) {
         return;
     }
-    if (partition_size < buf->sz) {
-        die("boot partition is smaller than boot image");
-    }
 
     std::string data;
     if (!android::base::ReadFdToString(buf->fd, &data)) {
@@ -1043,19 +1069,27 @@
     if (0 != data.compare(footer_offset, AVB_FOOTER_MAGIC_LEN, AVB_FOOTER_MAGIC)) {
         return;
     }
+    // If overflows and negative, it should be < buf->sz.
+    int64_t partition_size = static_cast<int64_t>(get_partition_size(partition));
 
-    int fd = make_temporary_fd("boot rewriting");
+    if (partition_size == buf->sz) {
+        return;
+    }
+    if (partition_size < buf->sz) {
+        die("boot partition is smaller than boot image");
+    }
+
+    unique_fd fd(make_temporary_fd("boot rewriting"));
     if (!android::base::WriteStringToFd(data, fd)) {
         die("Failed writing to modified boot");
     }
-    lseek(fd, partition_size - AVB_FOOTER_SIZE, SEEK_SET);
+    lseek(fd.get(), partition_size - AVB_FOOTER_SIZE, SEEK_SET);
     if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) {
         die("Failed copying AVB footer in boot");
     }
-    close(buf->fd);
-    buf->fd = fd;
+    buf->fd = std::move(fd);
     buf->sz = partition_size;
-    lseek(fd, 0, SEEK_SET);
+    lseek(buf->fd.get(), 0, SEEK_SET);
 }
 
 static void flash_buf(const std::string& partition, struct fastboot_buffer *buf)
@@ -1185,8 +1219,10 @@
                              const std::function<void(const std::string&)>& func, bool force_slot) {
     std::string has_slot;
     std::string current_slot;
+    // |part| can be vendor_boot:default. Append slot to the first token.
+    auto part_tokens = android::base::Split(part, ":");
 
-    if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) {
+    if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) {
         /* If has-slot is not supported, the answer is no. */
         has_slot = "no";
     }
@@ -1196,14 +1232,15 @@
             if (current_slot == "") {
                 die("Failed to identify current slot");
             }
-            func(part + "_" + current_slot);
+            part_tokens[0] += "_" + current_slot;
         } else {
-            func(part + '_' + slot);
+            part_tokens[0] += "_" + slot;
         }
+        func(android::base::Join(part_tokens, ":"));
     } else {
         if (force_slot && slot != "") {
-             fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n",
-                     part.c_str(), slot.c_str());
+            fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n",
+                    part_tokens[0].c_str(), slot.c_str());
         }
         func(part);
     }
@@ -1217,10 +1254,13 @@
 static void do_for_partitions(const std::string& part, const std::string& slot,
                               const std::function<void(const std::string&)>& func, bool force_slot) {
     std::string has_slot;
+    // |part| can be vendor_boot:default. Query has-slot on the first token only.
+    auto part_tokens = android::base::Split(part, ":");
 
     if (slot == "all") {
-        if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) {
-            die("Could not check if partition %s has slot %s", part.c_str(), slot.c_str());
+        if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) {
+            die("Could not check if partition %s has slot %s", part_tokens[0].c_str(),
+                slot.c_str());
         }
         if (has_slot == "yes") {
             for (int i=0; i < get_slot_count(); i++) {
@@ -1234,11 +1274,6 @@
     }
 }
 
-static bool is_logical(const std::string& partition) {
-    std::string value;
-    return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
-}
-
 static bool is_retrofit_device() {
     std::string value;
     if (fb->GetVar("super-partition-name", &value) != fastboot::SUCCESS) {
@@ -1247,7 +1282,74 @@
     return android::base::StartsWith(value, "system_");
 }
 
+// Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch
+// the full image.
+static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd) {
+    uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE);
+    if (fetch_size == 0) {
+        die("Unable to get %s. Device does not support fetch command.", FB_VAR_MAX_FETCH_SIZE);
+    }
+    uint64_t partition_size = get_partition_size(partition);
+    if (partition_size <= 0) {
+        die("Invalid partition size for partition %s: %" PRId64, partition.c_str(), partition_size);
+    }
+
+    uint64_t offset = 0;
+    while (offset < partition_size) {
+        uint64_t chunk_size = std::min(fetch_size, partition_size - offset);
+        if (fb->FetchToFd(partition, fd, offset, chunk_size) != fastboot::RetCode::SUCCESS) {
+            die("Unable to fetch %s (offset=%" PRIx64 ", size=%" PRIx64 ")", partition.c_str(),
+                offset, chunk_size);
+        }
+        offset += chunk_size;
+    }
+    return partition_size;
+}
+
+static void do_fetch(const std::string& partition, const std::string& slot_override,
+                     const std::string& outfile) {
+    unique_fd fd(TEMP_FAILURE_RETRY(
+            open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0644)));
+    auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd));
+    do_for_partitions(partition, slot_override, fetch, false /* force slot */);
+}
+
+// Return immediately if not flashing a vendor boot image. If flashing a vendor boot image,
+// repack vendor_boot image with an updated ramdisk. After execution, buf is set
+// to the new image to flash, and return value is the real partition name to flash.
+static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf) {
+    std::string_view pname_sv{pname};
+
+    if (!android::base::StartsWith(pname_sv, "vendor_boot:") &&
+        !android::base::StartsWith(pname_sv, "vendor_boot_a:") &&
+        !android::base::StartsWith(pname_sv, "vendor_boot_b:")) {
+        return std::string(pname_sv);
+    }
+    if (buf->type != FB_BUFFER_FD) {
+        die("Flashing sparse vendor ramdisk image is not supported.");
+    }
+    if (buf->sz <= 0) {
+        die("repack_ramdisk() sees negative size: %" PRId64, buf->sz);
+    }
+    std::string partition(pname_sv.substr(0, pname_sv.find(':')));
+    std::string ramdisk(pname_sv.substr(pname_sv.find(':') + 1));
+
+    unique_fd vendor_boot(make_temporary_fd("vendor boot repack"));
+    uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot);
+    auto repack_res = replace_vendor_ramdisk(vendor_boot, vendor_boot_size, ramdisk, buf->fd,
+                                             static_cast<uint64_t>(buf->sz));
+    if (!repack_res.ok()) {
+        die("%s", repack_res.error().message().c_str());
+    }
+
+    buf->fd = std::move(vendor_boot);
+    buf->sz = vendor_boot_size;
+    buf->image_size = vendor_boot_size;
+    return partition;
+}
+
 static void do_flash(const char* pname, const char* fname) {
+    verbose("Do flash %s %s", pname, fname);
     struct fastboot_buffer buf;
 
     if (!load_buf(fname, &buf)) {
@@ -1256,7 +1358,8 @@
     if (is_logical(pname)) {
         fb->ResizePartition(pname, std::to_string(buf.image_size));
     }
-    flash_buf(pname, &buf);
+    std::string flash_pname = repack_ramdisk(pname, &buf);
+    flash_buf(flash_pname, &buf);
 }
 
 // Sets slot_override as the active slot. If slot_override is blank,
@@ -1309,13 +1412,15 @@
 
 class ImageSource {
   public:
+    virtual ~ImageSource() {};
     virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0;
-    virtual int OpenFile(const std::string& name) const = 0;
+    virtual unique_fd OpenFile(const std::string& name) const = 0;
 };
 
 class FlashAllTool {
   public:
-    FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary, bool wipe);
+    FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary,
+                 bool wipe, bool force_flash);
 
     void Flash();
 
@@ -1331,16 +1436,19 @@
     std::string slot_override_;
     bool skip_secondary_;
     bool wipe_;
+    bool force_flash_;
     std::string secondary_slot_;
     std::vector<std::pair<const Image*, std::string>> boot_images_;
     std::vector<std::pair<const Image*, std::string>> os_images_;
 };
 
-FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary, bool wipe)
+FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override,
+                           bool skip_secondary, bool wipe, bool force_flash)
    : source_(source),
      slot_override_(slot_override),
      skip_secondary_(skip_secondary),
-     wipe_(wipe)
+     wipe_(wipe),
+     force_flash_(force_flash)
 {
 }
 
@@ -1388,7 +1496,7 @@
     if (!source_.ReadFile("android-info.txt", &contents)) {
         die("could not read android-info.txt");
     }
-    ::CheckRequirements({contents.data(), contents.size()});
+    ::CheckRequirements({contents.data(), contents.size()}, force_flash_);
 }
 
 void FlashAllTool::DetermineSecondarySlot() {
@@ -1428,8 +1536,8 @@
 void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
     for (const auto& [image, slot] : images) {
         fastboot_buffer buf;
-        int fd = source_.OpenFile(image->img_name);
-        if (fd < 0 || !load_buf_fd(fd, &buf)) {
+        unique_fd fd = source_.OpenFile(image->img_name);
+        if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) {
             if (image->optional_if_no_image) {
                 continue;
             }
@@ -1456,7 +1564,7 @@
 }
 
 void FlashAllTool::UpdateSuperPartition() {
-    int fd = source_.OpenFile("super_empty.img");
+    unique_fd fd = source_.OpenFile("super_empty.img");
     if (fd < 0) {
         return;
     }
@@ -1494,7 +1602,7 @@
   public:
     explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
     bool ReadFile(const std::string& name, std::vector<char>* out) const override;
-    int OpenFile(const std::string& name) const override;
+    unique_fd OpenFile(const std::string& name) const override;
 
   private:
     ZipArchiveHandle zip_;
@@ -1504,18 +1612,19 @@
     return UnzipToMemory(zip_, name, out);
 }
 
-int ZipImageSource::OpenFile(const std::string& name) const {
+unique_fd ZipImageSource::OpenFile(const std::string& name) const {
     return unzip_to_file(zip_, name.c_str());
 }
 
-static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary) {
+static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary,
+                      bool force_flash) {
     ZipArchiveHandle zip;
     int error = OpenArchive(filename, &zip);
     if (error != 0) {
         die("failed to open zip file '%s': %s", filename, ErrorCodeString(error));
     }
 
-    FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false);
+    FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false, force_flash);
     tool.Flash();
 
     CloseArchive(zip);
@@ -1524,7 +1633,7 @@
 class LocalImageSource final : public ImageSource {
   public:
     bool ReadFile(const std::string& name, std::vector<char>* out) const override;
-    int OpenFile(const std::string& name) const override;
+    unique_fd OpenFile(const std::string& name) const override;
 };
 
 bool LocalImageSource::ReadFile(const std::string& name, std::vector<char>* out) const {
@@ -1535,13 +1644,14 @@
     return ReadFileToVector(path, out);
 }
 
-int LocalImageSource::OpenFile(const std::string& name) const {
+unique_fd LocalImageSource::OpenFile(const std::string& name) const {
     auto path = find_item_given_name(name);
-    return open(path.c_str(), O_RDONLY | O_BINARY);
+    return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
 }
 
-static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) {
-    FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe);
+static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe,
+                        bool force_flash) {
+    FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe, force_flash);
     tool.Flash();
 }
 
@@ -1656,7 +1766,7 @@
     if (fd == -1) {
         die("Cannot open generated image: %s", strerror(errno));
     }
-    if (!load_buf_fd(fd.release(), &buf)) {
+    if (!load_buf_fd(std::move(fd), &buf)) {
         die("Cannot read image: %s", strerror(errno));
     }
     flash_buf(partition, &buf);
@@ -2089,9 +2199,9 @@
         } else if (command == "flashall") {
             if (slot_override == "all") {
                 fprintf(stderr, "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
-                do_flashall(slot_override, true, wants_wipe);
+                do_flashall(slot_override, true, wants_wipe, force_flash);
             } else {
-                do_flashall(slot_override, skip_secondary, wants_wipe);
+                do_flashall(slot_override, skip_secondary, wants_wipe, force_flash);
             }
             wants_reboot = true;
         } else if (command == "update") {
@@ -2103,7 +2213,7 @@
             if (!args.empty()) {
                 filename = next_arg(&args);
             }
-            do_update(filename.c_str(), slot_override, skip_secondary || slot_all);
+            do_update(filename.c_str(), slot_override, skip_secondary || slot_all, force_flash);
             wants_reboot = true;
         } else if (command == FB_CMD_SET_ACTIVE) {
             std::string slot = verify_slot(next_arg(&args), false);
@@ -2115,7 +2225,7 @@
             if (!load_buf(filename.c_str(), &buf) || buf.type != FB_BUFFER_FD) {
                 die("cannot load '%s'", filename.c_str());
             }
-            fb->Download(filename, buf.fd, buf.sz);
+            fb->Download(filename, buf.fd.get(), buf.sz);
         } else if (command == "get_staged") {
             std::string filename = next_arg(&args);
             fb->Upload(filename);
@@ -2169,6 +2279,10 @@
                 syntax_error("expected: snapshot-update [cancel|merge]");
             }
             fb->SnapshotUpdateCommand(arg);
+        } else if (command == FB_CMD_FETCH) {
+            std::string partition = next_arg(&args);
+            std::string outfile = next_arg(&args);
+            do_fetch(partition, slot_override, outfile);
         } else {
             syntax_error("unknown command %s", command.c_str());
         }
diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp
index 8d534ea..99a4873 100644
--- a/fastboot/fastboot_driver.cpp
+++ b/fastboot/fastboot_driver.cpp
@@ -30,6 +30,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -42,14 +43,17 @@
 
 #include <android-base/file.h>
 #include <android-base/mapped_file.h>
+#include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <storage_literals/storage_literals.h>
 
 #include "constants.h"
 #include "transport.h"
 
 using android::base::StringPrintf;
+using namespace android::storage_literals;
 
 namespace fastboot {
 
@@ -140,7 +144,8 @@
     return Flash(partition);
 }
 
-RetCode FastBootDriver::FlashPartition(const std::string& partition, int fd, uint32_t size) {
+RetCode FastBootDriver::FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
+                                       uint32_t size) {
     RetCode ret;
     if ((ret = Download(partition, fd, size))) {
         return ret;
@@ -178,15 +183,16 @@
     return SUCCESS;
 }
 
-RetCode FastBootDriver::Download(const std::string& name, int fd, size_t size,
-                                 std::string* response, std::vector<std::string>* info) {
+RetCode FastBootDriver::Download(const std::string& name, android::base::borrowed_fd fd,
+                                 size_t size, std::string* response,
+                                 std::vector<std::string>* info) {
     prolog_(StringPrintf("Sending '%s' (%zu KB)", name.c_str(), size / 1024));
     auto result = Download(fd, size, response, info);
     epilog_(result);
     return result;
 }
 
-RetCode FastBootDriver::Download(int fd, size_t size, std::string* response,
+RetCode FastBootDriver::Download(android::base::borrowed_fd fd, size_t size, std::string* response,
                                  std::vector<std::string>* info) {
     RetCode ret;
 
@@ -297,41 +303,85 @@
     return result;
 }
 
-RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
-                                    std::vector<std::string>* info) {
+// This function executes cmd, then expect a "DATA" response with a number N, followed
+// by N bytes, and another response.
+// This is the common way for the device to send data to the driver used by upload and fetch.
+RetCode FastBootDriver::RunAndReadBuffer(
+        const std::string& cmd, std::string* response, std::vector<std::string>* info,
+        const std::function<RetCode(const char* data, uint64_t size)>& write_fn) {
     RetCode ret;
     int dsize = 0;
-    if ((ret = RawCommand(FB_CMD_UPLOAD, response, info, &dsize))) {
-        error_ = "Upload request failed: " + error_;
+    if ((ret = RawCommand(cmd, response, info, &dsize))) {
+        error_ = android::base::StringPrintf("%s request failed: %s", cmd.c_str(), error_.c_str());
         return ret;
     }
 
-    if (!dsize) {
-        error_ = "Upload request failed, device reports 0 bytes available";
+    if (dsize <= 0) {
+        error_ = android::base::StringPrintf("%s request failed, device reports %d bytes available",
+                                             cmd.c_str(), dsize);
         return BAD_DEV_RESP;
     }
 
-    std::vector<char> data;
-    data.resize(dsize);
-
-    if ((ret = ReadBuffer(data))) {
-        return ret;
+    const uint64_t total_size = dsize;
+    const uint64_t buf_size = std::min<uint64_t>(total_size, 1_MiB);
+    std::vector<char> data(buf_size);
+    uint64_t current_offset = 0;
+    while (current_offset < total_size) {
+        uint64_t remaining = total_size - current_offset;
+        uint64_t chunk_size = std::min(buf_size, remaining);
+        if ((ret = ReadBuffer(data.data(), chunk_size)) != SUCCESS) {
+            return ret;
+        }
+        if ((ret = write_fn(data.data(), chunk_size)) != SUCCESS) {
+            return ret;
+        }
+        current_offset += chunk_size;
     }
+    return HandleResponse(response, info);
+}
 
+RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
+                                    std::vector<std::string>* info) {
     std::ofstream ofs;
     ofs.open(outfile, std::ofstream::out | std::ofstream::binary);
     if (ofs.fail()) {
         error_ = android::base::StringPrintf("Failed to open '%s'", outfile.c_str());
         return IO_ERROR;
     }
-    ofs.write(data.data(), data.size());
-    if (ofs.fail() || ofs.bad()) {
-        error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str());
-        return IO_ERROR;
-    }
+    auto write_fn = [&](const char* data, uint64_t size) {
+        ofs.write(data, size);
+        if (ofs.fail() || ofs.bad()) {
+            error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str());
+            return IO_ERROR;
+        }
+        return SUCCESS;
+    };
+    RetCode ret = RunAndReadBuffer(FB_CMD_UPLOAD, response, info, write_fn);
     ofs.close();
+    return ret;
+}
 
-    return HandleResponse(response, info);
+RetCode FastBootDriver::FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
+                                  int64_t offset, int64_t size, std::string* response,
+                                  std::vector<std::string>* info) {
+    prolog_(android::base::StringPrintf("Fetching %s (offset=%" PRIx64 ", size=%" PRIx64 ")",
+                                        partition.c_str(), offset, size));
+    std::string cmd = FB_CMD_FETCH ":" + partition;
+    if (offset >= 0) {
+        cmd += android::base::StringPrintf(":0x%08" PRIx64, offset);
+        if (size >= 0) {
+            cmd += android::base::StringPrintf(":0x%08" PRIx64, size);
+        }
+    }
+    RetCode ret = RunAndReadBuffer(cmd, response, info, [&](const char* data, uint64_t size) {
+        if (!android::base::WriteFully(fd, data, size)) {
+            error_ = android::base::StringPrintf("Cannot write: %s", strerror(errno));
+            return IO_ERROR;
+        }
+        return SUCCESS;
+    });
+    epilog_(ret);
+    return ret;
 }
 
 // Helpers
@@ -473,7 +523,7 @@
 }
 
 /******************************* PRIVATE **************************************/
-RetCode FastBootDriver::SendBuffer(int fd, size_t size) {
+RetCode FastBootDriver::SendBuffer(android::base::borrowed_fd fd, size_t size) {
     static constexpr uint32_t MAX_MAP_SIZE = 512 * 1024 * 1024;
     off64_t offset = 0;
     uint32_t remaining = size;
@@ -524,11 +574,6 @@
     return SUCCESS;
 }
 
-RetCode FastBootDriver::ReadBuffer(std::vector<char>& buf) {
-    // Read the buffer
-    return ReadBuffer(buf.data(), buf.size());
-}
-
 RetCode FastBootDriver::ReadBuffer(void* buf, size_t size) {
     // Read the buffer
     ssize_t tmp = transport_->Read(buf, size);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index 7265632..bccd668 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -34,6 +34,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
 #include <bootimg.h>
 #include <inttypes.h>
 #include <sparse/sparse.h>
@@ -76,9 +77,9 @@
     RetCode Continue(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
     RetCode CreatePartition(const std::string& partition, const std::string& size);
     RetCode DeletePartition(const std::string& partition);
-    RetCode Download(const std::string& name, int fd, size_t size, std::string* response = nullptr,
-                     std::vector<std::string>* info = nullptr);
-    RetCode Download(int fd, size_t size, std::string* response = nullptr,
+    RetCode Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
+                     std::string* response = nullptr, std::vector<std::string>* info = nullptr);
+    RetCode Download(android::base::borrowed_fd fd, size_t size, std::string* response = nullptr,
                      std::vector<std::string>* info = nullptr);
     RetCode Download(const std::string& name, const std::vector<char>& buf,
                      std::string* response = nullptr, std::vector<std::string>* info = nullptr);
@@ -106,10 +107,14 @@
                    std::vector<std::string>* info = nullptr);
     RetCode SnapshotUpdateCommand(const std::string& command, std::string* response = nullptr,
                                   std::vector<std::string>* info = nullptr);
+    RetCode FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
+                      int64_t offset = -1, int64_t size = -1, std::string* response = nullptr,
+                      std::vector<std::string>* info = nullptr);
 
     /* HIGHER LEVEL COMMANDS -- Composed of the commands above */
     RetCode FlashPartition(const std::string& partition, const std::vector<char>& data);
-    RetCode FlashPartition(const std::string& partition, int fd, uint32_t sz);
+    RetCode FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
+                           uint32_t sz);
     RetCode FlashPartition(const std::string& partition, sparse_file* s, uint32_t sz,
                            size_t current, size_t total);
 
@@ -145,15 +150,17 @@
     Transport* transport_;
 
   private:
-    RetCode SendBuffer(int fd, size_t size);
+    RetCode SendBuffer(android::base::borrowed_fd fd, size_t size);
     RetCode SendBuffer(const std::vector<char>& buf);
     RetCode SendBuffer(const void* buf, size_t size);
 
-    RetCode ReadBuffer(std::vector<char>& buf);
     RetCode ReadBuffer(void* buf, size_t size);
 
     RetCode UploadInner(const std::string& outfile, std::string* response = nullptr,
                         std::vector<std::string>* info = nullptr);
+    RetCode RunAndReadBuffer(const std::string& cmd, std::string* response,
+                             std::vector<std::string>* info,
+                             const std::function<RetCode(const char*, uint64_t)>& write_fn);
 
     int SparseWriteCallback(std::vector<char>& tpbuf, const char* data, size_t len);
 
diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp
index 34ab32c..b6beaf9 100644
--- a/fastboot/fuzzy_fastboot/main.cpp
+++ b/fastboot/fuzzy_fastboot/main.cpp
@@ -43,8 +43,10 @@
 #include <thread>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
 #include <sparse/sparse.h>
 
@@ -349,22 +351,35 @@
     EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'";
 }
 
+void AssertHexUint32(const std::string& name, const std::string& var) {
+    ASSERT_NE(var, "") << "getvar:" << name << " responded with empty string";
+    // This must start with 0x
+    ASSERT_FALSE(isspace(var.front()))
+            << "getvar:" << name << " responded with a string with leading whitespace";
+    ASSERT_FALSE(var.compare(0, 2, "0x"))
+            << "getvar:" << name << " responded with a string that does not start with 0x...";
+    int64_t size = strtoll(var.c_str(), nullptr, 16);
+    ASSERT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:" << name;
+    // At most 32-bits
+    ASSERT_LE(size, std::numeric_limits<uint32_t>::max())
+            << "getvar:" << name << " must fit in a uint32_t";
+    ASSERT_LE(var.size(), FB_RESPONSE_SZ - 4)
+            << "getvar:" << name << " responded with too large of string: " + var;
+}
+
 TEST_F(Conformance, GetVarDownloadSize) {
     std::string var;
     EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
-    EXPECT_NE(var, "") << "getvar:max-download-size responded with empty string";
-    // This must start with 0x
-    EXPECT_FALSE(isspace(var.front()))
-            << "getvar:max-download-size responded with a string with leading whitespace";
-    EXPECT_FALSE(var.compare(0, 2, "0x"))
-            << "getvar:max-download-size responded with a string that does not start with 0x...";
-    int64_t size = strtoll(var.c_str(), nullptr, 16);
-    EXPECT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:max-download-size";
-    // At most 32-bits
-    EXPECT_LE(size, std::numeric_limits<uint32_t>::max())
-            << "getvar:max-download-size must fit in a uint32_t";
-    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4)
-            << "getvar:max-download-size responded with too large of string: " + var;
+    AssertHexUint32("max-download-size", var);
+}
+
+// If fetch is supported, getvar:max-fetch-size must return a hex string.
+TEST_F(Conformance, GetVarFetchSize) {
+    std::string var;
+    if (SUCCESS != fb->GetVar("max-fetch-size", &var)) {
+        GTEST_SKIP() << "getvar:max-fetch-size failed";
+    }
+    AssertHexUint32("max-fetch-size", var);
 }
 
 TEST_F(Conformance, GetVarAll) {
@@ -656,6 +671,33 @@
     EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in unlocked mode";
 }
 
+// If the implementation supports getvar:max-fetch-size, it must also support fetch:vendor_boot*.
+TEST_F(UnlockPermissions, FetchVendorBoot) {
+    std::string var;
+    uint64_t fetch_size;
+    if (fb->GetVar("max-fetch-size", &var) != SUCCESS) {
+        GTEST_SKIP() << "This test is skipped because fetch is not supported.";
+    }
+    ASSERT_FALSE(var.empty());
+    ASSERT_TRUE(android::base::ParseUint(var, &fetch_size)) << var << " is not an integer";
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+    for (const auto& [partition, partition_size] : parts) {
+        if (!android::base::StartsWith(partition, "vendor_boot")) continue;
+        TemporaryFile fetched;
+
+        uint64_t offset = 0;
+        while (offset < partition_size) {
+            uint64_t chunk_size = std::min(fetch_size, partition_size - offset);
+            auto ret = fb->FetchToFd(partition, fetched.fd, offset, chunk_size);
+            ASSERT_EQ(fastboot::RetCode::SUCCESS, ret)
+                    << "Unable to fetch " << partition << " (offset=" << offset
+                    << ", size=" << chunk_size << ")";
+            offset += chunk_size;
+        }
+    }
+}
+
 TEST_F(LockPermissions, DownloadFlash) {
     std::vector<char> buf{'a', 'o', 's', 'p'};
     EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in locked mode";
@@ -717,6 +759,16 @@
     EXPECT_GT(resp.size(), 0) << "No error message was returned by device after FAIL";
 }
 
+TEST_F(LockPermissions, FetchVendorBoot) {
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+    for (const auto& [partition, _] : parts) {
+        TemporaryFile fetched;
+        ASSERT_EQ(fb->FetchToFd(partition, fetched.fd, 0, 0), DEVICE_FAIL)
+                << "fetch:" << partition << ":0:0 did not fail in locked mode";
+    }
+}
+
 TEST_F(Fuzz, DownloadSize) {
     std::string var;
     EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
diff --git a/fastboot/testdata/Android.bp b/fastboot/testdata/Android.bp
new file mode 100644
index 0000000..a490fe2
--- /dev/null
+++ b/fastboot/testdata/Android.bp
@@ -0,0 +1,140 @@
+// 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"],
+}
+
+python_binary_host {
+    name: "fastboot_gen_rand",
+    visibility: [":__subpackages__"],
+    srcs: ["fastboot_gen_rand.py"],
+}
+
+genrule_defaults {
+    name: "fastboot_test_data_gen_defaults",
+    visibility: ["//system/core/fastboot"],
+    tools: [
+        "fastboot_gen_rand",
+    ],
+}
+
+// Genrules for components of test vendor boot image.
+
+// Fake dtb image.
+genrule {
+    name: "fastboot_test_dtb",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_dtb.img"],
+    cmd: "$(location fastboot_gen_rand) --seed dtb --length 1024 > $(out)",
+}
+
+// Fake bootconfig image.
+genrule {
+    name: "fastboot_test_bootconfig",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_bootconfig.img"],
+    cmd: "$(location fastboot_gen_rand) --seed bootconfig --length 1024 > $(out)",
+}
+
+// Fake vendor ramdisk with type "none".
+genrule {
+    name: "fastboot_test_vendor_ramdisk_none",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_vendor_ramdisk_none.img"],
+    cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_none --length 1024 > $(out)",
+}
+
+// Fake vendor ramdisk with type "platform".
+genrule {
+    name: "fastboot_test_vendor_ramdisk_platform",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_vendor_ramdisk_platform.img"],
+    cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_platform --length 1024 > $(out)",
+}
+
+// Fake replacement ramdisk.
+genrule {
+    name: "fastboot_test_vendor_ramdisk_replace",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_vendor_ramdisk_replace.img"],
+    cmd: "$(location fastboot_gen_rand) --seed replace --length 3072 > $(out)",
+}
+
+// Genrules for test vendor boot images.
+
+fastboot_sign_test_image = "$(location avbtool) add_hash_footer --salt 00 --image $(out) " +
+    "--partition_name vendor_boot --partition_size $$(( 1 * 1024 * 1024 ))"
+
+genrule_defaults {
+    name: "fastboot_test_vendor_boot_gen_defaults",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    tools: [
+        "avbtool",
+        "mkbootimg",
+    ],
+}
+
+genrule {
+    name: "fastboot_test_vendor_boot_v3",
+    defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+    out: ["vendor_boot_v3.img"],
+    srcs: [
+        ":fastboot_test_dtb",
+        ":fastboot_test_vendor_ramdisk_none",
+    ],
+    cmd: "$(location mkbootimg) --header_version 3 " +
+        "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " +
+        "--dtb $(location :fastboot_test_dtb) " +
+        "--vendor_boot $(out) && " +
+        fastboot_sign_test_image,
+}
+
+genrule {
+    name: "fastboot_test_vendor_boot_v4_without_frag",
+    defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+    out: ["vendor_boot_v4_without_frag.img"],
+    srcs: [
+        ":fastboot_test_dtb",
+        ":fastboot_test_vendor_ramdisk_none",
+        ":fastboot_test_bootconfig",
+    ],
+    cmd: "$(location mkbootimg) --header_version 4 " +
+        "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " +
+        "--dtb $(location :fastboot_test_dtb) " +
+        "--vendor_bootconfig $(location :fastboot_test_bootconfig) " +
+        "--vendor_boot $(out) && " +
+        fastboot_sign_test_image,
+}
+
+genrule {
+    name: "fastboot_test_vendor_boot_v4_with_frag",
+    defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+    out: ["vendor_boot_v4_with_frag.img"],
+    srcs: [
+        ":fastboot_test_dtb",
+        ":fastboot_test_vendor_ramdisk_none",
+        ":fastboot_test_vendor_ramdisk_platform",
+        ":fastboot_test_bootconfig",
+    ],
+    cmd: "$(location mkbootimg) --header_version 4 " +
+        "--dtb $(location :fastboot_test_dtb) " +
+        "--vendor_bootconfig $(location :fastboot_test_bootconfig) " +
+        "--ramdisk_type none --ramdisk_name none_ramdisk " +
+        "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_none) " +
+        "--ramdisk_type platform --ramdisk_name platform_ramdisk " +
+        "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_platform) " +
+        "--vendor_boot $(out) && " +
+        fastboot_sign_test_image,
+}
diff --git a/fastboot/testdata/fastboot_gen_rand.py b/fastboot/testdata/fastboot_gen_rand.py
new file mode 100644
index 0000000..a87467b
--- /dev/null
+++ b/fastboot/testdata/fastboot_gen_rand.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+
+# 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.
+
+"""
+Write given number of random bytes, generated with optional seed.
+"""
+
+import random, argparse
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser(description=__doc__)
+  parser.add_argument('--seed', help='Seed to random generator')
+  parser.add_argument('--length', type=int, required=True, help='Length of output')
+  args = parser.parse_args()
+
+  if args.seed:
+    random.seed(args.seed)
+
+  print(''.join(chr(random.randrange(0,0xff)) for _ in range(args.length)))
diff --git a/fastboot/vendor_boot_img_utils.cpp b/fastboot/vendor_boot_img_utils.cpp
new file mode 100644
index 0000000..9e09abb
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils.cpp
@@ -0,0 +1,422 @@
+/*
+ * 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 "vendor_boot_img_utils.h"
+
+#include <string.h>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <bootimg.h>
+#include <libavb/libavb.h>
+
+namespace {
+
+using android::base::Result;
+
+// Updates a given buffer by creating a new one.
+class DataUpdater {
+  public:
+    DataUpdater(const std::string& old_data) : old_data_(&old_data) {
+        old_data_ptr_ = old_data_->data();
+        new_data_.resize(old_data_->size(), '\0');
+        new_data_ptr_ = new_data_.data();
+    }
+    // Copy |num_bytes| from src to dst.
+    [[nodiscard]] Result<void> Copy(uint32_t num_bytes) {
+        if (num_bytes == 0) return {};
+        if (auto res = CheckAdvance(old_data_ptr_, old_end(), num_bytes, __FUNCTION__); !res.ok())
+            return res;
+        if (auto res = CheckAdvance(new_data_ptr_, new_end(), num_bytes, __FUNCTION__); !res.ok())
+            return res;
+        memcpy(new_data_ptr_, old_data_ptr_, num_bytes);
+        old_data_ptr_ += num_bytes;
+        new_data_ptr_ += num_bytes;
+        return {};
+    }
+    // Replace |old_num_bytes| from src with new data.
+    [[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const std::string& new_data) {
+        return Replace(old_num_bytes, new_data.data(), new_data.size());
+    }
+    [[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const void* new_data,
+                                       uint32_t new_data_size) {
+        if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_num_bytes, __FUNCTION__);
+            !res.ok())
+            return res;
+        old_data_ptr_ += old_num_bytes;
+
+        if (new_data_size == 0) return {};
+        if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_data_size, __FUNCTION__);
+            !res.ok())
+            return res;
+        memcpy(new_data_ptr_, new_data, new_data_size);
+        new_data_ptr_ += new_data_size;
+        return {};
+    }
+    // Skip |old_skip| from src and |new_skip| from dst, respectively.
+    [[nodiscard]] Result<void> Skip(uint32_t old_skip, uint32_t new_skip) {
+        if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_skip, __FUNCTION__); !res.ok())
+            return res;
+        old_data_ptr_ += old_skip;
+        if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_skip, __FUNCTION__); !res.ok())
+            return res;
+        new_data_ptr_ += new_skip;
+        return {};
+    }
+
+    [[nodiscard]] Result<void> Seek(uint32_t offset) {
+        if (offset > size()) return Errorf("Cannot seek 0x{:x}, size is 0x{:x}", offset, size());
+        old_data_ptr_ = old_begin() + offset;
+        new_data_ptr_ = new_begin() + offset;
+        return {};
+    }
+
+    std::string Finish() {
+        new_data_ptr_ = nullptr;
+        return std::move(new_data_);
+    }
+
+    [[nodiscard]] Result<void> CheckOffset(uint32_t old_offset, uint32_t new_offset) {
+        if (old_begin() + old_offset != old_cur())
+            return Errorf("Old offset mismatch: expected: 0x{:x}, actual: 0x{:x}", old_offset,
+                          old_cur() - old_begin());
+        if (new_begin() + new_offset != new_cur())
+            return Errorf("New offset mismatch: expected: 0x{:x}, actual: 0x{:x}", new_offset,
+                          new_cur() - new_begin());
+        return {};
+    }
+
+    uint64_t size() const { return old_data_->size(); }
+    const char* old_begin() const { return old_data_->data(); }
+    const char* old_cur() { return old_data_ptr_; }
+    const char* old_end() const { return old_data_->data() + old_data_->size(); }
+    char* new_begin() { return new_data_.data(); }
+    char* new_cur() { return new_data_ptr_; }
+    char* new_end() { return new_data_.data() + new_data_.size(); }
+
+  private:
+    // Check if it is okay to advance |num_bytes| from |current|.
+    [[nodiscard]] Result<void> CheckAdvance(const char* current, const char* end,
+                                            uint32_t num_bytes, const char* op) {
+        auto new_end = current + num_bytes;
+        if (new_end < current /* add overflow */)
+            return Errorf("{}: Addition overflow: 0x{} + 0x{:x} < 0x{}", op, fmt::ptr(current),
+                          num_bytes, fmt::ptr(current));
+        if (new_end > end)
+            return Errorf("{}: Boundary overflow: 0x{} + 0x{:x} > 0x{}", op, fmt::ptr(current),
+                          num_bytes, fmt::ptr(end));
+        return {};
+    }
+    const std::string* old_data_;
+    std::string new_data_;
+    const char* old_data_ptr_;
+    char* new_data_ptr_;
+};
+
+// Get the size of vendor boot header.
+[[nodiscard]] Result<uint32_t> get_vendor_boot_header_size(const vendor_boot_img_hdr_v3* hdr) {
+    if (hdr->header_version == 3) return sizeof(vendor_boot_img_hdr_v3);
+    if (hdr->header_version == 4) return sizeof(vendor_boot_img_hdr_v4);
+    return Errorf("Unrecognized vendor boot header version {}", hdr->header_version);
+}
+
+// Check that content contains a valid vendor boot image header with a version at least |version|.
+[[nodiscard]] Result<void> check_vendor_boot_hdr(const std::string& content, uint32_t version) {
+    // get_vendor_boot_header_size reads header_version, so make sure reading it does not
+    // go out of bounds by ensuring that the content has at least the size of V3 header.
+    if (content.size() < sizeof(vendor_boot_img_hdr_v3)) {
+        return Errorf("Size of vendor boot is 0x{:x}, less than size of V3 header: 0x{:x}",
+                      content.size(), sizeof(vendor_boot_img_hdr_v3));
+    }
+    // Now read hdr->header_version and assert the size.
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(content.data());
+    auto expect_header_size = get_vendor_boot_header_size(hdr);
+    if (!expect_header_size.ok()) return expect_header_size.error();
+    if (content.size() < *expect_header_size) {
+        return Errorf("Size of vendor boot is 0x{:x}, less than size of V{} header: 0x{:x}",
+                      content.size(), version, *expect_header_size);
+    }
+    if (memcmp(hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE) != 0) {
+        return Errorf("Vendor boot image magic mismatch");
+    }
+    if (hdr->header_version < version) {
+        return Errorf("Require vendor boot header V{} but is V{}", version, hdr->header_version);
+    }
+    return {};
+}
+
+// Wrapper of ReadFdToString. Seek to the beginning and read the whole file to string.
+[[nodiscard]] Result<std::string> load_file(android::base::borrowed_fd fd, uint64_t expected_size,
+                                            const char* what) {
+    if (lseek(fd.get(), 0, SEEK_SET) != 0) {
+        return ErrnoErrorf("Can't seek to the beginning of {} image", what);
+    }
+    std::string content;
+    if (!android::base::ReadFdToString(fd, &content)) {
+        return ErrnoErrorf("Cannot read {} to string", what);
+    }
+    if (content.size() != expected_size) {
+        return Errorf("Size of {} does not match, expected 0x{:x}, read 0x{:x}", what,
+                      expected_size, content.size());
+    }
+    return content;
+}
+
+// Wrapper of WriteStringToFd. Seek to the beginning and write the whole file to string.
+[[nodiscard]] Result<void> store_file(android::base::borrowed_fd fd, const std::string& data,
+                                      const char* what) {
+    if (lseek(fd.get(), 0, SEEK_SET) != 0) {
+        return ErrnoErrorf("Cannot seek to beginning of {} before writing", what);
+    }
+    if (!android::base::WriteStringToFd(data, fd)) {
+        return ErrnoErrorf("Cannot write new content to {}", what);
+    }
+    if (TEMP_FAILURE_RETRY(ftruncate(fd.get(), data.size())) == -1) {
+        return ErrnoErrorf("Truncating new vendor boot image to 0x{:x} fails", data.size());
+    }
+    return {};
+}
+
+// Copy AVB footer if it exists in the old buffer.
+[[nodiscard]] Result<void> copy_avb_footer(DataUpdater* updater) {
+    if (updater->size() < AVB_FOOTER_SIZE) return {};
+    if (auto res = updater->Seek(updater->size() - AVB_FOOTER_SIZE); !res.ok()) return res;
+    if (memcmp(updater->old_cur(), AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) return {};
+    return updater->Copy(AVB_FOOTER_SIZE);
+}
+
+// round |value| up to a multiple of |page_size|.
+inline uint32_t round_up(uint32_t value, uint32_t page_size) {
+    return (value + page_size - 1) / page_size * page_size;
+}
+
+// Replace the vendor ramdisk as a whole.
+[[nodiscard]] Result<std::string> replace_default_vendor_ramdisk(const std::string& vendor_boot,
+                                                                 const std::string& new_ramdisk) {
+    if (auto res = check_vendor_boot_hdr(vendor_boot, 3); !res.ok()) return res.error();
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(vendor_boot.data());
+    auto hdr_size = get_vendor_boot_header_size(hdr);
+    if (!hdr_size.ok()) return hdr_size.error();
+    // Refer to bootimg.h for details. Numbers are in bytes.
+    const uint32_t o = round_up(*hdr_size, hdr->page_size);
+    const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+    const uint32_t q = round_up(hdr->dtb_size, hdr->page_size);
+
+    DataUpdater updater(vendor_boot);
+
+    // Copy header (O bytes), then update fields in header.
+    if (auto res = updater.Copy(o); !res.ok()) return res.error();
+    auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v3*>(updater.new_begin());
+    new_hdr->vendor_ramdisk_size = new_ramdisk.size();
+    // Because it is unknown how the new ramdisk is fragmented, the whole table is replaced
+    // with a single entry representing the full ramdisk.
+    if (new_hdr->header_version >= 4) {
+        auto new_hdr_v4 = static_cast<vendor_boot_img_hdr_v4*>(new_hdr);
+        new_hdr_v4->vendor_ramdisk_table_entry_size = sizeof(vendor_ramdisk_table_entry_v4);
+        new_hdr_v4->vendor_ramdisk_table_entry_num = 1;
+        new_hdr_v4->vendor_ramdisk_table_size = new_hdr_v4->vendor_ramdisk_table_entry_num *
+                                                new_hdr_v4->vendor_ramdisk_table_entry_size;
+    }
+
+    // Copy the new ramdisk.
+    if (auto res = updater.Replace(hdr->vendor_ramdisk_size, new_ramdisk); !res.ok())
+        return res.error();
+    const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size);
+    if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size);
+        !res.ok())
+        return res.error();
+    if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error();
+
+    // Copy DTB (Q bytes).
+    if (auto res = updater.Copy(q); !res.ok()) return res.error();
+
+    if (new_hdr->header_version >= 4) {
+        auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
+        const uint32_t r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
+        const uint32_t s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
+
+        auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur());
+        auto new_hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(new_hdr);
+        auto new_r = round_up(new_hdr_v4->vendor_ramdisk_table_size, new_hdr->page_size);
+        if (auto res = updater.Skip(r, new_r); !res.ok()) return res.error();
+        if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + new_r); !res.ok())
+            return res.error();
+
+        // Replace table with single entry representing the full ramdisk.
+        new_entry->ramdisk_size = new_hdr->vendor_ramdisk_size;
+        new_entry->ramdisk_offset = 0;
+        new_entry->ramdisk_type = VENDOR_RAMDISK_TYPE_NONE;
+        memset(new_entry->ramdisk_name, '\0', VENDOR_RAMDISK_NAME_SIZE);
+        memset(new_entry->board_id, '\0', VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE);
+
+        // Copy bootconfig (S bytes).
+        if (auto res = updater.Copy(s); !res.ok()) return res.error();
+    }
+
+    if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error();
+    return updater.Finish();
+}
+
+// Find a ramdisk fragment with a unique name. Abort if none or multiple fragments are found.
+[[nodiscard]] Result<const vendor_ramdisk_table_entry_v4*> find_unique_ramdisk(
+        const std::string& ramdisk_name, const vendor_ramdisk_table_entry_v4* table,
+        uint32_t size) {
+    const vendor_ramdisk_table_entry_v4* ret = nullptr;
+    uint32_t idx = 0;
+    const vendor_ramdisk_table_entry_v4* entry = table;
+    for (; idx < size; idx++, entry++) {
+        auto entry_name_c_str = reinterpret_cast<const char*>(entry->ramdisk_name);
+        auto entry_name_len = strnlen(entry_name_c_str, VENDOR_RAMDISK_NAME_SIZE);
+        std::string_view entry_name(entry_name_c_str, entry_name_len);
+        if (entry_name == ramdisk_name) {
+            if (ret != nullptr) {
+                return Errorf("Multiple vendor ramdisk '{}' found, name should be unique",
+                              ramdisk_name.c_str());
+            }
+            ret = entry;
+        }
+    }
+    if (ret == nullptr) {
+        return Errorf("Vendor ramdisk '{}' not found", ramdisk_name.c_str());
+    }
+    return ret;
+}
+
+// Find the vendor ramdisk fragment with |ramdisk_name| within the content of |vendor_boot|, and
+// replace it with the content of |new_ramdisk|.
+[[nodiscard]] Result<std::string> replace_vendor_ramdisk_fragment(const std::string& ramdisk_name,
+                                                                  const std::string& vendor_boot,
+                                                                  const std::string& new_ramdisk) {
+    if (auto res = check_vendor_boot_hdr(vendor_boot, 4); !res.ok()) return res.error();
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(vendor_boot.data());
+    auto hdr_size = get_vendor_boot_header_size(hdr);
+    if (!hdr_size.ok()) return hdr_size.error();
+    // Refer to bootimg.h for details. Numbers are in bytes.
+    const uint32_t o = round_up(*hdr_size, hdr->page_size);
+    const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+    const uint32_t q = round_up(hdr->dtb_size, hdr->page_size);
+    const uint32_t r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
+    const uint32_t s = round_up(hdr->bootconfig_size, hdr->page_size);
+
+    if (hdr->vendor_ramdisk_table_entry_num == std::numeric_limits<uint32_t>::max()) {
+        return Errorf("Too many vendor ramdisk entries in table, overflow");
+    }
+
+    // Find entry with name |ramdisk_name|.
+    auto old_table_start =
+            reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(vendor_boot.data() + o + p + q);
+    auto find_res =
+            find_unique_ramdisk(ramdisk_name, old_table_start, hdr->vendor_ramdisk_table_entry_num);
+    if (!find_res.ok()) return find_res.error();
+    const vendor_ramdisk_table_entry_v4* replace_entry = *find_res;
+    uint32_t replace_idx = replace_entry - old_table_start;
+
+    // Now reconstruct.
+    DataUpdater updater(vendor_boot);
+
+    // Copy header (O bytes), then update fields in header.
+    if (auto res = updater.Copy(o); !res.ok()) return res.error();
+    auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v4*>(updater.new_begin());
+
+    // Copy ramdisk fragments, replace for the matching index.
+    {
+        auto old_ramdisk_entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
+                vendor_boot.data() + o + p + q);
+        uint32_t new_total_ramdisk_size = 0;
+        for (uint32_t new_ramdisk_idx = 0; new_ramdisk_idx < hdr->vendor_ramdisk_table_entry_num;
+             new_ramdisk_idx++, old_ramdisk_entry++) {
+            if (new_ramdisk_idx == replace_idx) {
+                if (auto res = updater.Replace(replace_entry->ramdisk_size, new_ramdisk); !res.ok())
+                    return res.error();
+                new_total_ramdisk_size += new_ramdisk.size();
+            } else {
+                if (auto res = updater.Copy(old_ramdisk_entry->ramdisk_size); !res.ok())
+                    return res.error();
+                new_total_ramdisk_size += old_ramdisk_entry->ramdisk_size;
+            }
+        }
+        new_hdr->vendor_ramdisk_size = new_total_ramdisk_size;
+    }
+
+    // Pad ramdisk to page boundary.
+    const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size);
+    if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size);
+        !res.ok())
+        return res.error();
+    if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error();
+
+    // Copy DTB (Q bytes).
+    if (auto res = updater.Copy(q); !res.ok()) return res.error();
+
+    // Copy table, but with corresponding entries modified, including:
+    // - ramdisk_size of the entry replaced
+    // - ramdisk_offset of subsequent entries.
+    for (uint32_t new_total_ramdisk_size = 0, new_entry_idx = 0;
+         new_entry_idx < hdr->vendor_ramdisk_table_entry_num; new_entry_idx++) {
+        auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur());
+        if (auto res = updater.Copy(hdr->vendor_ramdisk_table_entry_size); !res.ok())
+            return res.error();
+        new_entry->ramdisk_offset = new_total_ramdisk_size;
+
+        if (new_entry_idx == replace_idx) {
+            new_entry->ramdisk_size = new_ramdisk.size();
+        }
+        new_total_ramdisk_size += new_entry->ramdisk_size;
+    }
+
+    // Copy padding of R pages; this is okay because table size is not changed.
+    if (auto res = updater.Copy(r - hdr->vendor_ramdisk_table_entry_num *
+                                            hdr->vendor_ramdisk_table_entry_size);
+        !res.ok())
+        return res.error();
+    if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + r); !res.ok())
+        return res.error();
+
+    // Copy bootconfig (S bytes).
+    if (auto res = updater.Copy(s); !res.ok()) return res.error();
+
+    if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error();
+    return updater.Finish();
+}
+
+}  // namespace
+
+[[nodiscard]] Result<void> replace_vendor_ramdisk(android::base::borrowed_fd vendor_boot_fd,
+                                                  uint64_t vendor_boot_size,
+                                                  const std::string& ramdisk_name,
+                                                  android::base::borrowed_fd new_ramdisk_fd,
+                                                  uint64_t new_ramdisk_size) {
+    if (new_ramdisk_size > std::numeric_limits<uint32_t>::max()) {
+        return Errorf("New vendor ramdisk is too big");
+    }
+
+    auto vendor_boot = load_file(vendor_boot_fd, vendor_boot_size, "vendor boot");
+    if (!vendor_boot.ok()) return vendor_boot.error();
+    auto new_ramdisk = load_file(new_ramdisk_fd, new_ramdisk_size, "new vendor ramdisk");
+    if (!new_ramdisk.ok()) return new_ramdisk.error();
+
+    Result<std::string> new_vendor_boot;
+    if (ramdisk_name == "default") {
+        new_vendor_boot = replace_default_vendor_ramdisk(*vendor_boot, *new_ramdisk);
+    } else {
+        new_vendor_boot = replace_vendor_ramdisk_fragment(ramdisk_name, *vendor_boot, *new_ramdisk);
+    }
+    if (!new_vendor_boot.ok()) return new_vendor_boot.error();
+    if (auto res = store_file(vendor_boot_fd, *new_vendor_boot, "new vendor boot image"); !res.ok())
+        return res.error();
+
+    return {};
+}
diff --git a/fastboot/vendor_boot_img_utils.h b/fastboot/vendor_boot_img_utils.h
new file mode 100644
index 0000000..0b702bc
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils.h
@@ -0,0 +1,34 @@
+/*
+ * 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 <inttypes.h>
+
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+// Replace the vendor ramdisk named |ramdisk_name| within the vendor boot image,
+// specified by |vendor_boot_fd|, with the ramdisk specified by |new_ramdisk_fd|. Checks
+// that the size of the files are |vendor_boot_size| and |new_ramdisk_size|, respectively.
+// If |ramdisk_name| is "default", replace the vendor ramdisk as a whole. Otherwise, replace
+// a vendor ramdisk fragment with the given unique name.
+[[nodiscard]] android::base::Result<void> replace_vendor_ramdisk(
+        android::base::borrowed_fd vendor_boot_fd, uint64_t vendor_boot_size,
+        const std::string& ramdisk_name, android::base::borrowed_fd new_ramdisk_fd,
+        uint64_t new_ramdisk_size);
diff --git a/fastboot/vendor_boot_img_utils_test.cpp b/fastboot/vendor_boot_img_utils_test.cpp
new file mode 100644
index 0000000..1563b89
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils_test.cpp
@@ -0,0 +1,429 @@
+/*
+ * 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 <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <optional>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <android-base/strings.h>
+#include <bootimg.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <libavb/libavb.h>
+
+#include "vendor_boot_img_utils.h"
+
+using android::base::borrowed_fd;
+using android::base::ErrnoError;
+using android::base::GetExecutableDirectory;
+using android::base::ReadFdToString;
+using android::base::Result;
+using testing::AllOf;
+using testing::Each;
+using testing::Eq;
+using testing::HasSubstr;
+using testing::Not;
+using testing::Property;
+using std::string_literals::operator""s;
+
+// Expect that the Result<T> returned by |expr| is successful, and value matches |result_matcher|.
+#define EXPECT_RESULT(expr, result_matcher)                          \
+    EXPECT_THAT(expr, AllOf(Property(&decltype(expr)::ok, Eq(true)), \
+                            Property(&decltype(expr)::value, result_matcher)))
+
+// Expect that the Result<T> returned by |expr| fails, and error message matches |error_matcher|.
+#define EXPECT_ERROR(expr, error_matcher)                                                        \
+    do {                                                                                         \
+        EXPECT_THAT(                                                                             \
+                expr,                                                                            \
+                AllOf(Property(&decltype(expr)::ok, Eq(false)),                                  \
+                      Property(&decltype(expr)::error,                                           \
+                               Property(&decltype(expr)::error_type::message, error_matcher)))); \
+    } while (0)
+
+namespace {
+
+// Wrapper of fstat.
+Result<uint64_t> FileSize(borrowed_fd fd, std::filesystem::path path) {
+    struct stat sb;
+    if (fstat(fd.get(), &sb) == -1) return ErrnoError() << "fstat(" << path << ")";
+    return sb.st_size;
+}
+
+// Seek to beginning then read the whole file.
+Result<std::string> ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) {
+    if (lseek64(fd.get(), 0, SEEK_SET) != 0)
+        return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)";
+    std::string content;
+    if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")";
+    return content;
+}
+
+// Round |value| up to page boundary.
+inline uint32_t round_up(uint32_t value, uint32_t page_size) {
+    return (value + page_size - 1) / page_size * page_size;
+}
+
+// Match is successful if |arg| is a zero-padded version of |expected|.
+MATCHER_P(IsPadded, expected, (negation ? "is" : "isn't") + " zero-padded of expected value"s) {
+    if (arg.size() < expected.size()) return false;
+    if (0 != memcmp(arg.data(), expected.data(), expected.size())) return false;
+    auto remainder = std::string_view(arg).substr(expected.size());
+    for (char e : remainder)
+        if (e != '\0') return false;
+    return true;
+}
+
+// Same as Eq, but don't print the content to avoid spam.
+MATCHER_P(MemEq, expected, (negation ? "is" : "isn't") + " expected value"s) {
+    if (arg.size() != expected.size()) return false;
+    return 0 == memcmp(arg.data(), expected.data(), expected.size());
+}
+
+// Expect that |arg| and |expected| has the same AVB footer.
+MATCHER_P(HasSameAvbFooter, expected,
+          (negation ? "has" : "does not have") + "expected AVB footer"s) {
+    if (expected.size() < AVB_FOOTER_SIZE || arg.size() < AVB_FOOTER_SIZE) return false;
+    return std::string_view(expected).substr(expected.size() - AVB_FOOTER_SIZE) ==
+           std::string_view(arg).substr(arg.size() - AVB_FOOTER_SIZE);
+}
+
+// A lazy handle of a file.
+struct TestFileHandle {
+    virtual ~TestFileHandle() = default;
+    // Lazily call OpenImpl(), cache result in open_result_.
+    android::base::Result<void> Open() {
+        if (!open_result_.has_value()) open_result_ = OpenImpl();
+        return open_result_.value();
+    }
+    // The original size at the time when the file is opened. If the file has been modified,
+    // this field is NOT updated.
+    uint64_t size() {
+        CHECK(open_result_.has_value());
+        return size_;
+    }
+    // The current size of the file. If the file has been modified since opened,
+    // this is updated.
+    Result<uint64_t> fsize() {
+        CHECK(open_result_.has_value());
+        return FileSize(fd_, abs_path_);
+    }
+    borrowed_fd fd() {
+        CHECK(open_result_.has_value());
+        return fd_;
+    }
+    Result<std::string> Read() {
+        CHECK(open_result_.has_value());
+        return ReadStartOfFdToString(fd_, abs_path_);
+    }
+
+  private:
+    std::filesystem::path abs_path_;
+    uint64_t size_;
+    std::optional<android::base::Result<void>> open_result_;
+    borrowed_fd fd_{-1};
+    // Opens |rel_path_| as a readonly fd, pass it to Transform, and store result to
+    // |borrowed_fd_|.
+    android::base::Result<void> OpenImpl() {
+        android::base::unique_fd read_fd(TEMP_FAILURE_RETRY(
+                open(abs_path_.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_BINARY)));
+        if (!read_fd.ok()) return ErrnoError() << "open(" << abs_path_ << ")";
+        auto size = FileSize(read_fd, abs_path_);
+        if (!size.ok()) return size.error();
+        size_ = *size;
+
+        auto borrowed_fd = Transform(abs_path_, std::move(read_fd));
+        if (!borrowed_fd.ok()) return borrowed_fd.error();
+        fd_ = borrowed_fd.value();
+
+        return {};
+    }
+
+  protected:
+    // |rel_path| is the relative path under test data directory.
+    TestFileHandle(const std::filesystem::path& rel_path)
+        : abs_path_(std::move(std::filesystem::path(GetExecutableDirectory()) / rel_path)) {}
+    // Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client
+    // to use. Implementation is responsible for managing the lifetime of the returned fd.
+    virtual android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
+                                                         android::base::unique_fd read_fd) = 0;
+};
+
+// A TestFileHandle where the file is readonly.
+struct ReadOnlyTestFileHandle : TestFileHandle {
+    ReadOnlyTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
+
+  private:
+    android::base::unique_fd owned_fd_;
+    android::base::Result<borrowed_fd> Transform(const std::filesystem::path&,
+                                                 android::base::unique_fd read_fd) override {
+        owned_fd_ = std::move(read_fd);
+        return owned_fd_;
+    }
+};
+
+// A TestFileHandle where the test file is copies, hence read-writable.
+struct ReadWriteTestFileHandle : TestFileHandle {
+    ReadWriteTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
+
+  private:
+    std::unique_ptr<TemporaryFile> temp_file_;
+
+    android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
+                                                 android::base::unique_fd read_fd) override {
+        // Make a copy to avoid writing to test data. Test files are small, so it is okay
+        // to read the whole file.
+        auto content = ReadStartOfFdToString(read_fd, abs_path);
+        if (!content.ok()) return content.error();
+        temp_file_ = std::make_unique<TemporaryFile>();
+        if (temp_file_->fd == -1)
+            return ErrnoError() << "copy " << abs_path << ": open temp file failed";
+        if (!android::base::WriteStringToFd(*content, temp_file_->fd))
+            return ErrnoError() << "copy " << abs_path << ": write temp file failed";
+
+        return temp_file_->fd;
+    }
+};
+
+class RepackVendorBootImgTestEnv : public ::testing::Environment {
+  public:
+    virtual void SetUp() {
+        OpenTestFile("test_dtb.img", &dtb, &dtb_content);
+        OpenTestFile("test_bootconfig.img", &bootconfig, &bootconfig_content);
+        OpenTestFile("test_vendor_ramdisk_none.img", &none, &none_content);
+        OpenTestFile("test_vendor_ramdisk_platform.img", &platform, &platform_content);
+        OpenTestFile("test_vendor_ramdisk_replace.img", &replace, &replace_content);
+    }
+
+    std::unique_ptr<TestFileHandle> dtb;
+    std::string dtb_content;
+    std::unique_ptr<TestFileHandle> bootconfig;
+    std::string bootconfig_content;
+    std::unique_ptr<TestFileHandle> none;
+    std::string none_content;
+    std::unique_ptr<TestFileHandle> platform;
+    std::string platform_content;
+    std::unique_ptr<TestFileHandle> replace;
+    std::string replace_content;
+
+  private:
+    void OpenTestFile(const char* rel_path, std::unique_ptr<TestFileHandle>* handle,
+                      std::string* content) {
+        *handle = std::make_unique<ReadOnlyTestFileHandle>(rel_path);
+        ASSERT_RESULT_OK((*handle)->Open());
+        auto content_res = (*handle)->Read();
+        ASSERT_RESULT_OK(content_res);
+        *content = *content_res;
+    }
+};
+RepackVendorBootImgTestEnv* env = nullptr;
+
+struct RepackVendorBootImgTestParam {
+    std::string vendor_boot_file_name;
+    uint32_t expected_header_version;
+    friend std::ostream& operator<<(std::ostream& os, const RepackVendorBootImgTestParam& param) {
+        return os << param.vendor_boot_file_name;
+    }
+};
+
+class RepackVendorBootImgTest : public ::testing::TestWithParam<RepackVendorBootImgTestParam> {
+  public:
+    virtual void SetUp() {
+        vboot = std::make_unique<ReadWriteTestFileHandle>(GetParam().vendor_boot_file_name);
+        ASSERT_RESULT_OK(vboot->Open());
+    }
+    std::unique_ptr<TestFileHandle> vboot;
+};
+
+TEST_P(RepackVendorBootImgTest, InvalidSize) {
+    EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size() + 1, "default",
+                                        env->replace->fd(), env->replace->size()),
+                 HasSubstr("Size of vendor boot does not match"));
+    EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(),
+                                        env->replace->size() + 1),
+                 HasSubstr("Size of new vendor ramdisk does not match"));
+}
+
+TEST_P(RepackVendorBootImgTest, ReplaceUnknown) {
+    auto res = replace_vendor_ramdisk(vboot->fd(), vboot->size(), "unknown", env->replace->fd(),
+                                      env->replace->size());
+    if (GetParam().expected_header_version == 3) {
+        EXPECT_ERROR(res, Eq("Require vendor boot header V4 but is V3"));
+    } else if (GetParam().expected_header_version == 4) {
+        EXPECT_ERROR(res, Eq("Vendor ramdisk 'unknown' not found"));
+    }
+}
+
+TEST_P(RepackVendorBootImgTest, ReplaceDefault) {
+    auto old_content = vboot->Read();
+    ASSERT_RESULT_OK(old_content);
+
+    ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default",
+                                            env->replace->fd(), env->replace->size()));
+    EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
+
+    auto new_content_res = vboot->Read();
+    ASSERT_RESULT_OK(new_content_res);
+    std::string_view new_content(*new_content_res);
+
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(new_content.data());
+    ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
+    ASSERT_EQ(GetParam().expected_header_version, hdr->header_version);
+    EXPECT_EQ(hdr->vendor_ramdisk_size, env->replace->size());
+    EXPECT_EQ(hdr->dtb_size, env->dtb->size());
+
+    auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
+    auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+    auto q = round_up(hdr->dtb_size, hdr->page_size);
+
+    EXPECT_THAT(new_content.substr(o, p), IsPadded(env->replace_content));
+    EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
+
+    if (hdr->header_version < 4) return;
+
+    auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
+    EXPECT_EQ(hdr_v4->vendor_ramdisk_table_entry_num, 1);
+    EXPECT_EQ(hdr_v4->vendor_ramdisk_table_size, 1 * hdr_v4->vendor_ramdisk_table_entry_size);
+    EXPECT_GE(hdr_v4->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
+    auto entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
+    EXPECT_EQ(entry->ramdisk_offset, 0);
+    EXPECT_EQ(entry->ramdisk_size, hdr_v4->vendor_ramdisk_size);
+    EXPECT_EQ(entry->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
+
+    EXPECT_EQ(hdr_v4->bootconfig_size, env->bootconfig->size());
+    auto r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
+    auto s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
+    EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
+
+    EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        RepackVendorBootImgTest, RepackVendorBootImgTest,
+        ::testing::Values(RepackVendorBootImgTestParam{"vendor_boot_v3.img", 3},
+                          RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", 4},
+                          RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", 4}),
+        [](const auto& info) {
+            return android::base::StringReplace(info.param.vendor_boot_file_name, ".", "_", false);
+        });
+
+std::string_view GetRamdiskName(const vendor_ramdisk_table_entry_v4* entry) {
+    auto ramdisk_name = reinterpret_cast<const char*>(entry->ramdisk_name);
+    return std::string_view(ramdisk_name, strnlen(ramdisk_name, VENDOR_RAMDISK_NAME_SIZE));
+}
+
+class RepackVendorBootImgTestV4 : public ::testing::TestWithParam<uint32_t /* ramdisk type */> {
+  public:
+    virtual void SetUp() {
+        vboot = std::make_unique<ReadWriteTestFileHandle>("vendor_boot_v4_with_frag.img");
+        ASSERT_RESULT_OK(vboot->Open());
+    }
+    std::unique_ptr<TestFileHandle> vboot;
+};
+
+TEST_P(RepackVendorBootImgTestV4, Replace) {
+    uint32_t replace_ramdisk_type = GetParam();
+    std::string replace_ramdisk_name;
+    std::string expect_new_ramdisk_content;
+    uint32_t expect_none_size = env->none->size();
+    uint32_t expect_platform_size = env->platform->size();
+    switch (replace_ramdisk_type) {
+        case VENDOR_RAMDISK_TYPE_NONE:
+            replace_ramdisk_name = "none_ramdisk";
+            expect_new_ramdisk_content = env->replace_content + env->platform_content;
+            expect_none_size = env->replace->size();
+            break;
+        case VENDOR_RAMDISK_TYPE_PLATFORM:
+            replace_ramdisk_name = "platform_ramdisk";
+            expect_new_ramdisk_content = env->none_content + env->replace_content;
+            expect_platform_size = env->replace->size();
+            break;
+        default:
+            LOG(FATAL) << "Ramdisk type " << replace_ramdisk_type
+                       << " is not supported by this test.";
+    }
+
+    auto old_content = vboot->Read();
+    ASSERT_RESULT_OK(old_content);
+
+    ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), replace_ramdisk_name,
+                                            env->replace->fd(), env->replace->size()));
+    EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
+
+    auto new_content_res = vboot->Read();
+    ASSERT_RESULT_OK(new_content_res);
+    std::string_view new_content(*new_content_res);
+
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(new_content.data());
+    ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
+    ASSERT_EQ(4, hdr->header_version);
+    EXPECT_EQ(hdr->vendor_ramdisk_size, expect_none_size + expect_platform_size);
+    EXPECT_EQ(hdr->dtb_size, env->dtb->size());
+    EXPECT_EQ(hdr->bootconfig_size, env->bootconfig->size());
+
+    auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
+    auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+    auto q = round_up(hdr->dtb_size, hdr->page_size);
+    auto r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
+    auto s = round_up(hdr->bootconfig_size, hdr->page_size);
+
+    EXPECT_THAT(new_content.substr(o, p), IsPadded(expect_new_ramdisk_content));
+    EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
+
+    // Check changes in table.
+    EXPECT_EQ(hdr->vendor_ramdisk_table_entry_num, 2);
+    EXPECT_EQ(hdr->vendor_ramdisk_table_size, 2 * hdr->vendor_ramdisk_table_entry_size);
+    EXPECT_GE(hdr->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
+    auto entry_none =
+            reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
+    EXPECT_EQ(entry_none->ramdisk_offset, 0);
+    EXPECT_EQ(entry_none->ramdisk_size, expect_none_size);
+    EXPECT_EQ(entry_none->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
+    EXPECT_EQ(GetRamdiskName(entry_none), "none_ramdisk");
+
+    auto entry_platform = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
+            &new_content[o + p + q + hdr->vendor_ramdisk_table_entry_size]);
+    EXPECT_EQ(entry_platform->ramdisk_offset, expect_none_size);
+    EXPECT_EQ(entry_platform->ramdisk_size, expect_platform_size);
+    EXPECT_EQ(entry_platform->ramdisk_type, VENDOR_RAMDISK_TYPE_PLATFORM);
+    EXPECT_EQ(GetRamdiskName(entry_platform), "platform_ramdisk");
+
+    EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
+
+    EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
+}
+INSTANTIATE_TEST_SUITE_P(RepackVendorBootImgTest, RepackVendorBootImgTestV4,
+                         ::testing::Values(VENDOR_RAMDISK_TYPE_NONE, VENDOR_RAMDISK_TYPE_PLATFORM),
+                         [](const auto& info) {
+                             return info.param == VENDOR_RAMDISK_TYPE_NONE ? "none" : "platform";
+                         });
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    env = static_cast<RepackVendorBootImgTestEnv*>(
+            testing::AddGlobalTestEnvironment(new RepackVendorBootImgTestEnv));
+    return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index a349408..84709b6 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -17,6 +17,9 @@
     },
     {
       "name": "libsnapshot_fuzzer_test"
+    },
+    {
+      "name": "cow_api_test"
     }
   ]
 }
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 6952cdf..ea9d333 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -264,12 +264,12 @@
                 F2FS_FSCK_BIN, "-f", "-c", "10000", "--debug-cache", blk_device.c_str()};
 
         if (should_force_check(*fs_stat)) {
-            LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache"
+            LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache "
                   << realpath(blk_device);
             ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_forced_argv), f2fs_fsck_forced_argv,
                                       &status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
         } else {
-            LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache"
+            LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache "
                   << realpath(blk_device);
             ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv, &status, false,
                                       LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
@@ -647,6 +647,46 @@
     return sb == cpu_to_le32(F2FS_SUPER_MAGIC);
 }
 
+static void SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb) {
+    std::string block_device;
+    if (!Realpath(entry_block_device, &block_device)) {
+        PERROR << "Failed to realpath " << entry_block_device;
+        return;
+    }
+
+    static constexpr std::string_view kDevBlockPrefix("/dev/block/");
+    if (!android::base::StartsWith(block_device, kDevBlockPrefix)) {
+        LWARNING << block_device << " is not a block device";
+        return;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    while (true) {
+        std::string block_name = block_device;
+        if (android::base::StartsWith(block_device, kDevBlockPrefix)) {
+            block_name = block_device.substr(kDevBlockPrefix.length());
+        }
+        std::string sys_partition =
+                android::base::StringPrintf("/sys/class/block/%s/partition", block_name.c_str());
+        struct stat info;
+        if (lstat(sys_partition.c_str(), &info) == 0) {
+            // it has a partition like "sda12".
+            block_name += "/..";
+        }
+        std::string sys_ra = android::base::StringPrintf("/sys/class/block/%s/queue/read_ahead_kb",
+                                                         block_name.c_str());
+        std::string size = android::base::StringPrintf("%llu", (long long)size_kb);
+        android::base::WriteStringToFile(size, sys_ra.c_str());
+        LINFO << "Set readahead_kb: " << size << " on " << sys_ra;
+
+        auto parent = dm.GetParentBlockDeviceByPath(block_device);
+        if (!parent) {
+            return;
+        }
+        block_device = *parent;
+    }
+}
+
 //
 // Prepare the filesystem on the given block device to be mounted.
 //
@@ -667,6 +707,11 @@
     }
     mkdir(mount_point.c_str(), 0755);
 
+    // Don't need to return error, since it's a salt
+    if (entry.readahead_size_kb != -1) {
+        SetReadAheadSize(blk_device, entry.readahead_size_kb);
+    }
+
     int fs_stat = 0;
 
     if (is_extfs(entry.fs_type)) {
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 0c0862e..853b24d 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -17,6 +17,7 @@
 #include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
+#include <fnmatch.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -254,6 +255,13 @@
             } else {
                 entry->reserved_size = static_cast<off64_t>(size);
             }
+        } else if (StartsWith(flag, "readahead_size_kb=")) {
+            int val;
+            if (ParseInt(arg, &val, 0, 16 * 1024)) {
+                entry->readahead_size_kb = val;
+            } else {
+                LWARNING << "Warning: readahead_size_kb= flag malformed (0 ~ 16MB): " << arg;
+            }
         } else if (StartsWith(flag, "eraseblk=")) {
             // The erase block size flag is followed by an = and the flash erase block size. Get it,
             // check that it is a power of 2 and at least 4096, and return it.
@@ -290,6 +298,8 @@
             if (!ParseByteCount(arg, &entry->zram_backingdev_size)) {
                 LWARNING << "Warning: zram_backingdev_size= flag malformed: " << arg;
             }
+        } else if (StartsWith(flag, "lowerdir=")) {
+            entry->lowerdir = arg;
         } else {
             LWARNING << "Warning: unknown flag: " << flag;
         }
@@ -680,7 +690,7 @@
     }
 }
 
-bool ReadFstabFromFile(const std::string& path, Fstab* fstab) {
+bool ReadFstabFromFile(const std::string& path, Fstab* fstab_out) {
     auto fstab_file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
     if (!fstab_file) {
         PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'";
@@ -689,41 +699,51 @@
 
     bool is_proc_mounts = path == "/proc/mounts";
 
-    if (!ReadFstabFile(fstab_file.get(), is_proc_mounts, fstab)) {
+    Fstab fstab;
+    if (!ReadFstabFile(fstab_file.get(), is_proc_mounts, &fstab)) {
         LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'";
         return false;
     }
-    if (!is_proc_mounts && !access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
-        // This is expected to fail if host is android Q, since Q doesn't
-        // support DSU slotting. The DSU "active" indicator file would be
-        // non-existent or empty if DSU is enabled within the guest system.
-        // In that case, just use the default slot name "dsu".
-        std::string dsu_slot;
-        if (!android::gsi::GetActiveDsu(&dsu_slot)) {
-            PWARNING << __FUNCTION__ << "(): failed to get active dsu slot";
+    if (!is_proc_mounts) {
+        if (!access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
+            // This is expected to fail if host is android Q, since Q doesn't
+            // support DSU slotting. The DSU "active" indicator file would be
+            // non-existent or empty if DSU is enabled within the guest system.
+            // In that case, just use the default slot name "dsu".
+            std::string dsu_slot;
+            if (!android::gsi::GetActiveDsu(&dsu_slot) && errno != ENOENT) {
+                PERROR << __FUNCTION__ << "(): failed to get active DSU slot";
+                return false;
+            }
+            if (dsu_slot.empty()) {
+                dsu_slot = "dsu";
+                LWARNING << __FUNCTION__ << "(): assuming default DSU slot: " << dsu_slot;
+            }
+            // This file is non-existent on Q vendor.
+            std::string lp_names;
+            if (!ReadFileToString(gsi::kGsiLpNamesFile, &lp_names) && errno != ENOENT) {
+                PERROR << __FUNCTION__ << "(): failed to read DSU LP names";
+                return false;
+            }
+            TransformFstabForDsu(&fstab, dsu_slot, Split(lp_names, ","));
+        } else if (errno != ENOENT) {
+            PERROR << __FUNCTION__ << "(): failed to access() DSU booted indicator";
+            return false;
         }
-        if (dsu_slot.empty()) {
-            dsu_slot = "dsu";
-        }
-
-        std::string lp_names;
-        ReadFileToString(gsi::kGsiLpNamesFile, &lp_names);
-        TransformFstabForDsu(fstab, dsu_slot, Split(lp_names, ","));
     }
 
-#ifndef NO_SKIP_MOUNT
-    SkipMountingPartitions(fstab);
-#endif
-    EnableMandatoryFlags(fstab);
+    SkipMountingPartitions(&fstab, false /* verbose */);
+    EnableMandatoryFlags(&fstab);
 
+    *fstab_out = std::move(fstab);
     return true;
 }
 
 // Returns fstab entries parsed from the device tree if they exist
-bool ReadFstabFromDt(Fstab* fstab, bool log) {
+bool ReadFstabFromDt(Fstab* fstab, bool verbose) {
     std::string fstab_buf = ReadFstabFromDt();
     if (fstab_buf.empty()) {
-        if (log) LINFO << __FUNCTION__ << "(): failed to read fstab from dt";
+        if (verbose) LINFO << __FUNCTION__ << "(): failed to read fstab from dt";
         return false;
     }
 
@@ -731,34 +751,36 @@
         fmemopen(static_cast<void*>(const_cast<char*>(fstab_buf.c_str())),
                  fstab_buf.length(), "r"), fclose);
     if (!fstab_file) {
-        if (log) PERROR << __FUNCTION__ << "(): failed to create a file stream for fstab dt";
+        if (verbose) PERROR << __FUNCTION__ << "(): failed to create a file stream for fstab dt";
         return false;
     }
 
     if (!ReadFstabFile(fstab_file.get(), false, fstab)) {
-        if (log) {
+        if (verbose) {
             LERROR << __FUNCTION__ << "(): failed to load fstab from kernel:" << std::endl
                    << fstab_buf;
         }
         return false;
     }
 
-#ifndef NO_SKIP_MOUNT
-    SkipMountingPartitions(fstab);
-#endif
+    SkipMountingPartitions(fstab, verbose);
 
     return true;
 }
 
-#ifndef NO_SKIP_MOUNT
+#ifdef NO_SKIP_MOUNT
+bool SkipMountingPartitions(Fstab*, bool) {
+    return true;
+}
+#else
 // For GSI to skip mounting /product and /system_ext, until there are well-defined interfaces
 // between them and /system. Otherwise, the GSI flashed on /system might not be able to work with
 // device-specific /product and /system_ext. skip_mount.cfg belongs to system_ext partition because
 // only common files for all targets can be put into system partition. It is under
 // /system/system_ext because GSI is a single system.img that includes the contents of system_ext
 // partition and product partition under /system/system_ext and /system/product, respectively.
-bool SkipMountingPartitions(Fstab* fstab) {
-    constexpr const char kSkipMountConfig[] = "/system/system_ext/etc/init/config/skip_mount.cfg";
+bool SkipMountingPartitions(Fstab* fstab, bool verbose) {
+    static constexpr char kSkipMountConfig[] = "/system/system_ext/etc/init/config/skip_mount.cfg";
 
     std::string skip_config;
     auto save_errno = errno;
@@ -767,29 +789,39 @@
         return true;
     }
 
-    for (const auto& skip_mount_point : Split(skip_config, "\n")) {
-        if (skip_mount_point.empty()) {
+    std::vector<std::string> skip_mount_patterns;
+    for (const auto& line : Split(skip_config, "\n")) {
+        if (line.empty() || StartsWith(line, "#")) {
             continue;
         }
-        auto it = std::remove_if(fstab->begin(), fstab->end(),
-                                 [&skip_mount_point](const auto& entry) {
-                                     return entry.mount_point == skip_mount_point;
-                                 });
-        if (it == fstab->end()) continue;
-        fstab->erase(it, fstab->end());
-        LOG(INFO) << "Skip mounting partition: " << skip_mount_point;
+        skip_mount_patterns.push_back(line);
     }
 
+    // Returns false if mount_point matches any of the skip mount patterns, so that the FstabEntry
+    // would be partitioned to the second group.
+    auto glob_pattern_mismatch = [&skip_mount_patterns](const FstabEntry& entry) -> bool {
+        for (const auto& pattern : skip_mount_patterns) {
+            if (!fnmatch(pattern.c_str(), entry.mount_point.c_str(), 0 /* flags */)) {
+                return false;
+            }
+        }
+        return true;
+    };
+    auto remove_from = std::stable_partition(fstab->begin(), fstab->end(), glob_pattern_mismatch);
+    if (verbose) {
+        for (auto it = remove_from; it != fstab->end(); ++it) {
+            LINFO << "Skip mounting mountpoint: " << it->mount_point;
+        }
+    }
+    fstab->erase(remove_from, fstab->end());
     return true;
 }
 #endif
 
 // Loads the fstab file and combines with fstab entries passed in from device tree.
 bool ReadDefaultFstab(Fstab* fstab) {
-    Fstab dt_fstab;
-    ReadFstabFromDt(&dt_fstab, false);
-
-    *fstab = std::move(dt_fstab);
+    fstab->clear();
+    ReadFstabFromDt(fstab, false /* verbose */);
 
     std::string default_fstab_path;
     // Use different fstab paths for normal boot and recovery boot, respectively
@@ -800,16 +832,14 @@
     }
 
     Fstab default_fstab;
-    if (!default_fstab_path.empty()) {
-        ReadFstabFromFile(default_fstab_path, &default_fstab);
+    if (!default_fstab_path.empty() && ReadFstabFromFile(default_fstab_path, &default_fstab)) {
+        for (auto&& entry : default_fstab) {
+            fstab->emplace_back(std::move(entry));
+        }
     } else {
         LINFO << __FUNCTION__ << "(): failed to find device default fstab";
     }
 
-    for (auto&& entry : default_fstab) {
-        fstab->emplace_back(std::move(entry));
-    }
-
     return !fstab->empty();
 }
 
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 1134f14..9a94d79 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -92,6 +92,10 @@
     return false;
 }
 
+bool fs_mgr_overlayfs_mount_fstab_entry(const std::string&, const std::string&) {
+    return false;
+}
+
 std::vector<std::string> fs_mgr_overlayfs_required_devices(Fstab*) {
     return {};
 }
@@ -116,6 +120,8 @@
 void MapScratchPartitionIfNeeded(Fstab*, const std::function<bool(const std::set<std::string>&)>&) {
 }
 
+void CleanupOldScratchFiles() {}
+
 void TeardownAllOverlayForMountPoint(const std::string&) {}
 
 }  // namespace fs_mgr
@@ -1293,6 +1299,18 @@
     }
 }
 
+bool fs_mgr_overlayfs_mount_fstab_entry(const std::string& lowers,
+                                        const std::string& mount_point) {
+    if (fs_mgr_overlayfs_invalid()) return false;
+
+    std::string aux = "lowerdir=" + lowers + ",override_creds=off";
+    auto rc = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME, aux.c_str());
+
+    if (rc == 0) return true;
+
+    return false;
+}
+
 bool fs_mgr_overlayfs_mount_all(Fstab* fstab) {
     auto ret = false;
     if (fs_mgr_overlayfs_invalid()) return ret;
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index d45e2de..ac95ef5 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -27,6 +27,7 @@
 android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab);
 
 bool fs_mgr_overlayfs_mount_all(android::fs_mgr::Fstab* fstab);
+bool fs_mgr_overlayfs_mount_fstab_entry (const std::string& lowers, const std::string& mount_point);
 std::vector<std::string> fs_mgr_overlayfs_required_devices(android::fs_mgr::Fstab* fstab);
 bool fs_mgr_overlayfs_setup(const char* backing = nullptr, const char* mount_point = nullptr,
                             bool* change = nullptr, bool force = true);
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 2d4de09..f33768b 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -47,6 +47,7 @@
     int max_comp_streams = 0;
     off64_t zram_size = 0;
     off64_t reserved_size = 0;
+    off64_t readahead_size_kb = -1;
     std::string encryption_options;
     off64_t erase_blk_size = 0;
     off64_t logical_blk_size = 0;
@@ -54,6 +55,7 @@
     std::string vbmeta_partition;
     uint64_t zram_backingdev_size = 0;
     std::string avb_keys;
+    std::string lowerdir;
 
     struct FsMgrFlags {
         bool wait : 1;
@@ -97,9 +99,9 @@
 using Fstab = std::vector<FstabEntry>;
 
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
-bool ReadFstabFromDt(Fstab* fstab, bool log = true);
+bool ReadFstabFromDt(Fstab* fstab, bool verbose = true);
 bool ReadDefaultFstab(Fstab* fstab);
-bool SkipMountingPartitions(Fstab* fstab);
+bool SkipMountingPartitions(Fstab* fstab, bool verbose = false);
 
 FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string& path);
 // The Fstab can contain multiple entries for the same mount point with different configurations.
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index ef46eb9..b0639e6 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -95,7 +95,9 @@
 }
 
 void DmTargetVerity::SetVerityMode(const std::string& mode) {
-    if (mode != "restart_on_corruption" && mode != "ignore_corruption") {
+    if (mode != "panic_on_corruption" &&
+        mode != "restart_on_corruption" &&
+        mode != "ignore_corruption") {
         LOG(ERROR) << "Unknown verity mode: " << mode;
         valid_ = false;
         return;
diff --git a/fs_mgr/libfiemap/binder.cpp b/fs_mgr/libfiemap/binder.cpp
index c8516ab..31a57a8 100644
--- a/fs_mgr/libfiemap/binder.cpp
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -224,8 +224,9 @@
     return false;
 }
 
-std::unique_ptr<IImageManager> IImageManager::Open(
-        const std::string& dir, const std::chrono::milliseconds& /*timeout_ms*/) {
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir,
+                                                   const std::chrono::milliseconds& /*timeout_ms*/,
+                                                   const DeviceInfo&) {
     android::sp<IGsiService> service = android::gsi::GetGsiService();
     android::sp<IImageService> manager;
 
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index 44f659b..dcbbc54 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -55,7 +55,8 @@
 static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test";
 static constexpr char kOtaTestImageMetadataDir[] = "/metadata/gsi/ota/test";
 
-std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix) {
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix,
+                                                 const DeviceInfo& device_info) {
     auto metadata_dir = "/metadata/gsi/" + dir_prefix;
     auto data_dir = "/data/gsi/" + dir_prefix;
     auto install_dir_file = gsi::DsuInstallDirFile(gsi::GetDsuSlot(dir_prefix));
@@ -63,17 +64,28 @@
     if (ReadFileToString(install_dir_file, &path)) {
         data_dir = path;
     }
-    return Open(metadata_dir, data_dir);
+    return Open(metadata_dir, data_dir, device_info);
 }
 
 std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir,
-                                                 const std::string& data_dir) {
-    return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir));
+                                                 const std::string& data_dir,
+                                                 const DeviceInfo& device_info) {
+    return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir, device_info));
 }
 
-ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir)
-    : metadata_dir_(metadata_dir), data_dir_(data_dir) {
+ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir,
+                           const DeviceInfo& device_info)
+    : metadata_dir_(metadata_dir), data_dir_(data_dir), device_info_(device_info) {
     partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
+
+    // Allow overriding whether ImageManager thinks it's in recovery, for testing.
+#ifdef __ANDROID_RECOVERY__
+    device_info_.is_recovery = {true};
+#else
+    if (!device_info_.is_recovery.has_value()) {
+        device_info_.is_recovery = {false};
+    }
+#endif
 }
 
 std::string ImageManager::GetImageHeaderPath(const std::string& name) {
@@ -261,10 +273,11 @@
         return false;
     }
 
-#if defined __ANDROID_RECOVERY__
-    LOG(ERROR) << "Cannot remove images backed by /data in recovery";
-    return false;
-#else
+    if (device_info_.is_recovery.value()) {
+        LOG(ERROR) << "Cannot remove images backed by /data in recovery";
+        return false;
+    }
+
     std::string message;
     auto header_file = GetImageHeaderPath(name);
     if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) {
@@ -278,7 +291,6 @@
         LOG(ERROR) << "Error removing " << status_file << ": " << message;
     }
     return RemoveImageMetadata(metadata_dir_, name);
-#endif
 }
 
 // Create a block device for an image file, using its extents in its
@@ -521,6 +533,9 @@
     // filesystem. This should only happen on devices with no encryption, or
     // devices with FBE and no metadata encryption. For these cases it suffices
     // to perform normal file writes to /data/gsi (which is unencrypted).
+    //
+    // Note: this is not gated on DeviceInfo, because the recovery-specific path
+    // must only be used in actual recovery.
     std::string block_device;
     bool can_use_devicemapper;
     if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) {
diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
index 50f4f33..3c87000 100644
--- a/fs_mgr/libfiemap/include/libfiemap/image_manager.h
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -21,6 +21,7 @@
 #include <chrono>
 #include <functional>
 #include <memory>
+#include <optional>
 #include <set>
 #include <string>
 
@@ -37,11 +38,17 @@
 
     virtual ~IImageManager() {}
 
+    // Helper for dependency injection.
+    struct DeviceInfo {
+        std::optional<bool> is_recovery;
+    };
+
     // When linking to libfiemap_binder, the Open() call will use binder.
     // Otherwise, the Open() call will use the ImageManager implementation
-    // below.
+    // below. In binder mode, device_info is ignored.
     static std::unique_ptr<IImageManager> Open(const std::string& dir_prefix,
-                                               const std::chrono::milliseconds& timeout_ms);
+                                               const std::chrono::milliseconds& timeout_ms,
+                                               const DeviceInfo& device_info = {});
 
     // Flags for CreateBackingImage().
     static constexpr int CREATE_IMAGE_DEFAULT = 0x0;
@@ -131,11 +138,13 @@
     // Return an ImageManager for the given metadata and data directories. Both
     // directories must already exist.
     static std::unique_ptr<ImageManager> Open(const std::string& metadata_dir,
-                                              const std::string& data_dir);
+                                              const std::string& data_dir,
+                                              const DeviceInfo& device_info = {});
 
     // Helper function that derives the metadata and data dirs given a single
     // prefix.
-    static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix);
+    static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix,
+                                              const DeviceInfo& device_info = {});
 
     // Methods that must be implemented from IImageManager.
     FiemapStatus CreateBackingImage(const std::string& name, uint64_t size, int flags,
@@ -166,7 +175,8 @@
     FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes);
 
   private:
-    ImageManager(const std::string& metadata_dir, const std::string& data_dir);
+    ImageManager(const std::string& metadata_dir, const std::string& data_dir,
+                 const DeviceInfo& device_info);
     std::string GetImageHeaderPath(const std::string& name);
     std::string GetStatusFilePath(const std::string& image_name);
     bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
@@ -187,6 +197,7 @@
     std::string metadata_dir_;
     std::string data_dir_;
     std::unique_ptr<IPartitionOpener> partition_opener_;
+    DeviceInfo device_info_;
 };
 
 // RAII helper class for mapping and opening devices with an ImageManager.
@@ -198,7 +209,7 @@
 
     ~MappedDevice();
 
-    int fd() const { return fd_; }
+    int fd() const { return fd_.get(); }
     const std::string& path() const { return path_; }
 
   protected:
diff --git a/fs_mgr/libfiemap/passthrough.cpp b/fs_mgr/libfiemap/passthrough.cpp
index 1ccd9a0..d521804 100644
--- a/fs_mgr/libfiemap/passthrough.cpp
+++ b/fs_mgr/libfiemap/passthrough.cpp
@@ -20,9 +20,10 @@
 namespace fiemap {
 
 std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir_prefix,
-                                                   const std::chrono::milliseconds& timeout_ms) {
+                                                   const std::chrono::milliseconds& timeout_ms,
+                                                   const DeviceInfo& device_info) {
     (void)timeout_ms;
-    return ImageManager::Open(dir_prefix);
+    return ImageManager::Open(dir_prefix, device_info);
 }
 
 }  // namespace fiemap
diff --git a/fs_mgr/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index c1c181a..6892025 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -78,6 +78,7 @@
     shared_libs: [
         "libbase",
         "libchrome",
+        "libcrypto",
     ],
     target: {
         darwin: {
@@ -107,9 +108,6 @@
     static_libs: [
         "libfs_avb_test_util",
     ],
-    shared_libs: [
-        "libcrypto",
-    ],
     compile_multilib: "first",
     data: [
         ":avbtool",
diff --git a/fs_mgr/libfs_avb/TEST_MAPPING b/fs_mgr/libfs_avb/TEST_MAPPING
deleted file mode 100644
index b0f36d4..0000000
--- a/fs_mgr/libfs_avb/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "postsubmit": [
-    {
-      "name": "libfs_avb_test",
-      "host": true
-    },
-    {
-      "name": "libfs_avb_internal_test",
-      "host": true
-    }
-  ]
-}
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index 2288674..31494c1 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -61,7 +61,9 @@
 
     // Converts veritymode to the format used in kernel.
     std::string dm_verity_mode;
-    if (verity_mode == "enforcing") {
+    if (verity_mode == "panicking") {
+        dm_verity_mode = "panic_on_corruption";
+    } else if (verity_mode == "enforcing") {
         dm_verity_mode = "restart_on_corruption";
     } else if (verity_mode == "logging") {
         dm_verity_mode = "ignore_corruption";
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
index 17f4c4e..1c95cf0 100644
--- a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
+++ b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
@@ -122,6 +122,7 @@
                                                  const size_t padding_size) {
     VBMetaImage vbmeta_image;
     vbmeta_image.path = test_dir_.Append(output_file_name);
+    GTEST_LOG_(INFO) << "ExtractVBMetaImage: " << image_path << " to " << output_file_name;
     EXPECT_COMMAND(0,
                    "avbtool extract_vbmeta_image"
                    " --image %s"
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index ea92d25..6a764e4 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -251,6 +251,7 @@
         "snapshot_metadata_updater_test.cpp",
         "snapshot_reader_test.cpp",
         "snapshot_test.cpp",
+        "snapshot_writer_test.cpp",
     ],
     shared_libs: [
         "libbinder",
@@ -264,7 +265,8 @@
         "android.hardware.boot@1.0",
         "android.hardware.boot@1.1",
         "libbrotli",
-        "libfs_mgr",
+        "libc++fs",
+        "libfs_mgr_binder",
         "libgsi",
         "libgmock",
         "liblp",
@@ -297,6 +299,7 @@
     ],
     static_libs: [
         "libbrotli",
+        "libc++fs",
         "libfstab",
         "libsnapshot",
         "libsnapshot_cow",
@@ -326,6 +329,7 @@
         "power_test.cpp",
     ],
     static_libs: [
+        "libc++fs",
         "libsnapshot",
         "update_metadata-protos",
     ],
@@ -355,6 +359,7 @@
     static_libs: [
         "libbase",
         "libbrotli",
+        "libc++fs",
         "libchrome",
         "libcrypto_static",
         "libcutils",
@@ -416,7 +421,8 @@
         "snapuserd_server.cpp",
         "snapuserd.cpp",
         "snapuserd_daemon.cpp",
-	"snapuserd_worker.cpp",
+        "snapuserd_worker.cpp",
+        "snapuserd_readahead.cpp",
     ],
 
     cflags: [
@@ -473,6 +479,9 @@
         "libgtest",
         "libsnapshot_cow",
     ],
+    test_suites: [
+        "device-tests"
+    ],
     test_min_api_level: 30,
     auto_gen_config: true,
     require_root: false,
@@ -555,7 +564,7 @@
     srcs: [
         "cow_snapuserd_test.cpp",
         "snapuserd.cpp",
-	"snapuserd_worker.cpp",
+        "snapuserd_worker.cpp",
     ],
     cflags: [
         "-Wall",
@@ -572,7 +581,7 @@
         "libsnapshot_snapuserd",
         "libcutils_sockets",
         "libz",
-	"libfs_mgr",
+        "libfs_mgr",
         "libdm",
     ],
     header_libs: [
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 1ebc29f..92aa55c 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -139,7 +139,28 @@
     Cancelled = 7;
 };
 
-// Next: 7
+// Next 14:
+//
+// To understand the source of each failure, read snapshot.cpp. To handle new
+// sources of failure, avoid reusing an existing code; add a new code instead.
+enum MergeFailureCode {
+    Ok = 0;
+    ReadStatus = 1;
+    GetTableInfo = 2;
+    UnknownTable = 3;
+    GetTableParams = 4;
+    ActivateNewTable = 5;
+    AcquireLock = 6;
+    ListSnapshots = 7;
+    WriteStatus = 8;
+    UnknownTargetType = 9;
+    QuerySnapshotStatus = 10;
+    ExpectedMergeTarget = 11;
+    UnmergedSectorsAfterCompletion = 12;
+    UnexpectedMergeState = 13;
+};
+
+// Next: 8
 message SnapshotUpdateStatus {
     UpdateState state = 1;
 
@@ -160,9 +181,12 @@
 
     // Merge phase (if state == MERGING).
     MergePhase merge_phase = 6;
+
+    // Merge failure code, filled if state == MergeFailed.
+    MergeFailureCode merge_failure_code = 7;
 }
 
-// Next: 9
+// Next: 10
 message SnapshotMergeReport {
     // Status of the update after the merge attempts.
     UpdateState state = 1;
@@ -188,4 +212,7 @@
 
     // Time from sys.boot_completed to merge start, in milliseconds.
     uint32 boot_complete_to_merge_start_time_ms = 8;
+
+    // Merge failure code, filled if state == MergeFailed.
+    MergeFailureCode merge_failure_code = 9;
 }
diff --git a/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt b/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt
new file mode 100644
index 0000000..c474f4c
--- /dev/null
+++ b/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt
@@ -0,0 +1,41 @@
+device_info_data {
+  allow_set_slot_as_unbootable: true
+  is_recovery: true
+}
+is_super_metadata_valid: true
+super_data {
+  partitions {
+    partition_name: "sys_a"
+    new_partition_info {
+      size: 3145728
+    }
+  }
+  partitions {
+    partition_name: "vnnd_"
+    new_partition_info {
+      size: 3145728
+    }
+  }
+  partitions {
+    partition_name: "prd_a"
+    new_partition_info {
+    }
+  }
+  dynamic_partition_metadata {
+    groups {
+      name: "group_google_dp_a"
+      size: 34375467008
+      partition_names: "sys_a"
+      partition_names: "vnd_a"
+      partition_names: "prd_a"
+    }
+  }
+}
+has_metadata_snapshots_dir: true
+actions {
+  handle_imminent_data_wipe: true
+}
+actions {
+  begin_update {
+  }
+}
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 5d63220..b75b154 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -25,6 +25,10 @@
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
 
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+
 namespace android {
 namespace snapshot {
 
@@ -781,6 +785,202 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 }
 
+AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) {
+    data.resize(writer->options().block_size, '\0');
+    if (!writer->AddRawBlocks(new_block, data.data(), data.size())) {
+        return AssertionFailure() << "Failed to add raw block";
+    }
+    return AssertionSuccess();
+}
+
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+                                 const std::string& data) {
+    CowHeader header;
+    reader->GetHeader(&header);
+
+    std::string cmp = data;
+    cmp.resize(header.block_size, '\0');
+
+    StringSink sink;
+    if (!reader->ReadData(op, &sink)) {
+        return AssertionFailure() << "Failed to read data block";
+    }
+    if (cmp != sink.stream()) {
+        return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got "
+                                  << sink.stream();
+    }
+
+    return AssertionSuccess();
+}
+
+TEST_F(CowTest, ResumeMidCluster) {
+    CowOptions options;
+    options.cluster_ops = 7;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_TRUE(writer->AddLabel(2));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 8);
+    ASSERT_EQ(max_in_cluster, 7);
+    ASSERT_EQ(num_clusters, 2);
+}
+
+TEST_F(CowTest, ResumeEndCluster) {
+    CowOptions options;
+    int cluster_ops = 5;
+    options.cluster_ops = cluster_ops;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_TRUE(writer->AddLabel(2));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 8);
+    ASSERT_EQ(max_in_cluster, cluster_ops);
+    ASSERT_EQ(num_clusters, 3);
+}
+
+TEST_F(CowTest, DeleteMidCluster) {
+    CowOptions options;
+    options.cluster_ops = 7;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 3);
+    ASSERT_EQ(max_in_cluster, 5);  // 3 data, 1 label, 1 cluster op
+    ASSERT_EQ(num_clusters, 1);
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 44a423c..2349e4a 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -94,11 +94,6 @@
                    << "Expected: " << kCowMagicNumber;
         return false;
     }
-    if (header_.header_size != sizeof(CowHeader)) {
-        LOG(ERROR) << "Header size unknown, read " << header_.header_size << ", expected "
-                   << sizeof(CowHeader);
-        return false;
-    }
     if (header_.footer_size != sizeof(CowFooter)) {
         LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
                    << sizeof(CowFooter);
@@ -123,8 +118,7 @@
         return false;
     }
 
-    if ((header_.major_version != kCowVersionMajor) ||
-        (header_.minor_version != kCowVersionMinor)) {
+    if ((header_.major_version > kCowVersionMajor) || (header_.minor_version != kCowVersionMinor)) {
         LOG(ERROR) << "Header version mismatch";
         LOG(ERROR) << "Major version: " << header_.major_version
                    << "Expected: " << kCowVersionMajor;
@@ -137,10 +131,25 @@
 }
 
 bool CowReader::ParseOps(std::optional<uint64_t> label) {
-    uint64_t pos = lseek(fd_.get(), sizeof(header_), SEEK_SET);
-    if (pos != sizeof(header_)) {
-        PLOG(ERROR) << "lseek ops failed";
-        return false;
+    uint64_t pos;
+
+    // Skip the scratch space
+    if (header_.major_version >= 2 && (header_.buffer_size > 0)) {
+        LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
+        size_t init_offset = header_.header_size + header_.buffer_size;
+        pos = lseek(fd_.get(), init_offset, SEEK_SET);
+        if (pos != init_offset) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+    } else {
+        pos = lseek(fd_.get(), header_.header_size, SEEK_SET);
+        if (pos != header_.header_size) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+        // Reading a v1 version of COW which doesn't have buffer_size.
+        header_.buffer_size = 0;
     }
 
     auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
@@ -250,6 +259,8 @@
     }
 
     ops_ = ops_buffer;
+    ops_->shrink_to_fit();
+
     return true;
 }
 
@@ -358,6 +369,24 @@
     //                        Replace-op-4, Zero-op-9, Replace-op-5 }
     //==============================================================
 
+    num_copy_ops = FindNumCopyops();
+
+    std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
+              [](CowOperation& op1, CowOperation& op2) -> bool {
+                  return op1.new_block > op2.new_block;
+              });
+
+    if (header_.num_merge_ops > 0) {
+        ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
+    }
+
+    num_copy_ops = FindNumCopyops();
+    set_copy_ops(num_copy_ops);
+}
+
+uint64_t CowReader::FindNumCopyops() {
+    uint64_t num_copy_ops = 0;
+
     for (uint64_t i = 0; i < ops_->size(); i++) {
         auto& current_op = ops_->data()[i];
         if (current_op.type != kCowCopyOp) {
@@ -366,15 +395,7 @@
         num_copy_ops += 1;
     }
 
-    std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
-              [](CowOperation& op1, CowOperation& op2) -> bool {
-                  return op1.new_block > op2.new_block;
-              });
-
-    if (header_.num_merge_ops > 0) {
-        CHECK(ops_->size() >= header_.num_merge_ops);
-        ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
-    }
+    return num_copy_ops;
 }
 
 bool CowReader::GetHeader(CowHeader* header) {
@@ -468,7 +489,7 @@
 
 bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
     // Validate the offset, taking care to acknowledge possible overflow of offset+len.
-    if (offset < sizeof(header_) || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
+    if (offset < header_.header_size || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
         offset + len > fd_size_ - sizeof(CowFooter)) {
         LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
         return false;
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 045d9db..3888eb1 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -96,6 +96,8 @@
 class CowSnapuserdTest final {
   public:
     bool Setup();
+    bool SetupCopyOverlap_1();
+    bool SetupCopyOverlap_2();
     bool Merge();
     void ValidateMerge();
     void ReadSnapshotDeviceAndValidate();
@@ -114,6 +116,9 @@
     void StartMerge();
 
     void CreateCowDevice();
+    void CreateCowDeviceWithCopyOverlap_1();
+    void CreateCowDeviceWithCopyOverlap_2();
+    bool SetupDaemon();
     void CreateBaseDevice();
     void InitCowDevice();
     void SetDeviceControlName();
@@ -191,6 +196,33 @@
     return setup_ok_;
 }
 
+bool CowSnapuserdTest::SetupCopyOverlap_1() {
+    CreateBaseDevice();
+    CreateCowDeviceWithCopyOverlap_1();
+    return SetupDaemon();
+}
+
+bool CowSnapuserdTest::SetupCopyOverlap_2() {
+    CreateBaseDevice();
+    CreateCowDeviceWithCopyOverlap_2();
+    return SetupDaemon();
+}
+
+bool CowSnapuserdTest::SetupDaemon() {
+    SetDeviceControlName();
+
+    StartSnapuserdDaemon();
+    InitCowDevice();
+
+    CreateDmUserDevice();
+    InitDaemon();
+
+    CreateSnapshotDevice();
+    setup_ok_ = true;
+
+    return setup_ok_;
+}
+
 void CowSnapuserdTest::StartSnapuserdDaemon() {
     pid_t pid = fork();
     ASSERT_GE(pid, 0);
@@ -255,6 +287,101 @@
     ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
 }
 
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t blk_src_copy = 0;
+
+    // Create overlapping copy operations
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
+        x -= 1;
+        if (x == 1) {
+            break;
+        }
+        blk_src_copy += 1;
+    }
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // Merged operations required for validation
+    int block_size = 4096;
+    x = num_blocks;
+    loff_t src_offset = block_size;
+    loff_t dest_offset = 0;
+
+    while (1) {
+        memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset,
+                block_size);
+        x -= 1;
+        if (x == 1) {
+            break;
+        }
+        src_offset += block_size;
+        dest_offset += block_size;
+    }
+}
+
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t blk_src_copy = num_blocks - 1;
+
+    // Create overlapping copy operations
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            ASSERT_EQ(blk_src_copy, 0);
+            break;
+        }
+        blk_src_copy -= 1;
+    }
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // Merged operations
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
+              true);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(
+                      base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
+              true);
+}
+
 void CowSnapuserdTest::CreateCowDevice() {
     unique_fd rnd_fd;
     loff_t offset = 0;
@@ -757,6 +884,31 @@
     harness.ValidateMerge();
     harness.Shutdown();
 }
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_1());
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_2());
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_1());
+    harness.MergeInterrupt();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 59f6d6f..51c00a9 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -94,6 +94,7 @@
     header_.block_size = options_.block_size;
     header_.num_merge_ops = 0;
     header_.cluster_ops = options_.cluster_ops;
+    header_.buffer_size = 0;
     footer_ = {};
     footer_.op.data_length = 64;
     footer_.op.type = kCowFooterOp;
@@ -139,12 +140,6 @@
     return true;
 }
 
-void CowWriter::InitializeMerge(borrowed_fd fd, CowHeader* header) {
-    fd_ = fd;
-    memcpy(&header_, header, sizeof(CowHeader));
-    merge_in_progress_ = true;
-}
-
 bool CowWriter::Initialize(unique_fd&& fd) {
     owned_fd_ = std::move(fd);
     return Initialize(borrowed_fd{owned_fd_});
@@ -172,7 +167,7 @@
 }
 
 void CowWriter::InitPos() {
-    next_op_pos_ = sizeof(header_);
+    next_op_pos_ = sizeof(header_) + header_.buffer_size;
     cluster_size_ = header_.cluster_ops * sizeof(CowOperation);
     if (header_.cluster_ops) {
         next_data_pos_ = next_op_pos_ + cluster_size_;
@@ -196,6 +191,10 @@
         return false;
     }
 
+    if (options_.scratch_space) {
+        header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+    }
+
     // Headers are not complete, but this ensures the file is at the right
     // position.
     if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
@@ -203,7 +202,27 @@
         return false;
     }
 
+    if (options_.scratch_space) {
+        // Initialize the scratch space
+        std::string data(header_.buffer_size, 0);
+        if (!android::base::WriteFully(fd_, data.data(), header_.buffer_size)) {
+            PLOG(ERROR) << "writing scratch space failed";
+            return false;
+        }
+    }
+
+    if (!Sync()) {
+        LOG(ERROR) << "Header sync failed";
+        return false;
+    }
+
+    if (lseek(fd_.get(), sizeof(header_) + header_.buffer_size, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek failed";
+        return false;
+    }
+
     InitPos();
+
     return true;
 }
 
@@ -232,15 +251,11 @@
     // Free reader so we own the descriptor position again.
     reader = nullptr;
 
-    // Remove excess data
-    if (!Truncate(next_op_pos_)) {
-        return false;
-    }
     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
         PLOG(ERROR) << "lseek failed";
         return false;
     }
-    return true;
+    return EmitClusterIfNeeded();
 }
 
 bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
@@ -319,6 +334,14 @@
     return WriteOperation(op);
 }
 
+bool CowWriter::EmitClusterIfNeeded() {
+    // If there isn't room for another op and the cluster end op, end the current cluster
+    if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) {
+        if (!EmitCluster()) return false;
+    }
+    return true;
+}
+
 std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
     switch (compression_) {
         case kCowCompressGz: {
@@ -379,6 +402,21 @@
     auto continue_num_ops = footer_.op.num_ops;
     bool extra_cluster = false;
 
+    // Blank out extra ops, in case we're in append mode and dropped ops.
+    if (cluster_size_) {
+        auto unused_cluster_space = cluster_size_ - current_cluster_size_;
+        std::string clr;
+        clr.resize(unused_cluster_space, '\0');
+        if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+            PLOG(ERROR) << "Failed to seek to footer position.";
+            return false;
+        }
+        if (!android::base::WriteFully(fd_, clr.data(), clr.size())) {
+            PLOG(ERROR) << "clearing unused cluster area failed";
+            return false;
+        }
+    }
+
     // Footer should be at the end of a file, so if there is data after the current block, end it
     // and start a new cluster.
     if (cluster_size_ && current_data_size_ > 0) {
@@ -403,6 +441,17 @@
         return false;
     }
 
+    // Remove excess data, if we're in append mode and threw away more data
+    // than we wrote before.
+    off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
+    if (offs < 0) {
+        PLOG(ERROR) << "Failed to lseek to find current position";
+        return false;
+    }
+    if (!Truncate(offs)) {
+        return false;
+    }
+
     // Reposition for additional Writing
     if (extra_cluster) {
         current_cluster_size_ = continue_cluster_size;
@@ -445,12 +494,7 @@
         if (!WriteRawData(data, size)) return false;
     }
     AddOperation(op);
-    // If there isn't room for another op and the cluster end op, end the current cluster
-    if (cluster_size_ && op.type != kCowClusterOp &&
-        cluster_size_ < current_cluster_size_ + 2 * sizeof(op)) {
-        if (!EmitCluster()) return false;
-    }
-    return true;
+    return EmitClusterIfNeeded();
 }
 
 void CowWriter::AddOperation(const CowOperation& op) {
@@ -492,24 +536,6 @@
     return true;
 }
 
-bool CowWriter::CommitMerge(int merged_ops) {
-    CHECK(merge_in_progress_);
-    header_.num_merge_ops += merged_ops;
-
-    if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek failed";
-        return false;
-    }
-
-    if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&header_),
-                                   sizeof(header_))) {
-        PLOG(ERROR) << "WriteFully failed";
-        return false;
-    }
-
-    return Sync();
-}
-
 bool CowWriter::Truncate(off_t length) {
     if (is_dev_null_ || is_block_device_) {
         return true;
diff --git a/fs_mgr/libsnapshot/device_info.cpp b/fs_mgr/libsnapshot/device_info.cpp
index 0e90100..14ce0ee 100644
--- a/fs_mgr/libsnapshot/device_info.cpp
+++ b/fs_mgr/libsnapshot/device_info.cpp
@@ -17,6 +17,7 @@
 #include <android-base/logging.h>
 #include <fs_mgr.h>
 #include <fs_mgr_overlayfs.h>
+#include <libfiemap/image_manager.h>
 
 namespace android {
 namespace snapshot {
@@ -26,6 +27,7 @@
 using android::hardware::boot::V1_0::CommandResult;
 #endif
 
+using namespace std::chrono_literals;
 using namespace std::string_literals;
 
 #ifdef __ANDROID_RECOVERY__
@@ -34,10 +36,6 @@
 constexpr bool kIsRecovery = false;
 #endif
 
-std::string DeviceInfo::GetGsidDir() const {
-    return "ota"s;
-}
-
 std::string DeviceInfo::GetMetadataDir() const {
     return "/metadata/ota"s;
 }
@@ -100,6 +98,10 @@
     return kIsRecovery;
 }
 
+bool DeviceInfo::IsFirstStageInit() const {
+    return first_stage_init_;
+}
+
 bool DeviceInfo::SetSlotAsUnbootable([[maybe_unused]] unsigned int slot) {
 #ifdef LIBSNAPSHOT_USE_HAL
     if (!EnsureBootHal()) {
@@ -120,5 +122,22 @@
 #endif
 }
 
+std::unique_ptr<android::fiemap::IImageManager> DeviceInfo::OpenImageManager() const {
+    return IDeviceInfo::OpenImageManager("ota");
+}
+
+std::unique_ptr<android::fiemap::IImageManager> ISnapshotManager::IDeviceInfo::OpenImageManager(
+        const std::string& gsid_dir) const {
+    if (IsRecovery() || IsFirstStageInit()) {
+        android::fiemap::ImageManager::DeviceInfo device_info = {
+                .is_recovery = {IsRecovery()},
+        };
+        return android::fiemap::ImageManager::Open(gsid_dir, device_info);
+    } else {
+        // For now, use a preset timeout.
+        return android::fiemap::IImageManager::Open(gsid_dir, 15000ms);
+    }
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/device_info.h b/fs_mgr/libsnapshot/device_info.h
index d8d3d91..7999c99 100644
--- a/fs_mgr/libsnapshot/device_info.h
+++ b/fs_mgr/libsnapshot/device_info.h
@@ -29,7 +29,6 @@
     using MergeStatus = android::hardware::boot::V1_1::MergeStatus;
 
   public:
-    std::string GetGsidDir() const override;
     std::string GetMetadataDir() const override;
     std::string GetSlotSuffix() const override;
     std::string GetOtherSlotSuffix() const override;
@@ -39,11 +38,16 @@
     bool SetBootControlMergeStatus(MergeStatus status) override;
     bool SetSlotAsUnbootable(unsigned int slot) override;
     bool IsRecovery() const override;
+    std::unique_ptr<IImageManager> OpenImageManager() const override;
+    bool IsFirstStageInit() const override;
+
+    void set_first_stage_init(bool value) { first_stage_init_ = value; }
 
   private:
     bool EnsureBootHal();
 
     android::fs_mgr::PartitionOpener opener_;
+    bool first_stage_init_ = false;
 #ifdef LIBSNAPSHOT_USE_HAL
     android::sp<android::hardware::boot::V1_1::IBootControl> boot_control_;
 #endif
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 797b8ef..000e5e1 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -21,15 +21,22 @@
 namespace snapshot {
 
 static constexpr uint64_t kCowMagicNumber = 0x436f77634f572121ULL;
-static constexpr uint32_t kCowVersionMajor = 1;
+static constexpr uint32_t kCowVersionMajor = 2;
 static constexpr uint32_t kCowVersionMinor = 0;
 
+static constexpr uint32_t kCowVersionManifest = 2;
+
+static constexpr uint32_t BLOCK_SZ = 4096;
+static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+
 // This header appears as the first sequence of bytes in the COW. All fields
 // in the layout are little-endian encoded. The on-disk layout is:
 //
 //      +-----------------------+
 //      |     Header (fixed)    |
 //      +-----------------------+
+//      |     Scratch space     |
+//      +-----------------------+
 //      | Operation  (variable) |
 //      | Data       (variable) |
 //      +-----------------------+
@@ -68,6 +75,9 @@
 
     // Tracks merge operations completed
     uint64_t num_merge_ops;
+
+    // Scratch space used during merge
+    uint32_t buffer_size;
 } __attribute__((packed));
 
 // This structure is the same size of a normal Operation, but is repurposed for the footer.
@@ -144,11 +154,31 @@
 static constexpr uint8_t kCowCompressGz = 1;
 static constexpr uint8_t kCowCompressBrotli = 2;
 
+static constexpr uint8_t kCowReadAheadNotStarted = 0;
+static constexpr uint8_t kCowReadAheadInProgress = 1;
+static constexpr uint8_t kCowReadAheadDone = 2;
+
 struct CowFooter {
     CowFooterOperation op;
     CowFooterData data;
 } __attribute__((packed));
 
+struct ScratchMetadata {
+    // Block of data in the image that operation modifies
+    // and read-ahead thread stores the modified data
+    // in the scratch space
+    uint64_t new_block;
+    // Offset within the file to read the data
+    uint64_t file_offset;
+} __attribute__((packed));
+
+struct BufferState {
+    uint8_t read_ahead_state;
+} __attribute__((packed));
+
+// 2MB Scratch space used for read-ahead
+static constexpr uint64_t BUFFER_REGION_DEFAULT_SIZE = (1ULL << 21);
+
 std::ostream& operator<<(std::ostream& os, CowOperation const& arg);
 
 int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_size);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 552fd96..9ebcfd9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -141,18 +141,21 @@
 
     bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
 
-    void UpdateMergeProgress(uint64_t merge_ops) { header_.num_merge_ops += merge_ops; }
-
     void InitializeMerge();
 
     void set_total_data_ops(uint64_t size) { total_data_ops_ = size; }
 
     uint64_t total_data_ops() { return total_data_ops_; }
 
+    void set_copy_ops(uint64_t size) { copy_ops_ = size; }
+
+    uint64_t total_copy_ops() { return copy_ops_; }
+
     void CloseCowFd() { owned_fd_ = {}; }
 
   private:
     bool ParseOps(std::optional<uint64_t> label);
+    uint64_t FindNumCopyops();
 
     android::base::unique_fd owned_fd_;
     android::base::borrowed_fd fd_;
@@ -162,6 +165,7 @@
     std::optional<uint64_t> last_label_;
     std::shared_ptr<std::vector<CowOperation>> ops_;
     uint64_t total_data_ops_;
+    uint64_t copy_ops_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 6ffd5d8..f43ea68 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -36,6 +36,8 @@
 
     // Number of CowOperations in a cluster. 0 for no clustering. Cannot be 1.
     uint32_t cluster_ops = 200;
+
+    bool scratch_space = true;
 };
 
 // Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -100,13 +102,12 @@
     bool InitializeAppend(android::base::unique_fd&&, uint64_t label);
     bool InitializeAppend(android::base::borrowed_fd fd, uint64_t label);
 
-    void InitializeMerge(android::base::borrowed_fd fd, CowHeader* header);
-    bool CommitMerge(int merged_ops);
-
     bool Finalize() override;
 
     uint64_t GetCowSize() override;
 
+    uint32_t GetCowVersion() { return header_.major_version; }
+
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
     virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
@@ -115,6 +116,7 @@
 
   private:
     bool EmitCluster();
+    bool EmitClusterIfNeeded();
     void SetupHeaders();
     bool ParseOptions();
     bool OpenForWrite();
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
index ef9d648..573a85b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
@@ -22,7 +22,6 @@
 
 class MockDeviceInfo : public SnapshotManager::IDeviceInfo {
   public:
-    MOCK_METHOD(std::string, GetGsidDir, (), (const, override));
     MOCK_METHOD(std::string, GetMetadataDir, (), (const, override));
     MOCK_METHOD(std::string, GetSlotSuffix, (), (const, override));
     MOCK_METHOD(std::string, GetOtherSlotSuffix, (), (const, override));
@@ -32,6 +31,9 @@
     MOCK_METHOD(bool, SetBootControlMergeStatus, (MergeStatus status), (override));
     MOCK_METHOD(bool, SetSlotAsUnbootable, (unsigned int slot), (override));
     MOCK_METHOD(bool, IsRecovery, (), (const, override));
+    MOCK_METHOD(bool, IsFirstStageInit, (), (const, override));
+    MOCK_METHOD(std::unique_ptr<android::fiemap::IImageManager>, OpenImageManager, (),
+                (const, override));
 };
 
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 1cb966b..94d5055 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -27,6 +27,7 @@
     MOCK_METHOD(bool, CancelUpdate, (), (override));
     MOCK_METHOD(bool, FinishedSnapshotWrites, (bool wipe), (override));
     MOCK_METHOD(void, UpdateCowStats, (ISnapshotMergeStats * stats), (override));
+    MOCK_METHOD(MergeFailureCode, ReadMergeFailureCode, (), (override));
     MOCK_METHOD(bool, InitiateMerge, (), (override));
 
     MOCK_METHOD(UpdateState, ProcessUpdateState,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
new file mode 100644
index 0000000..067f99c
--- /dev/null
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#pragma once
+
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <libsnapshot/snapshot_stats.h>
+
+namespace android::snapshot {
+
+class MockSnapshotMergeStats final : public ISnapshotMergeStats {
+  public:
+    virtual ~MockSnapshotMergeStats() = default;
+    // Called when merge starts or resumes.
+    MOCK_METHOD(bool, Start, (), (override));
+    MOCK_METHOD(void, set_state, (android::snapshot::UpdateState, bool), (override));
+    MOCK_METHOD(void, set_cow_file_size, (uint64_t), ());
+    MOCK_METHOD(void, set_total_cow_size_bytes, (uint64_t), (override));
+    MOCK_METHOD(void, set_estimated_cow_size_bytes, (uint64_t), (override));
+    MOCK_METHOD(void, set_boot_complete_time_ms, (uint32_t), (override));
+    MOCK_METHOD(void, set_boot_complete_to_merge_start_time_ms, (uint32_t), (override));
+    MOCK_METHOD(void, set_merge_failure_code, (MergeFailureCode), (override));
+    MOCK_METHOD(uint64_t, cow_file_size, (), (override));
+    MOCK_METHOD(uint64_t, total_cow_size_bytes, (), (override));
+    MOCK_METHOD(uint64_t, estimated_cow_size_bytes, (), (override));
+    MOCK_METHOD(uint32_t, boot_complete_time_ms, (), (override));
+    MOCK_METHOD(uint32_t, boot_complete_to_merge_start_time_ms, (), (override));
+    MOCK_METHOD(MergeFailureCode, merge_failure_code, (), (override));
+    MOCK_METHOD(std::unique_ptr<Result>, Finish, (), (override));
+
+    using ISnapshotMergeStats::Result;
+    // Return nullptr if any failure.
+};
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 7e74fac..603e896 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -94,8 +94,9 @@
     // Dependency injection for testing.
     class IDeviceInfo {
       public:
+        using IImageManager = android::fiemap::IImageManager;
+
         virtual ~IDeviceInfo() {}
-        virtual std::string GetGsidDir() const = 0;
         virtual std::string GetMetadataDir() const = 0;
         virtual std::string GetSlotSuffix() const = 0;
         virtual std::string GetOtherSlotSuffix() const = 0;
@@ -107,6 +108,11 @@
         virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
         virtual bool IsRecovery() const = 0;
         virtual bool IsTestDevice() const { return false; }
+        virtual bool IsFirstStageInit() const = 0;
+        virtual std::unique_ptr<IImageManager> OpenImageManager() const = 0;
+
+        // Helper method for implementing OpenImageManager.
+        std::unique_ptr<IImageManager> OpenImageManager(const std::string& gsid_dir) const;
     };
     virtual ~ISnapshotManager() = default;
 
@@ -167,6 +173,10 @@
     virtual UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
                                            const std::function<bool()>& before_cancel = {}) = 0;
 
+    // If ProcessUpdateState() returned MergeFailed, this returns the appropriate
+    // code. Otherwise, MergeFailureCode::Ok is returned.
+    virtual MergeFailureCode ReadMergeFailureCode() = 0;
+
     // Find the status of the current update, if any.
     //
     // |progress| depends on the returned status:
@@ -332,6 +342,7 @@
     bool CancelUpdate() override;
     bool FinishedSnapshotWrites(bool wipe) override;
     void UpdateCowStats(ISnapshotMergeStats* stats) override;
+    MergeFailureCode ReadMergeFailureCode() override;
     bool InitiateMerge() override;
     UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
                                    const std::function<bool()>& before_cancel = {}) override;
@@ -381,11 +392,13 @@
     FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
     FRIEND_TEST(SnapshotTest, MapSnapshot);
     FRIEND_TEST(SnapshotTest, Merge);
+    FRIEND_TEST(SnapshotTest, MergeFailureCode);
     FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
     FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
     FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
+    FRIEND_TEST(SnapshotUpdateTest, DataWipeWithStaleSnapshots);
     FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
     FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
@@ -413,7 +426,6 @@
     bool EnsureSnapuserdConnected();
 
     // Helpers for first-stage init.
-    bool ForceLocalImageManager();
     const std::unique_ptr<IDeviceInfo>& device() const { return device_; }
 
     // Helper functions for tests.
@@ -493,6 +505,9 @@
     // Unmap a COW image device previously mapped with MapCowImage().
     bool UnmapCowImage(const std::string& name);
 
+    // Unmap a COW and remove it from a MetadataBuilder.
+    void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata);
+
     // Unmap and remove all known snapshots.
     bool RemoveAllSnapshots(LockedFile* lock);
 
@@ -529,7 +544,8 @@
     // Interact with /metadata/ota/state.
     UpdateState ReadUpdateState(LockedFile* file);
     SnapshotUpdateStatus ReadSnapshotUpdateStatus(LockedFile* file);
-    bool WriteUpdateState(LockedFile* file, UpdateState state);
+    bool WriteUpdateState(LockedFile* file, UpdateState state,
+                          MergeFailureCode failure_code = MergeFailureCode::Ok);
     bool WriteSnapshotUpdateStatus(LockedFile* file, const SnapshotUpdateStatus& status);
     std::string GetStateFilePath() const;
 
@@ -538,12 +554,12 @@
     std::string GetMergeStateFilePath() const;
 
     // Helpers for merging.
-    bool MergeSecondPhaseSnapshots(LockedFile* lock);
-    bool SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
-    bool RewriteSnapshotDeviceTable(const std::string& dm_name);
+    MergeFailureCode MergeSecondPhaseSnapshots(LockedFile* lock);
+    MergeFailureCode SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
+    MergeFailureCode RewriteSnapshotDeviceTable(const std::string& dm_name);
     bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name);
     void AcknowledgeMergeSuccess(LockedFile* lock);
-    void AcknowledgeMergeFailure();
+    void AcknowledgeMergeFailure(MergeFailureCode failure_code);
     MergePhase DecideMergePhase(const SnapshotStatus& status);
     std::unique_ptr<LpMetadata> ReadCurrentMetadata();
 
@@ -570,14 +586,22 @@
                                  const SnapshotStatus& status);
     bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
 
+    struct MergeResult {
+        explicit MergeResult(UpdateState state,
+                             MergeFailureCode failure_code = MergeFailureCode::Ok)
+            : state(state), failure_code(failure_code) {}
+        UpdateState state;
+        MergeFailureCode failure_code;
+    };
+
     // Only the following UpdateStates are used here:
     //   UpdateState::Merging
     //   UpdateState::MergeCompleted
     //   UpdateState::MergeFailed
     //   UpdateState::MergeNeedsReboot
-    UpdateState CheckMergeState(const std::function<bool()>& before_cancel);
-    UpdateState CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel);
-    UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name,
+    MergeResult CheckMergeState(const std::function<bool()>& before_cancel);
+    MergeResult CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel);
+    MergeResult CheckTargetMergeState(LockedFile* lock, const std::string& name,
                                       const SnapshotUpdateStatus& update_status);
 
     // Interact with status files under /metadata/ota/snapshots.
@@ -738,11 +762,14 @@
     // Helper of UpdateUsesCompression
     bool UpdateUsesCompression(LockedFile* lock);
 
+    // Wrapper around libdm, with diagnostics.
+    bool DeleteDeviceIfExists(const std::string& name,
+                              const std::chrono::milliseconds& timeout_ms = {});
+
     std::string gsid_dir_;
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
     std::unique_ptr<IImageManager> images_;
-    bool has_local_image_manager_ = false;
     bool use_first_stage_snapuserd_ = false;
     bool in_factory_data_reset_ = false;
     std::function<bool(const std::string&)> uevent_regen_callback_;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index e617d7a..4ce5077 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -34,11 +34,13 @@
     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 void set_merge_failure_code(MergeFailureCode code) = 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;
+    virtual MergeFailureCode merge_failure_code() = 0;
 
     // Called when merge ends. Properly clean up permanent storage.
     class Result {
@@ -70,6 +72,8 @@
     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;
+    void set_merge_failure_code(MergeFailureCode code) override;
+    MergeFailureCode merge_failure_code() 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 cc75db8..a7cd939 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -29,6 +29,7 @@
     bool CancelUpdate() override;
     bool FinishedSnapshotWrites(bool wipe) override;
     void UpdateCowStats(ISnapshotMergeStats* stats) override;
+    MergeFailureCode ReadMergeFailureCode() override;
     bool InitiateMerge() override;
     UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
                                    const std::function<bool()>& before_cancel = {}) override;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
index 1dab361..280e857 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
@@ -69,6 +69,8 @@
     // must ONLY be called if the control device has already been deleted.
     bool WaitForDeviceDelete(const std::string& control_device);
 
+    void CloseConnection() { sockfd_ = {}; }
+
     // Detach snapuserd. This shuts down the listener socket, and will cause
     // snapuserd to gracefully exit once all handler threads have terminated.
     // This should only be used on first-stage instances of snapuserd.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
index 2b6c8ef..6bb7a39 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
@@ -47,9 +47,6 @@
 static constexpr uint32_t CHUNK_SIZE = 8;
 static constexpr uint32_t CHUNK_SHIFT = (__builtin_ffs(CHUNK_SIZE) - 1);
 
-static constexpr uint32_t BLOCK_SZ = 4096;
-static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
-
 #define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
 
 // This structure represents the kernel COW header.
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index b038527..4e7ccf1 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -77,7 +77,6 @@
         : TestDeviceInfo(fake_super) {
         set_slot_suffix(slot_suffix);
     }
-    std::string GetGsidDir() const override { return "ota/test"s; }
     std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
     std::string GetSlotSuffix() const override { return slot_suffix_; }
     std::string GetOtherSlotSuffix() const override { return slot_suffix_ == "_a" ? "_b" : "_a"; }
@@ -96,6 +95,10 @@
         return true;
     }
     bool IsTestDevice() const override { return true; }
+    bool IsFirstStageInit() const override { return first_stage_init_; }
+    std::unique_ptr<IImageManager> OpenImageManager() const override {
+        return IDeviceInfo::OpenImageManager("ota/test");
+    }
 
     bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; }
 
@@ -104,6 +107,7 @@
         opener_ = std::make_unique<TestPartitionOpener>(path);
     }
     void set_recovery(bool value) { recovery_ = value; }
+    void set_first_stage_init(bool value) { first_stage_init_ = value; }
     MergeStatus merge_status() const { return merge_status_; }
 
   private:
@@ -111,6 +115,7 @@
     std::unique_ptr<TestPartitionOpener> opener_;
     MergeStatus merge_status_;
     bool recovery_ = false;
+    bool first_stage_init_ = false;
     std::unordered_set<uint32_t> unbootable_slots_;
 };
 
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index 453b5c6..4a84fba 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -38,7 +38,8 @@
 static void usage(void) {
     LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
     LOG(ERROR) << "\t -s Run Silent";
-    LOG(ERROR) << "\t -d Attempt to decompress\n";
+    LOG(ERROR) << "\t -d Attempt to decompress";
+    LOG(ERROR) << "\t -b Show data for failed decompress\n";
 }
 
 // Sink that always appends to the end of a string.
@@ -59,7 +60,25 @@
     std::string stream_;
 };
 
-static bool Inspect(const std::string& path, bool silent, bool decompress) {
+static void ShowBad(CowReader& reader, const struct CowOperation& op) {
+    size_t count;
+    auto buffer = std::make_unique<uint8_t[]>(op.data_length);
+
+    if (!reader.GetRawBytes(op.source, buffer.get(), op.data_length, &count)) {
+        std::cerr << "Failed to read at all!\n";
+    } else {
+        std::cout << "The Block data is:\n";
+        for (int i = 0; i < op.data_length; i++) {
+            std::cout << std::hex << (int)buffer[i];
+        }
+        std::cout << std::dec << "\n\n";
+        if (op.data_length >= sizeof(CowOperation)) {
+            std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n";
+        }
+    }
+}
+
+static bool Inspect(const std::string& path, bool silent, bool decompress, bool show_bad) {
     android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
     if (fd < 0) {
         PLOG(ERROR) << "open failed: " << path;
@@ -107,6 +126,7 @@
             if (!reader.ReadData(op, &sink)) {
                 std::cerr << "Failed to decompress for :" << op << "\n";
                 success = false;
+                if (show_bad) ShowBad(reader, op);
             }
             sink.Reset();
         }
@@ -124,7 +144,8 @@
     int ch;
     bool silent = false;
     bool decompress = false;
-    while ((ch = getopt(argc, argv, "sd")) != -1) {
+    bool show_bad = false;
+    while ((ch = getopt(argc, argv, "sdb")) != -1) {
         switch (ch) {
             case 's':
                 silent = true;
@@ -132,6 +153,9 @@
             case 'd':
                 decompress = true;
                 break;
+            case 'b':
+                show_bad = true;
+                break;
             default:
                 android::snapshot::usage();
         }
@@ -143,7 +167,7 @@
         return 1;
     }
 
-    if (!android::snapshot::Inspect(argv[optind], silent, decompress)) {
+    if (!android::snapshot::Inspect(argv[optind], silent, decompress, show_bad)) {
         return 1;
     }
     return 0;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index bd1e284..e2c03ae 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 #include <sys/unistd.h>
 
+#include <filesystem>
 #include <optional>
 #include <thread>
 #include <unordered_set>
@@ -94,18 +95,16 @@
     if (!info) {
         info = new DeviceInfo();
     }
-    auto sm = std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
-    if (info->IsRecovery()) {
-        sm->ForceLocalImageManager();
-    }
-    return sm;
+    return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
 }
 
 std::unique_ptr<SnapshotManager> SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) {
-    auto sm = New(info);
-    if (!sm || !sm->ForceLocalImageManager()) {
-        return nullptr;
+    if (!info) {
+        DeviceInfo* impl = new DeviceInfo();
+        impl->set_first_stage_init(true);
+        info = impl;
     }
+    auto sm = New(info);
 
     // The first-stage version of snapuserd is explicitly started by init. Do
     // not attempt to using it during tests (which run in normal AOSP).
@@ -116,7 +115,6 @@
 }
 
 SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
-    gsid_dir_ = device_->GetGsidDir();
     metadata_dir_ = device_->GetMetadataDir();
 }
 
@@ -537,9 +535,7 @@
 
     bool ok;
     std::string cow_dev;
-    if (has_local_image_manager_) {
-        // If we forced a local image manager, it means we don't have binder,
-        // which means first-stage init. We must use device-mapper.
+    if (device_->IsRecovery() || device_->IsFirstStageInit()) {
         const auto& opener = device_->GetPartitionOpener();
         ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev);
     } else {
@@ -587,8 +583,7 @@
 bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
     CHECK(lock);
 
-    auto& dm = DeviceMapper::Instance();
-    if (!dm.DeleteDeviceIfExists(name)) {
+    if (!DeleteDeviceIfExists(name)) {
         LOG(ERROR) << "Could not delete snapshot device: " << name;
         return false;
     }
@@ -746,32 +741,35 @@
         return false;
     }
 
-    bool rewrote_all = true;
+    auto reported_code = MergeFailureCode::Ok;
     for (const auto& snapshot : *merge_group) {
         // If this fails, we have no choice but to continue. Everything must
         // be merged. This is not an ideal state to be in, but it is safe,
         // because we the next boot will try again.
-        if (!SwitchSnapshotToMerge(lock.get(), snapshot)) {
+        auto code = SwitchSnapshotToMerge(lock.get(), snapshot);
+        if (code != MergeFailureCode::Ok) {
             LOG(ERROR) << "Failed to switch snapshot to a merge target: " << snapshot;
-            rewrote_all = false;
+            if (reported_code == MergeFailureCode::Ok) {
+                reported_code = code;
+            }
         }
     }
 
     // If we couldn't switch everything to a merge target, pre-emptively mark
     // this merge as failed. It will get acknowledged when WaitForMerge() is
     // called.
-    if (!rewrote_all) {
-        WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+    if (reported_code != MergeFailureCode::Ok) {
+        WriteUpdateState(lock.get(), UpdateState::MergeFailed, reported_code);
     }
 
     // Return true no matter what, because a merge was initiated.
     return true;
 }
 
-bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
+MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
     SnapshotStatus status;
     if (!ReadSnapshotStatus(lock, name, &status)) {
-        return false;
+        return MergeFailureCode::ReadStatus;
     }
     if (status.state() != SnapshotState::CREATED) {
         LOG(WARNING) << "Snapshot " << name
@@ -780,8 +778,8 @@
 
     // After this, we return true because we technically did switch to a merge
     // target. Everything else we do here is just informational.
-    if (!RewriteSnapshotDeviceTable(name)) {
-        return false;
+    if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
+        return code;
     }
 
     status.set_state(SnapshotState::MERGING);
@@ -795,26 +793,26 @@
     if (!WriteSnapshotStatus(lock, status)) {
         LOG(ERROR) << "Could not update status file for snapshot: " << name;
     }
-    return true;
+    return MergeFailureCode::Ok;
 }
 
-bool SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
+MergeFailureCode SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
     auto& dm = DeviceMapper::Instance();
 
     std::vector<DeviceMapper::TargetInfo> old_targets;
     if (!dm.GetTableInfo(name, &old_targets)) {
         LOG(ERROR) << "Could not read snapshot device table: " << name;
-        return false;
+        return MergeFailureCode::GetTableInfo;
     }
     if (old_targets.size() != 1 || DeviceMapper::GetTargetType(old_targets[0].spec) != "snapshot") {
         LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << name;
-        return false;
+        return MergeFailureCode::UnknownTable;
     }
 
     std::string base_device, cow_device;
     if (!DmTargetSnapshot::GetDevicesFromParams(old_targets[0].data, &base_device, &cow_device)) {
         LOG(ERROR) << "Could not derive underlying devices for snapshot: " << name;
-        return false;
+        return MergeFailureCode::GetTableParams;
     }
 
     DmTable table;
@@ -822,10 +820,10 @@
                                     SnapshotStorageMode::Merge, kSnapshotChunkSize);
     if (!dm.LoadTableAndActivate(name, table)) {
         LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << name;
-        return false;
+        return MergeFailureCode::ActivateNewTable;
     }
     LOG(INFO) << "Successfully switched snapshot device to a merge target: " << name;
-    return true;
+    return MergeFailureCode::Ok;
 }
 
 enum class TableQuery {
@@ -897,20 +895,20 @@
 UpdateState SnapshotManager::ProcessUpdateState(const std::function<bool()>& callback,
                                                 const std::function<bool()>& before_cancel) {
     while (true) {
-        UpdateState state = CheckMergeState(before_cancel);
-        LOG(INFO) << "ProcessUpdateState handling state: " << state;
+        auto result = CheckMergeState(before_cancel);
+        LOG(INFO) << "ProcessUpdateState handling state: " << result.state;
 
-        if (state == UpdateState::MergeFailed) {
-            AcknowledgeMergeFailure();
+        if (result.state == UpdateState::MergeFailed) {
+            AcknowledgeMergeFailure(result.failure_code);
         }
-        if (state != UpdateState::Merging) {
+        if (result.state != UpdateState::Merging) {
             // Either there is no merge, or the merge was finished, so no need
             // to keep waiting.
-            return state;
+            return result.state;
         }
 
         if (callback && !callback()) {
-            return state;
+            return result.state;
         }
 
         // This wait is not super time sensitive, so we have a relatively
@@ -919,36 +917,36 @@
     }
 }
 
-UpdateState SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) {
+auto SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) -> MergeResult {
     auto lock = LockExclusive();
     if (!lock) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::AcquireLock);
     }
 
-    UpdateState state = CheckMergeState(lock.get(), before_cancel);
-    LOG(INFO) << "CheckMergeState for snapshots returned: " << state;
+    auto result = CheckMergeState(lock.get(), before_cancel);
+    LOG(INFO) << "CheckMergeState for snapshots returned: " << result.state;
 
-    if (state == UpdateState::MergeCompleted) {
+    if (result.state == UpdateState::MergeCompleted) {
         // Do this inside the same lock. Failures get acknowledged without the
         // lock, because flock() might have failed.
         AcknowledgeMergeSuccess(lock.get());
-    } else if (state == UpdateState::Cancelled) {
+    } else if (result.state == UpdateState::Cancelled) {
         if (!device_->IsRecovery() && !RemoveAllUpdateState(lock.get(), before_cancel)) {
             LOG(ERROR) << "Failed to remove all update state after acknowleding cancelled update.";
         }
     }
-    return state;
+    return result;
 }
 
-UpdateState SnapshotManager::CheckMergeState(LockedFile* lock,
-                                             const std::function<bool()>& before_cancel) {
+auto SnapshotManager::CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel)
+        -> MergeResult {
     SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
     switch (update_status.state()) {
         case UpdateState::None:
         case UpdateState::MergeCompleted:
             // Harmless races are allowed between two callers of WaitForMerge,
             // so in both of these cases we just propagate the state.
-            return update_status.state();
+            return MergeResult(update_status.state());
 
         case UpdateState::Merging:
         case UpdateState::MergeNeedsReboot:
@@ -963,26 +961,26 @@
             // via the merge poll below, but if we never started a merge, we
             // need to also check here.
             if (HandleCancelledUpdate(lock, before_cancel)) {
-                return UpdateState::Cancelled;
+                return MergeResult(UpdateState::Cancelled);
             }
-            return update_status.state();
+            return MergeResult(update_status.state());
 
         default:
-            return update_status.state();
+            return MergeResult(update_status.state());
     }
 
     std::vector<std::string> snapshots;
     if (!ListSnapshots(lock, &snapshots)) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ListSnapshots);
     }
 
     auto other_suffix = device_->GetOtherSlotSuffix();
 
     bool cancelled = false;
-    bool failed = false;
     bool merging = false;
     bool needs_reboot = false;
     bool wrong_phase = false;
+    MergeFailureCode failure_code = MergeFailureCode::Ok;
     for (const auto& snapshot : snapshots) {
         if (android::base::EndsWith(snapshot, other_suffix)) {
             // This will have triggered an error message in InitiateMerge already.
@@ -990,12 +988,15 @@
             continue;
         }
 
-        UpdateState snapshot_state = CheckTargetMergeState(lock, snapshot, update_status);
-        LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << snapshot_state;
+        auto result = CheckTargetMergeState(lock, snapshot, update_status);
+        LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << result.state;
 
-        switch (snapshot_state) {
+        switch (result.state) {
             case UpdateState::MergeFailed:
-                failed = true;
+                // Take the first failure code in case other failures compound.
+                if (failure_code == MergeFailureCode::Ok) {
+                    failure_code = result.failure_code;
+                }
                 break;
             case UpdateState::Merging:
                 merging = true;
@@ -1013,8 +1014,10 @@
                 break;
             default:
                 LOG(ERROR) << "Unknown merge status for \"" << snapshot << "\": "
-                           << "\"" << snapshot_state << "\"";
-                failed = true;
+                           << "\"" << result.state << "\"";
+                if (failure_code == MergeFailureCode::Ok) {
+                    failure_code = MergeFailureCode::UnexpectedMergeState;
+                }
                 break;
         }
     }
@@ -1023,24 +1026,25 @@
         // Note that we handle "Merging" before we handle anything else. We
         // want to poll until *nothing* is merging if we can, so everything has
         // a chance to get marked as completed or failed.
-        return UpdateState::Merging;
+        return MergeResult(UpdateState::Merging);
     }
-    if (failed) {
+    if (failure_code != MergeFailureCode::Ok) {
         // Note: since there are many drop-out cases for failure, we acknowledge
         // it in WaitForMerge rather than here and elsewhere.
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, failure_code);
     }
     if (wrong_phase) {
         // If we got here, no other partitions are being merged, and nothing
         // failed to merge. It's safe to move to the next merge phase.
-        if (!MergeSecondPhaseSnapshots(lock)) {
-            return UpdateState::MergeFailed;
+        auto code = MergeSecondPhaseSnapshots(lock);
+        if (code != MergeFailureCode::Ok) {
+            return MergeResult(UpdateState::MergeFailed, code);
         }
-        return UpdateState::Merging;
+        return MergeResult(UpdateState::Merging);
     }
     if (needs_reboot) {
         WriteUpdateState(lock, UpdateState::MergeNeedsReboot);
-        return UpdateState::MergeNeedsReboot;
+        return MergeResult(UpdateState::MergeNeedsReboot);
     }
     if (cancelled) {
         // This is an edge case, that we handle as correctly as we sensibly can.
@@ -1048,16 +1052,17 @@
         // removed the snapshot as a result. The exact state of the update is
         // undefined now, but this can only happen on an unlocked device where
         // partitions can be flashed without wiping userdata.
-        return UpdateState::Cancelled;
+        return MergeResult(UpdateState::Cancelled);
     }
-    return UpdateState::MergeCompleted;
+    return MergeResult(UpdateState::MergeCompleted);
 }
 
-UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name,
-                                                   const SnapshotUpdateStatus& update_status) {
+auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name,
+                                            const SnapshotUpdateStatus& update_status)
+        -> MergeResult {
     SnapshotStatus snapshot_status;
     if (!ReadSnapshotStatus(lock, name, &snapshot_status)) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ReadStatus);
     }
 
     std::unique_ptr<LpMetadata> current_metadata;
@@ -1070,7 +1075,7 @@
         if (!current_metadata ||
             GetMetadataPartitionState(*current_metadata, name) != MetadataPartitionState::Updated) {
             DeleteSnapshot(lock, name);
-            return UpdateState::Cancelled;
+            return MergeResult(UpdateState::Cancelled);
         }
 
         // During a check, we decided the merge was complete, but we were unable to
@@ -1081,11 +1086,11 @@
         if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
             // NB: It's okay if this fails now, we gave cleanup our best effort.
             OnSnapshotMergeComplete(lock, name, snapshot_status);
-            return UpdateState::MergeCompleted;
+            return MergeResult(UpdateState::MergeCompleted);
         }
 
         LOG(ERROR) << "Expected snapshot or snapshot-merge for device: " << name;
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
     }
 
     // This check is expensive so it is only enabled for debugging.
@@ -1095,29 +1100,30 @@
     std::string target_type;
     DmTargetSnapshot::Status status;
     if (!QuerySnapshotStatus(name, &target_type, &status)) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
     }
     if (target_type == "snapshot" &&
         DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
         update_status.merge_phase() == MergePhase::FIRST_PHASE) {
         // The snapshot is not being merged because it's in the wrong phase.
-        return UpdateState::None;
+        return MergeResult(UpdateState::None);
     }
     if (target_type != "snapshot-merge") {
         // We can get here if we failed to rewrite the target type in
         // InitiateMerge(). If we failed to create the target in first-stage
         // init, boot would not succeed.
         LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
     }
 
     // These two values are equal when merging is complete.
     if (status.sectors_allocated != status.metadata_sectors) {
         if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
             LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
-            return UpdateState::MergeFailed;
+            return MergeResult(UpdateState::MergeFailed,
+                               MergeFailureCode::UnmergedSectorsAfterCompletion);
         }
-        return UpdateState::Merging;
+        return MergeResult(UpdateState::Merging);
     }
 
     // Merging is done. First, update the status file to indicate the merge
@@ -1130,18 +1136,18 @@
     // snapshot device for this partition.
     snapshot_status.set_state(SnapshotState::MERGE_COMPLETED);
     if (!WriteSnapshotStatus(lock, snapshot_status)) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::WriteStatus);
     }
     if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
-        return UpdateState::MergeNeedsReboot;
+        return MergeResult(UpdateState::MergeNeedsReboot);
     }
-    return UpdateState::MergeCompleted;
+    return MergeResult(UpdateState::MergeCompleted, MergeFailureCode::Ok);
 }
 
-bool SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
+MergeFailureCode SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
     std::vector<std::string> snapshots;
     if (!ListSnapshots(lock, &snapshots)) {
-        return UpdateState::MergeFailed;
+        return MergeFailureCode::ListSnapshots;
     }
 
     SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
@@ -1150,24 +1156,27 @@
 
     update_status.set_merge_phase(MergePhase::SECOND_PHASE);
     if (!WriteSnapshotUpdateStatus(lock, update_status)) {
-        return false;
+        return MergeFailureCode::WriteStatus;
     }
 
-    bool rewrote_all = true;
+    MergeFailureCode result = MergeFailureCode::Ok;
     for (const auto& snapshot : snapshots) {
         SnapshotStatus snapshot_status;
         if (!ReadSnapshotStatus(lock, snapshot, &snapshot_status)) {
-            return UpdateState::MergeFailed;
+            return MergeFailureCode::ReadStatus;
         }
         if (DecideMergePhase(snapshot_status) != MergePhase::SECOND_PHASE) {
             continue;
         }
-        if (!SwitchSnapshotToMerge(lock, snapshot)) {
+        auto code = SwitchSnapshotToMerge(lock, snapshot);
+        if (code != MergeFailureCode::Ok) {
             LOG(ERROR) << "Failed to switch snapshot to a second-phase merge target: " << snapshot;
-            rewrote_all = false;
+            if (result == MergeFailureCode::Ok) {
+                result = code;
+            }
         }
     }
-    return rewrote_all;
+    return result;
 }
 
 std::string SnapshotManager::GetSnapshotBootIndicatorPath() {
@@ -1199,7 +1208,7 @@
     RemoveAllUpdateState(lock);
 }
 
-void SnapshotManager::AcknowledgeMergeFailure() {
+void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) {
     // Log first, so worst case, we always have a record of why the calls below
     // were being made.
     LOG(ERROR) << "Merge could not be completed and will be marked as failed.";
@@ -1216,7 +1225,7 @@
         return;
     }
 
-    WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+    WriteUpdateState(lock.get(), UpdateState::MergeFailed, failure_code);
 }
 
 bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
@@ -1252,25 +1261,6 @@
     return true;
 }
 
-static bool DeleteDmDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms) {
-    auto start = std::chrono::steady_clock::now();
-    auto& dm = DeviceMapper::Instance();
-    while (true) {
-        if (dm.DeleteDeviceIfExists(name)) {
-            break;
-        }
-        auto now = std::chrono::steady_clock::now();
-        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
-        if (elapsed >= timeout_ms) {
-            LOG(ERROR) << "DeleteDevice timeout: " << name;
-            return false;
-        }
-        std::this_thread::sleep_for(400ms);
-    }
-
-    return true;
-}
-
 bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
                                              const SnapshotStatus& status) {
     auto& dm = DeviceMapper::Instance();
@@ -1326,11 +1316,11 @@
         UnmapDmUserDevice(name);
     }
     auto base_name = GetBaseDeviceName(name);
-    if (!dm.DeleteDeviceIfExists(base_name)) {
+    if (!DeleteDeviceIfExists(base_name)) {
         LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
     }
 
-    if (!DeleteDmDevice(GetSourceDeviceName(name), 4000ms)) {
+    if (!DeleteDeviceIfExists(GetSourceDeviceName(name), 4000ms)) {
         LOG(ERROR) << "Unable to delete source device for snapshot: " << GetSourceDeviceName(name);
     }
 
@@ -1619,6 +1609,18 @@
         //    as dm-snapshot (for example, after merge completes).
         bool should_unmap = current_slot != Slot::Target;
         bool should_delete = ShouldDeleteSnapshot(flashing_status, current_slot, name);
+        if (should_unmap && android::base::EndsWith(name, device_->GetSlotSuffix())) {
+            // Something very unexpected has happened - we want to unmap this
+            // snapshot, but it's on the wrong slot. We can't unmap an active
+            // partition. If this is not really a snapshot, skip the unmap
+            // step.
+            auto& dm = DeviceMapper::Instance();
+            if (dm.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) {
+                LOG(ERROR) << "Detected snapshot " << name << " on " << current_slot << " slot"
+                           << " for source partition; removing without unmap.";
+                should_unmap = false;
+            }
+        }
 
         bool partition_ok = true;
         if (should_unmap && !UnmapPartitionWithSnapshot(lock, name)) {
@@ -1829,6 +1831,10 @@
         return false;
     }
 
+    if (!EnsureImageManager()) {
+        return false;
+    }
+
     for (const auto& partition : metadata->partitions) {
         if (GetPartitionGroupName(metadata->groups[partition.group_index]) == kCowGroupName) {
             LOG(INFO) << "Skip mapping partition " << GetPartitionName(partition) << " in group "
@@ -2071,15 +2077,14 @@
         return false;
     }
 
-    auto& dm = DeviceMapper::Instance();
     auto base_name = GetBaseDeviceName(target_partition_name);
-    if (!dm.DeleteDeviceIfExists(base_name)) {
+    if (!DeleteDeviceIfExists(base_name)) {
         LOG(ERROR) << "Cannot delete base device: " << base_name;
         return false;
     }
 
     auto source_name = GetSourceDeviceName(target_partition_name);
-    if (!dm.DeleteDeviceIfExists(source_name)) {
+    if (!DeleteDeviceIfExists(source_name)) {
         LOG(ERROR) << "Cannot delete source device: " << source_name;
         return false;
     }
@@ -2169,7 +2174,7 @@
         return false;
     }
 
-    if (!DeleteDmDevice(GetCowName(name), 4000ms)) {
+    if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
         LOG(ERROR) << "Cannot unmap: " << GetCowName(name);
         return false;
     }
@@ -2190,7 +2195,7 @@
         return true;
     }
 
-    if (!dm.DeleteDeviceIfExists(dm_user_name)) {
+    if (!DeleteDeviceIfExists(dm_user_name)) {
         LOG(ERROR) << "Cannot unmap " << dm_user_name;
         return false;
     }
@@ -2285,6 +2290,17 @@
             return false;
         }
     }
+
+    // Terminate the daemon and release the snapuserd_client_ object.
+    // If we need to re-connect with the daemon, EnsureSnapuserdConnected()
+    // will re-create the object and establish the socket connection.
+    if (snapuserd_client_) {
+        LOG(INFO) << "Shutdown snapuserd daemon";
+        snapuserd_client_->DetachSnapuserd();
+        snapuserd_client_->CloseConnection();
+        snapuserd_client_ = nullptr;
+    }
+
     return true;
 }
 
@@ -2411,10 +2427,15 @@
     return status;
 }
 
-bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state) {
+bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state,
+                                       MergeFailureCode failure_code) {
     SnapshotUpdateStatus status;
     status.set_state(state);
 
+    if (state == UpdateState::MergeFailed) {
+        status.set_merge_failure_code(failure_code);
+    }
+
     // If we're transitioning between two valid states (eg, we're not beginning
     // or ending an OTA), then make sure to propagate the compression bit.
     if (!(state == UpdateState::Initiated || state == UpdateState::None)) {
@@ -2534,8 +2555,7 @@
 bool SnapshotManager::EnsureImageManager() {
     if (images_) return true;
 
-    // For now, use a preset timeout.
-    images_ = android::fiemap::IImageManager::Open(gsid_dir_, 15000ms);
+    images_ = device_->OpenImageManager();
     if (!images_) {
         LOG(ERROR) << "Could not open ImageManager";
         return false;
@@ -2560,21 +2580,10 @@
     return true;
 }
 
-bool SnapshotManager::ForceLocalImageManager() {
-    images_ = android::fiemap::ImageManager::Open(gsid_dir_);
-    if (!images_) {
-        LOG(ERROR) << "Could not open ImageManager";
-        return false;
-    }
-    has_local_image_manager_ = true;
-    return true;
-}
-
-static void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
-    auto& dm = DeviceMapper::Instance();
+void SnapshotManager::UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
     std::vector<std::string> to_delete;
     for (auto* existing_cow_partition : current_metadata->ListPartitionsInGroup(kCowGroupName)) {
-        if (!dm.DeleteDeviceIfExists(existing_cow_partition->name())) {
+        if (!DeleteDeviceIfExists(existing_cow_partition->name())) {
             LOG(WARNING) << existing_cow_partition->name()
                          << " cannot be unmapped and its space cannot be reclaimed";
             continue;
@@ -2671,8 +2680,18 @@
     AutoDeviceList created_devices;
 
     const auto& dap_metadata = manifest.dynamic_partition_metadata();
-    bool use_compression =
-            IsCompressionEnabled() && dap_metadata.vabc_enabled() && !device_->IsRecovery();
+    CowOptions options;
+    CowWriter writer(options);
+    bool cow_format_support = true;
+    if (dap_metadata.cow_version() < writer.GetCowVersion()) {
+        cow_format_support = false;
+    }
+
+    LOG(INFO) << " dap_metadata.cow_version(): " << dap_metadata.cow_version()
+              << " writer.GetCowVersion(): " << writer.GetCowVersion();
+
+    bool use_compression = IsCompressionEnabled() && dap_metadata.vabc_enabled() &&
+                           !device_->IsRecovery() && cow_format_support;
 
     std::string compression_algorithm;
     if (use_compression) {
@@ -2939,7 +2958,13 @@
                 return Return::Error();
             }
 
-            CowWriter writer(CowOptions{.compression = it->second.compression_algorithm()});
+            CowOptions options;
+            if (device()->IsTestDevice()) {
+                options.scratch_space = false;
+            }
+            options.compression = it->second.compression_algorithm();
+
+            CowWriter writer(options);
             if (!writer.Initialize(fd) || !writer.Finalize()) {
                 LOG(ERROR) << "Could not initialize COW device for " << target_partition->name();
                 return Return::Error();
@@ -3048,6 +3073,10 @@
     CowOptions cow_options;
     cow_options.compression = status.compression_algorithm();
     cow_options.max_blocks = {status.device_size() / cow_options.block_size};
+    // Disable scratch space for vts tests
+    if (device()->IsTestDevice()) {
+        cow_options.scratch_space = false;
+    }
 
     // Currently we don't support partial snapshots, since partition_cow_creator
     // never creates this scenario.
@@ -3603,5 +3632,82 @@
     stats->set_estimated_cow_size_bytes(estimated_cow_size);
 }
 
+bool SnapshotManager::DeleteDeviceIfExists(const std::string& name,
+                                           const std::chrono::milliseconds& timeout_ms) {
+    auto& dm = DeviceMapper::Instance();
+    auto start = std::chrono::steady_clock::now();
+    while (true) {
+        if (dm.DeleteDeviceIfExists(name)) {
+            return true;
+        }
+        auto now = std::chrono::steady_clock::now();
+        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+        if (elapsed >= timeout_ms) {
+            break;
+        }
+        std::this_thread::sleep_for(400ms);
+    }
+
+    // Try to diagnose why this failed. First get the actual device path.
+    std::string full_path;
+    if (!dm.GetDmDevicePathByName(name, &full_path)) {
+        LOG(ERROR) << "Unable to diagnose DM_DEV_REMOVE failure.";
+        return false;
+    }
+
+    // Check for child dm-devices.
+    std::string block_name = android::base::Basename(full_path);
+    std::string sysfs_holders = "/sys/class/block/" + block_name + "/holders";
+
+    std::error_code ec;
+    std::filesystem::directory_iterator dir_iter(sysfs_holders, ec);
+    if (auto begin = std::filesystem::begin(dir_iter); begin != std::filesystem::end(dir_iter)) {
+        LOG(ERROR) << "Child device-mapper device still mapped: " << begin->path();
+        return false;
+    }
+
+    // Check for mounted partitions.
+    android::fs_mgr::Fstab fstab;
+    android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab);
+    for (const auto& entry : fstab) {
+        if (android::base::Basename(entry.blk_device) == block_name) {
+            LOG(ERROR) << "Partition still mounted: " << entry.mount_point;
+            return false;
+        }
+    }
+
+    // Check for detached mounted partitions.
+    for (const auto& fs : std::filesystem::directory_iterator("/sys/fs", ec)) {
+        std::string fs_type = android::base::Basename(fs.path().c_str());
+        if (!(fs_type == "ext4" || fs_type == "f2fs")) {
+            continue;
+        }
+
+        std::string path = fs.path().c_str() + "/"s + block_name;
+        if (access(path.c_str(), F_OK) == 0) {
+            LOG(ERROR) << "Block device was lazily unmounted and is still in-use: " << full_path
+                       << "; possibly open file descriptor or attached loop device.";
+            return false;
+        }
+    }
+
+    LOG(ERROR) << "Device-mapper device " << name << "(" << full_path << ")"
+               << " still in use."
+               << "  Probably a file descriptor was leaked or held open, or a loop device is"
+               << " attached.";
+    return false;
+}
+
+MergeFailureCode SnapshotManager::ReadMergeFailureCode() {
+    auto lock = LockExclusive();
+    if (!lock) return MergeFailureCode::AcquireLock;
+
+    SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
+    if (status.state() != UpdateState::MergeFailed) {
+        return MergeFailureCode::Ok;
+    }
+    return status.merge_failure_code();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index 8926535..0096f85 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -381,8 +381,9 @@
     CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)});
 }
 
-std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager(
-        const std::string& metadata_dir, const std::string& data_dir) {
+std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager() {
+    auto metadata_dir = fake_root_->tmp_path() + "/images_manager_metadata";
+    auto data_dir = fake_data_mount_point_ + "/image_manager_data";
     PCHECK(Mkdir(metadata_dir));
     PCHECK(Mkdir(data_dir));
     return SnapshotFuzzImageManager::Open(metadata_dir, data_dir);
@@ -428,13 +429,9 @@
         PCHECK(Mkdir(metadata_dir + "/snapshots"));
     }
 
-    ret.device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(),
+    ret.device_info = new SnapshotFuzzDeviceInfo(this, data.device_info_data(),
                                                  std::move(partition_opener), metadata_dir);
     auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */);
-    snapshot->images_ =
-            CheckCreateFakeImageManager(fake_root_->tmp_path() + "/images_manager_metadata",
-                                        fake_data_mount_point_ + "/image_manager_data");
-    snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager();
     ret.snapshot = std::move(snapshot);
 
     return ret;
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index 5319e69..3ed27c8 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -65,6 +65,8 @@
     // ISnapshotManager.
     SnapshotTestModule CheckCreateSnapshotManager(const SnapshotFuzzData& data);
 
+    std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager();
+
     // Return path to super partition.
     const std::string& super() const;
 
@@ -79,8 +81,6 @@
     std::string fake_data_block_device_;
     std::unique_ptr<AutoDevice> mounted_data_;
 
-    static std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager(
-            const std::string& metadata_dir, const std::string& data_dir);
     static std::unique_ptr<AutoDevice> CheckMapImage(const std::string& fake_persist_path,
                                                      uint64_t size,
                                                      android::dm::LoopControl* control,
@@ -95,15 +95,15 @@
 class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
   public:
     // Client is responsible for maintaining the lifetime of |data|.
-    SnapshotFuzzDeviceInfo(const FuzzDeviceInfoData& data,
+    SnapshotFuzzDeviceInfo(SnapshotFuzzEnv* env, const FuzzDeviceInfoData& data,
                            std::unique_ptr<TestPartitionOpener>&& partition_opener,
                            const std::string& metadata_dir)
-        : data_(&data),
+        : env_(env),
+          data_(&data),
           partition_opener_(std::move(partition_opener)),
           metadata_dir_(metadata_dir) {}
 
     // Following APIs are mocked.
-    std::string GetGsidDir() const override { return "fuzz_ota"; }
     std::string GetMetadataDir() const override { return metadata_dir_; }
     std::string GetSuperDevice(uint32_t) const override {
         // TestPartitionOpener can recognize this.
@@ -124,10 +124,15 @@
         return data_->allow_set_slot_as_unbootable();
     }
     bool IsRecovery() const override { return data_->is_recovery(); }
+    bool IsFirstStageInit() const override { return false; }
+    std::unique_ptr<IImageManager> OpenImageManager() const {
+        return env_->CheckCreateFakeImageManager();
+    }
 
     void SwitchSlot() { switched_slot_ = !switched_slot_; }
 
   private:
+    SnapshotFuzzEnv* env_;
     const FuzzDeviceInfoData* data_;
     std::unique_ptr<TestPartitionOpener> partition_opener_;
     std::string metadata_dir_;
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 4202d22..9373059 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -150,6 +150,7 @@
     CowOptions options;
     options.compression = "gz";
     options.max_blocks = {kBlockCount};
+    options.scratch_space = false;
 
     unique_fd cow_fd(dup(cow_->fd));
     ASSERT_GE(cow_fd, 0);
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 7fcfcea..4a93d65 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -130,6 +130,14 @@
     return report_.boot_complete_to_merge_start_time_ms();
 }
 
+void SnapshotMergeStats::set_merge_failure_code(MergeFailureCode code) {
+    report_.set_merge_failure_code(code);
+}
+
+MergeFailureCode SnapshotMergeStats::merge_failure_code() {
+    return report_.merge_failure_code();
+}
+
 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 43825cc..1a9eda5 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -135,6 +135,8 @@
     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; }
+    void set_merge_failure_code(MergeFailureCode) override {}
+    MergeFailureCode merge_failure_code() { return MergeFailureCode::Ok; }
 };
 
 ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -163,4 +165,9 @@
     LOG(ERROR) << __FUNCTION__ << " should never be called.";
 }
 
+auto SnapshotManagerStub::ReadMergeFailureCode() -> MergeFailureCode {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return MergeFailureCode::Ok;
+}
+
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 25500b5..6018643 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <libsnapshot/cow_format.h>
 #include <libsnapshot/snapshot.h>
 
 #include <fcntl.h>
@@ -327,6 +328,7 @@
 
         auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata();
         dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+        dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
 
         auto group = dynamic_partition_metadata->add_groups();
         group->set_name("group");
@@ -401,6 +403,7 @@
     }
 
     std::unique_ptr<SnapshotManager> NewManagerForFirstStageMount(TestDeviceInfo* info) {
+        info->set_first_stage_init(true);
         auto init = SnapshotManager::NewForFirstStageMount(info);
         if (!init) {
             return nullptr;
@@ -676,6 +679,18 @@
     ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
 }
 
+TEST_F(SnapshotTest, MergeFailureCode) {
+    ASSERT_TRUE(AcquireLock());
+
+    ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed,
+                                     MergeFailureCode::ListSnapshots));
+    ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
+
+    SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get());
+    ASSERT_EQ(status.state(), UpdateState::MergeFailed);
+    ASSERT_EQ(status.merge_failure_code(), MergeFailureCode::ListSnapshots);
+}
+
 enum class Request { UNKNOWN, LOCK_SHARED, LOCK_EXCLUSIVE, UNLOCK, EXIT };
 std::ostream& operator<<(std::ostream& os, Request request) {
     switch (request) {
@@ -841,6 +856,7 @@
 
         auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
         dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+        dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
 
         // Create a fake update package metadata.
         // Not using full name "system", "vendor", "product" because these names collide with the
@@ -1845,6 +1861,67 @@
     }
 }
 
+// Test update package that requests data wipe.
+TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) {
+    AddOperationForPartitions();
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    // Write some data to target partitions.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
+    }
+
+    // Create a stale snapshot that should not exist.
+    {
+        ASSERT_TRUE(AcquireLock());
+
+        PartitionCowCreator cow_creator = {
+                .compression_enabled = IsCompressionEnabled(),
+                .compression_algorithm = IsCompressionEnabled() ? "gz" : "none",
+        };
+        SnapshotStatus status;
+        status.set_name("sys_a");
+        status.set_device_size(1_MiB);
+        status.set_snapshot_size(2_MiB);
+        status.set_cow_partition_size(2_MiB);
+
+        ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
+        lock_ = nullptr;
+
+        ASSERT_TRUE(sm->EnsureImageManager());
+        ASSERT_TRUE(sm->image_manager()->CreateBackingImage("sys_a", 1_MiB, 0));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // Simulate a reboot into recovery.
+    auto test_device = new TestDeviceInfo(fake_super, "_b");
+    test_device->set_recovery(true);
+    auto new_sm = NewManagerForFirstStageMount(test_device);
+
+    ASSERT_TRUE(new_sm->HandleImminentDataWipe());
+    // Manually mount metadata so that we can call GetUpdateState() below.
+    MountMetadata();
+    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
+    ASSERT_FALSE(test_device->IsSlotUnbootable(1));
+    ASSERT_FALSE(test_device->IsSlotUnbootable(0));
+
+    // Now reboot into new slot.
+    test_device = new TestDeviceInfo(fake_super, "_b");
+    auto init = NewManagerForFirstStageMount(test_device);
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    // Verify that we are on the downgraded build.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name)) << name;
+    }
+}
+
 TEST_F(SnapshotUpdateTest, Hashtree) {
     constexpr auto partition_size = 4_MiB;
     constexpr auto data_size = 3_MiB;
@@ -2019,6 +2096,36 @@
 
     // Read bytes back and verify they match the cache.
     ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
+
+    ASSERT_TRUE(sm->UnmapAllSnapshots());
+}
+
+TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) {
+    AddOperationForPartitions();
+
+    // Execute the update from B->A.
+    test_device->set_slot_suffix("_b");
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    std::string path;
+    ASSERT_TRUE(CreateLogicalPartition(
+            CreateLogicalPartitionParams{
+                    .block_device = fake_super,
+                    .metadata_slot = 0,
+                    .partition_name = "sys_a",
+                    .timeout_ms = 1s,
+                    .partition_opener = opener_.get(),
+            },
+            &path));
+
+    // Hold sys_a open so it can't be unmapped.
+    unique_fd fd(open(path.c_str(), O_RDONLY));
+
+    // Switch back to "A", make sure we can cancel. Instead of unmapping sys_a
+    // we should simply delete the old snapshots.
+    test_device->set_slot_suffix("_a");
+    ASSERT_TRUE(sm->BeginUpdate());
 }
 
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
@@ -2202,9 +2309,27 @@
     void TearDown() override;
 
   private:
+    bool CreateFakeSuper();
+
     std::unique_ptr<IImageManager> super_images_;
 };
 
+bool SnapshotTestEnvironment::CreateFakeSuper() {
+    // Create and map the fake super partition.
+    static constexpr int kImageFlags =
+            IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
+    if (!super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) {
+        LOG(ERROR) << "Could not create fake super partition";
+        return false;
+    }
+    if (!super_images_->MapImageDevice("fake-super", 10s, &fake_super)) {
+        LOG(ERROR) << "Could not map fake super partition";
+        return false;
+    }
+    test_device->set_fake_super(fake_super);
+    return true;
+}
+
 void SnapshotTestEnvironment::SetUp() {
     // b/163082876: GTEST_SKIP in Environment will make atest report incorrect results. Until
     // that is fixed, don't call GTEST_SKIP here, but instead call GTEST_SKIP in individual test
@@ -2230,27 +2355,36 @@
     sm = SnapshotManager::New(test_device);
     ASSERT_NE(nullptr, sm) << "Could not create snapshot manager";
 
+    // Use a separate image manager for our fake super partition.
+    super_images_ = IImageManager::Open("ota/test/super", 10s);
+    ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
+
+    // Map the old image if one exists so we can safely unmap everything that
+    // depends on it.
+    bool recreate_fake_super;
+    if (super_images_->BackingImageExists("fake-super")) {
+        if (super_images_->IsImageMapped("fake-super")) {
+            ASSERT_TRUE(super_images_->GetMappedImageDevice("fake-super", &fake_super));
+        } else {
+            ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super));
+        }
+        test_device->set_fake_super(fake_super);
+        recreate_fake_super = true;
+    } else {
+        ASSERT_TRUE(CreateFakeSuper());
+        recreate_fake_super = false;
+    }
+
     // Clean up previous run.
     MetadataMountedTest().TearDown();
     SnapshotUpdateTest().Cleanup();
     SnapshotTest().Cleanup();
 
-    // Use a separate image manager for our fake super partition.
-    super_images_ = IImageManager::Open("ota/test/super", 10s);
-    ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
-
-    // Clean up any old copy.
-    DeleteBackingImage(super_images_.get(), "fake-super");
-
-    // Create and map the fake super partition.
-    static constexpr int kImageFlags =
-            IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
-    ASSERT_TRUE(super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags))
-            << "Could not create fake super partition";
-
-    ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super))
-            << "Could not map fake super partition";
-    test_device->set_fake_super(fake_super);
+    if (recreate_fake_super) {
+        // Clean up any old copy.
+        DeleteBackingImage(super_images_.get(), "fake-super");
+        ASSERT_TRUE(CreateFakeSuper());
+    }
 }
 
 void SnapshotTestEnvironment::TearDown() {
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 8e379e4..080f3b7 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -90,7 +90,9 @@
     }
 
     const auto& cow_options = options();
-    reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+    if (cow_options.max_blocks) {
+        reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+    }
 
     return reader;
 }
diff --git a/fs_mgr/libsnapshot/snapshot_writer_test.cpp b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
new file mode 100644
index 0000000..da48eb9
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
@@ -0,0 +1,62 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <libsnapshot/snapshot.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/snapshot_writer.h>
+#include <payload_consumer/file_descriptor.h>
+
+namespace android::snapshot {
+class CompressedSnapshotWriterTest : public ::testing::Test {
+  public:
+    static constexpr size_t BLOCK_SIZE = 4096;
+};
+
+TEST_F(CompressedSnapshotWriterTest, ReadAfterWrite) {
+    TemporaryFile cow_device_file{};
+    android::snapshot::CowOptions options{.block_size = BLOCK_SIZE};
+    android::snapshot::CompressedSnapshotWriter snapshot_writer{options};
+    snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd});
+    snapshot_writer.Initialize();
+    std::vector<unsigned char> buffer;
+    buffer.resize(BLOCK_SIZE);
+    std::fill(buffer.begin(), buffer.end(), 123);
+
+    ASSERT_TRUE(snapshot_writer.AddRawBlocks(0, buffer.data(), buffer.size()));
+    ASSERT_TRUE(snapshot_writer.Finalize());
+    auto cow_reader = snapshot_writer.OpenReader();
+    ASSERT_NE(cow_reader, nullptr);
+    ASSERT_TRUE(snapshot_writer.AddRawBlocks(1, buffer.data(), buffer.size()));
+    ASSERT_TRUE(snapshot_writer.AddRawBlocks(2, buffer.data(), buffer.size()));
+    ASSERT_TRUE(snapshot_writer.Finalize());
+    // After wrigin some data, if we call OpenReader() again, writes should
+    // be visible to the newly opened reader. update_engine relies on this
+    // behavior for verity writes.
+    cow_reader = snapshot_writer.OpenReader();
+    ASSERT_NE(cow_reader, nullptr);
+    std::vector<unsigned char> read_back;
+    read_back.resize(buffer.size());
+    cow_reader->Seek(BLOCK_SIZE, SEEK_SET);
+    const auto bytes_read = cow_reader->Read(read_back.data(), read_back.size());
+    ASSERT_EQ((size_t)(bytes_read), BLOCK_SIZE);
+    ASSERT_EQ(read_back, buffer);
+}
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index a44de84..5eb2003 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -48,6 +48,17 @@
     return SnapshotManager::New()->Dump(std::cout);
 }
 
+bool MapCmdHandler(int, char** argv) {
+    android::base::InitLogging(argv, &android::base::StderrLogger);
+    using namespace std::chrono_literals;
+    return SnapshotManager::New()->MapAllSnapshots(5000ms);
+}
+
+bool UnmapCmdHandler(int, char** argv) {
+    android::base::InitLogging(argv, &android::base::StderrLogger);
+    return SnapshotManager::New()->UnmapAllSnapshots();
+}
+
 bool MergeCmdHandler(int /*argc*/, char** argv) {
     android::base::InitLogging(argv, &android::base::StderrLogger);
     LOG(WARNING) << "Deprecated. Call update_engine_client --merge instead.";
@@ -58,6 +69,8 @@
         // clang-format off
         {"dump", DumpCmdHandler},
         {"merge", MergeCmdHandler},
+        {"map", MapCmdHandler},
+        {"unmap", UnmapCmdHandler},
         // clang-format on
 };
 
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index f1fcb70..03c2ef6 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -47,28 +47,205 @@
 
         worker_threads_.push_back(std::move(wt));
     }
+
+    read_ahead_thread_ = std::make_unique<ReadAheadThread>(cow_device_, backing_store_device_,
+                                                           misc_name_, GetSharedPtr());
     return true;
 }
 
 bool Snapuserd::CommitMerge(int num_merge_ops) {
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        CowHeader header;
+    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+    ch->num_merge_ops += num_merge_ops;
 
-        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;
-        }
-        merge_initiated_ = true;
+    if (read_ahead_feature_ && read_ahead_ops_.size() > 0) {
+        struct BufferState* ra_state = GetBufferState();
+        ra_state->read_ahead_state = kCowReadAheadInProgress;
     }
 
+    int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+    if (ret < 0) {
+        PLOG(ERROR) << "msync header failed: " << ret;
+        return false;
+    }
+
+    merge_initiated_ = true;
+
     return true;
 }
 
+void Snapuserd::PrepareReadAhead() {
+    if (!read_ahead_feature_) {
+        return;
+    }
+
+    struct BufferState* ra_state = GetBufferState();
+    // Check if the data has to be re-constructed from COW device
+    if (ra_state->read_ahead_state == kCowReadAheadDone) {
+        populate_data_from_cow_ = true;
+    } else {
+        populate_data_from_cow_ = false;
+    }
+
+    StartReadAhead();
+}
+
+bool Snapuserd::GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer) {
+    if (!lock->owns_lock()) {
+        SNAP_LOG(ERROR) << "GetRABuffer - Lock not held";
+        return false;
+    }
+    std::unordered_map<uint64_t, void*>::iterator it = read_ahead_buffer_map_.find(block);
+
+    // This will be true only for IO's generated as part of reading a root
+    // filesystem. IO's related to merge should always be in read-ahead cache.
+    if (it == read_ahead_buffer_map_.end()) {
+        return false;
+    }
+
+    // Theoretically, we can send the data back from the read-ahead buffer
+    // all the way to the kernel without memcpy. However, if the IO is
+    // un-aligned, the wrapper function will need to touch the read-ahead
+    // buffers and transitions will be bit more complicated.
+    memcpy(buffer, it->second, BLOCK_SZ);
+    return true;
+}
+
+// ========== State transition functions for read-ahead operations ===========
+
+bool Snapuserd::GetReadAheadPopulatedBuffer(uint64_t block, void* buffer) {
+    if (!read_ahead_feature_) {
+        return false;
+    }
+
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
+            return false;
+        }
+
+        if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS) {
+            return GetRABuffer(&lock, block, buffer);
+        }
+    }
+
+    {
+        // Read-ahead thread IO is in-progress. Wait for it to complete
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE ||
+                 io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS)) {
+            cv.wait(lock);
+        }
+
+        return GetRABuffer(&lock, block, buffer);
+    }
+}
+
+// This is invoked by read-ahead thread waiting for merge IO's
+// to complete
+bool Snapuserd::WaitForMergeToComplete() {
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN ||
+                 io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED)) {
+            cv.wait(lock);
+        }
+
+        if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED) {
+            return false;
+        }
+
+        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_IN_PROGRESS;
+        return true;
+    }
+}
+
+// This is invoked during the launch of worker threads. We wait
+// for read-ahead thread to by fully up before worker threads
+// are launched; else we will have a race between worker threads
+// and read-ahead thread specifically during re-construction.
+bool Snapuserd::WaitForReadAheadToStart() {
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS ||
+                 io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE)) {
+            cv.wait(lock);
+        }
+
+        if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
+            return false;
+        }
+
+        return true;
+    }
+}
+
+// Invoked by worker threads when a sequence of merge operation
+// is complete notifying read-ahead thread to make forward
+// progress.
+void Snapuserd::StartReadAhead() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN;
+    }
+
+    cv.notify_one();
+}
+
+void Snapuserd::MergeCompleted() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = READ_AHEAD_IO_TRANSITION::IO_TERMINATED;
+    }
+
+    cv.notify_one();
+}
+
+bool Snapuserd::ReadAheadIOCompleted(bool sync) {
+    if (sync) {
+        // Flush the entire buffer region
+        int ret = msync(mapped_addr_, total_mapped_addr_length_, MS_SYNC);
+        if (ret < 0) {
+            PLOG(ERROR) << "msync failed after ReadAheadIOCompleted: " << ret;
+            return false;
+        }
+
+        // Metadata and data are synced. Now, update the state.
+        // We need to update the state after flushing data; if there is a crash
+        // when read-ahead IO is in progress, the state of data in the COW file
+        // is unknown. kCowReadAheadDone acts as a checkpoint wherein the data
+        // in the scratch space is good and during next reboot, read-ahead thread
+        // can safely re-construct the data.
+        struct BufferState* ra_state = GetBufferState();
+        ra_state->read_ahead_state = kCowReadAheadDone;
+
+        ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+        if (ret < 0) {
+            PLOG(ERROR) << "msync failed to flush Readahead completion state...";
+            return false;
+        }
+    }
+
+    // Notify the worker threads
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS;
+    }
+
+    cv.notify_all();
+    return true;
+}
+
+void Snapuserd::ReadAheadIOFailed() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE;
+    }
+
+    cv.notify_all();
+}
+
+//========== End of state transition functions ====================
+
 bool Snapuserd::IsChunkIdMetadata(chunk_t chunk) {
     uint32_t stride = exceptions_per_area_ + 1;
     lldiv_t divresult = lldiv(chunk, stride);
@@ -93,9 +270,9 @@
         return;
     }
 
-    CowHeader header;
-    reader_->GetHeader(&header);
-    SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << header.num_merge_ops
+    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+
+    SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops
                    << " Total-data-ops: " << reader_->total_data_ops();
 }
 
@@ -170,13 +347,18 @@
         return false;
     }
 
-    CHECK(header.block_size == BLOCK_SZ);
+    if (!(header.block_size == BLOCK_SZ)) {
+        SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
+        return false;
+    }
 
     reader_->InitializeMerge();
     SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
 
-    writer_ = std::make_unique<CowWriter>(options);
-    writer_->InitializeMerge(cow_fd_.get(), &header);
+    if (!MmapMetadata()) {
+        SNAP_LOG(ERROR) << "mmap failed";
+        return false;
+    }
 
     // Initialize the iterator for reading metadata
     cowop_riter_ = reader_->GetRevOpIter();
@@ -230,7 +412,7 @@
 
 
         // Store operation pointer.
-        chunk_map_[ChunkToSector(data_chunk_id)] = cow_op;
+        chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
         num_ops += 1;
         offset += sizeof(struct disk_exception);
         cowop_riter_->Next();
@@ -258,13 +440,16 @@
         data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
     }
 
+    int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
     std::optional<chunk_t> prev_id = {};
     std::map<uint64_t, const CowOperation*> map;
     std::set<uint64_t> dest_blocks;
     size_t pending_copy_ops = exceptions_per_area_ - num_ops;
-    SNAP_LOG(INFO) << " Processing copy-ops at Area: " << vec_.size()
-                   << " Number of replace/zero ops completed in this area: " << num_ops
-                   << " Pending copy ops for this area: " << pending_copy_ops;
+    uint64_t total_copy_ops = reader_->total_copy_ops();
+
+    SNAP_LOG(DEBUG) << " Processing copy-ops at Area: " << vec_.size()
+                    << " Number of replace/zero ops completed in this area: " << num_ops
+                    << " Pending copy ops for this area: " << pending_copy_ops;
     while (!cowop_riter_->Done()) {
         do {
             const CowOperation* cow_op = &cowop_riter_->Get();
@@ -300,41 +485,20 @@
             // Op-6: 15 -> 18
             //
             // Note that the blocks numbers are contiguous. Hence, all 6 copy
-            // operations can potentially be batch merged. However, that will be
+            // operations can be batch merged. However, that will be
             // problematic if we have a crash as block 20, 19, 18 would have
             // been overwritten and hence subsequent recovery may end up with
             // a silent data corruption when op-1, op-2 and op-3 are
             // re-executed.
             //
-            // We will split these 6 operations into two batches viz:
-            //
-            // Batch-1:
-            // ===================
-            // Op-1: 20 -> 23
-            // Op-2: 19 -> 22
-            // Op-3: 18 -> 21
-            // ===================
-            //
-            // Batch-2:
-            // ==================
-            // Op-4: 17 -> 20
-            // Op-5: 16 -> 19
-            // Op-6: 15 -> 18
-            // ==================
-            //
-            // Now, merge sequence will look like:
-            //
-            // 1: Merge Batch-1 { op-1, op-2, op-3 }
-            // 2: Update Metadata in COW File that op-1, op-2, op-3 merge is
-            // done.
-            // 3: Merge Batch-2
-            // 4: Update Metadata in COW File that op-4, op-5, op-6 merge is
-            // done.
-            //
-            // Note, that the order of block operations are still the same.
-            // However, we have two batch merge operations. Any crash between
-            // either of this sequence should be safe as each of these
-            // batches are self-contained.
+            // To address the above problem, read-ahead thread will
+            // read all the 6 source blocks, cache them in the scratch
+            // space of the COW file. During merge, read-ahead
+            // thread will serve the blocks from the read-ahead cache.
+            // If there is a crash during merge; on subsequent reboot,
+            // read-ahead thread will recover the data from the
+            // scratch space and re-construct it thereby there
+            // is no loss of data.
             //
             //===========================================================
             //
@@ -398,6 +562,7 @@
                 if (diff != 1) {
                     break;
                 }
+
                 if (dest_blocks.count(cow_op->new_block) || map.count(cow_op->source) > 0) {
                     break;
                 }
@@ -422,10 +587,13 @@
             de->new_chunk = data_chunk_id;
 
             // Store operation pointer.
-            chunk_map_[ChunkToSector(data_chunk_id)] = it->second;
+            chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), it->second));
             offset += sizeof(struct disk_exception);
             num_ops += 1;
             copy_ops++;
+            if (read_ahead_feature_) {
+                read_ahead_ops_.push_back(it->second);
+            }
 
             SNAP_LOG(DEBUG) << num_ops << ":"
                             << " Copy-op: "
@@ -448,11 +616,24 @@
                     SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
                 }
 
-                CHECK(pending_copy_ops == 0);
+                if (!(pending_copy_ops == 0)) {
+                    SNAP_LOG(ERROR)
+                            << "Invalid pending_copy_ops: expected: 0 found: " << pending_copy_ops;
+                    return false;
+                }
                 pending_copy_ops = exceptions_per_area_;
             }
 
             data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+            total_copy_ops -= 1;
+            /*
+             * Split the number of ops based on the size of read-ahead buffer
+             * region. We need to ensure that kernel doesn't issue IO on blocks
+             * which are not read by the read-ahead thread.
+             */
+            if (read_ahead_feature_ && (total_copy_ops % num_ra_ops_per_iter == 0)) {
+                data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+            }
         }
         map.clear();
         dest_blocks.clear();
@@ -468,6 +649,13 @@
                         << "Areas : " << vec_.size();
     }
 
+    chunk_vec_.shrink_to_fit();
+    vec_.shrink_to_fit();
+    read_ahead_ops_.shrink_to_fit();
+
+    // Sort the vector based on sectors as we need this during un-aligned access
+    std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare);
+
     SNAP_LOG(INFO) << "ReadMetadata completed. Final-chunk-id: " << data_chunk_id
                    << " Num Sector: " << ChunkToSector(data_chunk_id)
                    << " Replace-ops: " << replace_ops << " Zero-ops: " << zero_ops
@@ -478,9 +666,41 @@
     // Total number of sectors required for creating dm-user device
     num_sectors_ = ChunkToSector(data_chunk_id);
     merge_initiated_ = false;
+    PrepareReadAhead();
+
     return true;
 }
 
+bool Snapuserd::MmapMetadata() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    if (header.major_version >= 2 && header.buffer_size > 0) {
+        total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+        read_ahead_feature_ = true;
+    } else {
+        // mmap the first 4k page - older COW format
+        total_mapped_addr_length_ = BLOCK_SZ;
+        read_ahead_feature_ = false;
+    }
+
+    mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED,
+                        cow_fd_.get(), 0);
+    if (mapped_addr_ == MAP_FAILED) {
+        SNAP_LOG(ERROR) << "mmap metadata failed";
+        return false;
+    }
+
+    return true;
+}
+
+void Snapuserd::UnmapBufferRegion() {
+    int ret = munmap(mapped_addr_, total_mapped_addr_length_);
+    if (ret < 0) {
+        SNAP_PLOG(ERROR) << "munmap failed";
+    }
+}
+
 void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
               unsigned int, const char* message) {
     if (severity == android::base::ERROR) {
@@ -501,11 +721,28 @@
 }
 
 /*
- * Entry point to launch worker threads
+ * Entry point to launch threads
  */
 bool Snapuserd::Start() {
     std::vector<std::future<bool>> threads;
+    std::future<bool> ra_thread;
+    bool rathread = (read_ahead_feature_ && (read_ahead_ops_.size() > 0));
 
+    // Start the read-ahead thread and wait
+    // for it as the data has to be re-constructed
+    // from COW device.
+    if (rathread) {
+        ra_thread = std::async(std::launch::async, &ReadAheadThread::RunThread,
+                               read_ahead_thread_.get());
+        if (!WaitForReadAheadToStart()) {
+            SNAP_LOG(ERROR) << "Failed to start Read-ahead thread...";
+            return false;
+        }
+
+        SNAP_LOG(INFO) << "Read-ahead thread started...";
+    }
+
+    // Launch worker threads
     for (int i = 0; i < worker_threads_.size(); i++) {
         threads.emplace_back(
                 std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get()));
@@ -516,8 +753,69 @@
         ret = t.get() && ret;
     }
 
+    if (rathread) {
+        // Notify the read-ahead thread that all worker threads
+        // are done. We need this explicit notification when
+        // there is an IO failure or there was a switch
+        // of dm-user table; thus, forcing the read-ahead
+        // thread to wake up.
+        MergeCompleted();
+        ret = ret && ra_thread.get();
+    }
+
     return ret;
 }
 
+uint64_t Snapuserd::GetBufferMetadataOffset() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    size_t size = header.header_size + sizeof(BufferState);
+    return size;
+}
+
+/*
+ * Metadata for read-ahead is 16 bytes. For a 2 MB region, we will
+ * end up with 8k (2 PAGE) worth of metadata. Thus, a 2MB buffer
+ * region is split into:
+ *
+ * 1: 8k metadata
+ *
+ */
+size_t Snapuserd::GetBufferMetadataSize() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    size_t metadata_bytes = (header.buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ;
+    return metadata_bytes;
+}
+
+size_t Snapuserd::GetBufferDataOffset() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    return (header.header_size + GetBufferMetadataSize());
+}
+
+/*
+ * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
+ */
+size_t Snapuserd::GetBufferDataSize() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    size_t size = header.buffer_size - GetBufferMetadataSize();
+    return size;
+}
+
+struct BufferState* Snapuserd::GetBufferState() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    struct BufferState* ra_state =
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+    return ra_state;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h
index 6ce64d8..0a5ab50 100644
--- a/fs_mgr/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd.h
@@ -17,8 +17,10 @@
 #include <linux/types.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <sys/mman.h>
 
 #include <bitset>
+#include <condition_variable>
 #include <csignal>
 #include <cstring>
 #include <future>
@@ -29,6 +31,7 @@
 #include <string>
 #include <thread>
 #include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include <android-base/file.h>
@@ -56,6 +59,35 @@
  */
 static constexpr int NUM_THREADS_PER_PARTITION = 4;
 
+/*
+ * State transitions between worker threads and read-ahead
+ * threads.
+ *
+ * READ_AHEAD_BEGIN: Worker threads initiates the read-ahead
+ *                   thread to begin reading the copy operations
+ *                   for each bounded region.
+ *
+ * READ_AHEAD_IN_PROGRESS: When read ahead thread is in-flight
+ *                         and reading the copy operations.
+ *
+ * IO_IN_PROGRESS: Merge operation is in-progress by worker threads.
+ *
+ * IO_TERMINATED: When all the worker threads are done, request the
+ *                read-ahead thread to terminate
+ *
+ * READ_AHEAD_FAILURE: If there are any IO failures when read-ahead
+ *                     thread is reading from COW device.
+ *
+ * The transition of each states is described in snapuserd_readahead.cpp
+ */
+enum class READ_AHEAD_IO_TRANSITION {
+    READ_AHEAD_BEGIN,
+    READ_AHEAD_IN_PROGRESS,
+    IO_IN_PROGRESS,
+    IO_TERMINATED,
+    READ_AHEAD_FAILURE,
+};
+
 class BufferSink : public IByteSink {
   public:
     void Initialize(size_t size);
@@ -76,6 +108,47 @@
 
 class Snapuserd;
 
+class ReadAheadThread {
+  public:
+    ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
+                    const std::string& misc_name, std::shared_ptr<Snapuserd> snapuserd);
+    bool RunThread();
+
+  private:
+    void InitializeIter();
+    bool IterDone();
+    void IterNext();
+    const CowOperation* GetIterOp();
+    void InitializeBuffer();
+
+    bool InitializeFds();
+    void CloseFds() {
+        cow_fd_ = {};
+        backing_store_fd_ = {};
+    }
+
+    bool ReadAheadIOStart();
+    void PrepareReadAhead(uint64_t* source_block, int* pending_ops, std::vector<uint64_t>& blocks);
+    bool ReconstructDataFromCow();
+    void CheckOverlap(const CowOperation* cow_op);
+
+    void* read_ahead_buffer_;
+    void* metadata_buffer_;
+    std::vector<const CowOperation*>::reverse_iterator read_ahead_iter_;
+    std::string cow_device_;
+    std::string backing_store_device_;
+    std::string misc_name_;
+
+    unique_fd cow_fd_;
+    unique_fd backing_store_fd_;
+
+    std::shared_ptr<Snapuserd> snapuserd_;
+
+    std::unordered_set<uint64_t> dest_blocks_;
+    std::unordered_set<uint64_t> source_blocks_;
+    bool overlap_;
+};
+
 class WorkerThread {
   public:
     WorkerThread(const std::string& cow_device, const std::string& backing_device,
@@ -108,7 +181,7 @@
     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);
+                            std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
 
     // Processing COW operations
     bool ProcessCowOp(const CowOperation* cow_op);
@@ -116,12 +189,16 @@
     bool ProcessCopyOp(const CowOperation* cow_op);
     bool ProcessZeroOp();
 
+    bool ReadFromBaseDevice(const CowOperation* cow_op);
+    bool GetReadAheadPopulatedBuffer(const CowOperation* cow_op);
+
     // 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);
+                             int unmerged_exceptions, bool* copy_op, bool* commit);
 
     sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
     chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
@@ -158,25 +235,65 @@
     bool CommitMerge(int num_merge_ops);
 
     void CloseFds() { cow_fd_ = {}; }
+    void FreeResources() {
+        worker_threads_.clear();
+        read_ahead_thread_ = nullptr;
+    }
     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_; }
+    std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; }
     const std::vector<std::unique_ptr<uint8_t[]>>& GetMetadataVec() const { return vec_; }
 
-  private:
-    std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+    static bool compare(std::pair<sector_t, const CowOperation*> p1,
+                        std::pair<sector_t, const CowOperation*> p2) {
+        return p1.first < p2.first;
+    }
 
-    bool ReadMetadata();
+    void UnmapBufferRegion();
+    bool MmapMetadata();
+
+    // Read-ahead related functions
+    std::vector<const CowOperation*>& GetReadAheadOpsVec() { return read_ahead_ops_; }
+    std::unordered_map<uint64_t, void*>& GetReadAheadMap() { return read_ahead_buffer_map_; }
+    void* GetMappedAddr() { return mapped_addr_; }
+    bool IsReadAheadFeaturePresent() { return read_ahead_feature_; }
+    void PrepareReadAhead();
+    void StartReadAhead();
+    void MergeCompleted();
+    bool ReadAheadIOCompleted(bool sync);
+    void ReadAheadIOFailed();
+    bool WaitForMergeToComplete();
+    bool GetReadAheadPopulatedBuffer(uint64_t block, void* buffer);
+    bool ReconstructDataFromCow() { return populate_data_from_cow_; }
+    void ReconstructDataFromCowFinish() { populate_data_from_cow_ = false; }
+    bool WaitForReadAheadToStart();
+
+    uint64_t GetBufferMetadataOffset();
+    size_t GetBufferMetadataSize();
+    size_t GetBufferDataOffset();
+    size_t GetBufferDataSize();
+
+    // Final block to be merged in a given read-ahead buffer region
+    void SetFinalBlockMerged(uint64_t x) { final_block_merged_ = x; }
+    uint64_t GetFinalBlockMerged() { return final_block_merged_; }
+    // Total number of blocks to be merged in a given read-ahead buffer region
+    void SetTotalRaBlocksMerged(int x) { total_ra_blocks_merged_ = x; }
+    int GetTotalRaBlocksMerged() { return total_ra_blocks_merged_; }
+
+  private:
     bool IsChunkIdMetadata(chunk_t chunk);
     chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
 
+    bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
+    bool ReadMetadata();
     sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
     chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
     bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
+    struct BufferState* GetBufferState();
 
     std::string cow_device_;
     std::string backing_store_device_;
@@ -191,23 +308,31 @@
     std::unique_ptr<ICowOpIter> cowop_iter_;
     std::unique_ptr<ICowOpReverseIter> cowop_riter_;
     std::unique_ptr<CowReader> reader_;
-    std::unique_ptr<CowWriter> writer_;
 
     // Vector of disk exception which is a
     // mapping of old-chunk to new-chunk
     std::vector<std::unique_ptr<uint8_t[]>> vec_;
 
-    // Key - Sector
-    // Value - cow operation
-    //
-    // chunk_map stores the pseudo mapping of sector
-    // to COW operations. Each COW op is 4k; however,
-    // we can get a read request which are as small
-    // as 512 bytes. Hence, we need to binary search
-    // in the chunk_map to find the nearest COW op.
-    std::map<sector_t, const CowOperation*> chunk_map_;
+    // chunk_vec stores the pseudo mapping of sector
+    // to COW operations.
+    std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
 
     std::mutex lock_;
+    std::condition_variable cv;
+
+    void* mapped_addr_;
+    size_t total_mapped_addr_length_;
+
+    std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+    // Read-ahead related
+    std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
+    std::vector<const CowOperation*> read_ahead_ops_;
+    bool populate_data_from_cow_ = false;
+    bool read_ahead_feature_;
+    uint64_t final_block_merged_;
+    int total_ra_blocks_merged_ = 0;
+    READ_AHEAD_IO_TRANSITION io_state_;
+    std::unique_ptr<ReadAheadThread> read_ahead_thread_;
 
     bool merge_initiated_ = false;
     bool attached_ = false;
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index 16d02e4..41ab344 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -113,7 +113,7 @@
 
 bool SnapuserdClient::Sendmsg(const std::string& msg) {
     LOG(DEBUG) << "Sendmsg: msg " << msg << " sockfd: " << sockfd_;
-    ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), 0));
+    ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), MSG_NOSIGNAL));
     if (numBytesSent < 0) {
         PLOG(ERROR) << "Send failed";
         return false;
diff --git a/fs_mgr/libsnapshot/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
new file mode 100644
index 0000000..16d5919
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd.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_ << ": "
+
+/*
+ * Merging a copy operation involves the following flow:
+ *
+ * 1: dm-snapshot layer requests merge for a 4k block. dm-user sends the request
+ *    to the daemon
+ * 2: daemon reads the source block
+ * 3: daemon copies the source data
+ * 4: IO completion sent back to dm-user (a switch from user space to kernel)
+ * 5: dm-snapshot merges the data to base device
+ * 6: dm-snapshot sends the merge-completion IO to dm-user
+ * 7: dm-user re-directs the merge completion IO to daemon (one more switch)
+ * 8: daemon updates the COW file about the completed merge request (a write syscall) and followed
+ * by a fysnc. 9: Send the IO completion back to dm-user
+ *
+ * The above sequence is a significant overhead especially when merging one 4k
+ * block at a time.
+ *
+ * Read-ahead layer will optimize the above path by reading the data from base
+ * device in the background so that merging thread can retrieve the data from
+ * the read-ahead cache. Additionally, syncing of merged data is deferred to
+ * read-ahead thread threadby the IO path is not bottlenecked.
+ *
+ * We create a scratch space of 2MB to store the read-ahead data in the COW
+ * device.
+ *
+ *      +-----------------------+
+ *      |     Header (fixed)    |
+ *      +-----------------------+
+ *      |    Scratch space      |  <-- 2MB
+ *      +-----------------------+
+ *
+ *      Scratch space is as follows:
+ *
+ *      +-----------------------+
+ *      |       Metadata        | <- 4k page
+ *      +-----------------------+
+ *      |       Metadata        | <- 4k page
+ *      +-----------------------+
+ *      |                       |
+ *      |    Read-ahead data    |
+ *      |                       |
+ *      +-----------------------+
+ *
+ * State transitions and communication between read-ahead thread and worker
+ * thread during merge:
+ * =====================================================================
+ *
+ *   Worker Threads                                 Read-Ahead thread
+ *   ------------------------------------------------------------------
+ *
+ *      |
+ *      |
+ *  --> -----------------READ_AHEAD_BEGIN------------->|
+ *  |   |                                              | READ_AHEAD_IN_PROGRESS
+ *  |  WAIT                                            |
+ *  |   |                                              |
+ *  |   |<-----------------IO_IN_PROGRESS---------------
+ *  |   |                                              |
+ *  |   | IO_IN_PRGRESS                               WAIT
+ *  |   |                                              |
+ *  |<--|                                              |
+ *      |                                              |
+ *      ------------------IO_TERMINATED--------------->|
+ *                                                     END
+ *
+ *
+ * ===================================================================
+ *
+ * Example:
+ *
+ * We have 6 copy operations to be executed in OTA and there is a overlap. Update-engine
+ * will write to COW file as follows:
+ *
+ * Op-1: 20 -> 23
+ * Op-2: 19 -> 22
+ * Op-3: 18 -> 21
+ * Op-4: 17 -> 20
+ * Op-5: 16 -> 19
+ * Op-6: 15 -> 18
+ *
+ * Read-ahead thread will read all the 6 source blocks and store the data in the
+ * scratch space. Metadata will contain the destination block numbers. Thus,
+ * scratch space will look something like this:
+ *
+ * +--------------+
+ * | Block   23   |
+ * | offset - 1   |
+ * +--------------+
+ * | Block   22   |
+ * | offset - 2   |
+ * +--------------+
+ * | Block   21   |
+ * | offset - 3   |
+ * +--------------+
+ *    ...
+ *    ...
+ * +--------------+
+ * | Data-Block 20| <-- offset - 1
+ * +--------------+
+ * | Data-Block 19| <-- offset - 2
+ * +--------------+
+ * | Data-Block 18| <-- offset - 3
+ * +--------------+
+ *     ...
+ *     ...
+ *
+ * ====================================================================
+ * IO Path:
+ *
+ * Read-ahead will serve the data to worker threads during merge only
+ * after metadata and data are persisted to the scratch space. Worker
+ * threads during merge will always retrieve the data from cache; if the
+ * cache is not populated, it will wait for the read-ahead thread to finish.
+ * Furthermore, the number of operations merged will by synced to the header
+ * only when all the blocks in the read-ahead cache are merged. In the above
+ * case, when all 6 operations are merged, COW Header is updated with
+ * num_merge_ops = 6.
+ *
+ * Merge resume after crash:
+ *
+ * Let's say we have a crash after 5 operations are merged. i.e. after
+ * Op-5: 16->19 is completed but before the Op-6 is merged. Thus, COW Header
+ * num_merge_ops will be 0 as the all the ops were not merged yet. During next
+ * reboot, read-ahead thread will re-construct the data in-memory from the
+ * scratch space; when merge resumes, Op-1 will be re-exectued. However,
+ * data will be served from read-ahead cache safely even though, block 20
+ * was over-written by Op-4.
+ *
+ */
+
+ReadAheadThread::ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
+                                 const std::string& misc_name,
+                                 std::shared_ptr<Snapuserd> snapuserd) {
+    cow_device_ = cow_device;
+    backing_store_device_ = backing_device;
+    misc_name_ = misc_name;
+    snapuserd_ = snapuserd;
+}
+
+void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
+    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(cow_op->source)) {
+        overlap_ = true;
+    }
+
+    dest_blocks_.insert(cow_op->source);
+    source_blocks_.insert(cow_op->new_block);
+}
+
+void ReadAheadThread::PrepareReadAhead(uint64_t* source_block, int* pending_ops,
+                                       std::vector<uint64_t>& blocks) {
+    int num_ops = *pending_ops;
+    int nr_consecutive = 0;
+
+    if (!IterDone() && num_ops) {
+        // Get the first block
+        const CowOperation* cow_op = GetIterOp();
+        *source_block = cow_op->source;
+        IterNext();
+        num_ops -= 1;
+        nr_consecutive = 1;
+        blocks.push_back(cow_op->new_block);
+
+        if (!overlap_) {
+            CheckOverlap(cow_op);
+        }
+
+        /*
+         * Find number of consecutive blocks working backwards.
+         */
+        while (!IterDone() && num_ops) {
+            const CowOperation* op = GetIterOp();
+            if (op->source != (*source_block - nr_consecutive)) {
+                break;
+            }
+            nr_consecutive += 1;
+            num_ops -= 1;
+            blocks.push_back(op->new_block);
+            IterNext();
+
+            if (!overlap_) {
+                CheckOverlap(op);
+            }
+        }
+    }
+}
+
+bool ReadAheadThread::ReconstructDataFromCow() {
+    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+    read_ahead_buffer_map.clear();
+    loff_t metadata_offset = 0;
+    loff_t start_data_offset = snapuserd_->GetBufferDataOffset();
+    int num_ops = 0;
+    int total_blocks_merged = 0;
+
+    while (true) {
+        struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+                (char*)metadata_buffer_ + metadata_offset);
+
+        // Done reading metadata
+        if (bm->new_block == 0 && bm->file_offset == 0) {
+            break;
+        }
+
+        loff_t buffer_offset = bm->file_offset - start_data_offset;
+        void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + buffer_offset);
+        read_ahead_buffer_map[bm->new_block] = bufptr;
+        num_ops += 1;
+        total_blocks_merged += 1;
+
+        metadata_offset += sizeof(struct ScratchMetadata);
+    }
+
+    // We are done re-constructing the mapping; however, we need to make sure
+    // all the COW operations to-be merged are present in the re-constructed
+    // mapping.
+    while (!IterDone()) {
+        const CowOperation* op = GetIterOp();
+        if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
+            num_ops -= 1;
+            snapuserd_->SetFinalBlockMerged(op->new_block);
+            IterNext();
+        } else {
+            // Verify that we have covered all the ops which were re-constructed
+            // from COW device - These are the ops which are being
+            // re-constructed after crash.
+            if (!(num_ops == 0)) {
+                SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
+                                << " Pending ops: " << num_ops;
+                snapuserd_->ReadAheadIOFailed();
+                return false;
+            }
+            break;
+        }
+    }
+
+    snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
+
+    snapuserd_->ReconstructDataFromCowFinish();
+
+    if (!snapuserd_->ReadAheadIOCompleted(true)) {
+        SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+        snapuserd_->ReadAheadIOFailed();
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+    return true;
+}
+
+bool ReadAheadThread::ReadAheadIOStart() {
+    // Check if the data has to be constructed from the COW file.
+    // This will be true only once during boot up after a crash
+    // during merge.
+    if (snapuserd_->ReconstructDataFromCow()) {
+        return ReconstructDataFromCow();
+    }
+
+    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+    read_ahead_buffer_map.clear();
+
+    int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
+    loff_t metadata_offset = 0;
+
+    struct ScratchMetadata* bm =
+            reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
+
+    bm->new_block = 0;
+    bm->file_offset = 0;
+
+    std::vector<uint64_t> blocks;
+
+    loff_t buffer_offset = 0;
+    loff_t offset = 0;
+    loff_t file_offset = snapuserd_->GetBufferDataOffset();
+    int total_blocks_merged = 0;
+    overlap_ = false;
+    dest_blocks_.clear();
+    source_blocks_.clear();
+
+    while (true) {
+        uint64_t source_block;
+        int linear_blocks;
+
+        PrepareReadAhead(&source_block, &num_ops, blocks);
+        linear_blocks = blocks.size();
+        if (linear_blocks == 0) {
+            // No more blocks to read
+            SNAP_LOG(DEBUG) << " Read-ahead completed....";
+            break;
+        }
+
+        // Get the first block in the consecutive set of blocks
+        source_block = source_block + 1 - linear_blocks;
+        size_t io_size = (linear_blocks * BLOCK_SZ);
+        num_ops -= linear_blocks;
+        total_blocks_merged += linear_blocks;
+
+        // Mark the block number as the one which will
+        // be the final block to be merged in this entire region.
+        // Read-ahead thread will get
+        // notified when this block is merged to make
+        // forward progress
+        snapuserd_->SetFinalBlockMerged(blocks.back());
+
+        while (linear_blocks) {
+            uint64_t new_block = blocks.back();
+            blocks.pop_back();
+            // Assign the mapping
+            void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
+            read_ahead_buffer_map[new_block] = bufptr;
+            offset += BLOCK_SZ;
+
+            bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ +
+                                                           metadata_offset);
+            bm->new_block = new_block;
+            bm->file_offset = file_offset;
+
+            metadata_offset += sizeof(struct ScratchMetadata);
+            file_offset += BLOCK_SZ;
+
+            linear_blocks -= 1;
+        }
+
+        // Read from the base device consecutive set of blocks in one shot
+        if (!android::base::ReadFullyAtOffset(backing_store_fd_,
+                                              (char*)read_ahead_buffer_ + buffer_offset, io_size,
+                                              source_block * BLOCK_SZ)) {
+            SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
+                             << "at block :" << source_block << " buffer_offset : " << buffer_offset
+                             << " io_size : " << io_size << " buf-addr : " << read_ahead_buffer_;
+
+            snapuserd_->ReadAheadIOFailed();
+            return false;
+        }
+
+        // This is important - explicitly set the contents to zero. This is used
+        // when re-constructing the data after crash. This indicates end of
+        // reading metadata contents when re-constructing the data
+        bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
+        bm->new_block = 0;
+        bm->file_offset = 0;
+
+        buffer_offset += io_size;
+    }
+
+    snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
+
+    // Flush the data only if we have a overlapping blocks in the region
+    if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
+        SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+        snapuserd_->ReadAheadIOFailed();
+        return false;
+    }
+
+    return true;
+}
+
+bool ReadAheadThread::RunThread() {
+    if (!InitializeFds()) {
+        return false;
+    }
+
+    InitializeIter();
+    InitializeBuffer();
+
+    while (!IterDone()) {
+        if (!ReadAheadIOStart()) {
+            return false;
+        }
+
+        bool status = snapuserd_->WaitForMergeToComplete();
+
+        if (status && !snapuserd_->CommitMerge(snapuserd_->GetTotalRaBlocksMerged())) {
+            return false;
+        }
+
+        if (!status) break;
+    }
+
+    CloseFds();
+    SNAP_LOG(INFO) << " ReadAhead thread terminating....";
+    return true;
+}
+
+// Initialization
+bool ReadAheadThread::InitializeFds() {
+    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+    if (backing_store_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+        return false;
+    }
+
+    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+    if (cow_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+        return false;
+    }
+
+    return true;
+}
+
+void ReadAheadThread::InitializeIter() {
+    std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
+    read_ahead_iter_ = read_ahead_ops.rbegin();
+}
+
+bool ReadAheadThread::IterDone() {
+    std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
+    return read_ahead_iter_ == read_ahead_ops.rend();
+}
+
+void ReadAheadThread::IterNext() {
+    read_ahead_iter_++;
+}
+
+const CowOperation* ReadAheadThread::GetIterOp() {
+    return *read_ahead_iter_;
+}
+
+void ReadAheadThread::InitializeBuffer() {
+    void* mapped_addr = snapuserd_->GetMappedAddr();
+    // Map the scratch space region into memory
+    metadata_buffer_ =
+            static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
+    read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 167895e..8339690 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -81,7 +81,7 @@
     : 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));
+    ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
     if (ret < 0) {
         PLOG(ERROR) << "Snapuserd:server: send() failed";
         return false;
@@ -191,7 +191,7 @@
         }
         case DaemonOperations::DETACH: {
             terminating_ = true;
-            return Sendmsg(fd, "success");
+            return true;
         }
         default: {
             LOG(ERROR) << "Received unknown message type from client";
@@ -209,17 +209,24 @@
     }
 
     handler->snapuserd()->CloseFds();
+    handler->snapuserd()->CheckMergeCompletionStatus();
+    handler->snapuserd()->UnmapBufferRegion();
 
     auto misc_name = handler->misc_name();
     LOG(INFO) << "Handler thread about to exit: " << misc_name;
-    handler->snapuserd()->CheckMergeCompletionStatus();
 
     {
         std::lock_guard<std::mutex> lock(lock_);
         auto iter = FindHandler(&lock, handler->misc_name());
         if (iter == dm_users_.end()) {
             // RemoveAndJoinHandler() already removed us from the list, and is
-            // now waiting on a join(), so just return.
+            // now waiting on a join(), so just return. Additionally, release
+            // all the resources held by snapuserd object which are shared
+            // by worker threads. This should be done when the last reference
+            // of "handler" is released; but we will explicitly release here
+            // to make sure snapuserd object is freed as it is the biggest
+            // consumer of memory in the daemon.
+            handler->FreeResources();
             LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
             return;
         }
@@ -371,7 +378,10 @@
 }
 
 bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
-    CHECK(!handler->snapuserd()->IsAttached());
+    if (handler->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Handler already attached";
+        return false;
+    }
 
     handler->snapuserd()->AttachControlDevice();
 
diff --git a/fs_mgr/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd_server.h
index e9d575d..6699189 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd_server.h
@@ -49,7 +49,15 @@
   public:
     explicit DmUserHandler(std::shared_ptr<Snapuserd> snapuserd);
 
-    void FreeResources() { snapuserd_ = nullptr; }
+    void FreeResources() {
+        // Each worker thread holds a reference to snapuserd.
+        // Clear them so that all the resources
+        // held by snapuserd is released
+        if (snapuserd_) {
+            snapuserd_->FreeResources();
+            snapuserd_ = nullptr;
+        }
+    }
     const std::shared_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
     std::thread& thread() { return thread_; }
 
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
index 16f47fe..7e0f493 100644
--- a/fs_mgr/libsnapshot/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -57,7 +57,9 @@
 }
 
 struct dm_user_header* BufferSink::GetHeaderPtr() {
-    CHECK(sizeof(struct dm_user_header) <= buffer_size_);
+    if (!(sizeof(struct dm_user_header) <= buffer_size_)) {
+        return nullptr;
+    }
     char* buf = reinterpret_cast<char*>(GetBufPtr());
     struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
     return header;
@@ -111,7 +113,6 @@
 // the header, zero out the remaining block.
 void WorkerThread::ConstructKernelCowHeader() {
     void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-    CHECK(buffer != nullptr);
 
     memset(buffer, 0, BLOCK_SZ);
 
@@ -135,14 +136,14 @@
     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) {
+bool WorkerThread::ReadFromBaseDevice(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 (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+        return false;
+    }
+    SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
+                    << " Source: " << cow_op->source;
     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_
@@ -153,17 +154,51 @@
     return true;
 }
 
+bool WorkerThread::GetReadAheadPopulatedBuffer(const CowOperation* cow_op) {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "GetReadAheadPopulatedBuffer: Failed to get payload buffer";
+        return false;
+    }
+
+    if (!snapuserd_->GetReadAheadPopulatedBuffer(cow_op->new_block, buffer)) {
+        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) {
+    if (!GetReadAheadPopulatedBuffer(cow_op)) {
+        SNAP_LOG(DEBUG) << " GetReadAheadPopulatedBuffer failed..."
+                        << " new_block: " << cow_op->new_block;
+        if (!ReadFromBaseDevice(cow_op)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
 bool WorkerThread::ProcessZeroOp() {
     // Zero out the entire block
     void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
-    CHECK(buffer != nullptr);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ProcessZeroOp: Failed to get payload buffer";
+        return false;
+    }
 
     memset(buffer, 0, BLOCK_SZ);
     return true;
 }
 
 bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) {
-    CHECK(cow_op != nullptr);
+    if (cow_op == nullptr) {
+        SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op";
+        return false;
+    }
 
     switch (cow_op->type) {
         case kCowReplaceOp: {
@@ -185,15 +220,17 @@
     return false;
 }
 
-int WorkerThread::ReadUnalignedSector(sector_t sector, size_t size,
-                                      std::map<sector_t, const CowOperation*>::iterator& it) {
+int WorkerThread::ReadUnalignedSector(
+        sector_t sector, size_t size,
+        std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) {
     size_t skip_sector_size = 0;
 
     SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
-                    << " Aligned sector: " << it->second;
+                    << " Aligned sector: " << it->first;
 
     if (!ProcessCowOp(it->second)) {
-        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
+        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size
+                        << " Aligned sector: " << it->first;
         return -1;
     }
 
@@ -223,7 +260,8 @@
  *
  */
 int WorkerThread::ReadData(sector_t sector, size_t size) {
-    std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+    std::vector<std::pair<sector_t, const CowOperation*>>::iterator it;
     /*
      * chunk_map stores COW operation at 4k granularity.
      * If the requested IO with the sector falls on the 4k
@@ -234,10 +272,19 @@
      * 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 = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
+                          Snapuserd::compare);
+
+    if (!(it != chunk_vec.end())) {
+        SNAP_LOG(ERROR) << "ReadData: Sector " << sector << " not found in chunk_vec";
+        return -1;
+    }
+
+    // We didn't find the required sector; hence find the previous sector
+    // as lower_bound will gives us the value greater than
+    // the requested sector
+    if (it->first != sector) {
+        if (it != chunk_vec.begin()) {
             --it;
         }
 
@@ -304,7 +351,10 @@
     }
 
     void* buffer = bufsink_.GetPayloadBuffer(size);
-    CHECK(buffer != nullptr);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ZerofillDiskExceptions: Failed to get payload buffer";
+        return false;
+    }
 
     memset(buffer, 0, size);
     return true;
@@ -334,10 +384,17 @@
     if (divresult.quot < vec.size()) {
         size = exceptions_per_area_ * sizeof(struct disk_exception);
 
-        CHECK(read_size == size);
+        if (read_size != size) {
+            SNAP_LOG(ERROR) << "ReadDiskExceptions: read_size: " << read_size
+                            << " does not match with size: " << size;
+            return false;
+        }
 
         void* buffer = bufsink_.GetPayloadBuffer(size);
-        CHECK(buffer != nullptr);
+        if (buffer == nullptr) {
+            SNAP_LOG(ERROR) << "ReadDiskExceptions: Failed to get payload buffer of size: " << size;
+            return false;
+        }
 
         memcpy(buffer, vec[divresult.quot].get(), size);
     } else {
@@ -360,8 +417,19 @@
 
         // 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);
+            if (!(merged_de->old_chunk == cow_de->old_chunk)) {
+                SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->old_chunk: "
+                                << merged_de->old_chunk
+                                << "cow_de->old_chunk: " << cow_de->old_chunk;
+                return -1;
+            }
+
+            if (!(merged_de->new_chunk == cow_de->new_chunk)) {
+                SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->new_chunk: "
+                                << merged_de->new_chunk
+                                << "cow_de->new_chunk: " << cow_de->new_chunk;
+                return -1;
+            }
 
             offset += sizeof(struct disk_exception);
             *unmerged_exceptions += 1;
@@ -371,16 +439,16 @@
         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 unmerged_exceptions, bool* copy_op, bool* commit) {
     int merged_ops_cur_iter = 0;
-    std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+    *copy_op = false;
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
 
     // Find the operations which are merged in this cycle.
     while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
@@ -389,16 +457,52 @@
         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 (!(merged_de->new_chunk == 0)) {
+            SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid new-chunk: " << merged_de->new_chunk;
+            return -1;
+        }
+
+        if (!(merged_de->old_chunk == 0)) {
+            SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid old-chunk: " << merged_de->old_chunk;
+            return -1;
+        }
 
         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);
+            auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+                                       std::make_pair(ChunkToSector(cow_de->new_chunk), nullptr),
+                                       Snapuserd::compare);
 
-            CHECK(cow_op->new_block == cow_de->old_chunk);
+            if (!(it != chunk_vec.end())) {
+                SNAP_LOG(ERROR) << "Sector not found: " << ChunkToSector(cow_de->new_chunk);
+                return -1;
+            }
+
+            if (!(it->first == ChunkToSector(cow_de->new_chunk))) {
+                SNAP_LOG(ERROR) << "Invalid sector: " << ChunkToSector(cow_de->new_chunk);
+                return -1;
+            }
+            const CowOperation* cow_op = it->second;
+
+            if (snapuserd_->IsReadAheadFeaturePresent() && cow_op->type == kCowCopyOp) {
+                *copy_op = true;
+                // Every single copy operation has to come from read-ahead
+                // cache.
+                if (read_ahead_buffer_map.find(cow_op->new_block) == read_ahead_buffer_map.end()) {
+                    SNAP_LOG(ERROR)
+                            << " Block: " << cow_op->new_block << " not found in read-ahead cache"
+                            << " Source: " << cow_op->source;
+                    return -1;
+                }
+                // If this is a final block merged in the read-ahead buffer
+                // region, notify the read-ahead thread to make forward
+                // progress
+                if (cow_op->new_block == snapuserd_->GetFinalBlockMerged()) {
+                    *commit = true;
+                }
+            }
+
             // zero out to indicate that operation is merged.
             cow_de->old_chunk = 0;
             cow_de->new_chunk = 0;
@@ -408,7 +512,6 @@
             //
             // 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: "
@@ -428,25 +531,53 @@
 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();
+    bool copy_op = false;
+    bool commit = false;
 
     // ChunkID to vector index
     lldiv_t divresult = lldiv(chunk, stride);
-    CHECK(divresult.quot < vec.size());
+
+    if (!(divresult.quot < vec.size())) {
+        SNAP_LOG(ERROR) << "ProcessMergeComplete: Invalid chunk: " << chunk
+                        << " Metadata-Index: " << divresult.quot << " Area-size: " << vec.size();
+        return false;
+    }
+
     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);
+    if (offset < 0) {
+        SNAP_LOG(ERROR) << "GetMergeStartOffset failed: unmerged_exceptions: "
+                        << unmerged_exceptions;
+        return false;
+    }
+
+    int merged_ops_cur_iter = GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset,
+                                                   unmerged_exceptions, &copy_op, &commit);
 
     // There should be at least one operation merged in this cycle
-    CHECK(merged_ops_cur_iter > 0);
-    if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
+    if (!(merged_ops_cur_iter > 0)) {
+        SNAP_LOG(ERROR) << "Merge operation failed: " << merged_ops_cur_iter;
         return false;
     }
 
+    if (copy_op) {
+        if (commit) {
+            // Push the flushing logic to read-ahead thread so that merge thread
+            // can make forward progress. Sync will happen in the background
+            snapuserd_->StartReadAhead();
+        }
+    } else {
+        // Non-copy ops and all ops in older COW format
+        if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
+            SNAP_LOG(ERROR) << "CommitMerge failed...";
+            return false;
+        }
+    }
+
     SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
     return true;
 }
@@ -502,40 +633,52 @@
     // 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 (!(header->len == 0)) {
+            header->type = DM_USER_RESP_ERROR;
+        } else {
+            header->type = DM_USER_RESP_SUCCESS;
+        }
+
         if (!WriteDmUserPayload(0)) {
             return false;
         }
         return true;
     }
 
-    std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
     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());
+    auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+                               std::make_pair(header->sector, nullptr), Snapuserd::compare);
 
-    void* buffer = bufsink_.GetPayloadBuffer(read_size);
-    CHECK(buffer != nullptr);
-    header->type = DM_USER_RESP_SUCCESS;
+    bool not_found = (it == chunk_vec.end() || it->first != header->sector);
 
-    if (!ReadDmUserPayload(buffer, read_size)) {
-        SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
-                        << "Sector: " << header->sector;
-        header->type = DM_USER_RESP_ERROR;
-    }
+    if (not_found) {
+        void* buffer = bufsink_.GetPayloadBuffer(read_size);
+        if (buffer == nullptr) {
+            SNAP_LOG(ERROR) << "DmuserWriteRequest: Failed to get payload buffer of size: "
+                            << read_size;
+            header->type = DM_USER_RESP_ERROR;
+        } else {
+            header->type = DM_USER_RESP_SUCCESS;
 
-    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;
+            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;
+        SNAP_LOG(ERROR) << "DmuserWriteRequest: Invalid sector received: header->sector";
+        header->type = DM_USER_RESP_ERROR;
     }
 
     if (!WriteDmUserPayload(0)) {
@@ -550,7 +693,7 @@
     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();
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
     do {
         size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
 
@@ -564,12 +707,13 @@
         // 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()) {
+            auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+                                       std::make_pair(header->sector, nullptr), Snapuserd::compare);
+            bool not_found = (it == chunk_vec.end() || it->first != header->sector);
+            if (!offset && (read_size == BLOCK_SZ) && not_found) {
                 if (!ReadDiskExceptions(chunk, read_size)) {
                     SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
                                     << "Sector: " << header->sector;
@@ -593,12 +737,21 @@
             }
         }
 
+        // Just return the header if it is an error
+        if (header->type == DM_USER_RESP_ERROR) {
+            ret = 0;
+        }
+
         // 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;
         }
 
+        if (header->type == DM_USER_RESP_ERROR) {
+            break;
+        }
+
         remaining_size -= ret;
         offset += ret;
     } while (remaining_size > 0);
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index f31ee31..69d72e1 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -75,6 +75,7 @@
     repeated DynamicPartitionGroup groups = 1;
     optional bool vabc_enabled = 3;
     optional string vabc_compression_param = 4;
+    optional uint32 cow_version = 5;
 }
 
 message DeltaArchiveManifest {
diff --git a/fs_mgr/libstorage_literals/Android.bp b/fs_mgr/libstorage_literals/Android.bp
index 635ca49..5b07168 100644
--- a/fs_mgr/libstorage_literals/Android.bp
+++ b/fs_mgr/libstorage_literals/Android.bp
@@ -8,4 +8,9 @@
     host_supported: true,
     recovery_available: true,
     export_include_dirs: ["."],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 5887641..eb2919b 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -184,6 +184,36 @@
         {"androidboot.space", "sha256 5248 androidboot.nospace = nope"},
 };
 
+bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
+    // clang-format off
+    return lhs.wait == rhs.wait &&
+           lhs.check == rhs.check &&
+           lhs.crypt == rhs.crypt &&
+           lhs.nonremovable == rhs.nonremovable &&
+           lhs.vold_managed == rhs.vold_managed &&
+           lhs.recovery_only == rhs.recovery_only &&
+           lhs.verify == rhs.verify &&
+           lhs.force_crypt == rhs.force_crypt &&
+           lhs.no_emulated_sd == rhs.no_emulated_sd &&
+           lhs.no_trim == rhs.no_trim &&
+           lhs.file_encryption == rhs.file_encryption &&
+           lhs.formattable == rhs.formattable &&
+           lhs.slot_select == rhs.slot_select &&
+           lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
+           lhs.late_mount == rhs.late_mount &&
+           lhs.no_fail == rhs.no_fail &&
+           lhs.verify_at_boot == rhs.verify_at_boot &&
+           lhs.quota == rhs.quota &&
+           lhs.avb == rhs.avb &&
+           lhs.logical == rhs.logical &&
+           lhs.checkpoint_blk == rhs.checkpoint_blk &&
+           lhs.checkpoint_fs == rhs.checkpoint_fs &&
+           lhs.first_stage_mount == rhs.first_stage_mount &&
+           lhs.slot_select_other == rhs.slot_select_other &&
+           lhs.fs_verity == rhs.fs_verity;
+    // clang-format on
+}
+
 }  // namespace
 
 TEST(fs_mgr, fs_mgr_parse_cmdline) {
@@ -291,19 +321,18 @@
     EXPECT_EQ(i, fstab.size());
 }
 
-// TODO(124837435): enable it later when it can pass TreeHugger.
-TEST(fs_mgr, DISABLED_ReadFstabFromFile_MountOptions) {
+TEST(fs_mgr, ReadFstabFromFile_MountOptions) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     std::string fstab_contents = R"fs(
-source /            ext4    ro,barrier=1                    wait,slotselect,avb
+source /            ext4    ro,barrier=1                    wait,avb
 source /metadata    ext4    noatime,nosuid,nodev,discard    wait,formattable
 
 source /data        f2fs    noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier    latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,sysfs_path=/sys/devices/platform/soc/1d84000.ufshc,reservedsize=128M
 
 source /misc        emmc    defaults                        defaults
 
-source /vendor/firmware_mnt    vfat    ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0    wait,slotselect
+source /vendor/firmware_mnt    vfat    ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0    wait
 
 source auto         vfat    defaults                        voldmanaged=usb:auto
 source none         swap    defaults                        zramsize=1073741824,max_comp_streams=8
@@ -316,94 +345,75 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(11U, fstab.size());
+    ASSERT_LE(11U, fstab.size());
 
-    EXPECT_EQ("/", fstab[0].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), fstab[0].flags);
-    EXPECT_EQ("barrier=1", fstab[0].fs_options);
+    FstabEntry* entry = GetEntryForMountPoint(&fstab, "/");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
+    EXPECT_EQ("barrier=1", entry->fs_options);
 
-    EXPECT_EQ("/metadata", fstab[1].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), fstab[1].flags);
-    EXPECT_EQ("discard", fstab[1].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "/metadata");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
+    EXPECT_EQ("discard", entry->fs_options);
 
-    EXPECT_EQ("/data", fstab[2].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), fstab[2].flags);
-    EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", fstab[2].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "/data");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
+    EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", entry->fs_options);
 
-    EXPECT_EQ("/misc", fstab[3].mount_point);
-    EXPECT_EQ(0U, fstab[3].flags);
-    EXPECT_EQ("", fstab[3].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "/misc");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(0U, entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("/vendor/firmware_mnt", fstab[4].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), fstab[4].flags);
+    entry = GetEntryForMountPoint(&fstab, "/vendor/firmware_mnt");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
     EXPECT_EQ(
             "shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,"
             "context=u:object_r:firmware_file:s0",
-            fstab[4].fs_options);
+            entry->fs_options);
 
-    EXPECT_EQ("auto", fstab[5].mount_point);
-    EXPECT_EQ(0U, fstab[5].flags);
-    EXPECT_EQ("", fstab[5].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "auto");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(0U, entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none", fstab[6].mount_point);
-    EXPECT_EQ(0U, fstab[6].flags);
-    EXPECT_EQ("", fstab[6].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(0U, entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none2", fstab[7].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), fstab[7].flags);
-    EXPECT_EQ("", fstab[7].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none2");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none3", fstab[8].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), fstab[8].flags);
-    EXPECT_EQ("", fstab[8].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none3");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none4", fstab[9].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), fstab[9].flags);
-    EXPECT_EQ("", fstab[9].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none4");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none5", fstab[10].mount_point);
-    EXPECT_EQ(0U, fstab[10].flags);  // rw is the same as defaults
-    EXPECT_EQ("", fstab[10].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none5");
+    ASSERT_NE(nullptr, entry);
+    // rw is the default.
+    EXPECT_EQ(0U, entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 }
 
-static bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
-    // clang-format off
-    return lhs.wait == rhs.wait &&
-           lhs.check == rhs.check &&
-           lhs.crypt == rhs.crypt &&
-           lhs.nonremovable == rhs.nonremovable &&
-           lhs.vold_managed == rhs.vold_managed &&
-           lhs.recovery_only == rhs.recovery_only &&
-           lhs.verify == rhs.verify &&
-           lhs.force_crypt == rhs.force_crypt &&
-           lhs.no_emulated_sd == rhs.no_emulated_sd &&
-           lhs.no_trim == rhs.no_trim &&
-           lhs.file_encryption == rhs.file_encryption &&
-           lhs.formattable == rhs.formattable &&
-           lhs.slot_select == rhs.slot_select &&
-           lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
-           lhs.late_mount == rhs.late_mount &&
-           lhs.no_fail == rhs.no_fail &&
-           lhs.verify_at_boot == rhs.verify_at_boot &&
-           lhs.quota == rhs.quota &&
-           lhs.avb == rhs.avb &&
-           lhs.logical == rhs.logical &&
-           lhs.checkpoint_blk == rhs.checkpoint_blk &&
-           lhs.checkpoint_fs == rhs.checkpoint_fs &&
-           lhs.first_stage_mount == rhs.first_stage_mount &&
-           lhs.slot_select_other == rhs.slot_select_other &&
-           lhs.fs_verity == rhs.fs_verity;
-    // clang-format on
-}
-
-// TODO(124837435): enable it later when it can pass TreeHugger.
-TEST(fs_mgr, DISABLED_ReadFstabFromFile_FsMgrFlags) {
+TEST(fs_mgr, ReadFstabFromFile_FsMgrFlags) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     std::string fstab_contents = R"fs(
 source none0       swap   defaults      wait,check,nonremovable,recoveryonly,verifyatboot,verify
-source none1       swap   defaults      avb,noemulatedsd,notrim,formattable,slotselect,nofail
-source none2       swap   defaults      first_stage_mount,latemount,quota,logical,slotselect_other
+source none1       swap   defaults      avb,noemulatedsd,notrim,formattable,nofail
+source none2       swap   defaults      first_stage_mount,latemount,quota,logical
 source none3       swap   defaults      checkpoint=block
 source none4       swap   defaults      checkpoint=fs
 source none5       swap   defaults      defaults
@@ -412,10 +422,10 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(6U, fstab.size());
+    ASSERT_LE(6U, fstab.size());
 
-    auto entry = fstab.begin();
-    EXPECT_EQ("none0", entry->mount_point);
+    FstabEntry* entry = GetEntryForMountPoint(&fstab, "none0");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.wait = true;
@@ -426,50 +436,48 @@
         flags.verify = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none1", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none1");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.avb = true;
         flags.no_emulated_sd = true;
         flags.no_trim = true;
         flags.formattable = true;
-        flags.slot_select = true;
         flags.no_fail = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none2", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none2");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.first_stage_mount = true;
         flags.late_mount = true;
         flags.quota = true;
         flags.logical = true;
-        flags.slot_select_other = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none3", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none3");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.checkpoint_blk = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none4", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none4");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.checkpoint_fs = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none5", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none5");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
@@ -491,7 +499,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(3U, fstab.size());
+    ASSERT_LE(3U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -561,7 +569,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
     flags.crypt = true;
@@ -585,7 +593,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
     flags.vold_managed = true;
@@ -626,7 +634,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(2U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -652,7 +660,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(2U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -682,7 +690,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(6U, fstab.size());
+    ASSERT_LE(6U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -728,7 +736,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -751,7 +759,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -775,7 +783,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
     flags.file_encryption = true;
@@ -797,7 +805,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(2U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -825,7 +833,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -863,7 +871,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -901,7 +909,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -938,7 +946,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(2U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -967,7 +975,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -989,7 +997,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("adiantum", entry->metadata_encryption);
@@ -1006,7 +1014,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("aes-256-xts:wrappedkey_v0", entry->metadata_encryption);
@@ -1027,7 +1035,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -1053,7 +1061,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     auto entry = fstab.begin();
 
@@ -1097,3 +1105,59 @@
     ASSERT_NE(nullptr, fs_mgr_get_mounted_entry_for_userdata(&fstab, block_device))
             << "/data wasn't mounted from default fstab";
 }
+
+TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Readahead_Size_KB) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    std::string fstab_contents = R"fs(
+source none0       swap   defaults      readahead_size_kb=blah
+source none1       swap   defaults      readahead_size_kb=128
+source none2       swap   defaults      readahead_size_kb=5%
+source none3       swap   defaults      readahead_size_kb=5kb
+source none4       swap   defaults      readahead_size_kb=16385
+source none5       swap   defaults      readahead_size_kb=-128
+source none6       swap   defaults      readahead_size_kb=0
+)fs";
+    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+    Fstab fstab;
+    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
+    ASSERT_LE(7U, fstab.size());
+
+    FstabEntry::FsMgrFlags flags = {};
+
+    auto entry = fstab.begin();
+    EXPECT_EQ("none0", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none1", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(128, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none2", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none3", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none4", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none5", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none6", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(0, entry->readahead_size_kb);
+}
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index 49e8085..95e814b 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -40,8 +40,6 @@
         "libbase",
         "libutils",
         "libcrypto",
-        "libkeystore_aidl",
-        "libkeystore_binder",
         "libhidlbase",
         "android.hardware.gatekeeper@1.0",
         "libgatekeeper_aidl",
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index f9c0cdd..8792c83 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -29,13 +29,11 @@
 #include <android-base/properties.h>
 #include <android/binder_ibinder.h>
 #include <android/binder_manager.h>
-#include <android/security/keystore/IKeystoreService.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
 #include <gatekeeper/password_handle.h>  // for password_handle_t
 #include <hardware/hw_auth_token.h>
-#include <keystore/keystore_return_types.h>
 #include <libgsi/libgsi.h>
 #include <log/log.h>
 #include <utils/String16.h>
@@ -303,7 +301,7 @@
             if (gkResponse->payload().size() != 0) {
                 // try to connect to IKeystoreAuthorization AIDL service first.
                 AIBinder* authzAIBinder =
-                        AServiceManager_checkService("android.security.authorization");
+                        AServiceManager_getService("android.security.authorization");
                 ::ndk::SpAIBinder authzBinder(authzAIBinder);
                 auto authzService = IKeystoreAuthorization::fromBinder(authzBinder);
                 if (authzService) {
@@ -328,21 +326,6 @@
                         LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService.";
                         return GK_ERROR;
                     }
-                }
-                sp<IServiceManager> sm = defaultServiceManager();
-
-                sp<IBinder> binder = sm->getService(String16("android.security.keystore"));
-                sp<security::keystore::IKeystoreService> service =
-                        interface_cast<security::keystore::IKeystoreService>(binder);
-
-                if (service) {
-                    int result = 0;
-                    auto binder_result = service->addAuthToken(gkResponse->payload(), &result);
-                    if (!binder_result.isOk() ||
-                        !keystore::KeyStoreServiceReturnCode(result).isOk()) {
-                        LOG(ERROR) << "Failure sending auth token to KeyStore: " << result;
-                        return GK_ERROR;
-                    }
                 } else {
                     LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with "
                                   "Keystore.";
diff --git a/init/README.md b/init/README.md
index 4a262c9..75dc328 100644
--- a/init/README.md
+++ b/init/README.md
@@ -277,6 +277,8 @@
   CLD_EXITED or an status other than '0', reboot the system with the target specified in
   _target_. _target_ takes the same format as the parameter to sys.powerctl. This is particularly
   intended to be used with the `exec_start` builtin for any must-have checks during boot.
+  A service being stopped by init (e.g. using the `stop` or `class_reset` commands) is not
+  considered a failure for the purpose of this setting.
 
 `restart_period <seconds>`
 > If a non-oneshot service exits, it will be restarted at its start time plus
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
index 3ffca88..d22f68f 100644
--- a/init/README.ueventd.md
+++ b/init/README.ueventd.md
@@ -130,6 +130,10 @@
 Will launch `/vendor/bin/led_coeffs.bin` as the system user instead of serving the default firmware
 for `/devices/leds/red/firmware/coeffs.bin`.
 
+The `devpath` argument may include asterisks (`*`) to match multiple paths. For example, the string
+`/dev/*/red` will match `/dev/leds/red` as well as `/dev/lights/red`. The pattern matching follows
+the rules of the fnmatch() function.
+
 Ueventd will provide the uevent `DEVPATH` and `FIRMWARE` to this external program on the environment
 via environment variables with the same names. Ueventd will use the string written to stdout as the
 new name of the firmware to load. It will still look for the new firmware in the list of firmware
diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp
index ba7e6bd..bdc2922 100644
--- a/init/firmware_handler.cpp
+++ b/init/firmware_handler.cpp
@@ -17,6 +17,7 @@
 #include "firmware_handler.h"
 
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <glob.h>
 #include <pwd.h>
 #include <signal.h>
@@ -46,6 +47,20 @@
 namespace android {
 namespace init {
 
+namespace {
+bool PrefixMatch(const std::string& pattern, const std::string& path) {
+    return android::base::StartsWith(path, pattern);
+}
+
+bool FnMatch(const std::string& pattern, const std::string& path) {
+    return fnmatch(pattern.c_str(), path.c_str(), 0) == 0;
+}
+
+bool EqualMatch(const std::string& pattern, const std::string& path) {
+    return pattern == path;
+}
+}  // namespace
+
 static void LoadFirmware(const std::string& firmware, const std::string& root, int fw_fd,
                          size_t fw_size, int loading_fd, int data_fd) {
     // Start transfer.
@@ -66,6 +81,22 @@
     return access("/dev/.booting", F_OK) == 0;
 }
 
+ExternalFirmwareHandler::ExternalFirmwareHandler(std::string devpath, uid_t uid,
+                                                 std::string handler_path)
+    : devpath(std::move(devpath)), uid(uid), handler_path(std::move(handler_path)) {
+    auto wildcard_position = this->devpath.find('*');
+    if (wildcard_position != std::string::npos) {
+        if (wildcard_position == this->devpath.length() - 1) {
+            this->devpath.pop_back();
+            match = std::bind(PrefixMatch, this->devpath, std::placeholders::_1);
+        } else {
+            match = std::bind(FnMatch, this->devpath, std::placeholders::_1);
+        }
+    } else {
+        match = std::bind(EqualMatch, this->devpath, std::placeholders::_1);
+    }
+}
+
 FirmwareHandler::FirmwareHandler(std::vector<std::string> firmware_directories,
                                  std::vector<ExternalFirmwareHandler> external_firmware_handlers)
     : firmware_directories_(std::move(firmware_directories)),
@@ -160,7 +191,7 @@
 
 std::string FirmwareHandler::GetFirmwarePath(const Uevent& uevent) const {
     for (const auto& external_handler : external_firmware_handlers_) {
-        if (external_handler.devpath == uevent.path) {
+        if (external_handler.match(uevent.path)) {
             LOG(INFO) << "Launching external firmware handler '" << external_handler.handler_path
                       << "' for devpath: '" << uevent.path << "' firmware: '" << uevent.firmware
                       << "'";
diff --git a/init/firmware_handler.h b/init/firmware_handler.h
index 8b758ae..3c35b1f 100644
--- a/init/firmware_handler.h
+++ b/init/firmware_handler.h
@@ -30,11 +30,13 @@
 namespace init {
 
 struct ExternalFirmwareHandler {
-    ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path)
-        : devpath(std::move(devpath)), uid(uid), handler_path(std::move(handler_path)) {}
+    ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path);
+
     std::string devpath;
     uid_t uid;
     std::string handler_path;
+
+    std::function<bool(const std::string&)> match;
 };
 
 class FirmwareHandler : public UeventHandler {
diff --git a/init/firmware_handler_test.cpp b/init/firmware_handler_test.cpp
index 5124a6f..f6e75b0 100644
--- a/init/firmware_handler_test.cpp
+++ b/init/firmware_handler_test.cpp
@@ -103,6 +103,23 @@
     return 0;
 }
 
+TEST(firmware_handler, Matching) {
+    ExternalFirmwareHandler h("/dev/path/a.bin", getuid(), "/test");
+    ASSERT_TRUE(h.match("/dev/path/a.bin"));
+    ASSERT_FALSE(h.match("/dev/path/a.bi"));
+
+    h = ExternalFirmwareHandler("/dev/path/a.*", getuid(), "/test");
+    ASSERT_TRUE(h.match("/dev/path/a.bin"));
+    ASSERT_TRUE(h.match("/dev/path/a.bix"));
+    ASSERT_FALSE(h.match("/dev/path/b.bin"));
+
+    h = ExternalFirmwareHandler("/dev/*/a.bin", getuid(), "/test");
+    ASSERT_TRUE(h.match("/dev/path/a.bin"));
+    ASSERT_TRUE(h.match("/dev/other/a.bin"));
+    ASSERT_FALSE(h.match("/dev/other/c.bin"));
+    ASSERT_FALSE(h.match("/dev/path/b.bin"));
+}
+
 }  // namespace init
 }  // namespace android
 
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index b2ab550..78e5b60 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -327,18 +327,8 @@
         LOG(INFO) << "Copied ramdisk prop to " << dest;
     }
 
-    if (ForceNormalBoot(cmdline, bootconfig)) {
-        mkdir("/first_stage_ramdisk", 0755);
-        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
-        // target directory to itself here.
-        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
-            LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
-        }
-        SwitchRoot("/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 "/force_debuggable" is present, the second-stage init will use a userdebug
+    // sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked.
     if (access("/force_debuggable", F_OK) == 0) {
         std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.
         if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
@@ -350,6 +340,16 @@
         }
     }
 
+    if (ForceNormalBoot(cmdline, bootconfig)) {
+        mkdir("/first_stage_ramdisk", 0755);
+        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
+        // target directory to itself here.
+        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
+            LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
+        }
+        SwitchRoot("/first_stage_ramdisk");
+    }
+
     if (!DoFirstStageMount(!created_devices)) {
         LOG(FATAL) << "Failed to mount required partitions early ...";
     }
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index a11bb28..a733839 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -97,7 +97,6 @@
 
     bool MountPartitions();
     bool TrySwitchSystemAsRoot();
-    bool TrySkipMountingPartitions();
     bool IsDmLinearEnabled();
     void GetSuperDeviceName(std::set<std::string>* devices);
     bool InitDmLinearBackingDevices(const android::fs_mgr::LpMetadata& metadata);
@@ -332,6 +331,12 @@
     if (devices.empty()) {
         return true;
     }
+    // excluding overlays
+    for (auto iter = devices.begin(); iter != devices.end(); ) {
+        if (*iter=="overlay")  iter = devices.erase(iter);
+        else iter++;
+    }
+
     return block_dev_init_.InitDevices(std::move(devices));
 }
 
@@ -534,7 +539,7 @@
 bool FirstStageMount::MountPartitions() {
     if (!TrySwitchSystemAsRoot()) return false;
 
-    if (!SkipMountingPartitions(&fstab_)) return false;
+    if (!SkipMountingPartitions(&fstab_, true /* verbose */)) return false;
 
     for (auto current = fstab_.begin(); current != fstab_.end();) {
         // We've already mounted /system above.
@@ -543,6 +548,11 @@
             continue;
         }
 
+        if (current->fs_type == "overlay") {
+            ++current;
+            continue;
+        }
+
         // Skip raw partition entries such as boot, dtbo, etc.
         // Having emmc fstab entries allows us to probe current->vbmeta_partition
         // in InitDevices() when they are AVB chained partitions.
@@ -592,6 +602,13 @@
     };
     MapScratchPartitionIfNeeded(&fstab_, init_devices);
 
+    for (auto current = fstab_.begin(); current != fstab_.end(); ) {
+        if (current->fs_type == "overlay") {
+            fs_mgr_overlayfs_mount_fstab_entry(current->lowerdir, current->mount_point);
+        }
+        ++current;
+    }
+
     fs_mgr_overlayfs_mount_all(&fstab_);
 
     return true;
diff --git a/init/init.cpp b/init/init.cpp
index 7264b22..a7325ca 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -849,21 +849,6 @@
     auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
     SetProperty(gsi::kGsiInstalledProp, is_installed);
 
-    /*
-     * For debug builds of S launching devices, init mounts debugfs for
-     * enabling vendor debug data collection setup at boot time. Init will unmount it on
-     * boot-complete after vendor code has performed the required initializations
-     * during boot. Dumpstate will then mount debugfs in order to read data
-     * from the same using the dumpstate HAL during bugreport creation.
-     * Dumpstate will also unmount debugfs after bugreport creation.
-     * first_api_level comparison is done here instead
-     * of init.rc since init.rc parser does not support >/< operators.
-     */
-    auto api_level = android::base::GetIntProperty("ro.product.first_api_level", 0);
-    bool is_debuggable = android::base::GetBoolProperty("ro.debuggable", false);
-    auto mount_debugfs = (is_debuggable && (api_level >= 31)) ? "1" : "0";
-    SetProperty("init.mount_debugfs", mount_debugfs);
-
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
     am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 382f430..ff9da42 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -94,6 +94,12 @@
 
 namespace android {
 namespace init {
+constexpr auto FINGERPRINT_PROP = "ro.build.fingerprint";
+constexpr auto LEGACY_FINGERPRINT_PROP = "ro.build.legacy.fingerprint";
+constexpr auto ID_PROP = "ro.build.id";
+constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
+constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
+constexpr auto DIGEST_SIZE_USED = 8;
 
 static bool persistent_properties_loaded = false;
 
@@ -857,15 +863,33 @@
     }
 }
 
-// If the ro.build.fingerprint property has not been set, derive it from constituent pieces
-static void property_derive_build_fingerprint() {
-    std::string build_fingerprint = GetProperty("ro.build.fingerprint", "");
-    if (!build_fingerprint.empty()) {
+static void property_initialize_build_id() {
+    std::string build_id = GetProperty(ID_PROP, "");
+    if (!build_id.empty()) {
         return;
     }
 
+    std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, "");
+    std::string vbmeta_digest = GetProperty(VBMETA_DIGEST_PROP, "");
+    if (vbmeta_digest.size() < DIGEST_SIZE_USED) {
+        LOG(ERROR) << "vbmeta digest size too small " << vbmeta_digest;
+        // Still try to set the id field in the unexpected case.
+        build_id = legacy_build_id;
+    } else {
+        // Derive the ro.build.id by appending the vbmeta digest to the base value.
+        build_id = legacy_build_id + "." + vbmeta_digest.substr(0, DIGEST_SIZE_USED);
+    }
+
+    std::string error;
+    auto res = PropertySet(ID_PROP, build_id, &error);
+    if (res != PROP_SUCCESS) {
+        LOG(ERROR) << "Failed to set " << ID_PROP << " to " << build_id;
+    }
+}
+
+static std::string ConstructBuildFingerprint(bool legacy) {
     const std::string UNKNOWN = "unknown";
-    build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
+    std::string build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
     build_fingerprint += '/';
     build_fingerprint += GetProperty("ro.product.name", UNKNOWN);
     build_fingerprint += '/';
@@ -873,7 +897,10 @@
     build_fingerprint += ':';
     build_fingerprint += GetProperty("ro.build.version.release_or_codename", UNKNOWN);
     build_fingerprint += '/';
-    build_fingerprint += GetProperty("ro.build.id", UNKNOWN);
+
+    std::string build_id =
+            legacy ? GetProperty(LEGACY_ID_PROP, UNKNOWN) : GetProperty(ID_PROP, UNKNOWN);
+    build_fingerprint += build_id;
     build_fingerprint += '/';
     build_fingerprint += GetProperty("ro.build.version.incremental", UNKNOWN);
     build_fingerprint += ':';
@@ -881,13 +908,49 @@
     build_fingerprint += '/';
     build_fingerprint += GetProperty("ro.build.tags", UNKNOWN);
 
-    LOG(INFO) << "Setting property 'ro.build.fingerprint' to '" << build_fingerprint << "'";
+    return build_fingerprint;
+}
+
+// Derive the legacy build fingerprint if we overwrite the build id at runtime.
+static void property_derive_legacy_build_fingerprint() {
+    std::string legacy_build_fingerprint = GetProperty(LEGACY_FINGERPRINT_PROP, "");
+    if (!legacy_build_fingerprint.empty()) {
+        return;
+    }
+
+    // The device doesn't have a legacy build id, skipping the legacy fingerprint.
+    std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, "");
+    if (legacy_build_id.empty()) {
+        return;
+    }
+
+    legacy_build_fingerprint = ConstructBuildFingerprint(true /* legacy fingerprint */);
+    LOG(INFO) << "Setting property '" << LEGACY_FINGERPRINT_PROP << "' to '"
+              << legacy_build_fingerprint << "'";
 
     std::string error;
-    uint32_t res = PropertySet("ro.build.fingerprint", build_fingerprint, &error);
+    uint32_t res = PropertySet(LEGACY_FINGERPRINT_PROP, legacy_build_fingerprint, &error);
     if (res != PROP_SUCCESS) {
-        LOG(ERROR) << "Error setting property 'ro.build.fingerprint': err=" << res << " (" << error
-                   << ")";
+        LOG(ERROR) << "Error setting property '" << LEGACY_FINGERPRINT_PROP << "': err=" << res
+                   << " (" << error << ")";
+    }
+}
+
+// If the ro.build.fingerprint property has not been set, derive it from constituent pieces
+static void property_derive_build_fingerprint() {
+    std::string build_fingerprint = GetProperty("ro.build.fingerprint", "");
+    if (!build_fingerprint.empty()) {
+        return;
+    }
+
+    build_fingerprint = ConstructBuildFingerprint(false /* legacy fingerprint */);
+    LOG(INFO) << "Setting property '" << FINGERPRINT_PROP << "' to '" << build_fingerprint << "'";
+
+    std::string error;
+    uint32_t res = PropertySet(FINGERPRINT_PROP, build_fingerprint, &error);
+    if (res != PROP_SUCCESS) {
+        LOG(ERROR) << "Error setting property '" << FINGERPRINT_PROP << "': err=" << res << " ("
+                   << error << ")";
     }
 }
 
@@ -1035,7 +1098,9 @@
     }
 
     property_initialize_ro_product_props();
+    property_initialize_build_id();
     property_derive_build_fingerprint();
+    property_derive_legacy_build_fingerprint();
     property_initialize_ro_cpu_abilist();
 
     update_sys_usb_config();
@@ -1169,22 +1234,24 @@
     ImportKernelCmdline([&](const std::string& key, const std::string& 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);
         }
     });
 }
 
+// bootconfig does not allow to populate `key=value` simultaneously with
+// `key.subkey=value` which does not work with the existing code for
+// `hardware` (e.g. we want both `ro.boot.hardware=value` and
+// `ro.boot.hardware.sku=value`) and for `qemu` (Android Stidio Emulator
+// specific).
+static bool IsAllowedBootconfigKey(const std::string_view key) {
+    return (key == "hardware"sv) || (key == "qemu"sv);
+}
+
 static void ProcessBootconfig() {
     ImportBootconfig([&](const std::string& key, const std::string& 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
+        } else if (IsAllowedBootconfigKey(key)) {
             InitPropertySet("ro.boot." + key, value);
         }
     });
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index c6dcfa2..ac6b7b2 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -23,6 +23,7 @@
 
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
 
 using android::base::GetProperty;
@@ -90,5 +91,39 @@
     EXPECT_FALSE(SetProperty("sys.powerctl", "reboot,userspace"));
 }
 
+TEST(property_service, check_fingerprint_with_legacy_build_id) {
+    std::string legacy_build_id = GetProperty("ro.build.legacy.id", "");
+    if (legacy_build_id.empty()) {
+        GTEST_SKIP() << "Skipping test, legacy build id isn't set.";
+    }
+
+    std::string vbmeta_digest = GetProperty("ro.boot.vbmeta.digest", "");
+    ASSERT_GE(vbmeta_digest.size(), 8u);
+    std::string build_id = GetProperty("ro.boot.build.id", "");
+    // Check that the build id is constructed with the prefix of vbmeta digest
+    std::string expected_build_id = legacy_build_id + "." + vbmeta_digest.substr(0, 8);
+    ASSERT_EQ(expected_build_id, build_id);
+    // Check that the fingerprint is constructed with the expected format.
+    std::string fingerprint = GetProperty("ro.build.fingerprint", "");
+    std::vector<std::string> fingerprint_fields = {
+            GetProperty("ro.product.brand", ""),
+            "/",
+            GetProperty("ro.product.name", ""),
+            "/",
+            GetProperty("ro.product.device", ""),
+            ":",
+            GetProperty("ro.build.version.release_or_codename", ""),
+            "/",
+            expected_build_id,
+            "/",
+            GetProperty("ro.build.version.incremental", ""),
+            ":",
+            GetProperty("ro.build.type", ""),
+            "/",
+            GetProperty("ro.build.tags", "")};
+
+    ASSERT_EQ(android::base::Join(fingerprint_fields, ""), fingerprint);
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/reboot.cpp b/init/reboot.cpp
index d9acee5..ab0e48e 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -450,10 +450,22 @@
 
 // zram is able to use backing device on top of a loopback device.
 // In order to unmount /data successfully, we have to kill the loopback device first
-#define ZRAM_DEVICE   "/dev/block/zram0"
-#define ZRAM_RESET    "/sys/block/zram0/reset"
-#define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev"
+#define ZRAM_DEVICE       "/dev/block/zram0"
+#define ZRAM_RESET        "/sys/block/zram0/reset"
+#define ZRAM_BACK_DEV     "/sys/block/zram0/backing_dev"
+#define ZRAM_INITSTATE    "/sys/block/zram0/initstate"
 static Result<void> KillZramBackingDevice() {
+    std::string zram_initstate;
+    if (!android::base::ReadFileToString(ZRAM_INITSTATE, &zram_initstate)) {
+        return ErrnoError() << "Failed to read " << ZRAM_INITSTATE;
+    }
+
+    zram_initstate.erase(zram_initstate.length() - 1);
+    if (zram_initstate == "0") {
+        LOG(INFO) << "Zram has not been swapped on";
+        return {};
+    }
+
     if (access(ZRAM_BACK_DEV, F_OK) != 0 && errno == ENOENT) {
         LOG(INFO) << "No zram backing device configured";
         return {};
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 62c4586..42d3023 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -240,25 +240,25 @@
     }
 
     // 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"},
+            {"/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
+             precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256"},
+            {"/product/etc/selinux/product_sepolicy_and_mapping.sha256",
+             precompiled_sepolicy + ".product_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");
-    }
-
-    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");
-    }
-
     for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) {
+        // Both of them should exist or both of them shouldn't exist.
+        if (access(actual_id_path.c_str(), R_OK) != 0) {
+            if (access(precompiled_id_path.c_str(), R_OK) == 0) {
+                return Error() << precompiled_id_path << " exists but " << actual_id_path
+                               << " doesn't";
+            }
+            continue;
+        }
+
         std::string actual_id;
         if (!ReadFirstLine(actual_id_path.c_str(), &actual_id)) {
             return ErrnoError() << "Failed to read " << actual_id_path;
@@ -372,6 +372,12 @@
         system_ext_mapping_file.clear();
     }
 
+    std::string system_ext_compat_cil_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +
+                                           ".compat.cil");
+    if (access(system_ext_compat_cil_file.c_str(), F_OK) == -1) {
+        system_ext_compat_cil_file.clear();
+    }
+
     std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");
     if (access(product_policy_cil_file.c_str(), F_OK) == -1) {
         product_policy_cil_file.clear();
@@ -426,6 +432,9 @@
     if (!system_ext_mapping_file.empty()) {
         compile_args.push_back(system_ext_mapping_file.c_str());
     }
+    if (!system_ext_compat_cil_file.empty()) {
+        compile_args.push_back(system_ext_compat_cil_file.c_str());
+    }
     if (!product_policy_cil_file.empty()) {
         compile_args.push_back(product_policy_cil_file.c_str());
     }
@@ -658,7 +667,7 @@
         extra_fstab.emplace_back(std::move(entry));
     }
 
-    SkipMountingPartitions(&extra_fstab);
+    SkipMountingPartitions(&extra_fstab, true /* verbose */);
     if (extra_fstab.empty()) {
         return;
     }
diff --git a/init/service.cpp b/init/service.cpp
index 836dc47..5af81bf 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -194,6 +194,8 @@
                   << ") process group...";
         int max_processes = 0;
         int r;
+
+        flags_ |= SVC_STOPPING;
         if (signal == SIGTERM) {
             r = killProcessGroupOnce(proc_attr_.uid, pid_, signal, &max_processes);
         } else {
@@ -277,7 +279,8 @@
         f(siginfo);
     }
 
-    if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_) {
+    if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_ &&
+        !(flags_ & SVC_STOPPING)) {
         LOG(ERROR) << "Service with 'reboot_on_failure' option failed, shutting down system.";
         trigger_shutdown(*on_failure_reboot_target_);
     }
@@ -287,7 +290,7 @@
     if (flags_ & SVC_TEMPORARY) return;
 
     pid_ = 0;
-    flags_ &= (~SVC_RUNNING);
+    flags_ &= ~(SVC_RUNNING | SVC_STOPPING);
     start_order_ = 0;
 
     // Oneshot processes go into the disabled state on exit,
@@ -411,7 +414,8 @@
     bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET));
     // Starting a service removes it from the disabled or reset state and
     // immediately takes it out of the restarting state if it was in there.
-    flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
+    flags_ &= (~(SVC_DISABLED | SVC_RESTARTING | SVC_RESET | SVC_RESTART | SVC_DISABLED_START |
+                 SVC_STOPPING));
 
     // Running processes require no additional work --- if they're in the
     // process of exiting, we've ensured that they will immediately restart
@@ -460,7 +464,11 @@
         scon = *result;
     }
 
-    if (!IsDefaultMountNamespaceReady() && name_ != "apexd") {
+    // APEXd is always started in the "current" namespace because it is the process to set up
+    // the current namespace.
+    const bool is_apexd = args_[0] == "/system/bin/apexd";
+
+    if (!IsDefaultMountNamespaceReady() && !is_apexd) {
         // If this service is started before APEXes and corresponding linker configuration
         // get available, mark it as pre-apexd one. Note that this marking is
         // permanent. So for example, if the service is re-launched (e.g., due
diff --git a/init/service.h b/init/service.h
index 043555f..89b1f09 100644
--- a/init/service.h
+++ b/init/service.h
@@ -54,6 +54,7 @@
                                      // should not be killed during shutdown
 #define SVC_TEMPORARY 0x1000  // This service was started by 'exec' and should be removed from the
                               // service list once it is reaped.
+#define SVC_STOPPING 0x2000  // service is being stopped by init
 
 #define NR_SVC_SUPP_GIDS 12    // twelve supplementary groups
 
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index cab988b..2221228 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -106,10 +106,10 @@
     }
 
     if (std::find_if(external_firmware_handlers->begin(), external_firmware_handlers->end(),
-                     [&args](const auto& other) { return other.devpath == args[2]; }) !=
+                     [&args](const auto& other) { return other.devpath == args[1]; }) !=
         external_firmware_handlers->end()) {
         return Error() << "found a previous external_firmware_handler with the same devpath, '"
-                       << args[2] << "'";
+                       << args[1] << "'";
     }
 
     passwd* pwd = getpwnam(args[2].c_str());
diff --git a/init/ueventd_parser_test.cpp b/init/ueventd_parser_test.cpp
index b604c53..c5aa9e3 100644
--- a/init/ueventd_parser_test.cpp
+++ b/init/ueventd_parser_test.cpp
@@ -45,6 +45,13 @@
     EXPECT_EQ(expected.attribute_, test.attribute_);
 }
 
+void TestExternalFirmwareHandler(const ExternalFirmwareHandler& expected,
+                                 const ExternalFirmwareHandler& test) {
+    EXPECT_EQ(expected.devpath, test.devpath) << expected.devpath;
+    EXPECT_EQ(expected.uid, test.uid) << expected.uid;
+    EXPECT_EQ(expected.handler_path, test.handler_path) << expected.handler_path;
+}
+
 template <typename T, typename F>
 void TestVector(const T& expected, const T& test, F function) {
     ASSERT_EQ(expected.size(), test.size());
@@ -67,6 +74,8 @@
     TestVector(expected.sysfs_permissions, result.sysfs_permissions, TestSysfsPermissions);
     TestVector(expected.dev_permissions, result.dev_permissions, TestPermissions);
     EXPECT_EQ(expected.firmware_directories, result.firmware_directories);
+    TestVector(expected.external_firmware_handlers, result.external_firmware_handlers,
+               TestExternalFirmwareHandler);
 }
 
 TEST(ueventd_parser, EmptyFile) {
@@ -144,7 +153,10 @@
     auto ueventd_file = R"(
 external_firmware_handler devpath root handler_path
 external_firmware_handler /devices/path/firmware/something001.bin system /vendor/bin/firmware_handler.sh
-external_firmware_handler /devices/path/firmware/something001.bin radio "/vendor/bin/firmware_handler.sh --has --arguments"
+external_firmware_handler /devices/path/firmware/something002.bin radio "/vendor/bin/firmware_handler.sh --has --arguments"
+external_firmware_handler /devices/path/firmware/* root "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/firmware/something* system "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/*/firmware/something*.bin radio "/vendor/bin/firmware_handler.sh"
 )";
 
     auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
@@ -159,10 +171,25 @@
                     "/vendor/bin/firmware_handler.sh",
             },
             {
-                    "/devices/path/firmware/something001.bin",
+                    "/devices/path/firmware/something002.bin",
                     AID_RADIO,
                     "/vendor/bin/firmware_handler.sh --has --arguments",
             },
+            {
+                    "/devices/path/firmware/",
+                    AID_ROOT,
+                    "/vendor/bin/firmware_handler.sh",
+            },
+            {
+                    "/devices/path/firmware/something",
+                    AID_SYSTEM,
+                    "/vendor/bin/firmware_handler.sh",
+            },
+            {
+                    "/devices/path/*/firmware/something*.bin",
+                    AID_RADIO,
+                    "/vendor/bin/firmware_handler.sh",
+            },
     };
 
     TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers});
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0d9f2c7..68b21c6 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -354,3 +354,18 @@
     defaults: ["libcutils_test_static_defaults"],
     test_config: "KernelLibcutilsTest.xml",
 }
+
+rust_bindgen {
+    name: "libcutils_bindgen",
+    wrapper_src: "rust/cutils.h",
+    crate_name: "cutils_bindgen",
+    source_stem: "bindings",
+    local_include_dirs: ["include"],
+    bindgen_flags: [
+        "--allowlist-function", "multiuser_get_app_id",
+        "--allowlist-function", "multiuser_get_uid",
+        "--allowlist-function", "multiuser_get_user_id",
+        "--allowlist-var", "AID_KEYSTORE",
+        "--allowlist-var", "AID_USER_OFFSET",
+    ],
+}
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index d69c038..e9497a8 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -86,7 +86,7 @@
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/bin" },
     { 00755, AID_ROOT,         AID_ROOT,         0, "system/etc/ppp" },
     { 00755, AID_ROOT,         AID_SHELL,        0, "system/vendor" },
-    { 00751, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
+    { 00750, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/apex/*/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/apex/*/bin" },
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index b4fe2e6..8f22d89 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -127,6 +127,9 @@
 #define AID_EXT_DATA_RW 1078      /* GID for app-private data directories on external storage */
 #define AID_EXT_OBB_RW 1079       /* GID for OBB directories on external storage */
 #define AID_CONTEXT_HUB 1080      /* GID for access to the Context Hub */
+#define AID_VIRTMANAGER 1081      /* VirtManager daemon */
+#define AID_ARTD 1082             /* ART Service daemon */
+#define AID_UWB 1083              /* UWB subsystem */
 /* Changes to this file must be made in AOSP, *not* in internal branches. */
 
 #define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libcutils/rust/cutils.h b/libcutils/rust/cutils.h
new file mode 100644
index 0000000..9b78af6
--- /dev/null
+++ b/libcutils/rust/cutils.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <cutils/multiuser.h>
+#include <private/android_filesystem_config.h>
diff --git a/libmodprobe/OWNERS b/libmodprobe/OWNERS
index e6b5bba..a6796cb 100644
--- a/libmodprobe/OWNERS
+++ b/libmodprobe/OWNERS
@@ -1 +1,2 @@
-smuckle@google.com
+dvander@google.com
+willmcvicker@google.com
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index baee4f9..c934860 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -24,7 +24,8 @@
 
 class Modprobe {
   public:
-    Modprobe(const std::vector<std::string>&, const std::string load_file = "modules.load");
+    Modprobe(const std::vector<std::string>&, const std::string load_file = "modules.load",
+             bool use_blocklist = true);
 
     bool LoadListedModules(bool strict = true);
     bool LoadWithAliases(const std::string& module_name, bool strict,
@@ -36,7 +37,6 @@
                             std::vector<std::string>* post_dependencies);
     void ResetModuleCount() { module_count_ = 0; }
     int GetModuleCount() { return module_count_; }
-    void EnableBlocklist(bool enable);
 
   private:
     std::string MakeCanonical(const std::string& module_path);
@@ -48,6 +48,7 @@
     void AddOption(const std::string& module_name, const std::string& option_name,
                    const std::string& value);
     std::string GetKernelCmdline();
+    bool IsBlocklisted(const std::string& module_name);
 
     bool ParseDepCallback(const std::string& base_path, const std::vector<std::string>& args);
     bool ParseAliasCallback(const std::vector<std::string>& args);
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index b3ae937..1a9d364 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -313,7 +313,9 @@
     }
 }
 
-Modprobe::Modprobe(const std::vector<std::string>& base_paths, const std::string load_file) {
+Modprobe::Modprobe(const std::vector<std::string>& base_paths, const std::string load_file,
+                   bool use_blocklist)
+    : blocklist_enabled(use_blocklist) {
     using namespace std::placeholders;
 
     for (const auto& base_path : base_paths) {
@@ -339,10 +341,6 @@
     ParseKernelCmdlineOptions();
 }
 
-void Modprobe::EnableBlocklist(bool enable) {
-    blocklist_enabled = enable;
-}
-
 std::vector<std::string> Modprobe::GetDependencies(const std::string& module) {
     auto it = module_deps_.find(module);
     if (it == module_deps_.end()) {
@@ -427,10 +425,23 @@
     return true;
 }
 
+bool Modprobe::IsBlocklisted(const std::string& module_name) {
+    if (!blocklist_enabled) return false;
+
+    auto canonical_name = MakeCanonical(module_name);
+    auto dependencies = GetDependencies(canonical_name);
+    for (auto dep = dependencies.begin(); dep != dependencies.end(); ++dep) {
+        if (module_blocklist_.count(MakeCanonical(*dep))) return true;
+    }
+
+    return module_blocklist_.count(canonical_name) > 0;
+}
+
 bool Modprobe::LoadListedModules(bool strict) {
     auto ret = true;
     for (const auto& module : module_load_) {
         if (!LoadWithAliases(module, true)) {
+            if (IsBlocklisted(module)) continue;
             ret = false;
             if (strict) break;
         }
@@ -440,16 +451,10 @@
 
 bool Modprobe::Remove(const std::string& module_name) {
     auto dependencies = GetDependencies(MakeCanonical(module_name));
-    if (dependencies.empty()) {
-        LOG(ERROR) << "Empty dependencies for module " << module_name;
-        return false;
-    }
-    if (!Rmmod(dependencies[0])) {
-        return false;
-    }
-    for (auto dep = dependencies.begin() + 1; dep != dependencies.end(); ++dep) {
+    for (auto dep = dependencies.begin(); dep != dependencies.end(); ++dep) {
         Rmmod(*dep);
     }
+    Rmmod(module_name);
     return true;
 }
 
diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp
index d50c10d..f960b61 100644
--- a/libmodprobe/libmodprobe_test.cpp
+++ b/libmodprobe/libmodprobe_test.cpp
@@ -78,6 +78,18 @@
             "/test13.ko",
     };
 
+    std::vector<std::string> expected_modules_blocklist_enabled = {
+            "/test1.ko option1=50 option2=60",
+            "/test6.ko",
+            "/test2.ko",
+            "/test5.ko option1=",
+            "/test8.ko",
+            "/test7.ko param1=4",
+            "/test12.ko",
+            "/test11.ko",
+            "/test13.ko",
+    };
+
     const std::string modules_dep =
             "test1.ko:\n"
             "test2.ko:\n"
@@ -146,7 +158,7 @@
         *i = dir.path + *i;
     }
 
-    Modprobe m({dir.path});
+    Modprobe m({dir.path}, "modules.load", false);
     EXPECT_TRUE(m.LoadListedModules());
 
     GTEST_LOG_(INFO) << "Expected modules loaded (in order):";
@@ -176,8 +188,22 @@
 
     EXPECT_TRUE(modules_loaded == expected_after_remove);
 
-    m.EnableBlocklist(true);
+    m = Modprobe({dir.path});
     EXPECT_FALSE(m.LoadWithAliases("test4", true));
+    while (modules_loaded.size() > 0) EXPECT_TRUE(m.Remove(modules_loaded.front()));
+    EXPECT_TRUE(m.LoadListedModules());
+
+    GTEST_LOG_(INFO) << "Expected modules loaded after enabling blocklist (in order):";
+    for (auto i = expected_modules_blocklist_enabled.begin();
+         i != expected_modules_blocklist_enabled.end(); ++i) {
+        *i = dir.path + *i;
+        GTEST_LOG_(INFO) << "\"" << *i << "\"";
+    }
+    GTEST_LOG_(INFO) << "Actual modules loaded with blocklist enabled (in order):";
+    for (auto i = modules_loaded.begin(); i != modules_loaded.end(); ++i) {
+        GTEST_LOG_(INFO) << "\"" << *i << "\"";
+    }
+    EXPECT_TRUE(modules_loaded == expected_modules_blocklist_enabled);
 }
 
 TEST(libmodprobe, ModuleDepLineWithoutColonIsSkipped) {
diff --git a/libprocessgroup/cgrouprc/Android.bp b/libprocessgroup/cgrouprc/Android.bp
index 0cbe0cc..7522cfe 100644
--- a/libprocessgroup/cgrouprc/Android.bp
+++ b/libprocessgroup/cgrouprc/Android.bp
@@ -28,7 +28,9 @@
     // defined below. The static library is built for tests.
     vendor_available: false,
     native_bridge_supported: true,
-    llndk_stubs: "libcgrouprc.llndk",
+    llndk: {
+        symbol_file: "libcgrouprc.map.txt",
+    },
     srcs: [
         "cgroup_controller.cpp",
         "cgroup_file.cpp",
@@ -59,12 +61,3 @@
         },
     },
 }
-
-llndk_library {
-    name: "libcgrouprc.llndk",
-    symbol_file: "libcgrouprc.map.txt",
-    native_bridge_supported: true,
-    export_include_dirs: [
-        "include",
-    ],
-}
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 5b57bdd..bd94621 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -70,11 +70,11 @@
       "Name": "Frozen",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "freezer",
-            "Path": ""
+            "Name": "FreezerState",
+            "Value": "1"
           }
         }
       ]
@@ -83,11 +83,11 @@
       "Name": "Unfrozen",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "freezer",
-            "Path": "../"
+            "Name": "FreezerState",
+            "Value": "0"
           }
         }
       ]
@@ -558,32 +558,6 @@
         }
       ]
     },
-    {
-      "Name": "FreezerDisabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "0"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "FreezerEnabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "1"
-          }
-        }
-      ]
-    }
   ],
 
   "AggregateProfiles": [
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index c51ee61..1a4196a 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -159,10 +159,9 @@
 
     if (!controller.IsUsable()) return -1;
 
-    if (!controller.GetTaskGroup(tid, &subgroup)) {
-        LOG(ERROR) << "Failed to find cgroup for tid " << tid;
+    if (!controller.GetTaskGroup(tid, &subgroup))
         return -1;
-    }
+
     return 0;
 }
 
@@ -174,11 +173,16 @@
     std::string group;
     if (schedboost_enabled()) {
         if ((getCGroupSubsys(tid, "schedtune", group) < 0) &&
-            (getCGroupSubsys(tid, "cpu", group) < 0))
-		return -1;
+            (getCGroupSubsys(tid, "cpu", group) < 0)) {
+                LOG(ERROR) << "Failed to find cpu cgroup for tid " << tid;
+                return -1;
+        }
     }
     if (group.empty() && cpusets_enabled()) {
-        if (getCGroupSubsys(tid, "cpuset", group) < 0) return -1;
+        if (getCGroupSubsys(tid, "cpuset", group) < 0) {
+            LOG(ERROR) << "Failed to find cpuset cgroup for tid " << tid;
+            return -1;
+        }
     }
 
     // TODO: replace hardcoded directories
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 092e2b2..cf74e65 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -516,10 +516,10 @@
                 std::string attr_filepath = params_val["FilePath"].asString();
                 std::string attr_value = params_val["Value"].asString();
                 if (!attr_filepath.empty() && !attr_value.empty()) {
-                    const Json::Value& logfailures = params_val["LogFailures"];
-                    bool attr_logfailures = logfailures.isNull() || logfailures.asBool();
+                    std::string attr_logfailures = params_val["LogFailures"].asString();
+                    bool logfailures = attr_logfailures.empty() || attr_logfailures == "true";
                     profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value,
-                                                                   attr_logfailures));
+                                                                   logfailures));
                 } else if (attr_filepath.empty()) {
                     LOG(WARNING) << "WriteFile: invalid parameter: "
                                  << "empty filepath";
diff --git a/libstats/pull_lazy/Android.bp b/libstats/pull_lazy/Android.bp
index b1d098b..65dce26 100644
--- a/libstats/pull_lazy/Android.bp
+++ b/libstats/pull_lazy/Android.bp
@@ -1,6 +1,10 @@
 // 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: [
@@ -41,4 +45,4 @@
             suffix: "32",
         },
     },
-}
\ No newline at end of file
+}
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
new file mode 100644
index 0000000..2a89e29
--- /dev/null
+++ b/libstats/pull_rust/Android.bp
@@ -0,0 +1,59 @@
+//
+// 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"],
+}
+
+rust_bindgen {
+    name: "libstatspull_bindgen",
+    wrapper_src: "statslog.h",
+    crate_name: "statspull_bindgen",
+    source_stem: "bindings",
+    bindgen_flags: [
+        "--size_t-is-usize",
+        "--allowlist-function=AStatsEventList_addStatsEvent",
+        "--allowlist-function=AStatsEvent_.*",
+        "--allowlist-function=AStatsManager_.*",
+        "--allowlist-var=AStatsManager_.*",
+    ],
+    target: {
+        android: {
+            shared_libs: [
+                "libstatspull",
+                "libstatssocket",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libstatspull",
+                "libstatssocket",
+            ],
+        },
+    },
+}
+
+rust_library {
+    name: "libstatspull_rust",
+    crate_name: "statspull_rust",
+    srcs: ["stats_pull.rs"],
+    rustlibs: [
+        "liblazy_static",
+        "liblog_rust",
+        "libstatslog_rust_header",
+        "libstatspull_bindgen",
+    ],
+}
diff --git a/libstats/pull_rust/stats_pull.rs b/libstats/pull_rust/stats_pull.rs
new file mode 100644
index 0000000..174125e
--- /dev/null
+++ b/libstats/pull_rust/stats_pull.rs
@@ -0,0 +1,170 @@
+// 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.
+
+//! A Rust interface for the StatsD pull API.
+
+use lazy_static::lazy_static;
+use statslog_rust_header::{Atoms, Stat, StatsError};
+use statspull_bindgen::*;
+use std::collections::HashMap;
+use std::convert::TryInto;
+use std::os::raw::c_void;
+use std::sync::Mutex;
+
+/// The return value of callbacks.
+pub type StatsPullResult = Vec<Box<dyn Stat>>;
+
+/// A wrapper for AStatsManager_PullAtomMetadata.
+/// It calls AStatsManager_PullAtomMetadata_release on drop.
+pub struct Metadata {
+    metadata: *mut AStatsManager_PullAtomMetadata,
+}
+
+impl Metadata {
+    /// Calls AStatsManager_PullAtomMetadata_obtain.
+    pub fn new() -> Self {
+        // Safety: We panic if the memory allocation fails.
+        let metadata = unsafe { AStatsManager_PullAtomMetadata_obtain() };
+        if metadata.is_null() {
+            panic!("Cannot obtain pull atom metadata.");
+        } else {
+            Metadata { metadata }
+        }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_setCoolDownMillis.
+    pub fn set_cooldown_millis(&mut self, cooldown_millis: i64) {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_setCoolDownMillis(self.metadata, cooldown_millis) }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_getCoolDownMillis.
+    pub fn get_cooldown_millis(&self) -> i64 {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_getCoolDownMillis(self.metadata) }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_setTimeoutMillis.
+    pub fn set_timeout_millis(&mut self, timeout_millis: i64) {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_setTimeoutMillis(self.metadata, timeout_millis) }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_getTimeoutMillis.
+    pub fn get_timeout_millis(&self) -> i64 {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_getTimeoutMillis(self.metadata) }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_setAdditiveFields.
+    pub fn set_additive_fields(&mut self, additive_fields: &mut Vec<i32>) {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe {
+            AStatsManager_PullAtomMetadata_setAdditiveFields(
+                self.metadata,
+                additive_fields.as_mut_ptr(),
+                additive_fields.len().try_into().expect("Cannot convert length to i32"),
+            )
+        }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_getAdditiveFields.
+    pub fn get_additive_fields(&self) -> Vec<i32> {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        // We call getNumAdditiveFields to ensure we pass getAdditiveFields a large enough array.
+        unsafe {
+            let num_fields = AStatsManager_PullAtomMetadata_getNumAdditiveFields(self.metadata)
+                .try_into()
+                .expect("Cannot convert num additive fields to usize");
+            let mut fields = vec![0; num_fields];
+            AStatsManager_PullAtomMetadata_getAdditiveFields(self.metadata, fields.as_mut_ptr());
+            fields
+        }
+    }
+}
+
+impl Drop for Metadata {
+    fn drop(&mut self) {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_release(self.metadata) }
+    }
+}
+
+impl Default for Metadata {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+lazy_static! {
+    static ref COOKIES: Mutex<HashMap<i32, fn() -> StatsPullResult>> = Mutex::new(HashMap::new());
+}
+
+// Safety: We store our callbacks in the global so they are valid.
+unsafe extern "C" fn callback_wrapper(
+    atom_tag: i32,
+    data: *mut AStatsEventList,
+    _cookie: *mut c_void,
+) -> AStatsManager_PullAtomCallbackReturn {
+    if !data.is_null() {
+        let map = COOKIES.lock().unwrap();
+        let cb = map.get(&atom_tag);
+        match cb {
+            None => log::error!("No callback found for {}", atom_tag),
+            Some(cb) => {
+                let stats = cb();
+                let result = stats
+                    .iter()
+                    .map(|stat| stat.add_astats_event(&mut *data))
+                    .collect::<Result<Vec<()>, StatsError>>();
+                match result {
+                    Ok(_) => {
+                        return AStatsManager_PULL_SUCCESS as AStatsManager_PullAtomCallbackReturn
+                    }
+                    _ => log::error!("Error adding astats events: {:?}", result),
+                }
+            }
+        }
+    }
+    AStatsManager_PULL_SKIP as AStatsManager_PullAtomCallbackReturn
+}
+
+/// Rust wrapper for AStatsManager_setPullAtomCallback.
+pub fn set_pull_atom_callback(
+    atom: Atoms,
+    metadata: Option<&Metadata>,
+    callback: fn() -> StatsPullResult,
+) {
+    COOKIES.lock().unwrap().insert(atom as i32, callback);
+    let metadata_raw = match metadata {
+        Some(m) => m.metadata,
+        None => std::ptr::null_mut(),
+    };
+    // Safety: We pass a valid function as the callback.
+    unsafe {
+        AStatsManager_setPullAtomCallback(
+            atom as i32,
+            metadata_raw,
+            Some(callback_wrapper),
+            std::ptr::null_mut(),
+        );
+    }
+}
+
+/// Rust wrapper for AStatsManager_clearPullAtomCallback.
+pub fn clear_pull_atom_callback(atom: Atoms) {
+    COOKIES.lock().unwrap().remove(&(atom as i32));
+    // Safety: No memory allocations.
+    unsafe { AStatsManager_clearPullAtomCallback(atom as i32) }
+}
diff --git a/libstats/pull_rust/statslog.h b/libstats/pull_rust/statslog.h
new file mode 100644
index 0000000..983fb7b
--- /dev/null
+++ b/libstats/pull_rust/statslog.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include "stats_pull_atom_callback.h"
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
index 4b2f40e..819066e 100644
--- a/libstats/push_compat/Android.bp
+++ b/libstats/push_compat/Android.bp
@@ -55,7 +55,7 @@
     export_header_lib_headers: [
         "libstatssocket_headers",
     ],
-    static_libs: ["libgtest_prod"],
+    header_libs: ["libgtest_prod_headers"],
     apex_available: ["com.android.resolv"],
     min_sdk_version: "29",
 }
diff --git a/libstats/socket_lazy/Android.bp b/libstats/socket_lazy/Android.bp
index ad6b4e0..b2cd7b2 100644
--- a/libstats/socket_lazy/Android.bp
+++ b/libstats/socket_lazy/Android.bp
@@ -1,6 +1,10 @@
 // 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: [
@@ -37,4 +41,4 @@
             suffix: "32",
         },
     },
-}
\ No newline at end of file
+}
diff --git a/libsync/Android.bp b/libsync/Android.bp
index 540a246..99c88cf 100644
--- a/libsync/Android.bp
+++ b/libsync/Android.bp
@@ -42,7 +42,9 @@
     recovery_available: true,
     native_bridge_supported: true,
     defaults: ["libsync_defaults"],
-    llndk_stubs: "libsync.llndk",
+    llndk: {
+        symbol_file: "libsync.map.txt",
+    },
     stubs: {
         symbol_file: "libsync.map.txt",
         versions: [
@@ -51,12 +53,6 @@
     },
 }
 
-llndk_library {
-    name: "libsync.llndk",
-    symbol_file: "libsync.map.txt",
-    export_include_dirs: ["include"],
-}
-
 cc_test {
     name: "sync-unit-tests",
     shared_libs: ["libsync"],
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 6201569..13e4c02 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -141,6 +141,7 @@
         "Errors.cpp",
         "FileMap.cpp",
         "JenkinsHash.cpp",
+        "LightRefBase.cpp",
         "NativeHandle.cpp",
         "Printer.cpp",
         "RefBase.cpp",
@@ -274,12 +275,6 @@
 }
 
 cc_fuzz {
-    name: "libutils_fuzz_stopwatch",
-    defaults: ["libutils_fuzz_defaults"],
-    srcs: ["StopWatch_fuzz.cpp"],
-}
-
-cc_fuzz {
     name: "libutils_fuzz_refbase",
     defaults: ["libutils_fuzz_defaults"],
     srcs: ["RefBase_fuzz.cpp"],
diff --git a/libutils/LightRefBase.cpp b/libutils/LightRefBase.cpp
new file mode 100644
index 0000000..e08ffec
--- /dev/null
+++ b/libutils/LightRefBase.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "LightRefBase"
+
+#include <utils/LightRefBase.h>
+
+#include <log/log.h>
+
+namespace android {
+
+void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz) {
+    LOG_ALWAYS_FATAL("incStrongRequireStrong() called on %p which isn't already owned", thiz);
+}
+
+}  // namespace android
diff --git a/libutils/README b/libutils/README
deleted file mode 100644
index 01741e0..0000000
--- a/libutils/README
+++ /dev/null
@@ -1,289 +0,0 @@
-Android Utility Function Library
-================================
-
-
-If you need a feature that is native to Linux but not present on other
-platforms, construct a platform-dependent implementation that shares
-the Linux interface.  That way the actual device runs as "light" as
-possible.
-
-If that isn't feasible, create a system-independent interface and hide
-the details.
-
-The ultimate goal is *not* to create a super-duper platform abstraction
-layer.  The goal is to provide an optimized solution for Linux with
-reasonable implementations for other platforms.
-
-
-
-Resource overlay
-================
-
-
-Introduction
-------------
-
-Overlay packages are special .apk files which provide no code but
-additional resource values (and possibly new configurations) for
-resources in other packages. When an application requests resources,
-the system will return values from either the application's original
-package or any associated overlay package. Any redirection is completely
-transparent to the calling application.
-
-Resource values have the following precedence table, listed in
-descending precedence.
-
- * overlay package, matching config (eg res/values-en-land)
-
- * original package, matching config
-
- * overlay package, no config (eg res/values)
-
- * original package, no config
-
-During compilation, overlay packages are differentiated from regular
-packages by passing the -o flag to aapt.
-
-
-Background
-----------
-
-This section provides generic background material on resources in
-Android.
-
-
-How resources are bundled in .apk files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Android .apk files are .zip files, usually housing .dex code,
-certificates and resources, though packages containing resources but
-no code are possible. Resources can be divided into the following
-categories; a `configuration' indicates a set of phone language, display
-density, network operator, etc.
-
- * assets: uncompressed, raw files packaged as part of an .apk and
-           explicitly referenced by filename. These files are
-           independent of configuration.
-
- * res/drawable: bitmap or xml graphics. Each file may have different
-                 values depending on configuration.
-
- * res/values: integers, strings, etc. Each resource may have different
-               values depending on configuration.
-
-Resource meta information and information proper is stored in a binary
-format in a named file resources.arsc, bundled as part of the .apk.
-
-Resource IDs and lookup
-~~~~~~~~~~~~~~~~~~~~~~~
-During compilation, the aapt tool gathers application resources and
-generates a resources.arsc file. Each resource name is assigned an
-integer ID 0xppttiii (translated to a symbolic name via R.java), where
-
- * pp: corresponds to the package namespace (details below).
-
- * tt: corresponds to the resource type (string, int, etc). Every
-       resource of the same type within the same package has the same
-       tt value, but depending on available types, the actual numerical
-       value may be different between packages.
-
- * iiii: sequential number, assigned in the order resources are found.
-
-Resource values are specified paired with a set of configuration
-constraints (the default being the empty set), eg res/values-sv-port
-which imposes restrictions on language (Swedish) and display orientation
-(portrait). During lookup, every constraint set is matched against the
-current configuration, and the value corresponding to the best matching
-constraint set is returned (ResourceTypes.{h,cpp}).
-
-Parsing of resources.arsc is handled by ResourceTypes.cpp; this utility
-is governed by AssetManager.cpp, which tracks loaded resources per
-process.
-
-Assets are looked up by path and filename in AssetManager.cpp. The path
-to resources in res/drawable are located by ResourceTypes.cpp and then
-handled like assets by AssetManager.cpp. Other resources are handled
-solely by ResourceTypes.cpp.
-
-Package ID as namespace
-~~~~~~~~~~~~~~~~~~~~~~~
-The pp part of a resource ID defines a namespace. Android currently
-defines two namespaces:
-
- * 0x01: system resources (pre-installed in framework-res.apk)
-
- * 0x7f: application resources (bundled in the application .apk)
-
-ResourceTypes.cpp supports package IDs between 0x01 and 0x7f
-(inclusive); values outside this range are invalid.
-
-Each running (Dalvik) process is assigned a unique instance of
-AssetManager, which in turn keeps a forest structure of loaded
-resource.arsc files. Normally, this forest is structured as follows,
-where mPackageMap is the internal vector employed in ResourceTypes.cpp.
-
-mPackageMap[0x00] -> system package
-mPackageMap[0x01] -> NULL
-mPackageMap[0x02] -> NULL
-...
-mPackageMap[0x7f - 2] -> NULL
-mPackageMap[0x7f - 1] -> application package
-
-
-
-The resource overlay extension
-------------------------------
-
-The resource overlay mechanism aims to (partly) shadow and extend
-existing resources with new values for defined and new configurations.
-Technically, this is achieved by adding resource-only packages (called
-overlay packages) to existing resource namespaces, like so:
-
-mPackageMap[0x00] -> system package -> system overlay package
-mPackageMap[0x01] -> NULL
-mPackageMap[0x02] -> NULL
-...
-mPackageMap[0x7f - 2] -> NULL
-mPackageMap[0x7f - 1] -> application package -> overlay 1 -> overlay 2
-
-The use of overlay resources is completely transparent to
-applications; no additional resource identifiers are introduced, only
-configuration/value pairs. Any number of overlay packages may be loaded
-at a time; overlay packages are agnostic to what they target -- both
-system and application resources are fair game.
-
-The package targeted by an overlay package is called the target or
-original package.
-
-Resource overlay operates on symbolic resources names. Hence, to
-override the string/str1 resources in a package, the overlay package
-would include a resource also named string/str1. The end user does not
-have to worry about the numeric resources IDs assigned by aapt, as this
-is resolved automatically by the system.
-
-As of this writing, the use of resource overlay has not been fully
-explored. Until it has, only OEMs are trusted to use resource overlay.
-For this reason, overlay packages must reside in /system/overlay.
-
-
-Resource ID mapping
-~~~~~~~~~~~~~~~~~~~
-Resource identifiers must be coherent within the same namespace (ie
-PackageGroup in ResourceTypes.cpp). Calling applications will refer to
-resources using the IDs defined in the original package, but there is no
-guarantee aapt has assigned the same ID to the corresponding resource in
-an overlay package. To translate between the two, a resource ID mapping
-{original ID -> overlay ID} is created during package installation
-(PackageManagerService.java) and used during resource lookup. The
-mapping is stored in /data/resource-cache, with a @idmap file name
-suffix.
-
-The idmap file format is documented in a separate section, below.
-
-
-Package management
-~~~~~~~~~~~~~~~~~~
-Packages are managed by the PackageManagerService. Addition and removal
-of packages are monitored via the inotify framework, exposed via
-android.os.FileObserver.
-
-During initialization of a Dalvik process, ActivityThread.java requests
-the process' AssetManager (by proxy, via AssetManager.java and JNI)
-to load a list of packages. This list includes overlay packages, if
-present.
-
-When a target package or a corresponding overlay package is installed,
-the target package's process is stopped and a new idmap is generated.
-This is similar to how applications are stopped when their packages are
-upgraded.
-
-
-Creating overlay packages
--------------------------
-
-Overlay packages should contain no code, define (some) resources with
-the same type and name as in the original package, and be compiled with
-the -o flag passed to aapt.
-
-The aapt -o flag instructs aapt to create an overlay package.
-Technically, this means the package will be assigned package id 0x00.
-
-There are no restrictions on overlay packages names, though the naming
-convention <original.package.name>.overlay.<name> is recommended.
-
-
-Example overlay package
-~~~~~~~~~~~~~~~~~~~~~~~
-
-To overlay the resource bool/b in package com.foo.bar, to be applied
-when the display is in landscape mode, create a new package with
-no source code and a single .xml file under res/values-land, with
-an entry for bool/b. Compile with aapt -o and place the results in
-/system/overlay by adding the following to Android.mk:
-
-LOCAL_AAPT_FLAGS := -o com.foo.bar
-LOCAL_MODULE_PATH := $(TARGET_OUT)/overlay
-
-
-The ID map (idmap) file format
-------------------------------
-
-The idmap format is designed for lookup performance. However, leading
-and trailing undefined overlay values are discarded to reduce the memory
-footprint.
-
-
-idmap grammar
-~~~~~~~~~~~~~
-All atoms (names in square brackets) are uint32_t integers. The
-idmap-magic constant spells "idmp" in ASCII. Offsets are given relative
-to the data_header, not to the beginning of the file.
-
-map          := header data
-header       := idmap-magic <crc32-original-pkg> <crc32-overlay-pkg>
-idmap-magic  := <0x706d6469>
-data         := data_header type_block+
-data_header  := <m> header_block{m}
-header_block := <0> | <type_block_offset>
-type_block   := <n> <id_offset> entry{n}
-entry        := <resource_id_in_target_package>
-
-
-idmap example
-~~~~~~~~~~~~~
-Given a pair of target and overlay packages with CRC sums 0x216a8fe2
-and 0x6b9beaec, each defining the following resources
-
-Name          Target package  Overlay package
-string/str0   0x7f010000      -
-string/str1   0x7f010001      0x7f010000
-string/str2   0x7f010002      -
-string/str3   0x7f010003      0x7f010001
-string/str4   0x7f010004      -
-bool/bool0    0x7f020000      -
-integer/int0  0x7f030000      0x7f020000
-integer/int1  0x7f030001      -
-
-the corresponding resource map is
-
-0x706d6469 0x216a8fe2 0x6b9beaec 0x00000003 \
-0x00000004 0x00000000 0x00000009 0x00000003 \
-0x00000001 0x7f010000 0x00000000 0x7f010001 \
-0x00000001 0x00000000 0x7f020000
-
-or, formatted differently
-
-0x706d6469  # magic: all idmap files begin with this constant
-0x216a8fe2  # CRC32 of the resources.arsc file in the original package
-0x6b9beaec  # CRC32 of the resources.arsc file in the overlay package
-0x00000003  # header; three types (string, bool, integer) in the target package
-0x00000004  #   header_block for type 0 (string) is located at offset 4
-0x00000000  #   no bool type exists in overlay package -> no header_block
-0x00000009  #   header_block for type 2 (integer) is located at offset 9
-0x00000003  # header_block for string; overlay IDs span 3 elements
-0x00000001  #   the first string in target package is entry 1 == offset
-0x7f010000  #   target 0x7f01001 -> overlay 0x7f010000
-0x00000000  #   str2 not defined in overlay package
-0x7f010001  #   target 0x7f010003 -> overlay 0x7f010001
-0x00000001  # header_block for integer; overlay IDs span 1 element
-0x00000000  #   offset == 0
-0x7f020000  #   target 0x7f030000 -> overlay 0x7f020000
diff --git a/libutils/RefBase.cpp b/libutils/RefBase.cpp
index 8e45226..b57e287 100644
--- a/libutils/RefBase.cpp
+++ b/libutils/RefBase.cpp
@@ -443,6 +443,20 @@
     refs->mBase->onFirstRef();
 }
 
+void RefBase::incStrongRequireStrong(const void* id) const {
+    weakref_impl* const refs = mRefs;
+    refs->incWeak(id);
+
+    refs->addStrongRef(id);
+    const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
+
+    LOG_ALWAYS_FATAL_IF(c <= 0 || c == INITIAL_STRONG_VALUE,
+                        "incStrongRequireStrong() called on %p which isn't already owned", refs);
+#if PRINT_REFS
+    ALOGD("incStrong (requiring strong) of %p from %p: cnt=%d\n", this, id, c);
+#endif
+}
+
 void RefBase::decStrong(const void* id) const
 {
     weakref_impl* const refs = mRefs;
@@ -521,6 +535,14 @@
     ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
 }
 
+void RefBase::weakref_type::incWeakRequireWeak(const void* id)
+{
+    weakref_impl* const impl = static_cast<weakref_impl*>(this);
+    impl->addWeakRef(id);
+    const int32_t c __unused = impl->mWeak.fetch_add(1,
+            std::memory_order_relaxed);
+    LOG_ALWAYS_FATAL_IF(c <= 0, "incWeakRequireWeak called on %p which has no weak refs", this);
+}
 
 void RefBase::weakref_type::decWeak(const void* id)
 {
diff --git a/libutils/RefBase_test.cpp b/libutils/RefBase_test.cpp
index c9b4894..93f9654 100644
--- a/libutils/RefBase_test.cpp
+++ b/libutils/RefBase_test.cpp
@@ -241,6 +241,30 @@
     ASSERT_FALSE(wp1 != wp2);
 }
 
+TEST(RefBase, AssertWeakRefExistsSuccess) {
+    bool isDeleted;
+    sp<Foo> foo = sp<Foo>::make(&isDeleted);
+    wp<Foo> weakFoo = foo;
+
+    EXPECT_EQ(weakFoo, wp<Foo>::fromExisting(foo.get()));
+    EXPECT_EQ(weakFoo.unsafe_get(), wp<Foo>::fromExisting(foo.get()).unsafe_get());
+
+    EXPECT_FALSE(isDeleted);
+    foo = nullptr;
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST(RefBase, AssertWeakRefExistsDeath) {
+    // uses some other refcounting method, or none at all
+    bool isDeleted;
+    Foo* foo = new Foo(&isDeleted);
+
+    // can only get a valid wp<> object when you construct it from an sp<>
+    EXPECT_DEATH(wp<Foo>::fromExisting(foo), "");
+
+    delete foo;
+}
+
 // Set up a situation in which we race with visit2AndRremove() to delete
 // 2 strong references.  Bar destructor checks that there are no early
 // deletions and prior updates are visible to destructor.
diff --git a/libutils/SharedBuffer_test.cpp b/libutils/SharedBuffer_test.cpp
index 3f960d2..1d6317f 100644
--- a/libutils/SharedBuffer_test.cpp
+++ b/libutils/SharedBuffer_test.cpp
@@ -32,10 +32,25 @@
     EXPECT_DEATH(android::SharedBuffer::alloc(SIZE_MAX - sizeof(android::SharedBuffer)), "");
 }
 
-TEST(SharedBufferTest, alloc_null) {
-    // Big enough to fail, not big enough to abort.
+TEST(SharedBufferTest, alloc_max) {
     SKIP_WITH_HWASAN;  // hwasan has a 2GiB allocation limit.
-    ASSERT_EQ(nullptr, android::SharedBuffer::alloc(SIZE_MAX / 2));
+
+    android::SharedBuffer* buf =
+            android::SharedBuffer::alloc(SIZE_MAX - sizeof(android::SharedBuffer) - 1);
+    if (buf != nullptr) {
+        EXPECT_NE(nullptr, buf->data());
+        buf->release();
+    }
+}
+
+TEST(SharedBufferTest, alloc_big) {
+    SKIP_WITH_HWASAN;  // hwasan has a 2GiB allocation limit.
+
+    android::SharedBuffer* buf = android::SharedBuffer::alloc(SIZE_MAX / 2);
+    if (buf != nullptr) {
+        EXPECT_NE(nullptr, buf->data());
+        buf->release();
+    }
 }
 
 TEST(SharedBufferTest, alloc_zero_size) {
@@ -56,7 +71,13 @@
     // Big enough to fail, not big enough to abort.
     SKIP_WITH_HWASAN;  // hwasan has a 2GiB allocation limit.
     android::SharedBuffer* buf = android::SharedBuffer::alloc(10);
-    ASSERT_EQ(nullptr, buf->editResize(SIZE_MAX / 2));
+    android::SharedBuffer* buf2 = buf->editResize(SIZE_MAX / 2);
+    if (buf2 == nullptr) {
+        buf->release();
+    } else {
+        EXPECT_NE(nullptr, buf2->data());
+        buf2->release();
+    }
 }
 
 TEST(SharedBufferTest, editResize_zero_size) {
diff --git a/libutils/StopWatch.cpp b/libutils/StopWatch.cpp
index d01865e..28e2d76 100644
--- a/libutils/StopWatch.cpp
+++ b/libutils/StopWatch.cpp
@@ -26,58 +26,26 @@
 
 #include <utils/Log.h>
 
-/*****************************************************************************/
-
 namespace android {
 
 StopWatch::StopWatch(const char* name, int clock) : mName(name), mClock(clock) {
     reset();
 }
 
-StopWatch::~StopWatch()
-{
-    nsecs_t elapsed = elapsedTime();
-    const int n = mNumLaps;
-    ALOGD("StopWatch %s (us): %" PRId64 " ", mName, ns2us(elapsed));
-    for (int i=0 ; i<n ; i++) {
-        const nsecs_t soFar = mLaps[i].soFar;
-        const nsecs_t thisLap = mLaps[i].thisLap;
-        ALOGD(" [%d: %" PRId64 ", %" PRId64, i, ns2us(soFar), ns2us(thisLap));
-    }
+StopWatch::~StopWatch() {
+    ALOGD("StopWatch %s (us): %" PRId64 " ", name(), ns2us(elapsedTime()));
 }
 
-const char* StopWatch::name() const
-{
+const char* StopWatch::name() const {
     return mName;
 }
 
-nsecs_t StopWatch::lap()
-{
-    nsecs_t elapsed = elapsedTime();
-    if (mNumLaps >= 8) {
-        elapsed = 0;
-    } else {
-        const int n = mNumLaps;
-        mLaps[n].soFar   = elapsed;
-        mLaps[n].thisLap = n ? (elapsed - mLaps[n-1].soFar) : elapsed;
-        mNumLaps = n+1;
-    }
-    return elapsed;
-}
-
-nsecs_t StopWatch::elapsedTime() const
-{
+nsecs_t StopWatch::elapsedTime() const {
     return systemTime(mClock) - mStartTime;
 }
 
-void StopWatch::reset()
-{
-    mNumLaps = 0;
+void StopWatch::reset() {
     mStartTime = systemTime(mClock);
 }
 
-
-/*****************************************************************************/
-
-}; // namespace android
-
+}  // namespace android
diff --git a/libutils/StopWatch_fuzz.cpp b/libutils/StopWatch_fuzz.cpp
deleted file mode 100644
index 63d8a28..0000000
--- a/libutils/StopWatch_fuzz.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 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 "fuzzer/FuzzedDataProvider.h"
-#include "utils/StopWatch.h"
-
-static constexpr int MAX_OPERATIONS = 100;
-static constexpr int MAX_NAME_LEN = 2048;
-
-static const std::vector<std::function<void(android::StopWatch)>> operations = {
-        [](android::StopWatch stopWatch) -> void { stopWatch.reset(); },
-        [](android::StopWatch stopWatch) -> void { stopWatch.lap(); },
-        [](android::StopWatch stopWatch) -> void { stopWatch.elapsedTime(); },
-        [](android::StopWatch stopWatch) -> void { stopWatch.name(); },
-};
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    FuzzedDataProvider dataProvider(data, size);
-    std::string nameStr = dataProvider.ConsumeRandomLengthString(MAX_NAME_LEN);
-    int clockVal = dataProvider.ConsumeIntegral<int>();
-    android::StopWatch stopWatch = android::StopWatch(nameStr.c_str(), clockVal);
-    std::vector<uint8_t> opsToRun = dataProvider.ConsumeRemainingBytes<uint8_t>();
-    int opsRun = 0;
-    for (auto it : opsToRun) {
-        if (opsRun++ >= MAX_OPERATIONS) {
-            break;
-        }
-        it = it % operations.size();
-        operations[it](stopWatch);
-    }
-    return 0;
-}
diff --git a/libutils/String16.cpp b/libutils/String16.cpp
index 70bf5a0..faf90c2 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -390,28 +390,6 @@
     return static_cast<size_t>(*(p - 1));
 }
 
-status_t String16::makeLower()
-{
-    const size_t N = size();
-    const char16_t* str = string();
-    char16_t* edited = nullptr;
-    for (size_t i=0; i<N; i++) {
-        const char16_t v = str[i];
-        if (v >= 'A' && v <= 'Z') {
-            if (!edited) {
-                SharedBuffer* buf = static_cast<SharedBuffer*>(edit());
-                if (!buf) {
-                    return NO_MEMORY;
-                }
-                edited = (char16_t*)buf->data();
-                mString = str = edited;
-            }
-            edited[i] = tolower((char)v);
-        }
-    }
-    return OK;
-}
-
 status_t String16::replaceAll(char16_t replaceThis, char16_t withThis)
 {
     const size_t N = size();
@@ -433,36 +411,4 @@
     return OK;
 }
 
-status_t String16::remove(size_t len, size_t begin)
-{
-    const size_t N = size();
-    if (begin >= N) {
-        release();
-        mString = getEmptyString();
-        return OK;
-    }
-    if (len > N || len > N - begin) len = N - begin;
-    if (begin == 0 && len == N) {
-        return OK;
-    }
-
-    if (begin > 0) {
-        SharedBuffer* buf = static_cast<SharedBuffer*>(editResize((N + 1) * sizeof(char16_t)));
-        if (!buf) {
-            return NO_MEMORY;
-        }
-        char16_t* str = (char16_t*)buf->data();
-        memmove(str, str+begin, (N-begin+1)*sizeof(char16_t));
-        mString = str;
-    }
-    SharedBuffer* buf = static_cast<SharedBuffer*>(editResize((len + 1) * sizeof(char16_t)));
-    if (buf) {
-        char16_t* str = (char16_t*)buf->data();
-        str[len] = 0;
-        mString = str;
-        return OK;
-    }
-    return NO_MEMORY;
-}
-
 }; // namespace android
diff --git a/libutils/String16_fuzz.cpp b/libutils/String16_fuzz.cpp
index 63c2800..d7e5ec7 100644
--- a/libutils/String16_fuzz.cpp
+++ b/libutils/String16_fuzz.cpp
@@ -34,11 +34,6 @@
                     str1.size();
                 }),
 
-                // Casing
-                ([](FuzzedDataProvider&, android::String16 str1, android::String16) -> void {
-                    str1.makeLower();
-                }),
-
                 // Comparison
                 ([](FuzzedDataProvider&, android::String16 str1, android::String16 str2) -> void {
                     str1.startsWith(str2);
@@ -77,12 +72,6 @@
                     char16_t replaceChar = dataProvider.ConsumeIntegral<char16_t>();
                     str1.replaceAll(findChar, replaceChar);
                 }),
-                ([](FuzzedDataProvider& dataProvider, android::String16 str1,
-                    android::String16) -> void {
-                    size_t len = dataProvider.ConsumeIntegral<size_t>();
-                    size_t begin = dataProvider.ConsumeIntegral<size_t>();
-                    str1.remove(len, begin);
-                }),
 };
 
 void callFunc(uint8_t index, FuzzedDataProvider& dataProvider, android::String16 str1,
@@ -116,7 +105,5 @@
         callFunc(op, dataProvider, str_one_utf16, str_two_utf16);
     }
 
-    str_one_utf16.remove(0, str_one_utf16.size());
-    str_two_utf16.remove(0, str_two_utf16.size());
     return 0;
 }
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index 2505f44..54662ac 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -90,20 +90,6 @@
     EXPECT_STR16EQ(u"VerifyInsert me", tmp);
 }
 
-TEST(String16Test, Remove) {
-    String16 tmp("Verify me");
-    tmp.remove(2, 6);
-    EXPECT_EQ(2U, tmp.size());
-    EXPECT_STR16EQ(u" m", tmp);
-}
-
-TEST(String16Test, MakeLower) {
-    String16 tmp("Verify Me!");
-    tmp.makeLower();
-    EXPECT_EQ(10U, tmp.size());
-    EXPECT_STR16EQ(u"verify me!", tmp);
-}
-
 TEST(String16Test, ReplaceAll) {
     String16 tmp("Verify verify Verify");
     tmp.replaceAll(u'r', u'!');
@@ -168,22 +154,6 @@
     EXPECT_FALSE(tmp.isStaticString());
 }
 
-TEST(String16Test, StaticStringRemove) {
-    StaticString16 tmp(u"Verify me");
-    tmp.remove(2, 6);
-    EXPECT_EQ(2U, tmp.size());
-    EXPECT_STR16EQ(u" m", tmp);
-    EXPECT_FALSE(tmp.isStaticString());
-}
-
-TEST(String16Test, StaticStringMakeLower) {
-    StaticString16 tmp(u"Verify me!");
-    tmp.makeLower();
-    EXPECT_EQ(10U, tmp.size());
-    EXPECT_STR16EQ(u"verify me!", tmp);
-    EXPECT_FALSE(tmp.isStaticString());
-}
-
 TEST(String16Test, StaticStringReplaceAll) {
     StaticString16 tmp(u"Verify verify Verify");
     tmp.replaceAll(u'r', u'!');
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 3dc2026..195e122 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -25,6 +25,8 @@
 
 #include <ctype.h>
 
+#include <string>
+
 #include "SharedBuffer.h"
 
 /*
@@ -163,9 +165,7 @@
 }
 
 String8::String8(const char32_t* o)
-    : mString(allocFromUTF32(o, strlen32(o)))
-{
-}
+    : mString(allocFromUTF32(o, std::char_traits<char32_t>::length(o))) {}
 
 String8::String8(const char32_t* o, size_t len)
     : mString(allocFromUTF32(o, len))
@@ -415,50 +415,15 @@
 
 void String8::toLower()
 {
-    toLower(0, size());
-}
+    const size_t length = size();
+    if (length == 0) return;
 
-void String8::toLower(size_t start, size_t length)
-{
-    const size_t len = size();
-    if (start >= len) {
-        return;
-    }
-    if (start+length > len) {
-        length = len-start;
-    }
-    char* buf = lockBuffer(len);
-    buf += start;
-    while (length > 0) {
+    char* buf = lockBuffer(length);
+    for (size_t i = length; i > 0; --i) {
         *buf = static_cast<char>(tolower(*buf));
         buf++;
-        length--;
     }
-    unlockBuffer(len);
-}
-
-void String8::toUpper()
-{
-    toUpper(0, size());
-}
-
-void String8::toUpper(size_t start, size_t length)
-{
-    const size_t len = size();
-    if (start >= len) {
-        return;
-    }
-    if (start+length > len) {
-        length = len-start;
-    }
-    char* buf = lockBuffer(len);
-    buf += start;
-    while (length > 0) {
-        *buf = static_cast<char>(toupper(*buf));
-        buf++;
-        length--;
-    }
-    unlockBuffer(len);
+    unlockBuffer(length);
 }
 
 // ---------------------------------------------------------------------------
diff --git a/libutils/String8_fuzz.cpp b/libutils/String8_fuzz.cpp
index b02683c..a45d675 100644
--- a/libutils/String8_fuzz.cpp
+++ b/libutils/String8_fuzz.cpp
@@ -42,9 +42,6 @@
 
                 // Casing
                 [](FuzzedDataProvider*, android::String8* str1, android::String8*) -> void {
-                    str1->toUpper();
-                },
-                [](FuzzedDataProvider*, android::String8* str1, android::String8*) -> void {
                     str1->toLower();
                 },
                 [](FuzzedDataProvider*, android::String8* str1, android::String8* str2) -> void {
diff --git a/libutils/StrongPointer_test.cpp b/libutils/StrongPointer_test.cpp
index d37c1de..f27c1f1 100644
--- a/libutils/StrongPointer_test.cpp
+++ b/libutils/StrongPointer_test.cpp
@@ -21,8 +21,8 @@
 
 using namespace android;
 
-class SPFoo : public LightRefBase<SPFoo> {
-public:
+class SPFoo : virtual public RefBase {
+  public:
     explicit SPFoo(bool* deleted_check) : mDeleted(deleted_check) {
         *mDeleted = false;
     }
@@ -30,17 +30,34 @@
     ~SPFoo() {
         *mDeleted = true;
     }
-private:
+
+  private:
     bool* mDeleted;
 };
 
-TEST(StrongPointer, move) {
+class SPLightFoo : virtual public VirtualLightRefBase {
+  public:
+    explicit SPLightFoo(bool* deleted_check) : mDeleted(deleted_check) { *mDeleted = false; }
+
+    ~SPLightFoo() { *mDeleted = true; }
+
+  private:
+    bool* mDeleted;
+};
+
+template <typename T>
+class StrongPointer : public ::testing::Test {};
+
+using RefBaseTypes = ::testing::Types<SPFoo, SPLightFoo>;
+TYPED_TEST_CASE(StrongPointer, RefBaseTypes);
+
+TYPED_TEST(StrongPointer, move) {
     bool isDeleted;
-    sp<SPFoo> sp1 = sp<SPFoo>::make(&isDeleted);
-    SPFoo* foo = sp1.get();
+    sp<TypeParam> sp1 = sp<TypeParam>::make(&isDeleted);
+    TypeParam* foo = sp1.get();
     ASSERT_EQ(1, foo->getStrongCount());
     {
-        sp<SPFoo> sp2 = std::move(sp1);
+        sp<TypeParam> sp2 = std::move(sp1);
         ASSERT_EQ(1, foo->getStrongCount()) << "std::move failed, incremented refcnt";
         ASSERT_EQ(nullptr, sp1.get()) << "std::move failed, sp1 is still valid";
         // The strong count isn't increasing, let's double check the old object
@@ -50,22 +67,42 @@
     ASSERT_FALSE(isDeleted) << "deleted too early! still has a reference!";
     {
         // Now let's double check it deletes on time
-        sp<SPFoo> sp2 = std::move(sp1);
+        sp<TypeParam> sp2 = std::move(sp1);
     }
     ASSERT_TRUE(isDeleted) << "foo was leaked!";
 }
 
-TEST(StrongPointer, NullptrComparison) {
-    sp<SPFoo> foo;
+TYPED_TEST(StrongPointer, NullptrComparison) {
+    sp<TypeParam> foo;
     ASSERT_EQ(foo, nullptr);
     ASSERT_EQ(nullptr, foo);
 }
 
-TEST(StrongPointer, PointerComparison) {
+TYPED_TEST(StrongPointer, PointerComparison) {
     bool isDeleted;
-    sp<SPFoo> foo = sp<SPFoo>::make(&isDeleted);
+    sp<TypeParam> foo = sp<TypeParam>::make(&isDeleted);
     ASSERT_EQ(foo.get(), foo);
     ASSERT_EQ(foo, foo.get());
     ASSERT_NE(nullptr, foo);
     ASSERT_NE(foo, nullptr);
 }
+
+TYPED_TEST(StrongPointer, Deleted) {
+    bool isDeleted;
+    sp<TypeParam> foo = sp<TypeParam>::make(&isDeleted);
+
+    auto foo2 = sp<TypeParam>::fromExisting(foo.get());
+
+    EXPECT_FALSE(isDeleted);
+    foo = nullptr;
+    EXPECT_FALSE(isDeleted);
+    foo2 = nullptr;
+    EXPECT_TRUE(isDeleted);
+}
+
+TYPED_TEST(StrongPointer, AssertStrongRefExists) {
+    bool isDeleted;
+    TypeParam* foo = new TypeParam(&isDeleted);
+    EXPECT_DEATH(sp<TypeParam>::fromExisting(foo), "");
+    delete foo;
+}
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 843a81a..3ffcf7e 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -22,20 +22,6 @@
 
 #include <log/log.h>
 
-#if defined(_WIN32)
-# undef  nhtol
-# undef  htonl
-# undef  nhtos
-# undef  htons
-
-# define ntohl(x)    ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) )
-# define htonl(x)    ntohl(x)
-# define ntohs(x)    ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) )
-# define htons(x)    ntohs(x)
-#else
-# include <netinet/in.h>
-#endif
-
 extern "C" {
 
 static const char32_t kByteMask = 0x000000BF;
@@ -115,24 +101,6 @@
     }
 }
 
-size_t strlen32(const char32_t *s)
-{
-  const char32_t *ss = s;
-  while ( *ss )
-    ss++;
-  return ss-s;
-}
-
-size_t strnlen32(const char32_t *s, size_t maxlen)
-{
-  const char32_t *ss = s;
-  while ((maxlen > 0) && *ss) {
-    ss++;
-    maxlen--;
-  }
-  return ss-s;
-}
-
 static inline int32_t utf32_at_internal(const char* cur, size_t *num_read)
 {
     const char first_char = *cur;
@@ -254,19 +222,6 @@
   return d;
 }
 
-char16_t *strcpy16(char16_t *dst, const char16_t *src)
-{
-  char16_t *q = dst;
-  const char16_t *p = src;
-  char16_t ch;
-
-  do {
-    *q++ = ch = *p++;
-  } while ( ch );
-
-  return dst;
-}
-
 size_t strlen16(const char16_t *s)
 {
   const char16_t *ss = s;
diff --git a/libutils/include/utils/LightRefBase.h b/libutils/include/utils/LightRefBase.h
index b04e5c1..40edf67 100644
--- a/libutils/include/utils/LightRefBase.h
+++ b/libutils/include/utils/LightRefBase.h
@@ -28,6 +28,8 @@
 
 class ReferenceRenamer;
 
+void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz);
+
 template <class T>
 class LightRefBase
 {
@@ -36,6 +38,11 @@
     inline void incStrong(__attribute__((unused)) const void* id) const {
         mCount.fetch_add(1, std::memory_order_relaxed);
     }
+    inline void incStrongRequireStrong(__attribute__((unused)) const void* id) const {
+        if (0 == mCount.fetch_add(1, std::memory_order_relaxed)) {
+            LightRefBase_reportIncStrongRequireStrongFailed(this);
+        }
+    }
     inline void decStrong(__attribute__((unused)) const void* id) const {
         if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
             std::atomic_thread_fence(std::memory_order_acquire);
@@ -59,7 +66,6 @@
     mutable std::atomic<int32_t> mCount;
 };
 
-
 // This is a wrapper around LightRefBase that simply enforces a virtual
 // destructor to eliminate the template requirement of LightRefBase
 class VirtualLightRefBase : public LightRefBase<VirtualLightRefBase> {
diff --git a/libutils/include/utils/RefBase.h b/libutils/include/utils/RefBase.h
index e7acd17..e07f574 100644
--- a/libutils/include/utils/RefBase.h
+++ b/libutils/include/utils/RefBase.h
@@ -140,7 +140,9 @@
 // count, and accidentally passed to f(sp<T>), a strong pointer to the object
 // will be temporarily constructed and destroyed, prematurely deallocating the
 // object, and resulting in heap corruption. None of this would be easily
-// visible in the source.
+// visible in the source. See below on
+// ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION for a compile time
+// option which helps avoid this case.
 
 // Extra Features:
 
@@ -167,6 +169,42 @@
 // to THE SAME sp<> or wp<>.  In effect, their thread-safety properties are
 // exactly like those of T*, NOT atomic<T*>.
 
+// Safety option: ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION
+//
+// This flag makes the semantics for using a RefBase object with wp<> and sp<>
+// much stricter by disabling implicit conversion from raw pointers to these
+// objects. In order to use this, apply this flag in Android.bp like so:
+//
+//    cflags: [
+//        "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
+//    ],
+//
+// REGARDLESS of whether this flag is on, best usage of sp<> is shown below. If
+// this flag is on, no other usage is possible (directly calling RefBase methods
+// is possible, but seeing code using 'incStrong' instead of 'sp<>', for
+// instance, should already set off big alarm bells. With carefully constructed
+// data structures, it should NEVER be necessary to directly use RefBase
+// methods). Proper RefBase usage:
+//
+//    class Foo : virtual public RefBase { ... };
+//
+//    // always construct an sp object with sp::make
+//    sp<Foo> myFoo = sp<Foo>::make(/*args*/);
+//
+//    // if you need a weak pointer, it must be constructed from a strong
+//    // pointer
+//    wp<Foo> weakFoo = myFoo; // NOT myFoo.get()
+//
+//    // If you are inside of a method of Foo and need access to a strong
+//    // explicitly call this function. This documents your intention to code
+//    // readers, and it will give a runtime error for what otherwise would
+//    // be potential double ownership
+//    .... Foo::someMethod(...) {
+//        // asserts if there is a memory issue
+//        sp<Foo> thiz = sp<Foo>::fromExisting(this);
+//    }
+//
+
 #ifndef ANDROID_REF_BASE_H
 #define ANDROID_REF_BASE_H
 
@@ -244,6 +282,7 @@
 {
 public:
             void            incStrong(const void* id) const;
+            void            incStrongRequireStrong(const void* id) const;
             void            decStrong(const void* id) const;
     
             void            forceIncStrong(const void* id) const;
@@ -257,6 +296,7 @@
         RefBase*            refBase() const;
 
         void                incWeak(const void* id);
+        void                incWeakRequireWeak(const void* id);
         void                decWeak(const void* id);
 
         // acquires a strong reference if there is already one.
@@ -365,10 +405,27 @@
 
     inline wp() : m_ptr(nullptr), m_refs(nullptr) { }
 
+    // if nullptr, returns nullptr
+    //
+    // if a weak pointer is already available, this will retrieve it,
+    // otherwise, this will abort
+    static inline wp<T> fromExisting(T* other);
+
+    // for more information about this flag, see above
+#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
+    wp(std::nullptr_t) : wp() {}
+#else
     wp(T* other);  // NOLINT(implicit)
+    template <typename U>
+    wp(U* other);  // NOLINT(implicit)
+    wp& operator=(T* other);
+    template <typename U>
+    wp& operator=(U* other);
+#endif
+
     wp(const wp<T>& other);
     explicit wp(const sp<T>& other);
-    template<typename U> wp(U* other);  // NOLINT(implicit)
+
     template<typename U> wp(const sp<U>& other);  // NOLINT(implicit)
     template<typename U> wp(const wp<U>& other);  // NOLINT(implicit)
 
@@ -376,11 +433,9 @@
 
     // Assignment
 
-    wp& operator = (T* other);
     wp& operator = (const wp<T>& other);
     wp& operator = (const sp<T>& other);
 
-    template<typename U> wp& operator = (U* other);
     template<typename U> wp& operator = (const wp<U>& other);
     template<typename U> wp& operator = (const sp<U>& other);
 
@@ -481,6 +536,20 @@
 // Note that the above comparison operations go out of their way to provide an ordering consistent
 // with ordinary pointer comparison; otherwise they could ignore m_ptr, and just compare m_refs.
 
+template <typename T>
+wp<T> wp<T>::fromExisting(T* other) {
+    if (!other) return nullptr;
+
+    auto refs = other->getWeakRefs();
+    refs->incWeakRequireWeak(other);
+
+    wp<T> ret;
+    ret.m_ptr = other;
+    ret.m_refs = refs;
+    return ret;
+}
+
+#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
 template<typename T>
 wp<T>::wp(T* other)
     : m_ptr(other)
@@ -488,6 +557,32 @@
     m_refs = other ? m_refs = other->createWeak(this) : nullptr;
 }
 
+template <typename T>
+template <typename U>
+wp<T>::wp(U* other) : m_ptr(other) {
+    m_refs = other ? other->createWeak(this) : nullptr;
+}
+
+template <typename T>
+wp<T>& wp<T>::operator=(T* other) {
+    weakref_type* newRefs = other ? other->createWeak(this) : nullptr;
+    if (m_ptr) m_refs->decWeak(this);
+    m_ptr = other;
+    m_refs = newRefs;
+    return *this;
+}
+
+template <typename T>
+template <typename U>
+wp<T>& wp<T>::operator=(U* other) {
+    weakref_type* newRefs = other ? other->createWeak(this) : 0;
+    if (m_ptr) m_refs->decWeak(this);
+    m_ptr = other;
+    m_refs = newRefs;
+    return *this;
+}
+#endif
+
 template<typename T>
 wp<T>::wp(const wp<T>& other)
     : m_ptr(other.m_ptr), m_refs(other.m_refs)
@@ -503,13 +598,6 @@
 }
 
 template<typename T> template<typename U>
-wp<T>::wp(U* other)
-    : m_ptr(other)
-{
-    m_refs = other ? other->createWeak(this) : nullptr;
-}
-
-template<typename T> template<typename U>
 wp<T>::wp(const wp<U>& other)
     : m_ptr(other.m_ptr)
 {
@@ -535,17 +623,6 @@
 }
 
 template<typename T>
-wp<T>& wp<T>::operator = (T* other)
-{
-    weakref_type* newRefs =
-        other ? other->createWeak(this) : nullptr;
-    if (m_ptr) m_refs->decWeak(this);
-    m_ptr = other;
-    m_refs = newRefs;
-    return *this;
-}
-
-template<typename T>
 wp<T>& wp<T>::operator = (const wp<T>& other)
 {
     weakref_type* otherRefs(other.m_refs);
@@ -570,17 +647,6 @@
 }
 
 template<typename T> template<typename U>
-wp<T>& wp<T>::operator = (U* other)
-{
-    weakref_type* newRefs =
-        other ? other->createWeak(this) : 0;
-    if (m_ptr) m_refs->decWeak(this);
-    m_ptr = other;
-    m_refs = newRefs;
-    return *this;
-}
-
-template<typename T> template<typename U>
 wp<T>& wp<T>::operator = (const wp<U>& other)
 {
     weakref_type* otherRefs(other.m_refs);
diff --git a/libutils/include/utils/StopWatch.h b/libutils/include/utils/StopWatch.h
index 9b14ac8..4e53eda 100644
--- a/libutils/include/utils/StopWatch.h
+++ b/libutils/include/utils/StopWatch.h
@@ -14,46 +14,30 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_STOPWATCH_H
-#define ANDROID_STOPWATCH_H
+#pragma once
 
 #include <stdint.h>
 #include <sys/types.h>
 
 #include <utils/Timers.h>
 
-// ---------------------------------------------------------------------------
-
 namespace android {
 
-class StopWatch
-{
-public:
-  StopWatch(const char* name, int clock = SYSTEM_TIME_MONOTONIC);
-  ~StopWatch();
+class StopWatch {
+  public:
+    StopWatch(const char* name, int clock = SYSTEM_TIME_MONOTONIC);
+    ~StopWatch();
 
-  const char* name() const;
-  nsecs_t lap();
-  nsecs_t elapsedTime() const;
+    const char* name() const;
+    nsecs_t elapsedTime() const;
 
-  void reset();
+    void reset();
 
-private:
-    const char*     mName;
-    int             mClock;
-    
-    struct lap_t {
-        nsecs_t     soFar;
-        nsecs_t     thisLap;
-    };
-    
-    nsecs_t         mStartTime;
-    lap_t           mLaps[8];
-    int             mNumLaps;
+  private:
+    const char* mName;
+    int mClock;
+
+    nsecs_t mStartTime;
 };
 
 }  // namespace android
-
-// ---------------------------------------------------------------------------
-
-#endif // ANDROID_STOPWATCH_H
diff --git a/libutils/include/utils/String16.h b/libutils/include/utils/String16.h
index 1a4b47e..60d523a 100644
--- a/libutils/include/utils/String16.h
+++ b/libutils/include/utils/String16.h
@@ -85,13 +85,9 @@
 
             bool                contains(const char16_t* chrs) const;
 
-            status_t            makeLower();
-
             status_t            replaceAll(char16_t replaceThis,
                                            char16_t withThis);
 
-            status_t            remove(size_t len, size_t begin=0);
-
     inline  int                 compare(const String16& other) const;
 
     inline  bool                operator<(const String16& other) const;
diff --git a/libutils/include/utils/String8.h b/libutils/include/utils/String8.h
index 0bcb716..cee5dc6 100644
--- a/libutils/include/utils/String8.h
+++ b/libutils/include/utils/String8.h
@@ -130,9 +130,6 @@
             bool                removeAll(const char* other);
 
             void                toLower();
-            void                toLower(size_t start, size_t numChars);
-            void                toUpper();
-            void                toUpper(size_t start, size_t numChars);
 
 
     /*
diff --git a/libutils/include/utils/StrongPointer.h b/libutils/include/utils/StrongPointer.h
index 11128f2..bb1941b 100644
--- a/libutils/include/utils/StrongPointer.h
+++ b/libutils/include/utils/StrongPointer.h
@@ -32,30 +32,64 @@
 public:
     inline sp() : m_ptr(nullptr) { }
 
-    // TODO: switch everyone to using this over new, and make RefBase operator
-    // new private to that class so that we can avoid RefBase being used with
-    // other memory management mechanisms.
+    // The old way of using sp<> was like this. This is bad because it relies
+    // on implicit conversion to sp<>, which we would like to remove (if an
+    // object is being managed some other way, this is double-ownership). We
+    // want to move away from this:
+    //
+    //     sp<Foo> foo = new Foo(...); // DO NOT DO THIS
+    //
+    // Instead, prefer to do this:
+    //
+    //     sp<Foo> foo = sp<Foo>::make(...); // DO THIS
+    //
+    // Sometimes, in order to use this, when a constructor is marked as private,
+    // you may need to add this to your class:
+    //
+    //     friend class sp<Foo>;
     template <typename... Args>
     static inline sp<T> make(Args&&... args);
 
+    // if nullptr, returns nullptr
+    //
+    // if a strong pointer is already available, this will retrieve it,
+    // otherwise, this will abort
+    static inline sp<T> fromExisting(T* other);
+
+    // for more information about this macro and correct RefBase usage, see
+    // the comment at the top of utils/RefBase.h
+#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
+    sp(std::nullptr_t) : sp() {}
+#else
     sp(T* other);  // NOLINT(implicit)
+    template <typename U>
+    sp(U* other);  // NOLINT(implicit)
+    sp& operator=(T* other);
+    template <typename U>
+    sp& operator=(U* other);
+#endif
+
     sp(const sp<T>& other);
     sp(sp<T>&& other) noexcept;
-    template<typename U> sp(U* other);  // NOLINT(implicit)
+
     template<typename U> sp(const sp<U>& other);  // NOLINT(implicit)
     template<typename U> sp(sp<U>&& other);  // NOLINT(implicit)
 
+    // Cast a strong pointer directly from one type to another. Constructors
+    // allow changing types, but only if they are pointer-compatible. This does
+    // a static_cast internally.
+    template <typename U>
+    static inline sp<T> cast(const sp<U>& other);
+
     ~sp();
 
     // Assignment
 
-    sp& operator = (T* other);
     sp& operator = (const sp<T>& other);
     sp& operator=(sp<T>&& other) noexcept;
 
     template<typename U> sp& operator = (const sp<U>& other);
     template<typename U> sp& operator = (sp<U>&& other);
-    template<typename U> sp& operator = (U* other);
 
     //! Special optimization for use by ProcessState (and nobody else).
     void force_set(T* other);
@@ -189,6 +223,19 @@
     return result;
 }
 
+template <typename T>
+sp<T> sp<T>::fromExisting(T* other) {
+    if (other) {
+        check_not_on_stack(other);
+        other->incStrongRequireStrong(other);
+        sp<T> result;
+        result.m_ptr = other;
+        return result;
+    }
+    return nullptr;
+}
+
+#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
 template<typename T>
 sp<T>::sp(T* other)
         : m_ptr(other) {
@@ -198,6 +245,29 @@
     }
 }
 
+template <typename T>
+template <typename U>
+sp<T>::sp(U* other) : m_ptr(other) {
+    if (other) {
+        check_not_on_stack(other);
+        (static_cast<T*>(other))->incStrong(this);
+    }
+}
+
+template <typename T>
+sp<T>& sp<T>::operator=(T* other) {
+    T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
+    if (other) {
+        check_not_on_stack(other);
+        other->incStrong(this);
+    }
+    if (oldPtr) oldPtr->decStrong(this);
+    if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
+    m_ptr = other;
+    return *this;
+}
+#endif
+
 template<typename T>
 sp<T>::sp(const sp<T>& other)
         : m_ptr(other.m_ptr) {
@@ -211,15 +281,6 @@
 }
 
 template<typename T> template<typename U>
-sp<T>::sp(U* other)
-        : m_ptr(other) {
-    if (other) {
-        check_not_on_stack(other);
-        (static_cast<T*>(other))->incStrong(this);
-    }
-}
-
-template<typename T> template<typename U>
 sp<T>::sp(const sp<U>& other)
         : m_ptr(other.m_ptr) {
     if (m_ptr)
@@ -232,6 +293,12 @@
     other.m_ptr = nullptr;
 }
 
+template <typename T>
+template <typename U>
+sp<T> sp<T>::cast(const sp<U>& other) {
+    return sp<T>::fromExisting(static_cast<T*>(other.get()));
+}
+
 template<typename T>
 sp<T>::~sp() {
     if (m_ptr)
@@ -260,19 +327,6 @@
     return *this;
 }
 
-template<typename T>
-sp<T>& sp<T>::operator =(T* other) {
-    T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
-    if (other) {
-        check_not_on_stack(other);
-        other->incStrong(this);
-    }
-    if (oldPtr) oldPtr->decStrong(this);
-    if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
-    m_ptr = other;
-    return *this;
-}
-
 template<typename T> template<typename U>
 sp<T>& sp<T>::operator =(const sp<U>& other) {
     T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
@@ -294,6 +348,7 @@
     return *this;
 }
 
+#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
 template<typename T> template<typename U>
 sp<T>& sp<T>::operator =(U* other) {
     T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
@@ -303,6 +358,7 @@
     m_ptr = other;
     return *this;
 }
+#endif
 
 template<typename T>
 void sp<T>::force_set(T* other) {
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index 0087383..d60d5d6 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -27,7 +27,6 @@
 int strncmp16(const char16_t *s1, const char16_t *s2, size_t n);
 size_t strlen16(const char16_t *);
 size_t strnlen16(const char16_t *, size_t);
-char16_t *strcpy16(char16_t *, const char16_t *);
 char16_t *strstr16(const char16_t*, const char16_t*);
 
 // Version of comparison that supports embedded NULs.
@@ -39,10 +38,6 @@
 // equivalent result as strcmp16 (unlike strncmp16).
 int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2);
 
-// Standard string functions on char32_t strings.
-size_t strlen32(const char32_t *);
-size_t strnlen32(const char32_t *, size_t);
-
 /**
  * Measure the length of a UTF-32 string in UTF-8. If the string is invalid
  * such as containing a surrogate character, -1 will be returned.
diff --git a/libvndksupport/Android.bp b/libvndksupport/Android.bp
index 11c75f7..f800bf7 100644
--- a/libvndksupport/Android.bp
+++ b/libvndksupport/Android.bp
@@ -5,7 +5,9 @@
 cc_library {
     name: "libvndksupport",
     native_bridge_supported: true,
-    llndk_stubs: "libvndksupport.llndk",
+    llndk: {
+        symbol_file: "libvndksupport.map.txt",
+    },
     srcs: ["linker.cpp"],
     cflags: [
         "-Wall",
@@ -23,10 +25,3 @@
         versions: ["29"],
     },
 }
-
-llndk_library {
-    name: "libvndksupport.llndk",
-    native_bridge_supported: true,
-    symbol_file: "libvndksupport.map.txt",
-    export_include_dirs: ["include"],
-}
diff --git a/cpio/Android.bp b/mkbootfs/Android.bp
similarity index 75%
rename from cpio/Android.bp
rename to mkbootfs/Android.bp
index 16af079..cd2a624 100644
--- a/cpio/Android.bp
+++ b/mkbootfs/Android.bp
@@ -8,7 +8,11 @@
     name: "mkbootfs",
     srcs: ["mkbootfs.c"],
     cflags: ["-Werror"],
-    shared_libs: ["libcutils"],
+    static_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+    ],
     dist: {
         targets: ["dist_files"],
     },
diff --git a/cpio/mkbootfs.c b/mkbootfs/mkbootfs.c
similarity index 100%
rename from cpio/mkbootfs.c
rename to mkbootfs/mkbootfs.c
diff --git a/qemu_pipe/Android.bp b/qemu_pipe/Android.bp
deleted file mode 100644
index 42a69db..0000000
--- a/qemu_pipe/Android.bp
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2011 The Android Open Source Project
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_library_static {
-    name: "libqemu_pipe",
-    vendor_available: true,
-    recovery_available: true,
-    apex_available: [
-        "com.android.adbd",
-        // TODO(b/151398197) remove the below
-        "//apex_available:platform",
-    ],
-    sanitize: {
-        misc_undefined: ["integer"],
-    },
-    srcs: ["qemu_pipe.cpp"],
-    local_include_dirs: ["include"],
-    static_libs: ["libbase"],
-    export_include_dirs: ["include"],
-    cflags: ["-Werror"],
-}
diff --git a/qemu_pipe/OWNERS b/qemu_pipe/OWNERS
deleted file mode 100644
index d67a329..0000000
--- a/qemu_pipe/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-bohu@google.com
-lfy@google.com
-rkir@google.com
diff --git a/qemu_pipe/include/qemu_pipe.h b/qemu_pipe/include/qemu_pipe.h
deleted file mode 100644
index 0987498..0000000
--- a/qemu_pipe/include/qemu_pipe.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef ANDROID_CORE_INCLUDE_QEMU_PIPE_H
-#define ANDROID_CORE_INCLUDE_QEMU_PIPE_H
-
-#include <stddef.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-// Try to open a new Qemu fast-pipe. This function returns a file descriptor
-// that can be used to communicate with a named service managed by the
-// emulator.
-//
-// This file descriptor can be used as a standard pipe/socket descriptor.
-//
-// 'pipeName' is the name of the emulator service you want to connect to,
-// and should begin with 'pipe:' (e.g. 'pipe:camera' or 'pipe:opengles').
-// For backward compatibility, the 'pipe:' prefix can be omitted, and in
-// that case, qemu_pipe_open will add it for you.
-
-// On success, return a valid file descriptor, or -1/errno on failure. E.g.:
-//
-// EINVAL  -> unknown/unsupported pipeName
-// ENOSYS  -> fast pipes not available in this system.
-//
-// ENOSYS should never happen, except if you're trying to run within a
-// misconfigured emulator.
-//
-// You should be able to open several pipes to the same pipe service,
-// except for a few special cases (e.g. GSM modem), where EBUSY will be
-// returned if more than one client tries to connect to it.
-int qemu_pipe_open(const char* pipeName);
-
-// Send a framed message |buff| of |len| bytes through the |fd| descriptor.
-// This really adds a 4-hexchar prefix describing the payload size.
-// Returns 0 on success, and -1 on error.
-int qemu_pipe_frame_send(int fd, const void* buff, size_t len);
-
-// Read a frame message from |fd|, and store it into |buff| of |len| bytes.
-// If the framed message is larger than |len|, then this returns -1 and the
-// content is lost. Otherwise, this returns the size of the message. NOTE:
-// empty messages are possible in a framed wire protocol and do not mean
-// end-of-stream.
-int qemu_pipe_frame_recv(int fd, void* buff, size_t len);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* ANDROID_CORE_INCLUDE_QEMU_PIPE_H */
diff --git a/qemu_pipe/qemu_pipe.cpp b/qemu_pipe/qemu_pipe.cpp
deleted file mode 100644
index 03afb21..0000000
--- a/qemu_pipe/qemu_pipe.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "qemu_pipe.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-#include <stdio.h>
-
-#include <android-base/file.h>
-
-using android::base::ReadFully;
-using android::base::WriteFully;
-
-// Define QEMU_PIPE_DEBUG if you want to print error messages when an error
-// occurs during pipe operations. The macro should simply take a printf-style
-// formatting string followed by optional arguments.
-#ifndef QEMU_PIPE_DEBUG
-#  define  QEMU_PIPE_DEBUG(...)   (void)0
-#endif
-
-int qemu_pipe_open(const char* pipeName) {
-    if (!pipeName) {
-        errno = EINVAL;
-        return -1;
-    }
-
-    int fd = TEMP_FAILURE_RETRY(open("/dev/qemu_pipe", O_RDWR));
-    if (fd < 0) {
-        QEMU_PIPE_DEBUG("%s: Could not open /dev/qemu_pipe: %s", __FUNCTION__,
-                        strerror(errno));
-        return -1;
-    }
-
-    // Write the pipe name, *including* the trailing zero which is necessary.
-    size_t pipeNameLen = strlen(pipeName);
-    if (WriteFully(fd, pipeName, pipeNameLen + 1U)) {
-        return fd;
-    }
-
-    // now, add 'pipe:' prefix and try again
-    // Note: host side will wait for the trailing '\0' to start
-    // service lookup.
-    const char pipe_prefix[] = "pipe:";
-    if (WriteFully(fd, pipe_prefix, strlen(pipe_prefix)) &&
-            WriteFully(fd, pipeName, pipeNameLen + 1U)) {
-        return fd;
-    }
-    QEMU_PIPE_DEBUG("%s: Could not write to %s pipe service: %s",
-            __FUNCTION__, pipeName, strerror(errno));
-    close(fd);
-    return -1;
-}
-
-int qemu_pipe_frame_send(int fd, const void* buff, size_t len) {
-    char header[5];
-    snprintf(header, sizeof(header), "%04zx", len);
-    if (!WriteFully(fd, header, 4)) {
-        QEMU_PIPE_DEBUG("Can't write qemud frame header: %s", strerror(errno));
-        return -1;
-    }
-    if (!WriteFully(fd, buff, len)) {
-        QEMU_PIPE_DEBUG("Can't write qemud frame payload: %s", strerror(errno));
-        return -1;
-    }
-    return 0;
-}
-
-int qemu_pipe_frame_recv(int fd, void* buff, size_t len) {
-    char header[5];
-    if (!ReadFully(fd, header, 4)) {
-        QEMU_PIPE_DEBUG("Can't read qemud frame header: %s", strerror(errno));
-        return -1;
-    }
-    header[4] = '\0';
-    size_t size;
-    if (sscanf(header, "%04zx", &size) != 1) {
-        QEMU_PIPE_DEBUG("Malformed qemud frame header: [%.*s]", 4, header);
-        return -1;
-    }
-    if (size > len) {
-        QEMU_PIPE_DEBUG("Oversized qemud frame (% bytes, expected <= %)", size,
-                        len);
-        return -1;
-    }
-    if (!ReadFully(fd, buff, size)) {
-        QEMU_PIPE_DEBUG("Could not read qemud frame payload: %s",
-                        strerror(errno));
-        return -1;
-    }
-    return size;
-}
diff --git a/rootdir/Android.bp b/rootdir/Android.bp
index 6a80808..ae21633 100644
--- a/rootdir/Android.bp
+++ b/rootdir/Android.bp
@@ -20,7 +20,10 @@
     name: "init.rc",
     src: "init.rc",
     sub_dir: "init/hw",
-    required: ["fsverity_init"],
+    required: [
+        "fsverity_init",
+        "platform-bootclasspath",
+    ],
 }
 
 prebuilt_etc {
@@ -35,3 +38,11 @@
     src: "etc/linker.config.json",
     installable: false,
 }
+
+// TODO(b/185211376) Scope the native APIs that microdroid will provide to the app payload
+prebuilt_etc {
+    name: "public.libraries.android.txt",
+    src: "etc/public.libraries.android.txt",
+    filename: "public.libraries.txt",
+    installable: false,
+}
\ No newline at end of file
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 5503cc1..99d8f9a 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -56,7 +56,6 @@
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
 LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
-LOCAL_REQUIRED_MODULES := etc_classpath
 
 EXPORT_GLOBAL_ASAN_OPTIONS :=
 ifneq ($(filter address,$(SANITIZE_TARGET)),)
@@ -186,21 +185,6 @@
 endef
 
 #######################################
-# /etc/classpath
-include $(CLEAR_VARS)
-LOCAL_MODULE := etc_classpath
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := classpath
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE):
-	@echo "Generate: $@"
-	@mkdir -p $(dir $@)
-	$(hide) echo "export BOOTCLASSPATH $(PRODUCT_BOOTCLASSPATH)" > $@
-	$(hide) echo "export DEX2OATBOOTCLASSPATH $(PRODUCT_DEX2OAT_BOOTCLASSPATH)" >> $@
-	$(hide) echo "export SYSTEMSERVERCLASSPATH $(PRODUCT_SYSTEM_SERVER_CLASSPATH)" >> $@
-
-#######################################
 # sanitizer.libraries.txt
 include $(CLEAR_VARS)
 LOCAL_MODULE := sanitizer.libraries.txt
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
index 83cb6ff..6b03a1d 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -1,14 +1,14 @@
 {
   "requireLibs": [
-    // Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
-    "libdexfile_external.so",
-    "libdexfiled_external.so",
+    "libandroidicu.so",
+    "libdexfile.so",
+    "libdexfiled.so",
+    "libicu.so",
+    "libjdwp.so",
     "libnativebridge.so",
     "libnativehelper.so",
     "libnativeloader.so",
     "libsigchain.so",
-    "libandroidicu.so",
-    "libicu.so",
     // TODO(b/122876336): Remove libpac.so once it's migrated to Webview
     "libpac.so",
     // TODO(b/120786417 or b/134659294): libicuuc.so
@@ -19,6 +19,7 @@
     "libnetd_resolv.so",
     // nn
     "libneuralnetworks.so",
+    "libneuralnetworks_shim.so",
     // statsd
     "libstatspull.so",
     "libstatssocket.so",
diff --git a/rootdir/init-debug.rc b/rootdir/init-debug.rc
index 435d4cb..77a80cd 100644
--- a/rootdir/init-debug.rc
+++ b/rootdir/init-debug.rc
@@ -6,3 +6,11 @@
 
 on property:persist.mmc.cache_size=*
     write /sys/block/mmcblk0/cache_size ${persist.mmc.cache_size}
+
+on early-init && property:ro.product.debugfs_restrictions.enabled=true
+    mount debugfs debugfs /sys/kernel/debug
+    chmod 0755 /sys/kernel/debug
+
+on property:sys.boot_completed=1 && property:ro.product.debugfs_restrictions.enabled=true && \
+   property:persist.dbg.keep_debugfs_mounted=""
+   umount /sys/kernel/debug
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 6b8868b..1013d41 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -471,9 +471,6 @@
     chmod 0664 /sys/module/lowmemorykiller/parameters/minfree
     start lmkd
 
-    # Set an initial boot level - start at 10 in case we need to add earlier ones.
-    setprop keystore.boot_level 10
-
     # Start essential services.
     start servicemanager
     start hwservicemanager
@@ -630,8 +627,6 @@
     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
 
@@ -652,6 +647,9 @@
     mkdir /data/bootchart 0755 shell shell encryption=Require
     bootchart start
 
+    # Avoid predictable entropy pool. Carry over entropy from previous boot.
+    copy /data/system/entropy.dat /dev/urandom
+
     mkdir /data/vendor 0771 root root encryption=Require
     mkdir /data/vendor_ce 0771 root root encryption=None
     mkdir /data/vendor_de 0771 root root encryption=None
@@ -667,22 +665,42 @@
     # Make sure that apexd is started in the default namespace
     enter_default_mount_ns
 
+    # set up keystore directory structure first so that we can end early boot
+    # and start apexd
+    mkdir /data/misc 01771 system misc encryption=Require
+    mkdir /data/misc/keystore 0700 keystore keystore
+    # work around b/183668221
+    restorecon /data/misc /data/misc/keystore
+
+    # Boot level 30
+    # odsign signing keys have MAX_BOOT_LEVEL=30
+    # This is currently the earliest boot level, but we start at 30
+    # to leave room for earlier levels.
+    setprop keystore.boot_level 30
+
+    # Now that /data is mounted and we have created /data/misc/keystore,
+    # we can tell keystore to stop allowing use of early-boot keys,
+    # and access its database for the first time to support creation and
+    # use of MAX_BOOT_LEVEL keys.
+    exec - system system -- /system/bin/vdc keymaster earlyBootEnded
+
     # /data/apex is now available. Start apexd to scan and activate APEXes.
+    #
+    # To handle userspace reboots as well as devices that use FDE, make sure
+    # that apexd is started cleanly here (set apexd.status="") and that it is
+    # restarted if it's already running.
     mkdir /data/apex 0755 root system encryption=None
     mkdir /data/apex/active 0755 root system
     mkdir /data/apex/backup 0700 root system
-    mkdir /data/apex/decompressed 0700 root system encryption=Require
+    mkdir /data/apex/decompressed 0755 root system encryption=Require
     mkdir /data/apex/hashtree 0700 root system
     mkdir /data/apex/sessions 0700 root system
     mkdir /data/app-staging 0751 system system encryption=DeleteIfNecessary
     mkdir /data/apex/ota_reserved 0700 root system encryption=Require
-    start apexd
+    setprop apexd.status ""
+    restart apexd
 
-    # Avoid predictable entropy pool. Carry over entropy from previous boot.
-    copy /data/system/entropy.dat /dev/urandom
-
-    # create basic filesystem structure
-    mkdir /data/misc 01771 system misc encryption=Require
+    # create rest of basic filesystem structure
     mkdir /data/misc/recovery 0770 system log
     copy /data/misc/recovery/ro.build.fingerprint /data/misc/recovery/ro.build.fingerprint.1
     chmod 0440 /data/misc/recovery/ro.build.fingerprint.1
@@ -706,7 +724,6 @@
     mkdir /data/misc/nfc 0770 nfc nfc
     mkdir /data/misc/nfc/logs 0770 nfc nfc
     mkdir /data/misc/credstore 0700 credstore credstore
-    mkdir /data/misc/keystore 0700 keystore keystore
     mkdir /data/misc/gatekeeper 0700 system system
     mkdir /data/misc/keychain 0771 system system
     mkdir /data/misc/net 0750 root shell
@@ -755,6 +772,8 @@
     mkdir /data/misc/snapshotctl_log 0755 root root
     # create location to store pre-reboot information
     mkdir /data/misc/prereboot 0700 system system
+    # directory used for on-device refresh metrics file.
+    mkdir /data/misc/odrefresh 0777 system system
     # directory used for on-device signing key blob
     mkdir /data/misc/odsign 0700 root root
 
@@ -821,6 +840,9 @@
     mkdir /data/ss 0700 system system encryption=Require
 
     mkdir /data/system 0775 system system encryption=Require
+    mkdir /data/system/environ 0700 system system
+    # b/183861600 attempt to fix selinux label before running derive_classpath service
+    restorecon /data/system/environ
     mkdir /data/system/dropbox 0700 system system
     mkdir /data/system/heapdump 0700 system system
     mkdir /data/system/users 0775 system system
@@ -884,13 +906,6 @@
     wait_for_prop apexd.status activated
     perform_apex_config
 
-    # Define and export *CLASSPATH variables
-    mkdir /data/system/environ 0700 system system
-    # Must start before 'odsign', as odsign depends on *CLASSPATH variables
-    exec_start derive_classpath
-    load_exports /data/system/environ/classpath
-    rm /data/system/environ/classpath
-
     # Special-case /data/media/obb per b/64566063
     mkdir /data/media 0770 media_rw media_rw encryption=None
     exec - media_rw media_rw -- /system/bin/chattr +F /data/media
@@ -903,19 +918,27 @@
     # Set SELinux security contexts on upgrade or policy update.
     restorecon --recursive --skip-ce /data
 
+    # Define and export *CLASSPATH variables
+    # Must start before 'odsign', as odsign depends on *CLASSPATH variables
+    exec_start derive_classpath
+    load_exports /data/system/environ/classpath
+
     # Start the on-device signing daemon, and wait for it to finish, to ensure
     # ART artifacts are generated if needed.
     # Must start after 'derive_classpath' to have *CLASSPATH variables set.
-    exec_start odsign
+    start odsign
 
-    # After apexes are mounted, tell keymaster early boot has ended, so it will
-    # stop allowing use of early-boot keys
-    exec - system system -- /system/bin/vdc keymaster earlyBootEnded
+    # Before we can lock keys and proceed to the next boot stage, wait for
+    # odsign to be done with the key
+    wait_for_prop odsign.key.done 1
 
     # Lock the fs-verity keyring, so no more keys can be added
     exec -- /system/bin/fsverity_init --lock
 
-    setprop keystore.boot_level 40
+    # Bump the boot level to 1000000000; this prevents further on-device signing.
+    # This is a special value that shuts down the thread which listens for
+    # further updates.
+    setprop keystore.boot_level 1000000000
 
     # 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
@@ -948,6 +971,7 @@
 # It is recommended to put unnecessary data/ initialization from post-fs-data
 # to start-zygote in device's init.rc to unblock zygote start.
 on zygote-start && property:ro.crypto.state=unencrypted
+    wait_for_prop odsign.verification.done 1
     # A/B update verifier that marks a successful boot.
     exec_start update_verifier_nonencrypted
     start statsd
@@ -956,6 +980,7 @@
     start zygote_secondary
 
 on zygote-start && property:ro.crypto.state=unsupported
+    wait_for_prop odsign.verification.done 1
     # A/B update verifier that marks a successful boot.
     exec_start update_verifier_nonencrypted
     start statsd
@@ -964,6 +989,7 @@
     start zygote_secondary
 
 on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
+    wait_for_prop odsign.verification.done 1
     # A/B update verifier that marks a successful boot.
     exec_start update_verifier_nonencrypted
     start statsd
@@ -976,9 +1002,6 @@
     write /proc/sys/vm/dirty_expire_centisecs 200
     write /proc/sys/vm/dirty_background_ratio  5
 
-on property:sys.boot_completed=1 && property:init.mount_debugfs=1
-   umount /sys/kernel/debug
-
 on boot
     # basic network init
     ifup lo
@@ -1138,6 +1161,11 @@
 on property:sys.sysctl.extra_free_kbytes=*
     write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}
 
+# Allow users to drop caches
+on property:perf.drop_caches=3
+    write /proc/sys/vm/drop_caches 3
+    setprop perf.drop_caches 0
+
 # "tcp_default_init_rwnd" Is too long!
 on property:net.tcp_def_init_rwnd=*
     write /proc/sys/net/ipv4/tcp_default_init_rwnd ${net.tcp_def_init_rwnd}
@@ -1217,7 +1245,6 @@
   setprop dev.bootcomplete ""
   setprop sys.init.updatable_crashing ""
   setprop sys.init.updatable_crashing_process_name ""
-  setprop apexd.status ""
   setprop sys.user.0.ce_available ""
   setprop sys.shutdown.requested ""
   setprop service.bootanim.exit ""
@@ -1249,10 +1276,6 @@
 on property:sys.boot_completed=1 && property:sys.init.userspace_reboot.in_progress=1
   setprop sys.init.userspace_reboot.in_progress ""
 
-on early-init && property:init.mount_debugfs=1
-    mount debugfs debugfs /sys/kernel/debug
-    chmod 0755 /sys/kernel/debug
-
 # Migrate tasks again in case kernel threads are created during boot
 on property:sys.boot_completed=1
   copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
diff --git a/rootdir/init.usb.rc b/rootdir/init.usb.rc
index 27b05ec..0730cce 100644
--- a/rootdir/init.usb.rc
+++ b/rootdir/init.usb.rc
@@ -19,6 +19,9 @@
     updatable
     seclabel u:r:adbd:s0
 
+on property:vendor.sys.usb.adb.disabled=*
+    setprop sys.usb.adb.disabled ${vendor.sys.usb.adb.disabled}
+
 # Set default value on sys.usb.configfs early in boot sequence. It will be
 # overridden in `on boot` action of init.hardware.rc.
 on init
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 65e29c1..56e774b 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -67,6 +67,10 @@
 # CDMA radio interface MUX
 /dev/ppp                  0660   radio      vpn
 
+# Virtualisation is managed by Virt Manager
+/dev/kvm                  0600   virtmanager root
+/dev/vhost-vsock          0600   virtmanager root
+
 # sysfs properties
 /sys/devices/platform/trusty.*      trusty_version        0440  root   log
 /sys/devices/virtual/input/input*   enable      0660  root   input
diff --git a/toolbox/OWNERS b/toolbox/OWNERS
index 7529cb9..5e2c581 100644
--- a/toolbox/OWNERS
+++ b/toolbox/OWNERS
@@ -1 +1,2 @@
 include platform/system/core:/janitors/OWNERS
+per-file modprobe.c=willmcvicker@google.com,dvander@google.com
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
index 7df7b71..711586a 100644
--- a/toolbox/modprobe.cpp
+++ b/toolbox/modprobe.cpp
@@ -215,10 +215,7 @@
         return EXIT_FAILURE;
     }
 
-    Modprobe m(mod_dirs);
-    if (blocklist) {
-        m.EnableBlocklist(true);
-    }
+    Modprobe m(mod_dirs, "modules.load", blocklist);
 
     for (const auto& module : modules) {
         switch (mode) {
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index 8ab6303..4aca375 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -96,13 +96,13 @@
 
     unique_fd file_fd(TEMP_FAILURE_RETRY(open(file_name, O_RDONLY)));
     if (!file_fd.ok()) {
-        fprintf(stderr, "Error opening file '%s': %s\n", file_name, strerror(errno));
+        PLOG(ERROR) << "Error opening file " << file_name;
         return {};
     }
 
     rc = fstat64(file_fd, &st);
     if (rc < 0) {
-        fprintf(stderr, "Error calling stat on file '%s': %s\n", file_name, strerror(errno));
+        PLOG(ERROR) << "Error calling stat on file '" << file_name << "'";
         return {};
     }
 
@@ -115,14 +115,14 @@
         file_page_offset = page_size - file_page_offset;
     }
     if (__builtin_add_overflow(file_size, file_page_offset, &file_page_size)) {
-        fprintf(stderr, "Failed to page-align file size\n");
+        LOG(ERROR) << "Failed to page-align file size";
         return {};
     }
 
     BufferAllocator alloc;
     unique_fd dmabuf_fd(alloc.Alloc(kDmabufSystemHeapName, file_page_size));
     if (!dmabuf_fd.ok()) {
-        fprintf(stderr, "Error creating dmabuf: %d\n", dmabuf_fd.get());
+        LOG(ERROR) << "Error creating dmabuf: " << dmabuf_fd.get();
         return dmabuf_fd;
     }
 
@@ -137,12 +137,12 @@
                 pread(file_fd, (char*)shm + file_offset, file_size - file_offset, file_offset));
 
         if (num_read < 0) {
-            fprintf(stderr, "Error reading package file '%s': %s\n", file_name, strerror(errno));
+            PLOG(ERROR) << "Error reading package file '" << file_name << "'";
             break;
         }
 
         if (num_read == 0) {
-            fprintf(stderr, "Unexpected end of file '%s'\n", file_name);
+            LOG(ERROR) << "Unexpected end of file '" << file_name << "'";
             break;
         }
 
@@ -182,17 +182,17 @@
     struct apploader_resp resp;
     ssize_t rc = read(tipc_fd, &resp, sizeof(resp));
     if (rc < 0) {
-        fprintf(stderr, "Failed to read response: %zd\n", rc);
+        PLOG(ERROR) << "Failed to read response";
         return rc;
     }
 
     if (rc < sizeof(resp)) {
-        fprintf(stderr, "Not enough data in response: %zd\n", rc);
+        LOG(ERROR) << "Not enough data in response: " << rc;
         return -EIO;
     }
 
     if (resp.hdr.cmd != (APPLOADER_CMD_LOAD_APPLICATION | APPLOADER_RESP_BIT)) {
-        fprintf(stderr, "Invalid command in response: %u\n", resp.hdr.cmd);
+        LOG(ERROR) << "Invalid command in response: " << resp.hdr.cmd;
         return -EINVAL;
     }
 
@@ -200,28 +200,28 @@
         case APPLOADER_NO_ERROR:
             break;
         case APPLOADER_ERR_UNKNOWN_CMD:
-            fprintf(stderr, "Error: unknown command\n");
+            LOG(ERROR) << "Error: unknown command";
             break;
         case APPLOADER_ERR_INVALID_CMD:
-            fprintf(stderr, "Error: invalid command arguments\n");
+            LOG(ERROR) << "Error: invalid command arguments";
             break;
         case APPLOADER_ERR_NO_MEMORY:
-            fprintf(stderr, "Error: out of Trusty memory\n");
+            LOG(ERROR) << "Error: out of Trusty memory";
             break;
         case APPLOADER_ERR_VERIFICATION_FAILED:
-            fprintf(stderr, "Error: failed to verify the package\n");
+            LOG(ERROR) << "Error: failed to verify the package";
             break;
         case APPLOADER_ERR_LOADING_FAILED:
-            fprintf(stderr, "Error: failed to load the package\n");
+            LOG(ERROR) << "Error: failed to load the package";
             break;
         case APPLOADER_ERR_ALREADY_EXISTS:
-            fprintf(stderr, "Error: application already exists\n");
+            LOG(ERROR) << "Error: application already exists";
             break;
         case APPLOADER_ERR_INTERNAL:
-            fprintf(stderr, "Error: internal apploader error\n");
+            LOG(ERROR) << "Error: internal apploader error";
             break;
         default:
-            fprintf(stderr, "Unrecognized error: %u\n", resp.error);
+            LOG(ERROR) << "Unrecognized error: " << resp.error;
             break;
     }
 
@@ -241,14 +241,14 @@
 
     tipc_fd = tipc_connect(dev_name, APPLOADER_PORT);
     if (tipc_fd < 0) {
-        fprintf(stderr, "Failed to connect to Trusty app loader: %s\n", strerror(-tipc_fd));
+        LOG(ERROR) << "Failed to connect to Trusty app loader: " << strerror(-tipc_fd);
         rc = tipc_fd;
         goto err_tipc_connect;
     }
 
     rc = send_load_message(tipc_fd, package_fd, package_size);
     if (rc < 0) {
-        fprintf(stderr, "Failed to send package: %zd\n", rc);
+        LOG(ERROR) << "Failed to send package: " << rc;
         goto err_send;
     }
 
diff --git a/trusty/fuzz/include/trusty/fuzz/utils.h b/trusty/fuzz/include/trusty/fuzz/utils.h
index bca84e9..c906412 100644
--- a/trusty/fuzz/include/trusty/fuzz/utils.h
+++ b/trusty/fuzz/include/trusty/fuzz/utils.h
@@ -34,6 +34,7 @@
     android::base::Result<void> Connect();
     android::base::Result<void> Read(void* buf, size_t len);
     android::base::Result<void> Write(const void* buf, size_t len);
+    void Disconnect();
 
     android::base::Result<int> GetRawFd();
 
diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp
index 3258944..f265ced 100644
--- a/trusty/fuzz/tipc_fuzzer.cpp
+++ b/trusty/fuzz/tipc_fuzzer.cpp
@@ -41,6 +41,7 @@
 #error "Binary file name must be parameterized using -DTRUSTY_APP_FILENAME."
 #endif
 
+static TrustyApp kTrustyApp(TIPC_DEV, TRUSTY_APP_PORT);
 static std::unique_ptr<CoverageRecord> record;
 
 extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
@@ -52,8 +53,7 @@
     }
 
     /* Make sure lazy-loaded TAs have started and connected to coverage service. */
-    TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
-    auto ret = ta.Connect();
+    auto ret = kTrustyApp.Connect();
     if (!ret.ok()) {
         std::cerr << ret.error() << std::endl;
         exit(-1);
@@ -79,22 +79,18 @@
     ExtraCounters counters(record.get());
     counters.Reset();
 
-    TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
-    auto ret = ta.Connect();
+    auto ret = kTrustyApp.Write(data, size);
+    if (ret.ok()) {
+        ret = kTrustyApp.Read(&buf, sizeof(buf));
+    }
+
+    // Reconnect to ensure that the service is still up
+    kTrustyApp.Disconnect();
+    ret = kTrustyApp.Connect();
     if (!ret.ok()) {
         std::cerr << ret.error() << std::endl;
         android::trusty::fuzz::Abort();
     }
 
-    ret = ta.Write(data, size);
-    if (!ret.ok()) {
-        return -1;
-    }
-
-    ret = ta.Read(&buf, sizeof(buf));
-    if (!ret.ok()) {
-        return -1;
-    }
-
-    return 0;
+    return ret.ok() ? 0 : -1;
 }
diff --git a/trusty/fuzz/utils.cpp b/trusty/fuzz/utils.cpp
index 3526337..bb096be 100644
--- a/trusty/fuzz/utils.cpp
+++ b/trusty/fuzz/utils.cpp
@@ -127,6 +127,10 @@
     return ta_fd_;
 }
 
+void TrustyApp::Disconnect() {
+    ta_fd_.reset();
+}
+
 void Abort() {
     PrintTrustyLog();
     exit(-1);
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index 94aedd7..29c6f93 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -914,7 +914,7 @@
     }
 
     size_t buf_size = PAGE_SIZE * num_pages;
-    dma_buf = DmabufHeapAlloc(allocator, "system", buf_size, 0);
+    dma_buf = DmabufHeapAlloc(allocator, "system", buf_size, 0, 0 /* legacy align */);
     if (dma_buf < 0) {
         ret = dma_buf;
         fprintf(stderr, "Failed to create dma-buf fd of size %zu err (%d)\n", buf_size, ret);
diff --git a/trusty/metrics/Android.bp b/trusty/metrics/Android.bp
new file mode 100644
index 0000000..e0533bc
--- /dev/null
+++ b/trusty/metrics/Android.bp
@@ -0,0 +1,51 @@
+// 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_library {
+    name: "libtrusty_metrics",
+    vendor: true,
+    srcs: [
+        "metrics.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libtrusty",
+    ],
+}
+
+cc_test {
+    name: "libtrusty_metrics_test",
+    vendor: true,
+    srcs: [
+        "metrics_test.cpp",
+    ],
+    static_libs: [
+        "libtrusty_metrics",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "liblog",
+        "libtrusty",
+    ],
+    require_root: true,
+}
diff --git a/trusty/metrics/include/trusty/metrics/metrics.h b/trusty/metrics/include/trusty/metrics/metrics.h
new file mode 100644
index 0000000..6949e9b
--- /dev/null
+++ b/trusty/metrics/include/trusty/metrics/metrics.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::Result;
+using android::base::unique_fd;
+
+class TrustyMetrics {
+  public:
+    /* Wait for next event with a given timeout. Negative timeout means infinite timeout. */
+    Result<void> WaitForEvent(int timeout_ms = -1);
+    /* Attempt to handle an event from Metrics TA in a non-blocking manner. */
+    Result<void> HandleEvent();
+    /* Expose TIPC channel so that client can integrate it into an event loop with other fds. */
+    int GetRawFd() { return metrics_fd_; };
+
+  protected:
+    TrustyMetrics(std::string tipc_dev) : tipc_dev_(std::move(tipc_dev)), metrics_fd_(-1) {}
+    virtual ~TrustyMetrics(){};
+
+    Result<void> Open();
+    virtual void HandleCrash(const std::string& app_id) = 0;
+    virtual void HandleEventDrop() = 0;
+
+  private:
+    std::string tipc_dev_;
+    unique_fd metrics_fd_;
+};
+
+}  // namespace metrics
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/metrics/include/trusty/metrics/tipc.h b/trusty/metrics/include/trusty/metrics/tipc.h
new file mode 100644
index 0000000..66d0876
--- /dev/null
+++ b/trusty/metrics/include/trusty/metrics/tipc.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/**
+ * DOC: Metrics
+ *
+ * Metrics interface provides a way for Android to get Trusty metrics data.
+ *
+ * Currently, only "push" model is supported. Clients are expected to connect to
+ * metrics service, listen for events, e.g. app crash events, and respond to
+ * every event with a &struct metrics_req.
+ *
+ * Communication is driven by metrics service, i.e. requests/responses are all
+ * sent from/to metrics service.
+ *
+ * Note that the type of the event is not known to the client ahead of time.
+ *
+ * In the future, if we need to have Android "pull" metrics data from Trusty,
+ * that can be done by introducing a separate port.
+ *
+ * This interface is shared between Android and Trusty. There is a copy in each
+ * repository. They must be kept in sync.
+ */
+
+#define METRICS_PORT "com.android.trusty.metrics"
+
+/**
+ * enum metrics_cmd - command identifiers for metrics interface
+ * @METRICS_CMD_RESP_BIT:          message is a response
+ * @METRICS_CMD_REQ_SHIFT:         number of bits used by @METRICS_CMD_RESP_BIT
+ * @METRICS_CMD_REPORT_EVENT_DROP: report gaps in the event stream
+ * @METRICS_CMD_REPORT_CRASH:      report an app crash event
+ */
+enum metrics_cmd {
+    METRICS_CMD_RESP_BIT = 1,
+    METRICS_CMD_REQ_SHIFT = 1,
+
+    METRICS_CMD_REPORT_EVENT_DROP = (1 << METRICS_CMD_REQ_SHIFT),
+    METRICS_CMD_REPORT_CRASH = (2 << METRICS_CMD_REQ_SHIFT),
+};
+
+/**
+ * enum metrics_error - metrics error codes
+ * @METRICS_NO_ERROR:        no error
+ * @METRICS_ERR_UNKNOWN_CMD: unknown or not implemented command
+ */
+enum metrics_error {
+    METRICS_NO_ERROR = 0,
+    METRICS_ERR_UNKNOWN_CMD = 1,
+};
+
+/**
+ * struct metrics_req - common structure for metrics requests
+ * @cmd:      command identifier - one of &enum metrics_cmd
+ * @reserved: must be 0
+ */
+struct metrics_req {
+    uint32_t cmd;
+    uint32_t reserved;
+} __attribute__((__packed__));
+
+/**
+ * struct metrics_resp - common structure for metrics responses
+ * @cmd: command identifier - %METRICS_CMD_RESP_BIT or'ed with a cmd in
+ *                            one of &enum metrics_cmd
+ * @status: response status, one of &enum metrics_error
+ */
+struct metrics_resp {
+    uint32_t cmd;
+    uint32_t status;
+} __attribute__((__packed__));
+
+/**
+ * struct metrics_report_crash_req - arguments of %METRICS_CMD_REPORT_CRASH
+ *                                   requests
+ * @app_id_len: length of app ID that follows this structure
+ */
+struct metrics_report_crash_req {
+    uint32_t app_id_len;
+} __attribute__((__packed__));
+
+#define METRICS_MAX_APP_ID_LEN 256
+
+#define METRICS_MAX_MSG_SIZE                                                \
+    (sizeof(struct metrics_req) + sizeof(struct metrics_report_crash_req) + \
+     METRICS_MAX_APP_ID_LEN)
diff --git a/trusty/metrics/metrics.cpp b/trusty/metrics/metrics.cpp
new file mode 100644
index 0000000..3ac128a
--- /dev/null
+++ b/trusty/metrics/metrics.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Sourete 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 "metrics"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <trusty/metrics/metrics.h>
+#include <trusty/metrics/tipc.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::ErrnoError;
+using android::base::Error;
+
+Result<void> TrustyMetrics::Open() {
+    int fd = tipc_connect(tipc_dev_.c_str(), METRICS_PORT);
+    if (fd < 0) {
+        return ErrnoError() << "failed to connect to Trusty metrics TA";
+    }
+
+    int flags = fcntl(fd, F_GETFL, 0);
+    if (flags < 0) {
+        return ErrnoError() << "failed F_GETFL";
+    }
+
+    int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+    if (rc < 0) {
+        return ErrnoError() << "failed F_SETFL";
+    }
+
+    metrics_fd_.reset(fd);
+    return {};
+}
+
+Result<void> TrustyMetrics::WaitForEvent(int timeout_ms) {
+    if (!metrics_fd_.ok()) {
+        return Error() << "connection to Metrics TA has not been initialized yet";
+    }
+
+    struct pollfd pfd = {
+            .fd = metrics_fd_,
+            .events = POLLIN,
+    };
+
+    int rc = poll(&pfd, 1, timeout_ms);
+    if (rc != 1) {
+        return ErrnoError() << "failed poll()";
+    }
+
+    if (!(pfd.revents & POLLIN)) {
+        return ErrnoError() << "channel not ready";
+    }
+
+    return {};
+}
+
+Result<void> TrustyMetrics::HandleEvent() {
+    if (!metrics_fd_.ok()) {
+        return Error() << "connection to Metrics TA has not been initialized yet";
+    }
+
+    uint8_t msg[METRICS_MAX_MSG_SIZE];
+
+    auto rc = read(metrics_fd_, msg, sizeof(msg));
+    if (rc < 0) {
+        return ErrnoError() << "failed to read metrics message";
+    }
+    size_t msg_len = rc;
+
+    if (msg_len < sizeof(metrics_req)) {
+        return Error() << "message too small: " << rc;
+    }
+    auto req = reinterpret_cast<metrics_req*>(msg);
+    size_t offset = sizeof(metrics_req);
+    uint32_t status = METRICS_NO_ERROR;
+
+    switch (req->cmd) {
+        case METRICS_CMD_REPORT_CRASH: {
+            if (msg_len < offset + sizeof(metrics_report_crash_req)) {
+                return Error() << "message too small: " << rc;
+            }
+            auto crash_args = reinterpret_cast<metrics_report_crash_req*>(msg + offset);
+            offset += sizeof(metrics_report_crash_req);
+
+            if (msg_len < offset + crash_args->app_id_len) {
+                return Error() << "message too small: " << rc;
+            }
+            auto app_id_ptr = reinterpret_cast<char*>(msg + offset);
+            std::string app_id(app_id_ptr, crash_args->app_id_len);
+
+            HandleCrash(app_id);
+            break;
+        }
+
+        case METRICS_CMD_REPORT_EVENT_DROP:
+            HandleEventDrop();
+            break;
+
+        default:
+            status = METRICS_ERR_UNKNOWN_CMD;
+            break;
+    }
+
+    metrics_resp resp = {
+            .cmd = req->cmd | METRICS_CMD_RESP_BIT,
+            .status = status,
+    };
+
+    rc = write(metrics_fd_, &resp, sizeof(resp));
+    if (rc < 0) {
+        return ErrnoError() << "failed to request next metrics event";
+    }
+
+    if (rc != (int)sizeof(resp)) {
+        return Error() << "unexpected number of bytes sent event: " << rc;
+    }
+
+    return {};
+}
+
+}  // namespace metrics
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/metrics/metrics_test.cpp b/trusty/metrics/metrics_test.cpp
new file mode 100644
index 0000000..407ddf2
--- /dev/null
+++ b/trusty/metrics/metrics_test.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/unique_fd.h>
+#include <binder/IPCThreadState.h>
+#include <gtest/gtest.h>
+#include <poll.h>
+#include <trusty/metrics/metrics.h>
+#include <trusty/tipc.h>
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define CRASHER_PORT "com.android.trusty.metrics.test.crasher"
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::unique_fd;
+
+static void TriggerCrash() {
+    size_t num_retries = 3;
+    int fd = -1;
+
+    for (size_t i = 0; i < num_retries; i++) {
+        /* It's possible to time out waiting for crasher TA to restart. */
+        fd = tipc_connect(TIPC_DEV, CRASHER_PORT);
+        if (fd >= 0) {
+            break;
+        }
+    }
+
+    unique_fd crasher(fd);
+    ASSERT_GE(crasher, 0);
+
+    int msg = 0;
+    int rc = write(crasher, &msg, sizeof(msg));
+    ASSERT_EQ(rc, sizeof(msg));
+}
+
+class TrustyMetricsTest : public TrustyMetrics, public ::testing::Test {
+  public:
+    TrustyMetricsTest() : TrustyMetrics(TIPC_DEV) {}
+
+    virtual void HandleCrash(const std::string& app_id) override { crashed_app_ = app_id; }
+
+    virtual void HandleEventDrop() override { event_drop_count_++; }
+
+    virtual void SetUp() override {
+        auto ret = Open();
+        ASSERT_TRUE(ret.ok()) << ret.error();
+    }
+
+    void WaitForAndHandleEvent() {
+        auto ret = WaitForEvent(30000 /* 30 second timeout */);
+        ASSERT_TRUE(ret.ok()) << ret.error();
+
+        ret = HandleEvent();
+        ASSERT_TRUE(ret.ok()) << ret.error();
+    }
+
+    std::string crashed_app_;
+    size_t event_drop_count_;
+};
+
+TEST_F(TrustyMetricsTest, Crash) {
+    TriggerCrash();
+    WaitForAndHandleEvent();
+
+    /* Check that correct TA crashed. */
+    ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
+}
+
+TEST_F(TrustyMetricsTest, PollSet) {
+    int binder_fd;
+    int rc = IPCThreadState::self()->setupPolling(&binder_fd);
+    ASSERT_EQ(rc, 0);
+    ASSERT_GE(binder_fd, 0);
+
+    TriggerCrash();
+
+    struct pollfd pfds[] = {
+            {
+                    .fd = binder_fd,
+                    .events = POLLIN,
+            },
+            {
+                    .fd = GetRawFd(),
+                    .events = POLLIN,
+            },
+    };
+
+    rc = poll(pfds, 2, 30000 /* 30 second timeout */);
+    /* We expect one event on the metrics fd. */
+    ASSERT_EQ(rc, 1);
+    ASSERT_TRUE(pfds[1].revents & POLLIN);
+
+    auto ret = HandleEvent();
+    ASSERT_TRUE(ret.ok()) << ret.error();
+
+    /* Check that correct TA crashed. */
+    ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
+}
+
+TEST_F(TrustyMetricsTest, EventDrop) {
+    /* We know the size of the internal event queue is less than this. */
+    size_t num_events = 3;
+
+    ASSERT_EQ(event_drop_count_, 0);
+
+    for (auto i = 0; i < num_events; i++) {
+        TriggerCrash();
+    }
+
+    for (auto i = 0; i < num_events; i++) {
+        WaitForAndHandleEvent();
+        if (event_drop_count_ > 0) {
+            break;
+        }
+    }
+
+    ASSERT_EQ(event_drop_count_, 1);
+}
+
+}  // namespace metrics
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/utils/acvp/Android.bp b/trusty/utils/acvp/Android.bp
index 6fe193e..b851e39 100644
--- a/trusty/utils/acvp/Android.bp
+++ b/trusty/utils/acvp/Android.bp
@@ -11,6 +11,10 @@
 // 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,