Merge "healthd: Delete health@2.0-service." into main
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 267571b..7d20995 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -32,9 +32,10 @@
     recovery_available: true,
     vendor_ramdisk_available: true,
     apex_available: [
+        "com.android.runtime",
         "com.android.virt",
         "//apex_available:platform",
-   ],
+    ],
 }
 
 cc_library_shared {
@@ -85,6 +86,7 @@
 
     export_header_lib_headers: ["libdebuggerd_common_headers"],
     export_include_dirs: ["tombstoned/include"],
+    apex_available: ["com.android.runtime"],
 }
 
 // Core implementation, linked into libdebuggerd_handler and the dynamic linker.
@@ -110,6 +112,9 @@
 
     export_header_lib_headers: ["libdebuggerd_common_headers"],
     export_include_dirs: ["include"],
+    apex_available: [
+        "com.android.runtime",
+    ],
 }
 
 // Implementation with a no-op fallback.
@@ -186,6 +191,41 @@
     export_include_dirs: ["include"],
 }
 
+cc_library {
+    name: "libdebuggerd_tombstone_proto_to_text",
+    defaults: ["debuggerd_defaults"],
+    ramdisk_available: true,
+    recovery_available: true,
+    vendor_ramdisk_available: true,
+
+    local_include_dirs: ["libdebuggerd/include"],
+    export_include_dirs: ["libdebuggerd/include"],
+
+    srcs: [
+        "libdebuggerd/tombstone_proto_to_text.cpp",
+    ],
+
+    header_libs: [
+        "bionic_libc_platform_headers",
+    ],
+
+    static_libs: [
+        "libbase",
+        "liblog_for_runtime_apex",
+        "libunwindstack",
+    ],
+
+    whole_static_libs: [
+        "libtombstone_proto",
+        "libprotobuf-cpp-lite",
+    ],
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.runtime",
+    ],
+}
+
 cc_library_static {
     name: "libdebuggerd",
     defaults: ["debuggerd_defaults"],
@@ -199,7 +239,6 @@
         "libdebuggerd/open_files_list.cpp",
         "libdebuggerd/tombstone.cpp",
         "libdebuggerd/tombstone_proto.cpp",
-        "libdebuggerd/tombstone_proto_to_text.cpp",
         "libdebuggerd/utility.cpp",
     ],
 
@@ -225,6 +264,7 @@
     ],
 
     whole_static_libs: [
+        "libdebuggerd_tombstone_proto_to_text",
         "libasync_safe",
         "gwp_asan_crash_handler",
         "libtombstone_proto",
@@ -276,6 +316,9 @@
             header_libs: ["scudo_headers"],
         },
     },
+    apex_available: [
+        "com.android.runtime",
+    ],
 }
 
 cc_binary {
@@ -309,7 +352,7 @@
         "libdebuggerd/test/elf_fake.cpp",
         "libdebuggerd/test/log_fake.cpp",
         "libdebuggerd/test/open_files_list_test.cpp",
-        "libdebuggerd/test/utility_test.cpp",
+        "libdebuggerd/test/tombstone_proto_to_text_test.cpp",
     ],
 
     target: {
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 3563436..0899111 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -317,6 +317,7 @@
       process_info->gwp_asan_state = crash_info->data.d.gwp_asan_state;
       process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata;
       process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot;
+      process_info->scudo_stack_depot_size = crash_info->data.d.scudo_stack_depot_size;
       process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
       process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer;
       process_info->scudo_ring_buffer_size = crash_info->data.d.scudo_ring_buffer_size;
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index c3e1302..c0522aa 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -94,7 +94,7 @@
     if (sigaction(SIGALRM, &new_sigaction, &old_sigaction) != 0) { \
       err(1, "sigaction failed");                                  \
     }                                                              \
-    alarm(seconds);                                                \
+    alarm(seconds * android::base::HwTimeoutMultiplier());         \
     auto value = expr;                                             \
     int saved_errno = errno;                                       \
     if (sigaction(SIGALRM, &old_sigaction, nullptr) != 0) {        \
@@ -2221,28 +2221,10 @@
   ASSERT_MATCH(result, match_str);
 }
 
-TEST(tombstoned, proto) {
-  const pid_t self = getpid();
-  unique_fd tombstoned_socket, text_fd, proto_fd;
-  ASSERT_TRUE(
-      tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto));
-
-  tombstoned_notify_completion(tombstoned_socket.get());
-
-  ASSERT_NE(-1, text_fd.get());
-  ASSERT_NE(-1, proto_fd.get());
-
-  struct stat text_st;
-  ASSERT_EQ(0, fstat(text_fd.get(), &text_st));
-
-  // Give tombstoned some time to link the files into place.
-  std::this_thread::sleep_for(100ms);
-
-  // Find the tombstone.
-  std::optional<std::string> tombstone_file;
+void CheckForTombstone(const struct stat& text_st, std::optional<std::string>& tombstone_file) {
+  static std::regex tombstone_re("tombstone_\\d+");
   std::unique_ptr<DIR, decltype(&closedir)> dir_h(opendir("/data/tombstones"), closedir);
   ASSERT_TRUE(dir_h != nullptr);
-  std::regex tombstone_re("tombstone_\\d+");
   dirent* entry;
   while ((entry = readdir(dir_h.get())) != nullptr) {
     if (!std::regex_match(entry->d_name, tombstone_re)) {
@@ -2260,8 +2242,38 @@
       break;
     }
   }
+}
 
-  ASSERT_TRUE(tombstone_file);
+TEST(tombstoned, proto) {
+  const pid_t self = getpid();
+  unique_fd tombstoned_socket, text_fd, proto_fd;
+  ASSERT_TRUE(
+      tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto));
+
+  tombstoned_notify_completion(tombstoned_socket.get());
+
+  ASSERT_NE(-1, text_fd.get());
+  ASSERT_NE(-1, proto_fd.get());
+
+  struct stat text_st;
+  ASSERT_EQ(0, fstat(text_fd.get(), &text_st));
+
+  std::optional<std::string> tombstone_file;
+  // Allow up to 5 seconds for the tombstone to be written to the system.
+  const auto max_wait_time = std::chrono::seconds(5) * android::base::HwTimeoutMultiplier();
+  const auto start = std::chrono::high_resolution_clock::now();
+  while (true) {
+    std::this_thread::sleep_for(100ms);
+    CheckForTombstone(text_st, tombstone_file);
+    if (tombstone_file) {
+      break;
+    }
+    if (std::chrono::high_resolution_clock::now() - start > max_wait_time) {
+      break;
+    }
+  }
+
+  ASSERT_TRUE(tombstone_file) << "Timed out trying to find tombstone file.";
   std::string proto_path = tombstone_file.value() + ".pb";
 
   struct stat proto_fd_st;
@@ -2825,7 +2837,8 @@
     }
 
     prev_file = match[1];
-    unwindstack::Elf elf(unwindstack::Memory::CreateFileMemory(prev_file, 0).release());
+    auto elf_memory = unwindstack::Memory::CreateFileMemory(prev_file, 0);
+    unwindstack::Elf elf(elf_memory);
     if (!elf.Init() || !elf.valid()) {
       // Skipping invalid elf files.
       continue;
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 01365f2..ea07ce2 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -395,6 +395,7 @@
     ASSERT_SAME_OFFSET(scudo_region_info, scudo_region_info);
     ASSERT_SAME_OFFSET(scudo_ring_buffer, scudo_ring_buffer);
     ASSERT_SAME_OFFSET(scudo_ring_buffer_size, scudo_ring_buffer_size);
+    ASSERT_SAME_OFFSET(scudo_stack_depot_size, scudo_stack_depot_size);
     ASSERT_SAME_OFFSET(recoverable_gwp_asan_crash, recoverable_gwp_asan_crash);
 #undef ASSERT_SAME_OFFSET
 
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index ebb5372..de12fc6 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -44,6 +44,7 @@
   const char* scudo_region_info;
   const char* scudo_ring_buffer;
   size_t scudo_ring_buffer_size;
+  size_t scudo_stack_depot_size;
   bool recoverable_gwp_asan_crash;
 };
 
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index 5a2a7ab..075b12c 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -51,6 +51,7 @@
   uintptr_t scudo_region_info = 0;
   uintptr_t scudo_ring_buffer = 0;
   size_t scudo_ring_buffer_size = 0;
+  size_t scudo_stack_depot_size = 0;
 
   bool has_fault_address = false;
   uintptr_t untagged_fault_address = 0;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 198de37..26c2cd4 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -91,8 +91,6 @@
 void get_signal_sender(char* buf, size_t n, const siginfo_t*);
 const char* get_signame(const siginfo_t*);
 const char* get_sigcode(const siginfo_t*);
-std::string describe_tagged_addr_ctrl(long ctrl);
-std::string describe_pac_enabled_keys(long keys);
 
 // Number of bytes per MTE granule.
 constexpr size_t kTagGranuleSize = 16;
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index 837f406..3fa3bd0 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -41,8 +41,6 @@
     return;
   }
 
-  auto stack_depot = AllocAndReadFully(process_memory, process_info.scudo_stack_depot,
-                                       __scudo_get_stack_depot_size());
   auto region_info = AllocAndReadFully(process_memory, process_info.scudo_region_info,
                                        __scudo_get_region_info_size());
   std::unique_ptr<char[]> ring_buffer;
@@ -50,7 +48,12 @@
     ring_buffer = AllocAndReadFully(process_memory, process_info.scudo_ring_buffer,
                                     process_info.scudo_ring_buffer_size);
   }
-  if (!stack_depot || !region_info) {
+  std::unique_ptr<char[]> stack_depot;
+  if (process_info.scudo_stack_depot_size != 0) {
+    stack_depot = AllocAndReadFully(process_memory, process_info.scudo_stack_depot,
+                                    process_info.scudo_stack_depot_size);
+  }
+  if (!region_info) {
     return;
   }
 
@@ -78,7 +81,8 @@
   }
 
   __scudo_get_error_info(&error_info_, process_info.maybe_tagged_fault_address, stack_depot.get(),
-                         region_info.get(), ring_buffer.get(), memory.get(), memory_tags.get(),
+                         process_info.scudo_stack_depot_size, region_info.get(), ring_buffer.get(),
+                         process_info.scudo_ring_buffer_size, memory.get(), memory_tags.get(),
                          memory_begin, memory_end - memory_begin);
 }
 
diff --git a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
new file mode 100644
index 0000000..ac92ac0
--- /dev/null
+++ b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <sys/prctl.h>
+
+#include <string>
+
+#include <android-base/test_utils.h>
+
+#include "libdebuggerd/tombstone.h"
+#include "tombstone.pb.h"
+
+using CallbackType = std::function<void(const std::string& line, bool should_log)>;
+
+class TombstoneProtoToTextTest : public ::testing::Test {
+ public:
+  void SetUp() {
+    tombstone_.reset(new Tombstone);
+
+    tombstone_->set_arch(Architecture::ARM64);
+    tombstone_->set_build_fingerprint("Test fingerprint");
+    tombstone_->set_timestamp("1970-01-01 00:00:00");
+    tombstone_->set_pid(100);
+    tombstone_->set_tid(100);
+    tombstone_->set_uid(0);
+    tombstone_->set_selinux_label("none");
+
+    Signal signal;
+    signal.set_number(SIGSEGV);
+    signal.set_name("SIGSEGV");
+    signal.set_code(0);
+    signal.set_code_name("none");
+
+    *tombstone_->mutable_signal_info() = signal;
+
+    Thread thread;
+    thread.set_id(100);
+    thread.set_name("main");
+    thread.set_tagged_addr_ctrl(0);
+    thread.set_pac_enabled_keys(0);
+
+    auto& threads = *tombstone_->mutable_threads();
+    threads[100] = thread;
+    main_thread_ = &threads[100];
+  }
+
+  void ProtoToString() {
+    text_ = "";
+    EXPECT_TRUE(
+        tombstone_proto_to_text(*tombstone_, [this](const std::string& line, bool should_log) {
+          if (should_log) {
+            text_ += "LOG ";
+          }
+          text_ += line + '\n';
+        }));
+  }
+
+  Thread* main_thread_;
+  std::string text_;
+  std::unique_ptr<Tombstone> tombstone_;
+};
+
+TEST_F(TombstoneProtoToTextTest, tagged_addr_ctrl) {
+  main_thread_->set_tagged_addr_ctrl(0);
+  ProtoToString();
+  EXPECT_MATCH(text_, "LOG tagged_addr_ctrl: 0000000000000000\\n");
+
+  main_thread_->set_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE);
+  ProtoToString();
+  EXPECT_MATCH(text_, "LOG tagged_addr_ctrl: 0000000000000001 \\(PR_TAGGED_ADDR_ENABLE\\)\\n");
+
+  main_thread_->set_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+                                     (0xfffe << PR_MTE_TAG_SHIFT));
+  ProtoToString();
+  EXPECT_MATCH(text_,
+               "LOG tagged_addr_ctrl: 000000000007fff3 \\(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, "
+               "mask 0xfffe\\)\\n");
+
+  main_thread_->set_tagged_addr_ctrl(0xf0000000 | PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+                                     PR_MTE_TCF_ASYNC | (0xfffe << PR_MTE_TAG_SHIFT));
+  ProtoToString();
+  EXPECT_MATCH(text_,
+               "LOG tagged_addr_ctrl: 00000000f007fff7 \\(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, "
+               "PR_MTE_TCF_ASYNC, mask 0xfffe, unknown 0xf0000000\\)\\n");
+}
+
+TEST_F(TombstoneProtoToTextTest, pac_enabled_keys) {
+  main_thread_->set_pac_enabled_keys(0);
+  ProtoToString();
+  EXPECT_MATCH(text_, "LOG pac_enabled_keys: 0000000000000000\\n");
+
+  main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY);
+  ProtoToString();
+  EXPECT_MATCH(text_, "LOG pac_enabled_keys: 0000000000000001 \\(PR_PAC_APIAKEY\\)\\n");
+
+  main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY);
+  ProtoToString();
+  EXPECT_MATCH(text_,
+               "LOG pac_enabled_keys: 0000000000000009 \\(PR_PAC_APIAKEY, PR_PAC_APDBKEY\\)\\n");
+
+  main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY | 0x1000);
+  ProtoToString();
+  EXPECT_MATCH(text_,
+               "LOG pac_enabled_keys: 0000000000001009 \\(PR_PAC_APIAKEY, PR_PAC_APDBKEY, unknown "
+               "0x1000\\)\\n");
+}
diff --git a/debuggerd/libdebuggerd/test/utility_test.cpp b/debuggerd/libdebuggerd/test/utility_test.cpp
deleted file mode 100644
index dad3380..0000000
--- a/debuggerd/libdebuggerd/test/utility_test.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-#include <sys/prctl.h>
-
-#include "libdebuggerd/utility.h"
-
-TEST(UtilityTest, describe_tagged_addr_ctrl) {
-  EXPECT_EQ("", describe_tagged_addr_ctrl(0));
-  EXPECT_EQ(" (PR_TAGGED_ADDR_ENABLE)", describe_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE));
-  EXPECT_EQ(" (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe)",
-            describe_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
-                                      (0xfffe << PR_MTE_TAG_SHIFT)));
-  EXPECT_EQ(
-      " (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, PR_MTE_TCF_ASYNC, mask 0xfffe, unknown "
-      "0xf0000000)",
-      describe_tagged_addr_ctrl(0xf0000000 | PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
-                                PR_MTE_TCF_ASYNC | (0xfffe << PR_MTE_TAG_SHIFT)));
-}
-
-TEST(UtilityTest, describe_pac_enabled_keys) {
-  EXPECT_EQ("", describe_pac_enabled_keys(0));
-  EXPECT_EQ(" (PR_PAC_APIAKEY)", describe_pac_enabled_keys(PR_PAC_APIAKEY));
-  EXPECT_EQ(" (PR_PAC_APIAKEY, PR_PAC_APDBKEY)",
-            describe_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY));
-  EXPECT_EQ(" (PR_PAC_APIAKEY, PR_PAC_APDBKEY, unknown 0x1000)",
-            describe_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY | 0x1000));
-}
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index eed81fc..ad91320 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -28,8 +28,8 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <async_safe/log.h>
 #include <bionic/macros.h>
+#include <sys/prctl.h>
 
 #include "tombstone.pb.h"
 
@@ -41,6 +41,42 @@
 #define CBS(...) CB(false, __VA_ARGS__)
 using CallbackType = std::function<void(const std::string& line, bool should_log)>;
 
+#define DESCRIBE_FLAG(flag) \
+  if (value & flag) {       \
+    desc += ", ";           \
+    desc += #flag;          \
+    value &= ~flag;         \
+  }
+
+static std::string describe_end(long value, std::string& desc) {
+  if (value) {
+    desc += StringPrintf(", unknown 0x%lx", value);
+  }
+  return desc.empty() ? "" : " (" + desc.substr(2) + ")";
+}
+
+static std::string describe_tagged_addr_ctrl(long value) {
+  std::string desc;
+  DESCRIBE_FLAG(PR_TAGGED_ADDR_ENABLE);
+  DESCRIBE_FLAG(PR_MTE_TCF_SYNC);
+  DESCRIBE_FLAG(PR_MTE_TCF_ASYNC);
+  if (value & PR_MTE_TAG_MASK) {
+    desc += StringPrintf(", mask 0x%04lx", (value & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
+    value &= ~PR_MTE_TAG_MASK;
+  }
+  return describe_end(value, desc);
+}
+
+static std::string describe_pac_enabled_keys(long value) {
+  std::string desc;
+  DESCRIBE_FLAG(PR_PAC_APIAKEY);
+  DESCRIBE_FLAG(PR_PAC_APIBKEY);
+  DESCRIBE_FLAG(PR_PAC_APDAKEY);
+  DESCRIBE_FLAG(PR_PAC_APDBKEY);
+  DESCRIBE_FLAG(PR_PAC_APGAKEY);
+  return describe_end(value, desc);
+}
+
 static const char* abi_string(const Tombstone& tombstone) {
   switch (tombstone.arch()) {
     case Architecture::ARM32:
@@ -138,7 +174,8 @@
       break;
 
     default:
-      async_safe_fatal("unknown architecture");
+      CBL("Unknown architecture %d printing thread registers", tombstone.arch());
+      return;
   }
 
   for (const auto& reg : thread.registers()) {
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index d71fc6c..742ac7c 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -382,8 +382,10 @@
           return "SEGV_MTEAERR";
         case SEGV_MTESERR:
           return "SEGV_MTESERR";
+        case SEGV_CPERR:
+          return "SEGV_CPERR";
       }
-      static_assert(NSIGSEGV == SEGV_MTESERR, "missing SEGV_* si_code");
+      static_assert(NSIGSEGV == SEGV_CPERR, "missing SEGV_* si_code");
       break;
     case SIGSYS:
       switch (si->si_code) {
@@ -443,42 +445,6 @@
   return "?";
 }
 
-#define DESCRIBE_FLAG(flag) \
-  if (value & flag) {       \
-    desc += ", ";           \
-    desc += #flag;          \
-    value &= ~flag;         \
-  }
-
-static std::string describe_end(long value, std::string& desc) {
-  if (value) {
-    desc += StringPrintf(", unknown 0x%lx", value);
-  }
-  return desc.empty() ? "" : " (" + desc.substr(2) + ")";
-}
-
-std::string describe_tagged_addr_ctrl(long value) {
-  std::string desc;
-  DESCRIBE_FLAG(PR_TAGGED_ADDR_ENABLE);
-  DESCRIBE_FLAG(PR_MTE_TCF_SYNC);
-  DESCRIBE_FLAG(PR_MTE_TCF_ASYNC);
-  if (value & PR_MTE_TAG_MASK) {
-    desc += StringPrintf(", mask 0x%04lx", (value & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
-    value &= ~PR_MTE_TAG_MASK;
-  }
-  return describe_end(value, desc);
-}
-
-std::string describe_pac_enabled_keys(long value) {
-  std::string desc;
-  DESCRIBE_FLAG(PR_PAC_APIAKEY);
-  DESCRIBE_FLAG(PR_PAC_APIBKEY);
-  DESCRIBE_FLAG(PR_PAC_APDAKEY);
-  DESCRIBE_FLAG(PR_PAC_APDBKEY);
-  DESCRIBE_FLAG(PR_PAC_APGAKEY);
-  return describe_end(value, desc);
-}
-
 void log_backtrace(log_t* log, unwindstack::AndroidUnwinder* unwinder,
                    unwindstack::AndroidUnwinderData& data, const char* prefix) {
   std::set<std::string> unreadable_elf_files;
diff --git a/debuggerd/proto/Android.bp b/debuggerd/proto/Android.bp
index 804f805..7be5d61 100644
--- a/debuggerd/proto/Android.bp
+++ b/debuggerd/proto/Android.bp
@@ -39,3 +39,17 @@
     recovery_available: true,
     vendor_ramdisk_available: true,
 }
+
+java_library_static {
+    name: "libtombstone_proto_java",
+    proto: {
+        type: "lite",
+    },
+    srcs: [
+        "tombstone.proto",
+    ],
+    jarjar_rules: "jarjar-rules.txt",
+    sdk_version: "current",
+    static_libs: ["libprotobuf-java-lite"],
+}
+
diff --git a/debuggerd/proto/jarjar-rules.txt b/debuggerd/proto/jarjar-rules.txt
new file mode 100644
index 0000000..66878a9
--- /dev/null
+++ b/debuggerd/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.** com.android.server.os.protobuf.@1
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index 212d6dc..793428a 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -99,6 +99,7 @@
   uintptr_t scudo_region_info;
   uintptr_t scudo_ring_buffer;
   size_t scudo_ring_buffer_size;
+  size_t scudo_stack_depot_size;
   bool recoverable_gwp_asan_crash;
 };
 
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 56cac88..c0445f3 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -170,7 +170,7 @@
         "android.hardware.fastboot@1.1",
         "android.hardware.fastboot-V1-ndk",
         "android.hardware.health@2.0",
-        "android.hardware.health-V2-ndk",
+        "android.hardware.health-V3-ndk",
         "libasyncio",
         "libbase",
         "libbinder_ndk",
@@ -446,3 +446,14 @@
     host_supported: true,
     export_include_dirs: ["."],
 }
+
+python_test_host {
+    name: "fastboot_integration_test",
+    main: "test_fastboot.py",
+    srcs: ["test_fastboot.py"],
+    data: [":fastboot"],
+    test_config: "fastboot_integration_test.xml",
+    test_options: {
+        unit_test: false,
+    },
+}
diff --git a/fastboot/constants.h b/fastboot/constants.h
index a803307..af4d1eb 100644
--- a/fastboot/constants.h
+++ b/fastboot/constants.h
@@ -82,3 +82,5 @@
 #define FB_VAR_TREBLE_ENABLED "treble-enabled"
 #define FB_VAR_MAX_FETCH_SIZE "max-fetch-size"
 #define FB_VAR_DMESG "dmesg"
+#define FB_VAR_BATTERY_SERIAL_NUMBER "battery-serial-number"
+#define FB_VAR_BATTERY_PART_STATUS "battery-part-status"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index bd936ae..e522f4d 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -147,6 +147,8 @@
         {FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}},
         {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}},
         {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}},
+        {FB_VAR_BATTERY_SERIAL_NUMBER, {GetBatterySerialNumber, nullptr}},
+        {FB_VAR_BATTERY_PART_STATUS, {GetBatteryPartStatus, nullptr}},
 };
 
 static bool GetVarAll(FastbootDevice* device) {
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index 2847e35..77210ab 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -570,3 +570,79 @@
 
     return true;
 }
+
+bool GetBatterySerialNumber(FastbootDevice* device, const std::vector<std::string>&,
+                            std::string* message) {
+    auto health_hal = device->health_hal();
+    if (!health_hal) {
+        return false;
+    }
+
+    if (GetDeviceLockStatus()) {
+        return device->WriteFail("Device is locked");
+    }
+
+    *message = "unsupported";
+
+    int32_t version = 0;
+    auto res = health_hal->getInterfaceVersion(&version);
+    if (!res.isOk()) {
+        return device->WriteFail("Unable to query battery data");
+    }
+    if (version >= 3) {
+        using aidl::android::hardware::health::BatteryHealthData;
+
+        BatteryHealthData data;
+        auto res = health_hal->getBatteryHealthData(&data);
+        if (!res.isOk()) {
+            return device->WriteFail("Unable to query battery data");
+        }
+        if (data.batterySerialNumber) {
+            *message = *data.batterySerialNumber;
+        }
+    }
+    return true;
+}
+
+bool GetBatteryPartStatus(FastbootDevice* device, const std::vector<std::string>&,
+                          std::string* message) {
+    auto health_hal = device->health_hal();
+    if (!health_hal) {
+        return false;
+    }
+
+    using aidl::android::hardware::health::BatteryPartStatus;
+
+    BatteryPartStatus status = BatteryPartStatus::UNSUPPORTED;
+
+    int32_t version = 0;
+    auto res = health_hal->getInterfaceVersion(&version);
+    if (!res.isOk()) {
+        return device->WriteFail("Unable to query battery data");
+    }
+    if (version >= 3) {
+        using aidl::android::hardware::health::BatteryHealthData;
+
+        BatteryHealthData data;
+        auto res = health_hal->getBatteryHealthData(&data);
+        if (!res.isOk()) {
+            return device->WriteFail("Unable to query battery data");
+        }
+        status = data.batteryPartStatus;
+    }
+    switch (status) {
+        case BatteryPartStatus::UNSUPPORTED:
+            *message = "unsupported";
+            break;
+        case BatteryPartStatus::ORIGINAL:
+            *message = "original";
+            break;
+        case BatteryPartStatus::REPLACED:
+            *message = "replaced";
+            break;
+        default:
+            *message = "unknown";
+            break;
+    }
+    return true;
+}
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index 9a46786..99d1355 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -67,6 +67,10 @@
                    std::string* message);
 bool GetBatterySoCOk(FastbootDevice* device, const std::vector<std::string>& args,
                      std::string* message);
+bool GetBatterySerialNumber(FastbootDevice* device, const std::vector<std::string>& args,
+                            std::string* message);
+bool GetBatteryPartStatus(FastbootDevice* device, const std::vector<std::string>& args,
+                          std::string* message);
 bool GetSuperPartitionName(FastbootDevice* device, const std::vector<std::string>& args,
                            std::string* message);
 bool GetSnapshotUpdateStatus(FastbootDevice* device, const std::vector<std::string>& args,
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index ac2a20f..12a1ddc 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -402,7 +402,7 @@
         transport = open_device(device.c_str(), false, false);
 
         if (print) {
-            PrintDevice(device.c_str(), transport ? "offline" : "fastboot");
+            PrintDevice(device.c_str(), transport ? "fastboot" : "offline");
         }
 
         if (transport) {
@@ -571,7 +571,8 @@
             "                            Format a flash partition.\n"
             " set_active SLOT            Set the active slot.\n"
             " oem [COMMAND...]           Execute OEM-specific command.\n"
-            " gsi wipe|disable           Wipe or disable a GSI installation (fastbootd only).\n"
+            " gsi wipe|disable|status    Wipe, disable or show status of a GSI installation\n"
+            "                            (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"
@@ -1675,7 +1676,7 @@
     }
     for (size_t i = 0; i < tasks->size(); i++) {
         if (auto flash_task = tasks->at(i)->AsFlashTask()) {
-            if (FlashTask::IsDynamicParitition(fp->source.get(), flash_task)) {
+            if (FlashTask::IsDynamicPartition(fp->source.get(), flash_task)) {
                 if (!loc) {
                     loc = i;
                 }
diff --git a/fastboot/fastboot_integration_test.xml b/fastboot/fastboot_integration_test.xml
new file mode 100644
index 0000000..ad14cab
--- /dev/null
+++ b/fastboot/fastboot_integration_test.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config to run fastboot integration tests">
+    <option name="test-suite-tag" value="fastboot_unittests" />
+    <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest" >
+        <option name="par-file-name" value="fastboot_integration_test" />
+        <option name="test-timeout" value="1m" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 25c5a6e..ea78a01 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -30,7 +30,7 @@
                      const bool apply_vbmeta, const FlashingPlan* fp)
     : pname_(pname), fname_(fname), slot_(slot), apply_vbmeta_(apply_vbmeta), fp_(fp) {}
 
-bool FlashTask::IsDynamicParitition(const ImageSource* source, const FlashTask* task) {
+bool FlashTask::IsDynamicPartition(const ImageSource* source, const FlashTask* task) {
     std::vector<char> contents;
     if (!source->ReadFile("super_empty.img", &contents)) {
         return false;
@@ -152,7 +152,7 @@
             continue;
         }
         auto flash_task = tasks[i + 2]->AsFlashTask();
-        if (!FlashTask::IsDynamicParitition(source, flash_task)) {
+        if (!FlashTask::IsDynamicPartition(source, flash_task)) {
             continue;
         }
         return true;
@@ -224,7 +224,7 @@
     auto remove_if_callback = [&](const auto& task) -> bool {
         if (auto flash_task = task->AsFlashTask()) {
             return helper->WillFlash(flash_task->GetPartitionAndSlot());
-        } else if (auto update_super_task = task->AsUpdateSuperTask()) {
+        } else if (task->AsUpdateSuperTask()) {
             return true;
         } else if (auto reboot_task = task->AsRebootTask()) {
             if (reboot_task->GetTarget() == "fastboot") {
diff --git a/fastboot/task.h b/fastboot/task.h
index a98c874..7a713cf 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -52,7 +52,7 @@
               const bool apply_vbmeta, const FlashingPlan* fp);
     virtual FlashTask* AsFlashTask() override { return this; }
 
-    static bool IsDynamicParitition(const ImageSource* source, const FlashTask* task);
+    static bool IsDynamicPartition(const ImageSource* source, const FlashTask* task);
     void Run() override;
     std::string ToString() const override;
     std::string GetPartition() const { return pname_; }
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
index 9cde1a8..519d4ed 100644
--- a/fastboot/task_test.cpp
+++ b/fastboot/task_test.cpp
@@ -233,7 +233,7 @@
             << "size of fastboot-info task list: " << fastboot_info_tasks.size()
             << " size of hardcoded task list: " << hardcoded_tasks.size();
 }
-TEST_F(ParseTest, IsDynamicParitiontest) {
+TEST_F(ParseTest, IsDynamicPartitiontest) {
     if (!get_android_product_out()) {
         GTEST_SKIP();
     }
@@ -258,7 +258,7 @@
                 ParseFastbootInfoLine(fp.get(), android::base::Tokenize(test.first, " "));
         auto flash_task = task->AsFlashTask();
         ASSERT_FALSE(flash_task == nullptr);
-        ASSERT_EQ(FlashTask::IsDynamicParitition(fp->source.get(), flash_task), test.second);
+        ASSERT_EQ(FlashTask::IsDynamicPartition(fp->source.get(), flash_task), test.second);
     }
 }
 
@@ -354,11 +354,11 @@
                                 const std::vector<std::unique_ptr<Task>>& tasks) {
             bool contains_optimized_task = false;
             for (auto& task : tasks) {
-                if (auto optimized_task = task->AsOptimizedFlashSuperTask()) {
+                if (task->AsOptimizedFlashSuperTask()) {
                     contains_optimized_task = true;
                 }
                 if (auto flash_task = task->AsFlashTask()) {
-                    if (FlashTask::IsDynamicParitition(fp->source.get(), flash_task)) {
+                    if (FlashTask::IsDynamicPartition(fp->source.get(), flash_task)) {
                         return false;
                     }
                 }
diff --git a/fastboot/test_fastboot.py b/fastboot/test_fastboot.py
new file mode 100644
index 0000000..adcaec7
--- /dev/null
+++ b/fastboot/test_fastboot.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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.
+#
+"""Tests for the fastboot command line utility.
+"""
+
+import re
+import subprocess
+import unittest
+
+
+class DevicesTest(unittest.TestCase):
+    """Tests for `fastboot devices`."""
+
+
+    def test_devices(self):
+        """Ensure that the format of `fastboot devices` does not change.
+
+        `fastboot devices` should alternate between a line containing the
+        device's serial number and fastboot state and an empty line
+        """
+        output = subprocess.check_output(["fastboot", "devices"])
+
+        previous_line_was_empty = True
+        for line in output.decode().splitlines():
+            if previous_line_was_empty:
+                if not re.match(r"[a-zA-Z\d]+\s+(bootloader|fastbootd)", line):
+                    self.fail("%s does not match the expected format <serial no>\\s+(bootloader|fastbootd)" % line)
+                previous_line_was_empty = False
+            else:
+                if line:
+                    self.fail("Expected an empty line. Received '%s'" % line)
+                previous_line_was_empty = True
+
+        if len(output) == 0:
+            self.fail("Output is empty. Are any devices connected?")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index 324f50a..1989a5c 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -25,7 +25,7 @@
     {
       "name": "vab_legacy_tests"
     },
-    // TODO: b/279009697
+    // TODO(b/279009697):
     //{"name": "vabc_legacy_tests"},
     {
       "name": "cow_api_test"
@@ -42,12 +42,9 @@
       "name": "liblp_test"
     },
     {
-      "name": "vts_libsnapshot_test"
-    },
-    {
       "name": "vab_legacy_tests"
     },
-    // TODO: b/279009697
+    // TODO(b/279009697):
     //{"name": "vabc_legacy_tests"}
     {
       "name": "snapuserd_test"
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index d55f8d3..b0e2d6d 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -16,7 +16,6 @@
 
 #include "fs_mgr.h"
 
-#include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -701,6 +700,29 @@
 }
 
 //
+// Mechanism to allow fsck to be triggered by setting ro.preventative_fsck
+// Introduced to address b/305658663
+// If the property value is not equal to the flag file contents, trigger
+// fsck and store the property value in the flag file
+// If we want to trigger again, simply change the property value
+//
+static bool check_if_preventative_fsck_needed(const FstabEntry& entry) {
+    const char* flag_file = "/metadata/vold/preventative_fsck";
+    if (entry.mount_point != "/data") return false;
+
+    // Don't error check - both default to empty string, which is OK
+    std::string prop = android::base::GetProperty("ro.preventative_fsck", "");
+    std::string flag;
+    android::base::ReadFileToString(flag_file, &flag);
+    if (prop == flag) return false;
+    // fsck is run immediately, so assume it runs or there is some deeper problem
+    if (!android::base::WriteStringToFile(prop, flag_file))
+        PERROR << "Failed to write file " << flag_file;
+    LINFO << "Run preventative fsck on /data";
+    return true;
+}
+
+//
 // Prepare the filesystem on the given block device to be mounted.
 //
 // If the "check" option was given in the fstab record, or it seems that the
@@ -750,7 +772,7 @@
         }
     }
 
-    if (entry.fs_mgr_flags.check ||
+    if (check_if_preventative_fsck_needed(entry) || entry.fs_mgr_flags.check ||
         (fs_stat & (FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED))) {
         check_fs(blk_device, entry.fs_type, mount_point, &fs_stat);
     }
@@ -796,9 +818,13 @@
 // __mount(): wrapper around the mount() system call which also
 // sets the underlying block device to read-only if the mount is read-only.
 // See "man 2 mount" for return values.
-static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry) {
+static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry,
+                   bool read_only = false) {
     errno = 0;
     unsigned long mountflags = entry.flags;
+    if (read_only) {
+        mountflags |= MS_RDONLY;
+    }
     int ret = 0;
     int save_errno = 0;
     int gc_allowance = 0;
@@ -828,7 +854,14 @@
                   << ",type=" << entry.fs_type << ", gc_allowance=" << gc_allowance << "%)=" << ret
                   << "(" << save_errno << ")";
         }
-        ret = mount(source.c_str(), target.c_str(), entry.fs_type.c_str(), mountflags,
+
+        // Let's get the raw dm target, if it's a symlink, since some existing applications
+        // rely on /proc/mounts to find the userdata's dm target path. Don't break that assumption.
+        std::string real_source;
+        if (!android::base::Realpath(source, &real_source)) {
+            real_source = source;
+        }
+        ret = mount(real_source.c_str(), target.c_str(), entry.fs_type.c_str(), mountflags,
                     opts.c_str());
         save_errno = errno;
         if (try_f2fs_gc_allowance) gc_allowance += 10;
@@ -885,6 +918,10 @@
     return true;
 }
 
+static bool should_use_metadata_encryption(const FstabEntry& entry) {
+    return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption;
+}
+
 // Tries to mount any of the consecutive fstab entries that match
 // the mountpoint of the one given by fstab[start_idx].
 //
@@ -892,8 +929,7 @@
 // attempted_idx: On return, will indicate which fstab entry
 //     succeeded. In case of failure, it will be the start_idx.
 // Sets errno to match the 1st mount failure on failure.
-static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx,
-                                    int* attempted_idx) {
+static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx, int* attempted_idx) {
     unsigned long i;
     int mount_errno = 0;
     bool mounted = false;
@@ -931,8 +967,15 @@
         }
 
         int retry_count = 2;
+        const auto read_only = should_use_metadata_encryption(fstab[i]);
+        if (read_only) {
+            LOG(INFO) << "Mount point " << fstab[i].blk_device << " @ " << fstab[i].mount_point
+                      << " uses metadata encryption, which means we need to unmount it later and "
+                         "call encryptFstab/encrypt_inplace. To avoid file operations before "
+                         "encryption, we will mount it as read-only first";
+        }
         while (retry_count-- > 0) {
-            if (!__mount(fstab[i].blk_device, fstab[i].mount_point, fstab[i])) {
+            if (!__mount(fstab[i].blk_device, fstab[i].mount_point, fstab[i], read_only)) {
                 *attempted_idx = i;
                 mounted = true;
                 if (i != start_idx) {
@@ -1024,10 +1067,6 @@
     return false;
 }
 
-static bool should_use_metadata_encryption(const FstabEntry& entry) {
-    return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption;
-}
-
 // Check to see if a mountable volume has encryption requirements
 static int handle_encryptable(const FstabEntry& entry) {
     if (should_use_metadata_encryption(entry)) {
@@ -1237,17 +1276,27 @@
 };
 
 std::string fs_mgr_find_bow_device(const std::string& block_device) {
-    if (block_device.substr(0, 5) != "/dev/") {
-        LOG(ERROR) << "Expected block device, got " << block_device;
-        return std::string();
+    // handle symlink such as "/dev/block/mapper/userdata"
+    std::string real_path;
+    if (!android::base::Realpath(block_device, &real_path)) {
+        real_path = block_device;
     }
 
-    std::string sys_dir = std::string("/sys/") + block_device.substr(5);
-
+    struct stat st;
+    if (stat(real_path.c_str(), &st) < 0) {
+        PLOG(ERROR) << "stat failed: " << real_path;
+        return std::string();
+    }
+    if (!S_ISBLK(st.st_mode)) {
+        PLOG(ERROR) << real_path << " is not block device";
+        return std::string();
+    }
+    std::string sys_dir = android::base::StringPrintf("/sys/dev/block/%u:%u", major(st.st_rdev),
+                                                      minor(st.st_rdev));
     for (;;) {
         std::string name;
         if (!android::base::ReadFileToString(sys_dir + "/dm/name", &name)) {
-            PLOG(ERROR) << block_device << " is not dm device";
+            PLOG(ERROR) << real_path << " is not dm device";
             return std::string();
         }
 
@@ -1381,6 +1430,8 @@
         return {FS_MGR_MNTALL_FAIL, userdata_mounted};
     }
 
+    bool scratch_can_be_mounted = true;
+
     // Keep i int to prevent unsigned integer overflow from (i = top_idx - 1),
     // where top_idx is 0. It will give SIGABRT
     for (int i = 0; i < static_cast<int>(fstab->size()); i++) {
@@ -1513,6 +1564,9 @@
             if (current_entry.mount_point == "/data") {
                 userdata_mounted = true;
             }
+
+            MountOverlayfs(attempted_entry, &scratch_can_be_mounted);
+
             // Success!  Go get the next one.
             continue;
         }
@@ -1597,10 +1651,6 @@
 
     set_type_property(encryptable);
 
-#if ALLOW_ADBD_DISABLE_VERITY == 1  // "userdebug" build
-    fs_mgr_overlayfs_mount_all(fstab);
-#endif
-
     if (error_count) {
         return {FS_MGR_MNTALL_FAIL, userdata_mounted};
     } else {
@@ -1937,6 +1987,8 @@
                 if (retry_count <= 0) break;  // run check_fs only once
                 if (!first_mount_errno) first_mount_errno = errno;
                 mount_errors++;
+                PERROR << "Cannot mount filesystem on " << n_blk_device << " at " << mount_point
+                       << " with fstype " << fstab_entry.fs_type;
                 fs_stat |= FS_STAT_FULL_MOUNT_FAILED;
                 // try again after fsck
                 check_fs(n_blk_device, fstab_entry.fs_type, mount_point, &fs_stat);
diff --git a/fs_mgr/fs_mgr_format.cpp b/fs_mgr/fs_mgr_format.cpp
index 622f181..8e76150 100644
--- a/fs_mgr/fs_mgr_format.cpp
+++ b/fs_mgr/fs_mgr_format.cpp
@@ -136,6 +136,7 @@
     /* Format the partition using the calculated length */
 
     const auto size_str = std::to_string(dev_sz / getpagesize());
+    std::string block_size = std::to_string(getpagesize());
 
     std::vector<const char*> args = {"/system/bin/make_f2fs", "-g", "android"};
     if (needs_projid) {
@@ -154,6 +155,10 @@
         args.push_back("-O");
         args.push_back("extra_attr");
     }
+    args.push_back("-w");
+    args.push_back(block_size.c_str());
+    args.push_back("-b");
+    args.push_back(block_size.c_str());
     if (!zoned_device.empty()) {
         args.push_back("-c");
         args.push_back(zoned_device.c_str());
diff --git a/fs_mgr/fs_mgr_overlayfs_control.cpp b/fs_mgr/fs_mgr_overlayfs_control.cpp
index 50d8280..08ad80c 100644
--- a/fs_mgr/fs_mgr_overlayfs_control.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_control.cpp
@@ -219,6 +219,35 @@
     return OverlayfsTeardownResult::Ok;
 }
 
+bool GetOverlaysActiveFlag() {
+    auto slot_number = fs_mgr_overlayfs_slot_number();
+    const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name();
+
+    auto metadata = ReadMetadata(super_device, slot_number);
+    if (!metadata) {
+        return false;
+    }
+    return !!(metadata->header.flags & LP_HEADER_FLAG_OVERLAYS_ACTIVE);
+}
+
+bool SetOverlaysActiveFlag(bool flag) {
+    // Mark overlays as active in the partition table, to detect re-flash.
+    auto slot_number = fs_mgr_overlayfs_slot_number();
+    const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name();
+    auto builder = MetadataBuilder::New(super_device, slot_number);
+    if (!builder) {
+        LERROR << "open " << super_device << " metadata";
+        return false;
+    }
+    builder->SetOverlaysActiveFlag(flag);
+    auto metadata = builder->Export();
+    if (!metadata || !UpdatePartitionTable(super_device, *metadata.get(), slot_number)) {
+        LERROR << "update super metadata";
+        return false;
+    }
+    return true;
+}
+
 OverlayfsTeardownResult fs_mgr_overlayfs_teardown_scratch(const std::string& overlay,
                                                           bool* change) {
     // umount and delete kScratchMountPoint storage if we have logical partitions
@@ -232,6 +261,10 @@
         return OverlayfsTeardownResult::Error;
     }
 
+    // Note: we don't care if SetOverlaysActiveFlag fails, since
+    // the overlays are removed no matter what.
+    SetOverlaysActiveFlag(false);
+
     bool was_mounted = fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false);
     if (was_mounted) {
         fs_mgr_overlayfs_umount_scratch();
@@ -347,33 +380,6 @@
     return "";
 }
 
-// This returns the scratch device that was detected during early boot (first-
-// stage init). If the device was created later, for example during setup for
-// the adb remount command, it can return an empty string since it does not
-// query ImageManager. (Note that ImageManager in first-stage init will always
-// use device-mapper, since /data is not available to use loop devices.)
-static std::string GetBootScratchDevice() {
-    // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
-    if (fs_mgr_is_dsu_running()) {
-        return GetDsuScratchDevice();
-    }
-
-    auto& dm = DeviceMapper::Instance();
-
-    // If there is a scratch partition allocated in /data or on super, we
-    // automatically prioritize that over super_other or system_other.
-    // Some devices, for example, have a write-protected eMMC and the
-    // super partition cannot be used even if it exists.
-    std::string device;
-    auto partition_name = android::base::Basename(kScratchMountPoint);
-    if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
-        dm.GetDmDevicePathByName(partition_name, &device)) {
-        return device;
-    }
-
-    return "";
-}
-
 bool MakeScratchFilesystem(const std::string& scratch_device) {
     // Force mkfs by design for overlay support of adb remount, simplify and
     // thus do not rely on fsck to correct problems that could creep in.
@@ -383,6 +389,8 @@
         fs_type = "f2fs";
         command = kMkF2fs + " -w "s;
         command += std::to_string(getpagesize());
+        command = kMkF2fs + " -b "s;
+        command += std::to_string(getpagesize());
         command += " -f -d1 -l" + android::base::Basename(kScratchMountPoint);
     } else if (!access(kMkExt4, X_OK) && fs_mgr_filesystem_available("ext4")) {
         fs_type = "ext4";
@@ -473,6 +481,7 @@
             }
         }
     }
+
     // land the update back on to the partition
     if (changed) {
         auto metadata = builder->Export();
@@ -617,6 +626,12 @@
         return false;
     }
 
+    if (!SetOverlaysActiveFlag(true)) {
+        LOG(ERROR) << "Failed to update dynamic partition data";
+        fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr);
+        return false;
+    }
+
     // If the partition exists, assume first that it can be mounted.
     if (partition_exists) {
         if (MountScratch(scratch_device)) {
@@ -881,21 +896,9 @@
         return;
     }
 
-    bool want_scratch = false;
-    for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
-        if (fs_mgr_is_verity_enabled(entry)) {
-            continue;
-        }
-        if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) {
-            continue;
-        }
-        want_scratch = true;
-        break;
-    }
-    if (!want_scratch) {
+    if (!GetOverlaysActiveFlag()) {
         return;
     }
-
     if (ScratchIsOnData()) {
         if (auto images = IImageManager::Open("remount", 0ms)) {
             images->MapAllImages(init);
@@ -919,9 +922,39 @@
     }
     if (auto images = IImageManager::Open("remount", 0ms)) {
         images->RemoveDisabledImages();
+        if (!GetOverlaysActiveFlag()) {
+            fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr);
+        }
     }
 }
 
+// This returns the scratch device that was detected during early boot (first-
+// stage init). If the device was created later, for example during setup for
+// the adb remount command, it can return an empty string since it does not
+// query ImageManager. (Note that ImageManager in first-stage init will always
+// use device-mapper, since /data is not available to use loop devices.)
+std::string GetBootScratchDevice() {
+    // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
+    if (fs_mgr_is_dsu_running()) {
+        return GetDsuScratchDevice();
+    }
+
+    auto& dm = DeviceMapper::Instance();
+
+    // If there is a scratch partition allocated in /data or on super, we
+    // automatically prioritize that over super_other or system_other.
+    // Some devices, for example, have a write-protected eMMC and the
+    // super partition cannot be used even if it exists.
+    std::string device;
+    auto partition_name = android::base::Basename(kScratchMountPoint);
+    if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
+        dm.GetDmDevicePathByName(partition_name, &device)) {
+        return device;
+    }
+
+    return "";
+}
+
 void TeardownAllOverlayForMountPoint(const std::string& mount_point) {
     if (!OverlayfsTeardownAllowed()) {
         return;
diff --git a/fs_mgr/fs_mgr_overlayfs_control.h b/fs_mgr/fs_mgr_overlayfs_control.h
index b175101..3516c46 100644
--- a/fs_mgr/fs_mgr_overlayfs_control.h
+++ b/fs_mgr/fs_mgr_overlayfs_control.h
@@ -38,5 +38,7 @@
 
 void CleanupOldScratchFiles();
 
+std::string GetBootScratchDevice();
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/fs_mgr_overlayfs_mount.cpp b/fs_mgr/fs_mgr_overlayfs_mount.cpp
index ae7ed4c..e168436 100644
--- a/fs_mgr/fs_mgr_overlayfs_mount.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_mount.cpp
@@ -34,6 +34,7 @@
 #include <android-base/file.h>
 #include <android-base/macros.h>
 #include <android-base/properties.h>
+#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <ext4_utils/ext4_utils.h>
@@ -41,15 +42,14 @@
 #include <fs_mgr/file_wait.h>
 #include <fs_mgr_overlayfs.h>
 #include <fstab/fstab.h>
-#include <libdm/dm.h>
 #include <libgsi/libgsi.h>
 #include <storage_literals/storage_literals.h>
 
+#include "fs_mgr_overlayfs_control.h"
 #include "fs_mgr_overlayfs_mount.h"
 #include "fs_mgr_priv.h"
 
 using namespace std::literals;
-using namespace android::dm;
 using namespace android::fs_mgr;
 using namespace android::storage_literals;
 
@@ -58,6 +58,9 @@
 constexpr char kCacheMountPoint[] = "/cache";
 constexpr char kPhysicalDevice[] = "/dev/block/by-name/";
 
+// Mount tree to temporarily hold references to submounts.
+constexpr char kMoveMountTempDir[] = "/dev/remount";
+
 constexpr char kLowerdirOption[] = "lowerdir=";
 constexpr char kUpperdirOption[] = "upperdir=";
 constexpr char kWorkdirOption[] = "workdir=";
@@ -284,10 +287,6 @@
     if (ret) {
         PERROR << "__mount(target=" << mount_point
                << ",flag=" << (shared_flag ? "MS_SHARED" : "MS_PRIVATE") << ")=" << ret;
-        // If "/system" doesn't look like a mountpoint, retry with "/".
-        if (errno == EINVAL && mount_point == "/system") {
-            return fs_mgr_overlayfs_set_shared_mount("/", shared_flag);
-        }
         return false;
     }
     return true;
@@ -302,6 +301,25 @@
     return true;
 }
 
+static bool fs_mgr_overlayfs_mount(const std::string& mount_point, const std::string& options) {
+    auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
+    for (const auto& opt : android::base::Split(options, ",")) {
+        if (android::base::StartsWith(opt, kUpperdirOption)) {
+            report = report + "," + opt;
+            break;
+        }
+    }
+    report = report + ")=";
+    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
+                     options.c_str());
+    if (ret) {
+        PERROR << report << ret;
+    } else {
+        LINFO << report << ret;
+    }
+    return !ret;
+}
+
 struct mount_info {
     std::string mount_point;
     bool shared_flag;
@@ -374,24 +392,23 @@
     return info;
 }
 
-static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
-    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
-    const auto options = fs_mgr_get_overlayfs_options(entry);
+static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) {
+    const auto mount_point = fs_mgr_mount_point(fstab_entry.mount_point);
+    const auto options = fs_mgr_get_overlayfs_options(fstab_entry);
     if (options.empty()) return false;
 
-    auto retval = true;
-
     struct MoveEntry {
         std::string mount_point;
         std::string dir;
         bool shared_flag;
     };
-
     std::vector<MoveEntry> moved_mounts;
-    auto parent_private = false;
-    auto parent_made_private = false;
-    auto dev_private = false;
-    auto dev_made_private = false;
+
+    bool retval = true;
+    bool move_dir_shared = true;
+    bool parent_shared = true;
+    bool root_shared = true;
+    bool root_made_private = false;
 
     // There could be multiple mount entries with the same mountpoint.
     // Group these entries together with stable_sort, and keep only the last entry of a group.
@@ -411,18 +428,32 @@
     // mountinfo is reversed twice, so still is in lexical sorted order.
 
     for (const auto& entry : mountinfo) {
-        if ((entry.mount_point == mount_point) && !entry.shared_flag) {
-            parent_private = true;
+        if (entry.mount_point == kMoveMountTempDir) {
+            move_dir_shared = entry.shared_flag;
         }
-        if ((entry.mount_point == "/dev") && !entry.shared_flag) {
-            dev_private = true;
+        if (entry.mount_point == mount_point ||
+            (mount_point == "/system" && entry.mount_point == "/")) {
+            parent_shared = entry.shared_flag;
         }
+        if (entry.mount_point == "/") {
+            root_shared = entry.shared_flag;
+        }
+    }
+
+    // Precondition is that kMoveMountTempDir is MS_PRIVATE, otherwise don't try to move any
+    // submount in to or out of it.
+    if (move_dir_shared) {
+        mountinfo.clear();
     }
 
     // Need to make the original mountpoint MS_PRIVATE, so that the overlayfs can be MS_MOVE.
     // This could happen if its parent mount is remounted later.
-    if (!parent_private) {
-        parent_made_private = fs_mgr_overlayfs_set_shared_mount(mount_point, false);
+    if (!fs_mgr_overlayfs_set_shared_mount(mount_point, false)) {
+        // If failed to set "/system" mount type, it might be due to "/system" not being a valid
+        // mountpoint after switch root. Retry with "/" in this case.
+        if (errno == EINVAL && mount_point == "/system") {
+            root_made_private = fs_mgr_overlayfs_set_shared_mount("/", false);
+        }
     }
 
     for (const auto& entry : mountinfo) {
@@ -440,8 +471,8 @@
         // mountinfo is in lexical order, so no need to worry about |entry| being a parent mount of
         // entries of |moved_mounts|.
 
-        // use as the bound directory in /dev.
-        MoveEntry new_entry{entry.mount_point, "/dev/TemporaryDir-XXXXXX", entry.shared_flag};
+        MoveEntry new_entry{entry.mount_point, kMoveMountTempDir + "/TemporaryDir-XXXXXX"s,
+                            entry.shared_flag};
         {
             AutoSetFsCreateCon createcon;
             auto new_context = fs_mgr_get_context(entry.mount_point);
@@ -475,32 +506,10 @@
         moved_mounts.push_back(std::move(new_entry));
     }
 
-    // hijack __mount() report format to help triage
-    auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
-    const auto opt_list = android::base::Split(options, ",");
-    for (const auto& opt : opt_list) {
-        if (android::base::StartsWith(opt, kUpperdirOption)) {
-            report = report + "," + opt;
-            break;
-        }
-    }
-    report = report + ")=";
-
-    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
-                     options.c_str());
-    if (ret) {
-        retval = false;
-        PERROR << report << ret;
-    } else {
-        LINFO << report << ret;
-    }
+    retval &= fs_mgr_overlayfs_mount(mount_point, options);
 
     // Move submounts back.
     for (const auto& entry : moved_mounts) {
-        if (!dev_private && !dev_made_private) {
-            dev_made_private = fs_mgr_overlayfs_set_shared_mount("/dev", false);
-        }
-
         if (!fs_mgr_overlayfs_move_mount(entry.dir, entry.mount_point)) {
             retval = false;
         } else if (entry.shared_flag &&
@@ -509,12 +518,13 @@
         }
         rmdir(entry.dir.c_str());
     }
-    if (dev_made_private) {
-        fs_mgr_overlayfs_set_shared_mount("/dev", true);
-    }
-    if (parent_made_private) {
+    // If the original (overridden) mount was MS_SHARED, then set the overlayfs mount to MS_SHARED.
+    if (parent_shared) {
         fs_mgr_overlayfs_set_shared_mount(mount_point, true);
     }
+    if (root_shared && root_made_private) {
+        fs_mgr_overlayfs_set_shared_mount("/", true);
+    }
 
     return retval;
 }
@@ -582,45 +592,6 @@
     return true;
 }
 
-// Note: The scratch partition of DSU is managed by gsid, and should be initialized during
-// first-stage-mount. Just check if the DM device for DSU scratch partition is created or not.
-static std::string GetDsuScratchDevice() {
-    auto& dm = DeviceMapper::Instance();
-    std::string device;
-    if (dm.GetState(android::gsi::kDsuScratch) != DmDeviceState::INVALID &&
-        dm.GetDmDevicePathByName(android::gsi::kDsuScratch, &device)) {
-        return device;
-    }
-    return "";
-}
-
-// This returns the scratch device that was detected during early boot (first-
-// stage init). If the device was created later, for example during setup for
-// the adb remount command, it can return an empty string since it does not
-// query ImageManager. (Note that ImageManager in first-stage init will always
-// use device-mapper, since /data is not available to use loop devices.)
-static std::string GetBootScratchDevice() {
-    // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
-    if (fs_mgr_is_dsu_running()) {
-        return GetDsuScratchDevice();
-    }
-
-    auto& dm = DeviceMapper::Instance();
-
-    // If there is a scratch partition allocated in /data or on super, we
-    // automatically prioritize that over super_other or system_other.
-    // Some devices, for example, have a write-protected eMMC and the
-    // super partition cannot be used even if it exists.
-    std::string device;
-    auto partition_name = android::base::Basename(kScratchMountPoint);
-    if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
-        dm.GetDmDevicePathByName(partition_name, &device)) {
-        return device;
-    }
-
-    return "";
-}
-
 // NOTE: OverlayfsSetupAllowed() must be "stricter" than OverlayfsTeardownAllowed().
 // Setup is allowed only if teardown is also allowed.
 bool OverlayfsSetupAllowed(bool verbose) {
@@ -730,6 +701,25 @@
     if (!OverlayfsSetupAllowed()) {
         return false;
     }
+
+    // Ensure kMoveMountTempDir is standalone mount tree with 'private' propagation by bind mounting
+    // to itself and set to MS_PRIVATE.
+    // Otherwise mounts moved in to it would have their propagation type changed unintentionally.
+    // Section 5d, https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
+    if (!fs_mgr_overlayfs_already_mounted(kMoveMountTempDir, false)) {
+        if (mkdir(kMoveMountTempDir, 0755) && errno != EEXIST) {
+            PERROR << "mkdir " << kMoveMountTempDir;
+        }
+        if (mount(kMoveMountTempDir, kMoveMountTempDir, nullptr, MS_BIND, nullptr)) {
+            PERROR << "bind mount " << kMoveMountTempDir;
+        }
+    }
+    fs_mgr_overlayfs_set_shared_mount(kMoveMountTempDir, false);
+    android::base::ScopeGuard umountDir([]() {
+        umount(kMoveMountTempDir);
+        rmdir(kMoveMountTempDir);
+    });
+
     auto ret = true;
     auto scratch_can_be_mounted = !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false);
     for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
@@ -742,7 +732,7 @@
             scratch_can_be_mounted = false;
             TryMountScratch();
         }
-        ret &= fs_mgr_overlayfs_mount(entry);
+        ret &= fs_mgr_overlayfs_mount_one(entry);
     }
     return ret;
 }
@@ -785,3 +775,38 @@
     }
     return false;
 }
+
+namespace android {
+namespace fs_mgr {
+
+void MountOverlayfs(const FstabEntry& fstab_entry, bool* scratch_can_be_mounted) {
+    if (!OverlayfsSetupAllowed()) {
+        return;
+    }
+    const auto candidates = fs_mgr_overlayfs_candidate_list({fstab_entry});
+    if (candidates.empty()) {
+        return;
+    }
+    const auto& entry = candidates.front();
+    if (fs_mgr_is_verity_enabled(entry)) {
+        return;
+    }
+    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
+    if (fs_mgr_overlayfs_already_mounted(mount_point)) {
+        return;
+    }
+    if (*scratch_can_be_mounted) {
+        *scratch_can_be_mounted = false;
+        if (!fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
+            TryMountScratch();
+        }
+    }
+    const auto options = fs_mgr_get_overlayfs_options(entry);
+    if (options.empty()) {
+        return;
+    }
+    fs_mgr_overlayfs_mount(mount_point, options);
+}
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 18892f9..733ba2f 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -158,15 +158,25 @@
     // not checkpointing.
     auto vold = GetVold();
     bool checkpointing = false;
-    if (!vold->isCheckpointing(&checkpointing).isOk()) {
-        LOG(ERROR) << "Could not determine checkpointing status.";
-        return false;
-    }
-    if (checkpointing) {
-        LOG(ERROR) << "Cannot use remount when a checkpoint is in progress.";
-        LOG(ERROR) << "To force end checkpointing, call 'vdc checkpoint commitChanges'";
-        LOG(ERROR) << "Warning: this can lead to data corruption if rolled back.";
-        return false;
+    bool show_help = true;
+
+    while (true) {
+        if (!vold->isCheckpointing(&checkpointing).isOk()) {
+            LOG(ERROR) << "Could not determine checkpointing status.";
+            return false;
+        }
+        if (!checkpointing) {
+            break;
+        }
+        if (show_help) {
+            show_help = false;
+            std::cerr << "WARNING: Userdata checkpoint is in progress. To force end checkpointing, "
+                         "call 'vdc checkpoint commitChanges'. This can lead to data corruption if "
+                         "rolled back."
+                      << std::endl;
+            LOG(INFO) << "Waiting for checkpoint to complete and then continue remount.";
+        }
+        std::this_thread::sleep_for(4s);
     }
     return true;
 }
@@ -370,8 +380,8 @@
 
     // Now remount!
     for (const auto& mnt_point : {mount_point, entry.mount_point}) {
-        if (::mount(blk_device.c_str(), mnt_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
-                    nullptr) == 0) {
+        if (::mount(blk_device.c_str(), mnt_point.c_str(), entry.fs_type.c_str(),
+                    MS_REMOUNT | MS_NOATIME, nullptr) == 0) {
             LOG(INFO) << "Remounted " << mnt_point << " as RW";
             return true;
         }
diff --git a/fs_mgr/fs_mgr_roots.cpp b/fs_mgr/fs_mgr_roots.cpp
index 2ad8125..a697fb3 100644
--- a/fs_mgr/fs_mgr_roots.cpp
+++ b/fs_mgr/fs_mgr_roots.cpp
@@ -115,8 +115,8 @@
         return true;
     }
 
-    static const std::vector<std::string> supported_fs{"ext4", "squashfs", "vfat", "f2fs", "erofs",
-                                                       "none"};
+    static const std::vector<std::string> supported_fs{"ext4", "squashfs", "vfat", "exfat", "f2fs",
+                                                       "erofs", "none"};
     if (std::find(supported_fs.begin(), supported_fs.end(), rec->fs_type) == supported_fs.end()) {
         LERROR << "unknown fs_type \"" << rec->fs_type << "\" for " << mount_point;
         return false;
diff --git a/fs_mgr/fs_mgr_vendor_overlay.cpp b/fs_mgr/fs_mgr_vendor_overlay.cpp
index bacfa4b..6b742e5 100644
--- a/fs_mgr/fs_mgr_vendor_overlay.cpp
+++ b/fs_mgr/fs_mgr_vendor_overlay.cpp
@@ -112,6 +112,7 @@
     // properties are loaded.
     static const auto vndk_version = android::base::GetProperty(kVndkVersionPropertyName, "");
     if (vndk_version.empty()) {
+        // Vendor overlay is disabled from VNDK deprecated devices.
         LINFO << "vendor overlay: vndk version not defined";
         return false;
     }
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index bdaabbf..bf68b2c 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -30,6 +30,9 @@
 namespace android {
 namespace fs_mgr {
 
+// Mount the overlayfs override for |fstab_entry|.
+void MountOverlayfs(const FstabEntry& fstab_entry, bool* scratch_can_be_mounted);
+
 void MapScratchPartitionIfNeeded(Fstab* fstab,
                                  const std::function<bool(const std::set<std::string>&)>& init);
 
diff --git a/fs_mgr/include_fstab b/fs_mgr/include_fstab
deleted file mode 120000
index 728737f..0000000
--- a/fs_mgr/include_fstab
+++ /dev/null
@@ -1 +0,0 @@
-libfstab/include
\ No newline at end of file
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 3a9ed9b..e261aa3 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -612,8 +612,12 @@
 
 std::optional<std::string> ExtractBlockDeviceName(const std::string& path) {
     static constexpr std::string_view kDevBlockPrefix("/dev/block/");
-    if (android::base::StartsWith(path, kDevBlockPrefix)) {
-        return path.substr(kDevBlockPrefix.length());
+    std::string real_path;
+    if (!android::base::Realpath(path, &real_path)) {
+        real_path = path;
+    }
+    if (android::base::StartsWith(real_path, kDevBlockPrefix)) {
+        return real_path.substr(kDevBlockPrefix.length());
     }
     return {};
 }
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 3e7ecc6..22c475f 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -52,8 +52,8 @@
 
 static constexpr uint64_t kSectorSize = 512;
 
-// Returns `path` without /dev/block prefix if and only if `path` starts with
-// that prefix.
+// Returns `path` without /dev/block prefix if `path` starts with that prefix.
+// Or, if `path` is a symlink, do the same with its real path.
 std::optional<std::string> ExtractBlockDeviceName(const std::string& path);
 
 // This interface is for testing purposes. See DeviceMapper proper for what these methods do.
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index fb22423..be48de6 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -288,14 +288,82 @@
     return false;
 }
 
-AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta(const FstabEntry& fstab_entry,
-                                            const std::vector<std::string>& preload_avb_key_blobs) {
+bool IsPublicKeyMatching(const FstabEntry& fstab_entry, const std::string& public_key_data,
+                         const std::vector<std::string>& preload_avb_key_blobs) {
     // At least one of the following should be provided for public key matching.
     if (preload_avb_key_blobs.empty() && fstab_entry.avb_keys.empty()) {
         LERROR << "avb_keys=/path/to/key(s) is missing for " << fstab_entry.mount_point;
-        return nullptr;
+        return false;
     }
 
+    // Expected key shouldn't be empty.
+    if (public_key_data.empty()) {
+        LERROR << "public key data shouldn't be empty for " << fstab_entry.mount_point;
+        return false;
+    }
+
+    // Performs key matching for preload_avb_key_blobs first, if it is present.
+    if (!preload_avb_key_blobs.empty()) {
+        if (std::find(preload_avb_key_blobs.begin(), preload_avb_key_blobs.end(),
+                      public_key_data) != preload_avb_key_blobs.end()) {
+            return true;
+        }
+    }
+
+    // Performs key matching for fstab_entry.avb_keys if necessary.
+    // Note that it is intentional to match both preload_avb_key_blobs and fstab_entry.avb_keys.
+    // Some keys might only be available before init chroots into /system, e.g., /avb/key1
+    // in the first-stage ramdisk, while other keys might only be available after the chroot,
+    // e.g., /system/etc/avb/key2.
+    // fstab_entry.avb_keys might be either a directory containing multiple keys,
+    // or a string indicating multiple keys separated by ':'.
+    std::vector<std::string> allowed_avb_keys;
+    auto list_avb_keys_in_dir = ListFiles(fstab_entry.avb_keys);
+    if (list_avb_keys_in_dir.ok()) {
+        std::sort(list_avb_keys_in_dir->begin(), list_avb_keys_in_dir->end());
+        allowed_avb_keys = *list_avb_keys_in_dir;
+    } else {
+        allowed_avb_keys = Split(fstab_entry.avb_keys, ":");
+    }
+    return ValidatePublicKeyBlob(public_key_data, allowed_avb_keys);
+}
+
+bool IsHashtreeDescriptorRootDigestMatching(const FstabEntry& fstab_entry,
+                                            const std::vector<VBMetaData>& vbmeta_images,
+                                            const std::string& ab_suffix,
+                                            const std::string& ab_other_suffix) {
+    // Read expected value of hashtree descriptor root digest from fstab_entry.
+    std::string root_digest_expected;
+    if (!ReadFileToString(fstab_entry.avb_hashtree_digest, &root_digest_expected)) {
+        LERROR << "Failed to load expected root digest for " << fstab_entry.mount_point;
+        return false;
+    }
+
+    // Read actual hashtree descriptor from vbmeta image.
+    std::string partition_name = DeriveAvbPartitionName(fstab_entry, ab_suffix, ab_other_suffix);
+    if (partition_name.empty()) {
+        LERROR << "Failed to find partition name for " << fstab_entry.mount_point;
+        return false;
+    }
+    std::unique_ptr<FsAvbHashtreeDescriptor> hashtree_descriptor =
+            android::fs_mgr::GetHashtreeDescriptor(partition_name, vbmeta_images);
+    if (!hashtree_descriptor) {
+        LERROR << "Not found hashtree descriptor for " << fstab_entry.mount_point;
+        return false;
+    }
+
+    // Performs hashtree descriptor root digest matching.
+    if (hashtree_descriptor->root_digest != root_digest_expected) {
+        LERROR << "root digest (" << hashtree_descriptor->root_digest
+               << ") is different from expected value (" << root_digest_expected << ")";
+        return false;
+    }
+
+    return true;
+}
+
+AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta(const FstabEntry& fstab_entry,
+                                            const std::vector<std::string>& preload_avb_key_blobs) {
     // Binds allow_verification_error and rollback_protection to device unlock state.
     bool allow_verification_error = IsAvbPermissive();
     bool rollback_protection = !allow_verification_error;
@@ -333,40 +401,24 @@
             return nullptr;
     }
 
-    bool public_key_match = false;
-    // Performs key matching for preload_avb_key_blobs first, if it is present.
-    if (!public_key_data.empty() && !preload_avb_key_blobs.empty()) {
-        if (std::find(preload_avb_key_blobs.begin(), preload_avb_key_blobs.end(),
-                      public_key_data) != preload_avb_key_blobs.end()) {
-            public_key_match = true;
+    // Verify vbmeta image checking by either public key or hashtree descriptor root digest.
+    if (!preload_avb_key_blobs.empty() || !fstab_entry.avb_keys.empty()) {
+        if (!IsPublicKeyMatching(fstab_entry, public_key_data, preload_avb_key_blobs)) {
+            avb_handle->status_ = AvbHandleStatus::kVerificationError;
+            LWARNING << "Found unknown public key used to sign " << fstab_entry.mount_point;
+            if (!allow_verification_error) {
+                LERROR << "Unknown public key is not allowed";
+                return nullptr;
+            }
         }
-    }
-    // Performs key matching for fstab_entry.avb_keys if necessary.
-    // Note that it is intentional to match both preload_avb_key_blobs and fstab_entry.avb_keys.
-    // Some keys might only be availble before init chroots into /system, e.g., /avb/key1
-    // in the first-stage ramdisk, while other keys might only be available after the chroot,
-    // e.g., /system/etc/avb/key2.
-    if (!public_key_data.empty() && !public_key_match) {
-        // fstab_entry.avb_keys might be either a directory containing multiple keys,
-        // or a string indicating multiple keys separated by ':'.
-        std::vector<std::string> allowed_avb_keys;
-        auto list_avb_keys_in_dir = ListFiles(fstab_entry.avb_keys);
-        if (list_avb_keys_in_dir.ok()) {
-            std::sort(list_avb_keys_in_dir->begin(), list_avb_keys_in_dir->end());
-            allowed_avb_keys = *list_avb_keys_in_dir;
-        } else {
-            allowed_avb_keys = Split(fstab_entry.avb_keys, ":");
-        }
-        if (ValidatePublicKeyBlob(public_key_data, allowed_avb_keys)) {
-            public_key_match = true;
-        }
-    }
-
-    if (!public_key_match) {
+    } else if (!IsHashtreeDescriptorRootDigestMatching(fstab_entry, avb_handle->vbmeta_images_,
+                                                       avb_handle->slot_suffix_,
+                                                       avb_handle->other_slot_suffix_)) {
         avb_handle->status_ = AvbHandleStatus::kVerificationError;
-        LWARNING << "Found unknown public key used to sign " << fstab_entry.mount_point;
+        LWARNING << "Found unknown hashtree descriptor root digest used on "
+                 << fstab_entry.mount_point;
         if (!allow_verification_error) {
-            LERROR << "Unknown public key is not allowed";
+            LERROR << "Verification based on root digest failed. Vbmeta image is not allowed.";
             return nullptr;
         }
     }
diff --git a/fs_mgr/libfstab/fstab.cpp b/fs_mgr/libfstab/fstab.cpp
index 32460b1..6fa22fe 100644
--- a/fs_mgr/libfstab/fstab.cpp
+++ b/fs_mgr/libfstab/fstab.cpp
@@ -286,6 +286,10 @@
             }
         } else if (StartsWith(flag, "avb_keys=")) {  // must before the following "avb"
             entry->avb_keys = arg;
+        } else if (StartsWith(flag, "avb_hashtree_digest=")) {
+            // "avb_hashtree_digest" must before the following "avb"
+            // The path where hex-encoded hashtree descriptor root digest is located.
+            entry->avb_hashtree_digest = arg;
         } else if (StartsWith(flag, "avb")) {
             entry->fs_mgr_flags.avb = true;
             entry->vbmeta_partition = arg;
@@ -716,7 +720,7 @@
     if (!ReadFstabFromFileCommon(path, fstab)) {
         return false;
     }
-    if (path != kProcMountsPath) {
+    if (path != kProcMountsPath && !InRecovery()) {
         if (!access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
             std::string dsu_slot;
             if (!android::gsi::GetActiveDsu(&dsu_slot)) {
diff --git a/fs_mgr/libfstab/include/fstab/fstab.h b/fs_mgr/libfstab/include/fstab/fstab.h
index 09471f0..5e4019c 100644
--- a/fs_mgr/libfstab/include/fstab/fstab.h
+++ b/fs_mgr/libfstab/include/fstab/fstab.h
@@ -57,6 +57,7 @@
     uint64_t zram_backingdev_size = 0;
     std::string avb_keys;
     std::string lowerdir;
+    std::string avb_hashtree_digest;
 
     struct FsMgrFlags {
         bool wait : 1;
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 6cb2c51..4e6e97b 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -1211,6 +1211,15 @@
     header_.flags |= LP_HEADER_FLAG_VIRTUAL_AB_DEVICE;
 }
 
+void MetadataBuilder::SetOverlaysActiveFlag(bool flag) {
+    RequireExpandedMetadataHeader();
+    if (flag) {
+        header_.flags |= LP_HEADER_FLAG_OVERLAYS_ACTIVE;
+    } else {
+        header_.flags &= ~LP_HEADER_FLAG_OVERLAYS_ACTIVE;
+    }
+}
+
 bool MetadataBuilder::IsABDevice() {
     return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty();
 }
diff --git a/fs_mgr/liblp/fuzzer/README.md b/fs_mgr/liblp/fuzzer/README.md
index f831e2e..015c59b 100644
--- a/fs_mgr/liblp/fuzzer/README.md
+++ b/fs_mgr/liblp/fuzzer/README.md
@@ -18,15 +18,14 @@
 9. alignmentOffset (parameter name: "alignment_offset")
 10. logicalBlockSize (parameter name: "logical_block_size")
 11. maxMetadataSize (parameter name: "max_metadata_size")
-12. numSlots (parameter name: "metadata_slot_count")
-13. deviceIndex (parameter name: "device_index")
-14. start (parameter name: "start")
-15. end (parameter name: "end")
-16. addedGroupName (parameter name: "group_name")
-17. partitionGroupName (parameter name: "partition_name")
-18. numSectors (parameter name: "num_sectors")
-19. physicalSector (parameter name: "physical_sector")
-20. resizedPartitionSize (parameter name: "requested_size")
+12. deviceIndex (parameter name: "device_index")
+13. start (parameter name: "start")
+14. end (parameter name: "end")
+15. addedGroupName (parameter name: "group_name")
+16. partitionGroupName (parameter name: "partition_name")
+17. numSectors (parameter name: "num_sectors")
+18. physicalSector (parameter name: "physical_sector")
+19. resizedPartitionSize (parameter name: "requested_size")
 
 | Parameter| Valid Values| Configured Value|
 |------------- |-------------| ----- |
@@ -41,7 +40,6 @@
 |`alignmentOffset`| Integer |Value obtained from FuzzedDataProvider|
 |`logicalBlockSize`| Integer |Value obtained from FuzzedDataProvider|
 |`maxMetadataSize`| Integer value from `0` to `10000` |Value obtained from FuzzedDataProvider|
-|`numSlots`| Integer value from `0` to `2` |Value obtained from FuzzedDataProvider|
 |`deviceIndex`| Integer |Value obtained from FuzzedDataProvider|
 |`start`| Integer |Value obtained from FuzzedDataProvider|
 |`end`| Integer |Value obtained from FuzzedDataProvider|
@@ -67,17 +65,15 @@
 1. kAttributeTypes (parameter name: "attribute")
 2. blockDevSize (parameter name: "blockdev_size")
 3. metadataMaxSize (parameter name: "metadata_max_size")
-4. metadataSlotCount (parameter name: "metadata_slot_count")
-5. partitionName (parameter name: "partition_name")
-6. data (parameter name: "data")
-7. imageName (parameter name: "image_name")
+4. partitionName (parameter name: "partition_name")
+5. data (parameter name: "data")
+6. imageName (parameter name: "image_name")
 
 | Parameter| Valid Values| Configured Value|
 |------------- |-------------| ----- |
 |`kAttributeTypes`| 1.`LP_PARTITION_ATTR_NONE`,<br/> 2.`LP_PARTITION_ATTR_READONLY`,<br/> 3.`LP_PARTITION_ATTR_SLOT_SUFFIXED`,<br/> 4.`LP_PARTITION_ATTR_UPDATED`,<br/> 5.`LP_PARTITION_ATTR_DISABLED`|Value obtained from FuzzedDataProvider|
 |`blockDevSize`| Integer value from `0` to `100000`|Value obtained from FuzzedDataProvider|
 |`metadataMaxSize`| Integer value from `0` to `10000` |Value obtained from FuzzedDataProvider|
-|`metadataSlotCount`| Integer value from `0` to `2` |Value obtained from FuzzedDataProvider|
 |`partitionName`| String |Value obtained from FuzzedDataProvider|
 |`data`| String |Value obtained from FuzzedDataProvider|
 |`imageName`| String |Value obtained from FuzzedDataProvider|
@@ -102,12 +98,11 @@
 4. logicalBlockSize (parameter name: "logical_block_size")
 5. blockDevSize (parameter name: "blockdev_size")
 6. metadataMaxSize (parameter name: "metadata_max_size")
-7. metadataSlotCount (parameter name: "metadata_slot_count")
-8. blockDeviceInfoName (parameter name: "block_device_info_name")
-9. numSectors (parameter name: "num_sectors")
-10. physicalSector (parameter name: "physical_sector")
-11. sparsify (parameter name: "sparsify")
-12. buffer (parameter name: "data")
+7. blockDeviceInfoName (parameter name: "block_device_info_name")
+8. numSectors (parameter name: "num_sectors")
+9. physicalSector (parameter name: "physical_sector")
+10. sparsify (parameter name: "sparsify")
+11. buffer (parameter name: "data")
 
 | Parameter| Valid Values| Configured Value|
 |------------- |-------------| ----- |
@@ -117,7 +112,6 @@
 |`logicalBlockSize`| Integer |Value obtained from FuzzedDataProvider|
 |`blockDevSize`| Integer value in multiples of `LP_SECTOR_SIZE`|Value obtained from FuzzedDataProvider|
 |`metadataMaxSize`| Integer value from `0` to `10000` |Value obtained from FuzzedDataProvider|
-|`metadataSlotCount`| Integer value from `0` to `2` |Value obtained from FuzzedDataProvider|
 |`blockDeviceInfoName`| String |Value obtained from FuzzedDataProvider|
 |`numSectors`| Integer value from `1` to `1000000` |Value obtained from FuzzedDataProvider|
 |`physicalSector`| Integer value from `1` to `1000000` |Value obtained from FuzzedDataProvider|
diff --git a/fs_mgr/liblp/fuzzer/liblp_apis_fuzzer.cpp b/fs_mgr/liblp/fuzzer/liblp_apis_fuzzer.cpp
index b6fbc14..cb192ea 100644
--- a/fs_mgr/liblp/fuzzer/liblp_apis_fuzzer.cpp
+++ b/fs_mgr/liblp/fuzzer/liblp_apis_fuzzer.cpp
@@ -39,8 +39,6 @@
 static constexpr uint32_t kValidLogicalBlockSize = 4096;
 static constexpr uint32_t kMinMetadataSize = 0;
 static constexpr uint32_t kMaxMetadataSize = 10000;
-static constexpr uint32_t kMinSlot = 0;
-static constexpr uint32_t kMaxSlot = 10;
 static constexpr uint32_t kMinFactor = 0;
 static constexpr uint32_t kMaxFactor = 10;
 static constexpr uint32_t kMetadataGeometrySize = 4096;
@@ -108,7 +106,7 @@
     uint32_t randomMetadataMaxSize =
             mFdp.ConsumeIntegralInRange<uint32_t>(kMinMetadataSize, kMaxMetadataSize);
     uint32_t metadataMaxSize = mFdp.ConsumeBool() ? kMetadataSize : randomMetadataMaxSize;
-    uint32_t metadataSlotCount = mFdp.ConsumeIntegralInRange<uint32_t>(kMinSlot, kMaxSlot);
+    uint32_t metadataSlotCount = mFdp.ConsumeBool() ? 0 : 1;
     mBuilder = MetadataBuilder::New(blockDevSize, metadataMaxSize, metadataSlotCount);
 
     if (mBuilder.get()) {
@@ -128,14 +126,15 @@
         Partition* super = mBuilder->AddPartition(mSuperPartitionName, LP_PARTITION_ATTR_READONLY);
         mBuilder->AddPartition(mPartitionName, LP_PARTITION_ATTR_READONLY);
 
-        int64_t numSectors = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange<uint64_t>(
-                                                          kMinSectorValue, kMaxSectorValue)
-                                                : kValidNumSectors;
-        int64_t physicalSector = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange<uint64_t>(
+        if (super) {
+            int64_t numSectors = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange<uint64_t>(
                                                               kMinSectorValue, kMaxSectorValue)
-                                                    : kValidPhysicalSector;
-
-        mBuilder->AddLinearExtent(super, mBlockDeviceInfoName, numSectors, physicalSector);
+                                                    : kValidNumSectors;
+            int64_t physicalSector = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange<uint64_t>(
+                                                                  kMinSectorValue, kMaxSectorValue)
+                                                        : kValidPhysicalSector;
+            mBuilder->AddLinearExtent(super, mBlockDeviceInfoName, numSectors, physicalSector);
+        }
     }
 }
 
@@ -198,13 +197,13 @@
                     [&]() {
                         uint32_t groupVectorSize = metadata->groups.size();
                         uint32_t randomGroupIndex =
-                                mFdp.ConsumeIntegralInRange<uint32_t>(0, groupVectorSize);
+                                mFdp.ConsumeIntegralInRange<uint32_t>(0, groupVectorSize - 1);
                         GetPartitionGroupName(metadata->groups[randomGroupIndex]);
                     },
                     [&]() {
                         uint32_t blockDeviceVectorSize = metadata->block_devices.size();
                         uint32_t randomBlockDeviceIndex =
-                                mFdp.ConsumeIntegralInRange<uint32_t>(0, blockDeviceVectorSize);
+                                mFdp.ConsumeIntegralInRange<uint32_t>(0, blockDeviceVectorSize - 1);
                         GetBlockDevicePartitionName(
                                 metadata->block_devices[randomBlockDeviceIndex]);
                     },
@@ -224,7 +223,7 @@
                     [&]() {
                         uint32_t partitionVectorSize = metadata->partitions.size();
                         uint32_t randomPartitionIndex =
-                                mFdp.ConsumeIntegralInRange<uint32_t>(0, partitionVectorSize);
+                                mFdp.ConsumeIntegralInRange<uint32_t>(0, partitionVectorSize - 1);
                         GetPartitionName(metadata->partitions[randomPartitionIndex]);
                     },
                     [&]() { GetTotalSuperPartitionSize(metadataValue); },
diff --git a/fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp b/fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp
index e5fbe27..7f09ac8 100644
--- a/fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp
+++ b/fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp
@@ -48,8 +48,6 @@
 static constexpr uint32_t kZeroAlignment = 0;
 static constexpr uint32_t kZeroAlignmentOffset = 0;
 static constexpr uint32_t kMaxBytes = 20;
-static constexpr uint32_t kMinSlot = 0;
-static constexpr uint32_t kMaxSlot = 10;
 static constexpr uint32_t kMinBuilder = 0;
 static constexpr uint32_t kMaxBuilder = 4;
 
@@ -108,9 +106,7 @@
     switch (randomBuilder) {
         case 0: {
             uint32_t maxMetadataSize = getParamValue(kValidMaxMetadataSize);
-            uint32_t numSlots = mFdp.ConsumeBool()
-                                        ? kMaxSlot
-                                        : mFdp.ConsumeIntegralInRange<uint32_t>(kMinSlot, kMaxSlot);
+            uint32_t numSlots = mFdp.ConsumeBool() ? 0 : 1;
             mBuilder = MetadataBuilder::New(mBlockDevices, superBlockDeviceName, maxMetadataSize,
                                             numSlots);
             break;
@@ -120,14 +116,14 @@
                     mFdp.ConsumeIntegralInRange<uint64_t>(kMinBlockDevValue, kMaxBlockDevValue);
             uint32_t metadataMaxSize =
                     mFdp.ConsumeIntegralInRange<uint32_t>(kMinMetadataValue, kMaxMetadataValue);
-            uint32_t metadataSlotCount = mFdp.ConsumeIntegralInRange<uint32_t>(kMinSlot, kMaxSlot);
+            uint32_t metadataSlotCount = mFdp.ConsumeBool() ? 0 : 1;
             mBuilder = MetadataBuilder::New(blockDevSize, metadataMaxSize, metadataSlotCount);
             break;
         }
         case 2: {
             uint64_t blockDevSize = getParamValue(kValidBlockSize);
             uint32_t metadataSize = getParamValue(kValidMetadataSize);
-            uint32_t metadataSlotCount = mFdp.ConsumeIntegralInRange<uint32_t>(kMinSlot, kMaxSlot);
+            uint32_t metadataSlotCount = mFdp.ConsumeBool() ? 0 : 1;
             mBuilder = MetadataBuilder::New(blockDevSize, metadataSize, metadataSlotCount);
             break;
         }
@@ -213,13 +209,13 @@
 void BuilderFuzzer::callVerifyExtentsAgainstSourceMetadata() {
     uint64_t sourceBlockDevSize = getParamValue(kValidBlockSize);
     uint32_t sourceMetadataMaxSize = getParamValue(kValidMetadataSize);
-    uint32_t sourceSlotCount = mFdp.ConsumeIntegralInRange<uint32_t>(0, 2);
+    uint32_t sourceSlotCount = mFdp.ConsumeIntegralInRange<uint32_t>(0, 1);
     auto sourceBuilder =
             MetadataBuilder::New(sourceBlockDevSize, sourceMetadataMaxSize, sourceSlotCount);
 
     uint64_t targetBlockDevSize = getParamValue(kValidBlockSize);
     uint32_t targetMetadataMaxSize = getParamValue(kValidMetadataSize);
-    uint32_t targetSlotCount = mFdp.ConsumeIntegralInRange<uint32_t>(0, 2);
+    uint32_t targetSlotCount = mFdp.ConsumeIntegralInRange<uint32_t>(0, 1);
     auto targetBuilder =
             MetadataBuilder::New(targetBlockDevSize, targetMetadataMaxSize, targetSlotCount);
 
@@ -292,17 +288,20 @@
 
                         int64_t numExtents =
                                 mFdp.ConsumeIntegralInRange<int64_t>(kMinElements, kMaxElements);
-                        bool extentAdded = false;
-                        for (int64_t i = 0; i <= numExtents; ++i) {
-                            extentAdded = mBuilder->AddLinearExtent(mFuzzPartition, kDeviceInfoName,
-                                                                    numSectors, physicalSector);
-                        }
+                        if (mFuzzPartition) {
+                            bool extentAdded = false;
+                            for (int64_t i = 0; i <= numExtents; ++i) {
+                                extentAdded =
+                                        mBuilder->AddLinearExtent(mFuzzPartition, kDeviceInfoName,
+                                                                  numSectors, physicalSector);
+                            }
 
-                        if (extentAdded) {
-                            unique_ptr<LpMetadata> metadata = mBuilder->Export();
-                            uint64_t alignedSize =
-                                    mFdp.ConsumeIntegralInRange<uint64_t>(kMinValue, kMaxValue);
-                            mFuzzPartition->GetBeginningExtents(LP_SECTOR_SIZE * numExtents);
+                            if (extentAdded) {
+                                unique_ptr<LpMetadata> metadata = mBuilder->Export();
+                                uint64_t alignedSize =
+                                        mFdp.ConsumeIntegralInRange<uint64_t>(kMinValue, kMaxValue);
+                                mFuzzPartition->GetBeginningExtents(LP_SECTOR_SIZE * numExtents);
+                            }
                         }
                     },
                     [&]() { callVerifyExtentsAgainstSourceMetadata(); },
diff --git a/fs_mgr/liblp/fuzzer/liblp_super_layout_builder_fuzzer.cpp b/fs_mgr/liblp/fuzzer/liblp_super_layout_builder_fuzzer.cpp
index 887093c..a6642d7 100644
--- a/fs_mgr/liblp/fuzzer/liblp_super_layout_builder_fuzzer.cpp
+++ b/fs_mgr/liblp/fuzzer/liblp_super_layout_builder_fuzzer.cpp
@@ -38,8 +38,6 @@
 static constexpr uint32_t kMinMetadataValue = 0;
 static constexpr uint32_t kMaxMetadataValue = 10000;
 static constexpr uint32_t kMaxBytes = 20;
-static constexpr uint32_t kMinSlot = 0;
-static constexpr uint32_t kMaxSlot = 10;
 static constexpr uint32_t kMinOpen = 0;
 static constexpr uint32_t kMaxOpen = 2;
 
@@ -58,14 +56,14 @@
     void invokeSuperLayoutBuilderAPIs();
     void callRandomOpen(int32_t open);
     void addMultiplePartitions(int32_t numPartitions);
-    void setupSuperLayoutBuilder(string fuzzPartitionName);
+    void setupSuperLayoutBuilder();
     SuperLayoutBuilder mSuperLayoutBuilder;
     unique_ptr<MetadataBuilder> mSuperBuilder;
     unique_ptr<LpMetadata> mMetadata;
     bool mOpenSuccess = false;
 };
 
-void SuperLayoutBuilderFuzzer::setupSuperLayoutBuilder(string fuzzPartitionName) {
+void SuperLayoutBuilderFuzzer::setupSuperLayoutBuilder() {
     uint64_t randomBlockDevSize =
             mFdp.ConsumeIntegralInRange<uint64_t>(kMinBlockDevValue, kMaxBlockDevValue);
     uint64_t blockDevSize = mFdp.ConsumeBool() ? kSuperLayoutValidBlockDevSize : randomBlockDevSize;
@@ -73,7 +71,7 @@
             mFdp.ConsumeIntegralInRange<uint32_t>(kMinMetadataValue, kMaxMetadataValue);
     uint32_t metadataMaxSize =
             mFdp.ConsumeBool() ? kSuperLayoutValidMetadataSize : randomMetadataMaxSize;
-    uint32_t metadataSlotCount = mFdp.ConsumeIntegralInRange<uint32_t>(kMinSlot, kMaxSlot);
+    uint32_t metadataSlotCount = mFdp.ConsumeBool() ? 0 : 1;
     mSuperBuilder = MetadataBuilder::New(blockDevSize, metadataMaxSize, metadataSlotCount);
 
     if (mSuperBuilder.get()) {
@@ -85,10 +83,6 @@
 
         uint32_t randomOpen = mFdp.ConsumeIntegralInRange<uint32_t>(kMinOpen, kMaxOpen);
         callRandomOpen(randomOpen);
-
-        if (!fuzzPartitionName.size()) {
-            fuzzPartitionName = "builder_partition";
-        }
     }
 }
 
@@ -125,7 +119,10 @@
     string imageName = mFdp.ConsumeRandomLengthString(kMaxBytes);
     string fuzzPartitionName =
             mFdp.ConsumeBool() ? "builder_partition" : mFdp.ConsumeRandomLengthString(kMaxBytes);
-    setupSuperLayoutBuilder(fuzzPartitionName);
+    if (!fuzzPartitionName.size()) {
+        fuzzPartitionName = "builder_partition";
+    }
+    setupSuperLayoutBuilder();
     if (mOpenSuccess) {
         while (mFdp.remaining_bytes()) {
             auto invokeSuperAPIs = mFdp.PickValueInArray<const function<void()>>({
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index 54f31bc..957b96b 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -346,6 +346,8 @@
     void SetAutoSlotSuffixing();
     // Set the LP_HEADER_FLAG_VIRTUAL_AB_DEVICE flag.
     void SetVirtualABDeviceFlag();
+    // Set or unset the LP_HEADER_FLAG_OVERLAYS_ACTIVE flag.
+    void SetOverlaysActiveFlag(bool flag);
 
     bool GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const;
     bool UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& info);
diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h
index 41d8b0c..8d77097 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -240,6 +240,9 @@
  */
 #define LP_HEADER_FLAG_VIRTUAL_AB_DEVICE 0x1
 
+/* This device has overlays activated via "adb remount". */
+#define LP_HEADER_FLAG_OVERLAYS_ACTIVE 0x2
+
 /* This struct defines a logical partition entry, similar to what would be
  * present in a GUID Partition Table.
  */
diff --git a/fs_mgr/liblp/super_layout_builder.cpp b/fs_mgr/liblp/super_layout_builder.cpp
index 5349e41..fd7416b 100644
--- a/fs_mgr/liblp/super_layout_builder.cpp
+++ b/fs_mgr/liblp/super_layout_builder.cpp
@@ -17,6 +17,8 @@
 
 #include <liblp/liblp.h>
 
+#include <algorithm>
+
 #include "images.h"
 #include "utility.h"
 #include "writer.h"
diff --git a/fs_mgr/liblp/utility.cpp b/fs_mgr/liblp/utility.cpp
index d8e171b..70c7b79 100644
--- a/fs_mgr/liblp/utility.cpp
+++ b/fs_mgr/liblp/utility.cpp
@@ -25,6 +25,7 @@
 #include <sys/ioctl.h>
 #endif
 
+#include <algorithm>
 #include <map>
 #include <string>
 #include <vector>
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 6fad662..a8a7716 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -198,6 +198,7 @@
         "libsnapshot_cow/cow_format.cpp",
         "libsnapshot_cow/cow_reader.cpp",
         "libsnapshot_cow/parser_v2.cpp",
+        "libsnapshot_cow/parser_v3.cpp",
         "libsnapshot_cow/snapshot_reader.cpp",
         "libsnapshot_cow/writer_base.cpp",
         "libsnapshot_cow/writer_v2.cpp",
@@ -419,6 +420,7 @@
     },
     data: [
         "tools/testdata/cow_v2",
+        "tools/testdata/incompressible_block",
     ],
     auto_gen_config: true,
     require_root: false,
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index fa04c43..7e97dc0 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -114,6 +114,9 @@
 
     // Enable batching for COW writes
     bool batched_writes = 14;
+
+    // Size of v3 operation buffer. Needs to be determined during writer initialization
+    uint64 estimated_ops_buffer_size = 15;
 }
 
 // Next: 8
@@ -250,4 +253,7 @@
 
     // Whether this update attempt used io_uring.
     bool iouring_used = 13;
+
+    // Size of v3 operation buffer. Needs to be determined during writer initialization
+    uint64 estimated_op_count_max = 14;
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index c9777a3..9401c66 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -16,8 +16,10 @@
 
 #include <stdint.h>
 
+#include <limits>
 #include <optional>
 #include <string_view>
+#include <type_traits>
 
 namespace android {
 namespace snapshot {
@@ -32,7 +34,7 @@
 static constexpr uint32_t kCowVersionManifest = 2;
 
 static constexpr uint32_t kMinCowVersion = 1;
-static constexpr uint32_t kMaxCowVersion = 2;
+static constexpr uint32_t kMaxCowVersion = 3;
 
 // Normally, this should be kMaxCowVersion. When a new version is under testing
 // it may be the previous value of kMaxCowVersion.
@@ -94,21 +96,55 @@
 
 } __attribute__((packed));
 
+// Resume point structure used for resume buffer
+struct ResumePoint {
+    // monotonically increasing value used by update_engine
+    uint64_t label;
+    // Index of last CowOperation guaranteed to be resumable
+    uint32_t op_index;
+} __attribute__((packed));
+
+static constexpr uint8_t kNumResumePoints = 4;
+
 struct CowHeaderV3 : public CowHeader {
-    // Location of sequence buffer in COW.
-    uint64_t sequence_buffer_offset;
-    // Size, in bytes, of the CowResumePoint buffer.
-    uint32_t resume_buffer_size;
-    // Size, in bytes, of the CowOperation buffer.
-    uint32_t op_buffer_size;
+    // Number of sequence data stored (each of which is a 32 bit integer)
+    uint64_t sequence_data_count;
+    // Number of currently written resume points &&
+    uint32_t resume_point_count;
+    // Number of max resume points that can be written
+    uint32_t resume_point_max;
+    // Number of CowOperationV3 structs in the operation buffer, currently and total
+    // region size.
+    uint32_t op_count;
+    uint32_t op_count_max;
     // Compression Algorithm
     uint32_t compression_algorithm;
 } __attribute__((packed));
 
+enum class CowOperationType : uint8_t {
+    kCowCopyOp = 1,
+    kCowReplaceOp = 2,
+    kCowZeroOp = 3,
+    kCowLabelOp = 4,
+    kCowClusterOp = 5,
+    kCowXorOp = 6,
+    kCowSequenceOp = 7,
+    kCowFooterOp = std::numeric_limits<uint8_t>::max(),
+};
+
+static constexpr CowOperationType kCowCopyOp = CowOperationType::kCowCopyOp;
+static constexpr CowOperationType kCowReplaceOp = CowOperationType::kCowReplaceOp;
+static constexpr CowOperationType kCowZeroOp = CowOperationType::kCowZeroOp;
+static constexpr CowOperationType kCowLabelOp = CowOperationType::kCowLabelOp;
+static constexpr CowOperationType kCowClusterOp = CowOperationType::kCowClusterOp;
+static constexpr CowOperationType kCowXorOp = CowOperationType::kCowXorOp;
+static constexpr CowOperationType kCowSequenceOp = CowOperationType::kCowSequenceOp;
+static constexpr CowOperationType kCowFooterOp = CowOperationType::kCowFooterOp;
+
 // This structure is the same size of a normal Operation, but is repurposed for the footer.
 struct CowFooterOperation {
     // The operation code (always kCowFooterOp).
-    uint8_t type;
+    CowOperationType type;
 
     // If this operation reads from the data section of the COW, this contains
     // the compression type of that data (see constants below).
@@ -127,7 +163,7 @@
 // V2 version of COW. On disk format for older devices
 struct CowOperationV2 {
     // The operation code (see the constants and structures below).
-    uint8_t type;
+    CowOperationType type;
 
     // If this operation reads from the data section of the COW, this contains
     // the compression type of that data (see constants below).
@@ -159,11 +195,12 @@
     uint64_t source;
 } __attribute__((packed));
 
+static constexpr uint64_t kCowOpSourceInfoDataMask = (1ULL << 48) - 1;
+static constexpr uint64_t kCowOpSourceInfoTypeBit = 60;
+static constexpr uint64_t kCowOpSourceInfoTypeNumBits = 4;
+static constexpr uint64_t kCowOpSourceInfoTypeMask = (1ULL << kCowOpSourceInfoTypeNumBits) - 1;
 // The on disk format of cow (currently ==  CowOperation)
 struct CowOperationV3 {
-    // The operation code (see the constants and structures below).
-    uint8_t type;
-
     // If this operation reads from the data section of the COW, this contains
     // the length.
     uint16_t data_length;
@@ -171,6 +208,10 @@
     // The block of data in the new image that this operation modifies.
     uint32_t new_block;
 
+    // source_info with have the following layout
+    // |---4 bits ---| ---12 bits---| --- 48 bits ---|
+    // |--- type --- | -- unused -- | --- source --- |
+    //
     // The value of |source| depends on the operation code.
     //
     // CopyOp: a 32-bit block location in the source image.
@@ -182,20 +223,34 @@
     // For ops other than Label:
     //  Bits 47-62 are reserved and must be zero.
     // A block is compressed if it’s data is < block_sz
-    uint64_t source_info;
+    uint64_t source_info_;
+    constexpr uint64_t source() const { return source_info_ & kCowOpSourceInfoDataMask; }
+    constexpr void set_source(uint64_t source) {
+        // Clear the first 48 bit first
+        source_info_ &= ~kCowOpSourceInfoDataMask;
+        // Set the actual source field
+        source_info_ |= source & kCowOpSourceInfoDataMask;
+    }
+    constexpr CowOperationType type() const {
+        // this is a mask to grab the first 4 bits of a 64 bit integer
+        const auto type = (source_info_ >> kCowOpSourceInfoTypeBit) & kCowOpSourceInfoTypeMask;
+        return static_cast<CowOperationType>(type);
+    }
+    constexpr void set_type(CowOperationType type) {
+        // Clear the top 4 bits first
+        source_info_ &= ((1ULL << kCowOpSourceInfoTypeBit) - 1);
+        // set the actual type bits
+        source_info_ |= (static_cast<uint64_t>(type) & kCowOpSourceInfoTypeMask)
+                        << kCowOpSourceInfoTypeBit;
+    }
 } __attribute__((packed));
 
+// Ensure that getters/setters added to CowOperationV3 does not increases size
+// of CowOperationV3 struct(no virtual method tables added).
+static_assert(std::is_trivially_copyable_v<CowOperationV3>);
+static_assert(std::is_standard_layout_v<CowOperationV3>);
 static_assert(sizeof(CowOperationV2) == sizeof(CowFooterOperation));
 
-static constexpr uint8_t kCowCopyOp = 1;
-static constexpr uint8_t kCowReplaceOp = 2;
-static constexpr uint8_t kCowZeroOp = 3;
-static constexpr uint8_t kCowLabelOp = 4;
-static constexpr uint8_t kCowClusterOp = 5;
-static constexpr uint8_t kCowXorOp = 6;
-static constexpr uint8_t kCowSequenceOp = 7;
-static constexpr uint8_t kCowFooterOp = -1;
-
 enum CowCompressionAlgorithm : uint8_t {
     kCowCompressNone = 0,
     kCowCompressGz = 1,
@@ -212,10 +267,21 @@
 static constexpr uint8_t kCowReadAheadInProgress = 1;
 static constexpr uint8_t kCowReadAheadDone = 2;
 
-static constexpr uint64_t kCowOpSourceInfoDataMask = (1ULL << 48) - 1;
+static constexpr off_t GetSequenceOffset(const CowHeaderV3& header) {
+    return header.prefix.header_size + header.buffer_size;
+}
 
-static inline uint64_t GetCowOpSourceInfoData(const CowOperation& op) {
-    return op.source_info & kCowOpSourceInfoDataMask;
+static constexpr off_t GetResumeOffset(const CowHeaderV3& header) {
+    return GetSequenceOffset(header) + (header.sequence_data_count * sizeof(uint32_t));
+}
+
+static constexpr off_t GetOpOffset(uint32_t op_index, const CowHeaderV3& header) {
+    return GetResumeOffset(header) + (header.resume_point_max * sizeof(ResumePoint)) +
+           (op_index * sizeof(CowOperationV3));
+}
+
+static constexpr off_t GetDataOffset(const CowHeaderV3& header) {
+    return GetOpOffset(header.op_count_max, header);
 }
 
 struct CowFooter {
@@ -241,7 +307,11 @@
 
 std::ostream& operator<<(std::ostream& os, CowOperationV2 const& arg);
 
-std::ostream& operator<<(std::ostream& os, CowOperation const& arg);
+std::ostream& operator<<(std::ostream& os, CowOperationV3 const& arg);
+
+std::ostream& operator<<(std::ostream& os, ResumePoint const& arg);
+
+std::ostream& operator<<(std::ostream& os, CowOperationType cow_type);
 
 int64_t GetNextOpOffset(const CowOperationV2& op, uint32_t cluster_size);
 int64_t GetNextDataOffset(const CowOperationV2& 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 debaf36..bf4c79f 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -16,7 +16,6 @@
 
 #include <stdint.h>
 
-#include <functional>
 #include <memory>
 #include <optional>
 #include <unordered_map>
@@ -145,6 +144,7 @@
                      size_t ignore_bytes = 0) override;
 
     CowHeader& GetHeader() override { return header_; }
+    const CowHeaderV3& header_v3() const { return header_; }
 
     bool GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read);
     bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
@@ -167,6 +167,12 @@
   private:
     bool ParseV2(android::base::borrowed_fd fd, std::optional<uint64_t> label);
     bool PrepMergeOps();
+    // sequence data is stored as an operation with actual data residing in the data offset.
+    bool GetSequenceDataV2(std::vector<uint32_t>* merge_op_blocks, std::vector<int>* other_ops,
+                           std::unordered_map<uint32_t, int>* block_map);
+    // v3 of the cow writes sequence data within its own separate sequence buffer.
+    bool GetSequenceData(std::vector<uint32_t>* merge_op_blocks, std::vector<int>* other_ops,
+                         std::unordered_map<uint32_t, int>* block_map);
     uint64_t FindNumCopyops();
     uint8_t GetCompressionType();
 
@@ -182,16 +188,14 @@
     uint64_t num_total_data_ops_{};
     uint64_t num_ordered_ops_to_merge_{};
     bool has_seq_ops_{};
-    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> xor_data_loc_;
     ReaderFlags reader_flag_;
     bool is_merge_{};
-    uint8_t compression_type_ = kCowCompressNone;
 };
 
 // Though this function takes in a CowHeaderV3, the struct could be populated as a v1/v2 CowHeader.
 // The extra fields will just be filled as 0. V3 header is strictly a superset of v1/v2 header and
 // contains all of the latter's field
 bool ReadCowHeader(android::base::borrowed_fd fd, CowHeaderV3* header);
-
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 3016e93..7df976d 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -33,7 +33,10 @@
 
 namespace android {
 namespace snapshot {
-
+struct CowSizeInfo {
+    uint64_t cow_size;
+    uint64_t op_count_max;
+};
 struct CowOptions {
     uint32_t block_size = 4096;
     std::string compression;
@@ -54,6 +57,9 @@
 
     // Batch write cluster ops
     bool batch_write = false;
+
+    // Size of the cow operation buffer; used in v3 only.
+    uint64_t op_count_max = 0;
 };
 
 // Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -89,8 +95,9 @@
     // to ensure that the correct headers and footers are written.
     virtual bool Finalize() = 0;
 
-    // Return number of bytes the cow image occupies on disk.
-    virtual uint64_t GetCowSize() = 0;
+    // Return number of bytes the cow image occupies on disk + the size of sequence && ops buffer
+    // The latter two fields are used in v3 cow format and left as 0 for v2 cow format
+    virtual CowSizeInfo GetCowSizeInfo() const = 0;
 
     virtual uint32_t GetBlockSize() const = 0;
     virtual std::optional<uint32_t> GetMaxBlocks() const = 0;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
index c58c654..8491fb0 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
@@ -24,8 +24,7 @@
     using FileDescriptor = chromeos_update_engine::FileDescriptor;
 
     MOCK_METHOD(bool, Finalize, (), (override));
-
-    MOCK_METHOD(uint64_t, GetCowSize, (), (override));
+    MOCK_METHOD(CowSizeInfo, GetCowSizeInfo, (), (const, override));
 
     MOCK_METHOD(bool, AddCopy, (uint64_t, uint64_t, uint64_t), (override));
     MOCK_METHOD(bool, AddRawBlocks, (uint64_t, const void*, size_t), (override));
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 08a79ba..d102863 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -826,6 +826,9 @@
     bool DeleteDeviceIfExists(const std::string& name,
                               const std::chrono::milliseconds& timeout_ms = {});
 
+    // Set read-ahead size during OTA
+    void SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb);
+
     android::dm::IDeviceMapper& dm_;
     std::unique_ptr<IDeviceInfo> device_;
     std::string metadata_dir_;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index b905291..8d1786c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -14,7 +14,6 @@
 // limitations under the License.
 //
 
-#include <inttypes.h>
 #include <libsnapshot/cow_format.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -30,7 +29,7 @@
 
 using android::base::unique_fd;
 
-std::ostream& EmitCowTypeString(std::ostream& os, uint8_t cow_type) {
+std::ostream& EmitCowTypeString(std::ostream& os, CowOperationType cow_type) {
     switch (cow_type) {
         case kCowCopyOp:
             return os << "kCowCopyOp";
@@ -53,6 +52,10 @@
     }
 }
 
+std::ostream& operator<<(std::ostream& os, CowOperationType cow_type) {
+    return EmitCowTypeString(os, cow_type);
+}
+
 std::ostream& operator<<(std::ostream& os, CowOperationV2 const& op) {
     os << "CowOperationV2(";
     EmitCowTypeString(os, op.type) << ", ";
@@ -80,26 +83,30 @@
     return os;
 }
 
-std::ostream& operator<<(std::ostream& os, CowOperation const& op) {
+std::ostream& operator<<(std::ostream& os, CowOperationV3 const& op) {
     os << "CowOperation(";
-    EmitCowTypeString(os, op.type);
-    if (op.type == kCowReplaceOp || op.type == kCowXorOp || op.type == kCowSequenceOp) {
+    EmitCowTypeString(os, op.type());
+    if (op.type() == kCowReplaceOp || op.type() == kCowXorOp || op.type() == kCowSequenceOp) {
         os << ", data_length:" << op.data_length;
     }
-    if (op.type != kCowClusterOp && op.type != kCowSequenceOp && op.type != kCowLabelOp) {
+    if (op.type() != kCowClusterOp && op.type() != kCowSequenceOp && op.type() != kCowLabelOp) {
         os << ", new_block:" << op.new_block;
     }
-    if (op.type == kCowXorOp || op.type == kCowReplaceOp || op.type == kCowCopyOp) {
-        os << ", source:" << (op.source_info & kCowOpSourceInfoDataMask);
-    } else if (op.type == kCowClusterOp) {
-        os << ", cluster_data:" << (op.source_info & kCowOpSourceInfoDataMask);
-    } else {
-        os << ", label:0x" << android::base::StringPrintf("%" PRIx64, op.source_info);
+    if (op.type() == kCowXorOp || op.type() == kCowReplaceOp || op.type() == kCowCopyOp) {
+        os << ", source:" << op.source();
+    } else if (op.type() == kCowClusterOp) {
+        os << ", cluster_data:" << op.source();
     }
+    // V3 op stores resume points in header, so CowOp can never be Label.
     os << ")";
     return os;
 }
 
+std::ostream& operator<<(std::ostream& os, ResumePoint const& resume_point) {
+    os << "ResumePoint(" << resume_point.label << " , " << resume_point.op_index << ")";
+    return os;
+}
+
 int64_t GetNextOpOffset(const CowOperationV2& op, uint32_t cluster_ops) {
     if (op.type == kCowClusterOp) {
         return op.source;
@@ -121,7 +128,7 @@
 }
 
 bool IsMetadataOp(const CowOperation& op) {
-    switch (op.type) {
+    switch (op.type()) {
         case kCowLabelOp:
         case kCowClusterOp:
         case kCowFooterOp:
@@ -133,7 +140,7 @@
 }
 
 bool IsOrderedOp(const CowOperation& op) {
-    switch (op.type) {
+    switch (op.type()) {
         case kCowCopyOp:
         case kCowXorOp:
             return true;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 3b84c95..1b4a971 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -24,12 +24,13 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <libsnapshot/cow_format.h>
 #include <libsnapshot/cow_reader.h>
 #include <zlib.h>
 
 #include "cow_decompress.h"
-#include "libsnapshot/cow_format.h"
 #include "parser_v2.h"
+#include "parser_v3.h"
 
 namespace android {
 namespace snapshot {
@@ -82,10 +83,9 @@
     cow->merge_op_start_ = merge_op_start_;
     cow->num_total_data_ops_ = num_total_data_ops_;
     cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
-    cow->data_loc_ = data_loc_;
+    cow->xor_data_loc_ = xor_data_loc_;
     cow->block_pos_index_ = block_pos_index_;
     cow->is_merge_ = is_merge_;
-    cow->compression_type_ = compression_type_;
     return cow;
 }
 
@@ -104,11 +104,14 @@
         PLOG(ERROR) << "lseek header failed";
         return false;
     }
-    if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) {
+
+    CHECK_GE(header_.prefix.header_size, sizeof(CowHeader));
+    CHECK_LE(header_.prefix.header_size, sizeof(header_));
+
+    if (!android::base::ReadFully(fd_, &header_, header_.prefix.header_size)) {
         PLOG(ERROR) << "read header failed";
         return false;
     }
-
     return true;
 }
 
@@ -124,52 +127,35 @@
         return false;
     }
 
-    CowParserV2 parser;
-    if (!parser.Parse(fd, header_, label)) {
+    std::unique_ptr<CowParserBase> parser;
+    switch (header_.prefix.major_version) {
+        case 1:
+        case 2:
+            parser = std::make_unique<CowParserV2>();
+            break;
+        case 3:
+            parser = std::make_unique<CowParserV3>();
+            break;
+        default:
+            LOG(ERROR) << "Unknown version: " << header_.prefix.major_version;
+            return false;
+    }
+    if (!parser->Parse(fd, header_, label)) {
         return false;
     }
 
-    footer_ = parser.footer();
-    fd_size_ = parser.fd_size();
-    last_label_ = parser.last_label();
-    data_loc_ = parser.data_loc();
-    ops_ = std::make_shared<std::vector<CowOperation>>(parser.ops()->size());
-
-    // Translate the operation buffer from on disk to in memory
-    for (size_t i = 0; i < parser.ops()->size(); i++) {
-        const auto& v2_op = parser.ops()->at(i);
-
-        auto& new_op = ops_->at(i);
-        new_op.type = v2_op.type;
-        new_op.data_length = v2_op.data_length;
-
-        if (v2_op.new_block > std::numeric_limits<uint32_t>::max()) {
-            LOG(ERROR) << "Out-of-range new block in COW op: " << v2_op;
-            return false;
-        }
-        new_op.new_block = v2_op.new_block;
-
-        uint64_t source_info = v2_op.source;
-        if (new_op.type != kCowLabelOp) {
-            source_info &= kCowOpSourceInfoDataMask;
-            if (source_info != v2_op.source) {
-                LOG(ERROR) << "Out-of-range source value in COW op: " << v2_op;
-                return false;
-            }
-        }
-        if (v2_op.compression != kCowCompressNone) {
-            if (compression_type_ == kCowCompressNone) {
-                compression_type_ = v2_op.compression;
-            } else if (compression_type_ != v2_op.compression) {
-                LOG(ERROR) << "COW has mixed compression types which is not supported;"
-                           << " previously saw " << compression_type_ << ", got "
-                           << v2_op.compression << ", op: " << v2_op;
-                return false;
-            }
-        }
-        new_op.source_info = source_info;
+    TranslatedCowOps ops_info;
+    if (!parser->Translate(&ops_info)) {
+        return false;
     }
 
+    header_ = ops_info.header;
+    ops_ = std::move(ops_info.ops);
+    footer_ = parser->footer();
+    fd_size_ = parser->fd_size();
+    last_label_ = parser->last_label();
+    xor_data_loc_ = parser->xor_data_loc();
+
     // If we're resuming a write, we're not ready to merge
     if (label.has_value()) return true;
     return PrepMergeOps();
@@ -280,52 +266,31 @@
 //                        Replace-op-4, Zero-op-9, Replace-op-5 }
 //==============================================================
 bool CowReader::PrepMergeOps() {
-    auto merge_op_blocks = std::make_unique<std::vector<uint32_t>>();
     std::vector<int> other_ops;
-    auto seq_ops_set = std::unordered_set<uint32_t>();
-    auto block_map = std::make_unique<std::unordered_map<uint32_t, int>>();
-    size_t num_seqs = 0;
-    size_t read;
+    std::vector<uint32_t> merge_op_blocks;
+    std::unordered_map<uint32_t, int> block_map;
 
-    for (size_t i = 0; i < ops_->size(); i++) {
-        auto& current_op = ops_->data()[i];
-
-        if (current_op.type == kCowSequenceOp) {
-            size_t seq_len = current_op.data_length / sizeof(uint32_t);
-
-            merge_op_blocks->resize(merge_op_blocks->size() + seq_len);
-            if (!GetRawBytes(&current_op, &merge_op_blocks->data()[num_seqs],
-                             current_op.data_length, &read)) {
-                PLOG(ERROR) << "Failed to read sequence op!";
-                return false;
-            }
-            for (size_t j = num_seqs; j < num_seqs + seq_len; j++) {
-                seq_ops_set.insert(merge_op_blocks->data()[j]);
-            }
-            num_seqs += seq_len;
-        }
-
-        if (IsMetadataOp(current_op)) {
-            continue;
-        }
-
-        // Sequence ops must be the first ops in the stream.
-        if (seq_ops_set.empty() && IsOrderedOp(current_op)) {
-            merge_op_blocks->emplace_back(current_op.new_block);
-        } else if (seq_ops_set.count(current_op.new_block) == 0) {
-            other_ops.push_back(current_op.new_block);
-        }
-        block_map->insert({current_op.new_block, i});
+    switch (header_.prefix.major_version) {
+        case 1:
+        case 2:
+            GetSequenceDataV2(&merge_op_blocks, &other_ops, &block_map);
+            break;
+        case 3:
+            GetSequenceData(&merge_op_blocks, &other_ops, &block_map);
+            break;
+        default:
+            break;
     }
-    for (auto block : *merge_op_blocks) {
-        if (block_map->count(block) == 0) {
+
+    for (auto block : merge_op_blocks) {
+        if (block_map.count(block) == 0) {
             LOG(ERROR) << "Invalid Sequence Ops. Could not find Cow Op for new block " << block;
             return false;
         }
     }
 
-    if (merge_op_blocks->size() > header_.num_merge_ops) {
-        num_ordered_ops_to_merge_ = merge_op_blocks->size() - header_.num_merge_ops;
+    if (merge_op_blocks.size() > header_.num_merge_ops) {
+        num_ordered_ops_to_merge_ = merge_op_blocks.size() - header_.num_merge_ops;
     } else {
         num_ordered_ops_to_merge_ = 0;
     }
@@ -341,9 +306,9 @@
         std::sort(other_ops.begin(), other_ops.end(), std::greater<int>());
     }
 
-    merge_op_blocks->insert(merge_op_blocks->end(), other_ops.begin(), other_ops.end());
+    merge_op_blocks.insert(merge_op_blocks.end(), other_ops.begin(), other_ops.end());
 
-    num_total_data_ops_ = merge_op_blocks->size();
+    num_total_data_ops_ = merge_op_blocks.size();
     if (header_.num_merge_ops > 0) {
         merge_op_start_ = header_.num_merge_ops;
     }
@@ -353,24 +318,94 @@
         // the ops vector as required for merge operations.
         auto merge_ops_buffer = std::make_shared<std::vector<CowOperation>>();
         merge_ops_buffer->reserve(num_total_data_ops_);
-        for (auto block : *merge_op_blocks) {
-            merge_ops_buffer->emplace_back(ops_->data()[block_map->at(block)]);
+        for (auto block : merge_op_blocks) {
+            merge_ops_buffer->emplace_back(ops_->data()[block_map.at(block)]);
         }
         ops_->clear();
         ops_ = merge_ops_buffer;
         ops_->shrink_to_fit();
     } else {
-        for (auto block : *merge_op_blocks) {
-            block_pos_index_->push_back(block_map->at(block));
+        for (auto block : merge_op_blocks) {
+            block_pos_index_->push_back(block_map.at(block));
         }
     }
 
-    block_map->clear();
-    merge_op_blocks->clear();
+    block_map.clear();
+    merge_op_blocks.clear();
 
     return true;
 }
 
+bool CowReader::GetSequenceDataV2(std::vector<uint32_t>* merge_op_blocks,
+                                  std::vector<int>* other_ops,
+                                  std::unordered_map<uint32_t, int>* block_map) {
+    auto seq_ops_set = std::unordered_set<uint32_t>();
+    size_t num_seqs = 0;
+    size_t read;
+    for (size_t i = 0; i < ops_->size(); i++) {
+        auto& current_op = ops_->data()[i];
+
+        if (current_op.type() == kCowSequenceOp) {
+            size_t seq_len = current_op.data_length / sizeof(uint32_t);
+
+            merge_op_blocks->resize(merge_op_blocks->size() + seq_len);
+            if (!GetRawBytes(&current_op, &merge_op_blocks->data()[num_seqs],
+                             current_op.data_length, &read)) {
+                PLOG(ERROR) << "Failed to read sequence op!";
+                return false;
+            }
+            for (size_t j = num_seqs; j < num_seqs + seq_len; j++) {
+                seq_ops_set.insert(merge_op_blocks->at(j));
+            }
+            num_seqs += seq_len;
+        }
+
+        if (IsMetadataOp(current_op)) {
+            continue;
+        }
+
+        // Sequence ops must be the first ops in the stream.
+        if (seq_ops_set.empty() && IsOrderedOp(current_op)) {
+            merge_op_blocks->emplace_back(current_op.new_block);
+        } else if (seq_ops_set.count(current_op.new_block) == 0) {
+            other_ops->push_back(current_op.new_block);
+        }
+        block_map->insert({current_op.new_block, i});
+    }
+    return false;
+}
+
+bool CowReader::GetSequenceData(std::vector<uint32_t>* merge_op_blocks, std::vector<int>* other_ops,
+                                std::unordered_map<uint32_t, int>* block_map) {
+    std::unordered_set<uint32_t> seq_ops_set;
+    // read sequence ops data
+    merge_op_blocks->resize(header_.sequence_data_count);
+    if (!android::base::ReadFullyAtOffset(
+                fd_, merge_op_blocks->data(),
+                header_.sequence_data_count * sizeof(merge_op_blocks->at(0)),
+                GetSequenceOffset(header_))) {
+        PLOG(ERROR) << "failed to read sequence buffer. seq_data_count: "
+                    << header_.sequence_data_count << " at offset: " << GetSequenceOffset(header_);
+        return false;
+    }
+    seq_ops_set.reserve(merge_op_blocks->size());
+    for (auto& i : *merge_op_blocks) {
+        seq_ops_set.insert(i);
+    }
+    // read ordered op data
+    for (size_t i = 0; i < ops_->size(); i++) {
+        auto& current_op = ops_->data()[i];
+        // Sequence ops must be the first ops in the stream.
+        if (seq_ops_set.empty()) {
+            merge_op_blocks->emplace_back(current_op.new_block);
+        } else if (seq_ops_set.count(current_op.new_block) == 0) {
+            other_ops->push_back(current_op.new_block);
+        }
+        block_map->insert({current_op.new_block, i});
+    }
+    return true;
+}
+
 bool CowReader::VerifyMergeOps() {
     auto itr = GetMergeOpIter(true);
     std::unordered_map<uint64_t, const CowOperation*> overwritten_blocks;
@@ -602,11 +637,11 @@
 }
 
 bool CowReader::GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read) {
-    switch (op->type) {
+    switch (op->type()) {
         case kCowSequenceOp:
         case kCowReplaceOp:
         case kCowXorOp:
-            return GetRawBytes(GetCowOpSourceInfoData(*op), buffer, len, read);
+            return GetRawBytes(op->source(), buffer, len, read);
         default:
             LOG(ERROR) << "Cannot get raw bytes of non-data op: " << *op;
             return false;
@@ -615,8 +650,8 @@
 
 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 < header_.prefix.header_size || offset >= fd_size_ - sizeof(CowFooter) ||
-        len >= fd_size_ || offset + len > fd_size_ - sizeof(CowFooter)) {
+    if (offset < header_.prefix.header_size || offset >= fd_size_ || offset + len > fd_size_ ||
+        len >= fd_size_) {
         LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
         return false;
     }
@@ -664,7 +699,7 @@
 };
 
 uint8_t CowReader::GetCompressionType() {
-    return compression_type_;
+    return header_.compression_algorithm;
 }
 
 ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
@@ -695,13 +730,13 @@
     }
 
     uint64_t offset;
-    if (op->type == kCowXorOp) {
-        offset = data_loc_->at(op->new_block);
+    if (op->type() == kCowXorOp) {
+        offset = xor_data_loc_->at(op->new_block);
     } else {
-        offset = GetCowOpSourceInfoData(*op);
+        offset = op->source();
     }
-
-    if (!decompressor) {
+    if (!decompressor ||
+        ((op->data_length == header_.block_size) && (header_.prefix.major_version == 3))) {
         CowDataStream stream(this, offset + ignore_bytes, op->data_length - ignore_bytes);
         return stream.ReadFully(buffer, buffer_size);
     }
@@ -712,12 +747,12 @@
 }
 
 bool CowReader::GetSourceOffset(const CowOperation* op, uint64_t* source_offset) {
-    switch (op->type) {
+    switch (op->type()) {
         case kCowCopyOp:
-            *source_offset = GetCowOpSourceInfoData(*op) * header_.block_size;
+            *source_offset = op->source() * header_.block_size;
             return true;
         case kCowXorOp:
-            *source_offset = GetCowOpSourceInfoData(*op);
+            *source_offset = op->source();
             return true;
         default:
             return false;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index a291469..3718851 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -74,15 +74,16 @@
     }
 }
 
-static bool ShowRawOpStreamV2(borrowed_fd fd, const CowHeader& header) {
+static bool ShowRawOpStreamV2(borrowed_fd fd, const CowHeaderV3& header) {
     CowParserV2 parser;
     if (!parser.Parse(fd, header)) {
         LOG(ERROR) << "v2 parser failed";
         return false;
     }
-    for (const auto& op : *parser.ops()) {
+    for (const auto& op : *parser.get_v2ops()) {
         std::cout << op << "\n";
-        if (auto iter = parser.data_loc()->find(op.new_block); iter != parser.data_loc()->end()) {
+        if (auto iter = parser.xor_data_loc()->find(op.new_block);
+            iter != parser.xor_data_loc()->end()) {
             std::cout << "    data loc: " << iter->second << "\n";
         }
     }
@@ -198,7 +199,7 @@
 
         if (!FLAGS_silent && FLAGS_show_ops) std::cout << *op << "\n";
 
-        if ((FLAGS_decompress || extract_to >= 0) && op->type == kCowReplaceOp) {
+        if ((FLAGS_decompress || extract_to >= 0) && op->type() == kCowReplaceOp) {
             if (reader.ReadData(op, buffer.data(), buffer.size()) < 0) {
                 std::cerr << "Failed to decompress for :" << *op << "\n";
                 success = false;
@@ -212,12 +213,12 @@
                     return false;
                 }
             }
-        } else if (extract_to >= 0 && !IsMetadataOp(*op) && op->type != kCowZeroOp) {
+        } else if (extract_to >= 0 && !IsMetadataOp(*op) && op->type() != kCowZeroOp) {
             PLOG(ERROR) << "Cannot extract op yet: " << *op;
             return false;
         }
 
-        if (op->type == kCowSequenceOp && FLAGS_show_merge_sequence) {
+        if (op->type() == kCowSequenceOp && FLAGS_show_merge_sequence) {
             size_t read;
             std::vector<uint32_t> merge_op_blocks;
             size_t seq_len = op->data_length / sizeof(uint32_t);
@@ -235,13 +236,13 @@
             }
         }
 
-        if (op->type == kCowCopyOp) {
+        if (op->type() == kCowCopyOp) {
             copy_ops++;
-        } else if (op->type == kCowReplaceOp) {
+        } else if (op->type() == kCowReplaceOp) {
             replace_ops++;
-        } else if (op->type == kCowZeroOp) {
+        } else if (op->type() == kCowZeroOp) {
             zero_ops++;
-        } else if (op->type == kCowXorOp) {
+        } else if (op->type() == kCowXorOp) {
             xor_ops++;
         }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_base.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_base.h
new file mode 100644
index 0000000..837b33e
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_base.h
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2023 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 <optional>
+#include <unordered_map>
+
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_format.h>
+
+namespace android {
+namespace snapshot {
+
+struct TranslatedCowOps {
+    CowHeaderV3 header;
+    std::shared_ptr<std::vector<CowOperationV3>> ops;
+};
+
+class CowParserBase {
+  public:
+    virtual ~CowParserBase() = default;
+
+    virtual bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header,
+                       std::optional<uint64_t> label = {}) = 0;
+    virtual bool Translate(TranslatedCowOps* out) = 0;
+    virtual std::optional<CowFooter> footer() const { return std::nullopt; }
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> xor_data_loc() {
+        return xor_data_loc_;
+    };
+
+    uint64_t fd_size() const { return fd_size_; }
+    const std::optional<uint64_t>& last_label() const { return last_label_; }
+
+  protected:
+    CowHeaderV3 header_ = {};
+    uint64_t fd_size_;
+    std::optional<uint64_t> last_label_;
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> xor_data_loc_ = {};
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
index 8f20317..fe977b7 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
@@ -18,12 +18,14 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 
+#include <libsnapshot/cow_format.h>
+
 namespace android {
 namespace snapshot {
 
 using android::base::borrowed_fd;
 
-bool CowParserV2::Parse(borrowed_fd fd, const CowHeader& header, std::optional<uint64_t> label) {
+bool CowParserV2::Parse(borrowed_fd fd, const CowHeaderV3& header, std::optional<uint64_t> label) {
     auto pos = lseek(fd.get(), 0, SEEK_END);
     if (pos < 0) {
         PLOG(ERROR) << "lseek end failed";
@@ -47,8 +49,7 @@
         return false;
     }
 
-    if ((header_.prefix.major_version > kCowVersionMajor) ||
-        (header_.prefix.minor_version != kCowVersionMinor)) {
+    if (header_.prefix.major_version > 2 || header_.prefix.minor_version != 0) {
         LOG(ERROR) << "Header version mismatch, "
                    << "major version: " << header_.prefix.major_version
                    << ", expected: " << kCowVersionMajor
@@ -62,7 +63,7 @@
 
 bool CowParserV2::ParseOps(borrowed_fd fd, std::optional<uint64_t> label) {
     uint64_t pos;
-    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
+    auto xor_data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
 
     // Skip the scratch space
     if (header_.prefix.major_version >= 2 && (header_.buffer_size > 0)) {
@@ -110,7 +111,7 @@
             auto& current_op = ops_buffer->data()[current_op_num];
             current_op_num++;
             if (current_op.type == kCowXorOp) {
-                data_loc->insert({current_op.new_block, data_pos});
+                xor_data_loc->insert({current_op.new_block, data_pos});
             }
             pos += sizeof(CowOperationV2) + GetNextOpOffset(current_op, header_.cluster_ops);
             data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
@@ -190,9 +191,51 @@
         }
     }
 
-    ops_ = ops_buffer;
-    ops_->shrink_to_fit();
-    data_loc_ = data_loc;
+    v2_ops_ = ops_buffer;
+    v2_ops_->shrink_to_fit();
+    xor_data_loc_ = xor_data_loc;
+    return true;
+}
+
+bool CowParserV2::Translate(TranslatedCowOps* out) {
+    out->ops = std::make_shared<std::vector<CowOperationV3>>(v2_ops_->size());
+
+    // Translate the operation buffer from on disk to in memory
+    for (size_t i = 0; i < out->ops->size(); i++) {
+        const auto& v2_op = v2_ops_->at(i);
+
+        auto& new_op = out->ops->at(i);
+        new_op.set_type(v2_op.type);
+        new_op.data_length = v2_op.data_length;
+
+        if (v2_op.new_block > std::numeric_limits<uint32_t>::max()) {
+            LOG(ERROR) << "Out-of-range new block in COW op: " << v2_op;
+            return false;
+        }
+        new_op.new_block = v2_op.new_block;
+
+        uint64_t source_info = v2_op.source;
+        if (new_op.type() != kCowLabelOp) {
+            source_info &= kCowOpSourceInfoDataMask;
+            if (source_info != v2_op.source) {
+                LOG(ERROR) << "Out-of-range source value in COW op: " << v2_op;
+                return false;
+            }
+        }
+        if (v2_op.compression != kCowCompressNone) {
+            if (header_.compression_algorithm == kCowCompressNone) {
+                header_.compression_algorithm = v2_op.compression;
+            } else if (header_.compression_algorithm != v2_op.compression) {
+                LOG(ERROR) << "COW has mixed compression types which is not supported;"
+                           << " previously saw " << header_.compression_algorithm << ", got "
+                           << v2_op.compression << ", op: " << v2_op;
+                return false;
+            }
+        }
+        new_op.set_source(source_info);
+    }
+
+    out->header = header_;
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
index f51ff88..f9ee2e5 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
@@ -17,36 +17,29 @@
 
 #include <memory>
 #include <optional>
-#include <unordered_map>
 #include <vector>
 
 #include <android-base/unique_fd.h>
 #include <libsnapshot/cow_format.h>
+#include <libsnapshot_cow/parser_base.h>
 
 namespace android {
 namespace snapshot {
 
-class CowParserV2 {
+class CowParserV2 final : public CowParserBase {
   public:
-    bool Parse(android::base::borrowed_fd fd, const CowHeader& header,
-               std::optional<uint64_t> label = {});
+    bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header,
+               std::optional<uint64_t> label = {}) override;
+    bool Translate(TranslatedCowOps* out) override;
+    std::optional<CowFooter> footer() const override { return footer_; }
 
     const CowHeader& header() const { return header_; }
-    const std::optional<CowFooter>& footer() const { return footer_; }
-    std::shared_ptr<std::vector<CowOperationV2>> ops() { return ops_; }
-    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc() const { return data_loc_; }
-    uint64_t fd_size() const { return fd_size_; }
-    const std::optional<uint64_t>& last_label() const { return last_label_; }
+    std::shared_ptr<std::vector<CowOperationV2>> get_v2ops() { return v2_ops_; }
 
   private:
     bool ParseOps(android::base::borrowed_fd fd, std::optional<uint64_t> label);
-
-    CowHeader header_ = {};
+    std::shared_ptr<std::vector<CowOperationV2>> v2_ops_;
     std::optional<CowFooter> footer_;
-    std::shared_ptr<std::vector<CowOperationV2>> ops_;
-    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
-    uint64_t fd_size_;
-    std::optional<uint64_t> last_label_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
new file mode 100644
index 0000000..036d335
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
@@ -0,0 +1,142 @@
+// Copyright (C) 2023 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 "parser_v3.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include <libsnapshot/cow_format.h>
+#include <libsnapshot/cow_reader.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::borrowed_fd;
+
+bool CowParserV3::Parse(borrowed_fd fd, const CowHeaderV3& header, std::optional<uint64_t> label) {
+    auto pos = lseek(fd.get(), 0, SEEK_END);
+    if (pos < 0) {
+        PLOG(ERROR) << "lseek end failed";
+        return false;
+    }
+    fd_size_ = pos;
+    header_ = header;
+
+    if (header_.footer_size != 0) {
+        LOG(ERROR) << "Footer size isn't 0, read " << header_.footer_size;
+        return false;
+    }
+
+    if (header_.op_size != sizeof(CowOperationV3)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperationV3);
+        return false;
+    }
+    if (header_.cluster_ops != 0) {
+        LOG(ERROR) << "Cluster ops not supported in v3";
+        return false;
+    }
+
+    if (header_.prefix.major_version != 3 || header_.prefix.minor_version != 0) {
+        LOG(ERROR) << "Header version mismatch, "
+                   << "major version: " << header_.prefix.major_version
+                   << ", expected: " << kCowVersionMajor
+                   << ", minor version: " << header_.prefix.minor_version
+                   << ", expected: " << kCowVersionMinor;
+        return false;
+    }
+
+    std::optional<uint32_t> op_index = header_.op_count;
+    if (label) {
+        if (!ReadResumeBuffer(fd)) {
+            PLOG(ERROR) << "Failed to read resume buffer";
+            return false;
+        }
+        op_index = FindResumeOp(label.value());
+        if (op_index == std::nullopt) {
+            LOG(ERROR) << "failed to get op index from given label: " << label.value();
+            return false;
+        }
+    }
+
+    return ParseOps(fd, op_index.value());
+}
+
+bool CowParserV3::ReadResumeBuffer(borrowed_fd fd) {
+    resume_points_ = std::make_shared<std::vector<ResumePoint>>(header_.resume_point_count);
+
+    return android::base::ReadFullyAtOffset(fd, resume_points_->data(),
+                                            header_.resume_point_count * sizeof(ResumePoint),
+                                            GetResumeOffset(header_));
+}
+
+std::optional<uint32_t> CowParserV3::FindResumeOp(const uint64_t label) {
+    for (auto& resume_point : *resume_points_) {
+        if (resume_point.label == label) {
+            return resume_point.op_index;
+        }
+    }
+    LOG(ERROR) << "failed to find label: " << label << "from following labels";
+    LOG(ERROR) << android::base::Join(*resume_points_, " ");
+
+    return std::nullopt;
+}
+
+bool CowParserV3::ParseOps(borrowed_fd fd, const uint32_t op_index) {
+    ops_ = std::make_shared<std::vector<CowOperationV3>>();
+    ops_->resize(op_index);
+
+    // read beginning of operation buffer -> so op_index = 0
+    const off_t offset = GetOpOffset(0, header_);
+    if (!android::base::ReadFullyAtOffset(fd, ops_->data(), ops_->size() * sizeof(CowOperationV3),
+                                          offset)) {
+        PLOG(ERROR) << "read ops failed";
+        return false;
+    }
+
+    // fill out mapping of XOR op data location
+    uint64_t data_pos = GetDataOffset(header_);
+
+    xor_data_loc_ = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
+
+    for (auto op : *ops_) {
+        if (op.type() == kCowXorOp) {
+            xor_data_loc_->insert({op.new_block, data_pos});
+        } else if (op.type() == kCowReplaceOp) {
+            if (data_pos != op.source()) {
+                LOG(ERROR) << "Invalid data location for operation " << op
+                           << ", expected: " << data_pos;
+                return false;
+            }
+        }
+        data_pos += op.data_length;
+    }
+    // :TODO: sequence buffer & resume buffer follow
+    // Once we implement labels, we'll have to discard unused ops and adjust
+    // the header as needed.
+
+    ops_->shrink_to_fit();
+
+    return true;
+}
+
+bool CowParserV3::Translate(TranslatedCowOps* out) {
+    out->ops = ops_;
+    out->header = header_;
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h
new file mode 100644
index 0000000..afc01af
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2023 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.
+// Copyright (C) 2023 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>
+
+#include <memory>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_format.h>
+#include <libsnapshot_cow/parser_base.h>
+
+namespace android {
+namespace snapshot {
+
+class CowParserV3 final : public CowParserBase {
+  public:
+    bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header,
+               std::optional<uint64_t> label = {}) override;
+    bool Translate(TranslatedCowOps* out) override;
+    std::shared_ptr<std::vector<ResumePoint>> resume_points() const { return resume_points_; }
+
+  private:
+    bool ParseOps(android::base::borrowed_fd fd, const uint32_t op_index);
+    std::optional<uint32_t> FindResumeOp(const uint64_t label);
+    CowHeaderV3 header_ = {};
+    std::shared_ptr<std::vector<CowOperationV3>> ops_;
+    bool ReadResumeBuffer(android::base::borrowed_fd fd);
+    std::shared_ptr<std::vector<ResumePoint>> resume_points_;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
index a3e40d9..4e90a0f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
@@ -147,7 +147,7 @@
         op = ops_[chunk];
     }
 
-    if (!op || op->type == kCowCopyOp) {
+    if (!op || op->type() == kCowCopyOp) {
         borrowed_fd fd = GetSourceFd();
         if (fd < 0) {
             // GetSourceFd sets errno.
@@ -169,15 +169,15 @@
             // ReadFullyAtOffset sets errno.
             return -1;
         }
-    } else if (op->type == kCowZeroOp) {
+    } else if (op->type() == kCowZeroOp) {
         memset(buffer, 0, bytes_to_read);
-    } else if (op->type == kCowReplaceOp) {
+    } else if (op->type() == kCowReplaceOp) {
         if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
             LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
             errno = EIO;
             return -1;
         }
-    } else if (op->type == kCowXorOp) {
+    } else if (op->type() == kCowXorOp) {
         borrowed_fd fd = GetSourceFd();
         if (fd < 0) {
             // GetSourceFd sets errno.
@@ -208,7 +208,7 @@
             ((char*)buffer)[i] ^= data[i];
         }
     } else {
-        LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type);
+        LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type());
         errno = EINVAL;
         return -1;
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
index 35d74ba..1d1d24c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
@@ -85,10 +85,10 @@
     size_t i = 0;
     while (!iter->AtEnd()) {
         auto op = iter->Get();
-        ASSERT_EQ(op->type, kCowCopyOp);
+        ASSERT_EQ(op->type(), kCowCopyOp);
         ASSERT_EQ(op->data_length, 0);
         ASSERT_EQ(op->new_block, 10 + i);
-        ASSERT_EQ(GetCowOpSourceInfoData(*op), 1000 + i);
+        ASSERT_EQ(op->source(), 1000 + i);
         iter->Next();
         i += 1;
     }
@@ -131,10 +131,10 @@
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowCopyOp);
+    ASSERT_EQ(op->type(), kCowCopyOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 10);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 20);
+    ASSERT_EQ(op->source(), 20);
 
     std::string sink(data.size(), '\0');
 
@@ -142,7 +142,7 @@
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -153,19 +153,19 @@
     op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+    ASSERT_EQ(op->source(), 0);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 52);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+    ASSERT_EQ(op->source(), 0);
 
     iter->Next();
     ASSERT_TRUE(iter->AtEnd());
@@ -206,10 +206,10 @@
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowCopyOp);
+    ASSERT_EQ(op->type(), kCowCopyOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 10);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 20);
+    ASSERT_EQ(op->source(), 20);
 
     std::string sink(data.size(), '\0');
 
@@ -217,10 +217,10 @@
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowXorOp);
+    ASSERT_EQ(op->type(), kCowXorOp);
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314);  // 4096 * 24 + 10
+    ASSERT_EQ(op->source(), 98314);  // 4096 * 24 + 10
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
@@ -229,19 +229,19 @@
     op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+    ASSERT_EQ(op->source(), 0);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 52);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+    ASSERT_EQ(op->source(), 0);
 
     iter->Next();
     ASSERT_TRUE(iter->AtEnd());
@@ -273,7 +273,7 @@
 
     std::string sink(data.size(), '\0');
 
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -325,16 +325,16 @@
     while (!iter->AtEnd()) {
         auto op = iter->Get();
 
-        if (op->type == kCowXorOp) {
+        if (op->type() == kCowXorOp) {
             total_blocks += 1;
             std::string sink(xor_data.size(), '\0');
             ASSERT_EQ(op->new_block, 50);
-            ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314);  // 4096 * 24 + 10
+            ASSERT_EQ(op->source(), 98314);  // 4096 * 24 + 10
             ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
             ASSERT_EQ(sink, xor_data);
         }
 
-        if (op->type == kCowReplaceOp) {
+        if (op->type() == kCowReplaceOp) {
             total_blocks += 1;
             if (op->new_block == 100) {
                 data.resize(options.block_size);
@@ -399,7 +399,7 @@
     while (!iter->AtEnd()) {
         auto op = iter->Get();
 
-        if (op->type == kCowReplaceOp) {
+        if (op->type() == kCowReplaceOp) {
             total_blocks += 1;
             if (op->new_block == 50) {
                 data.resize(options.block_size);
@@ -519,7 +519,7 @@
 
     std::string sink(data.size(), '\0');
 
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -529,7 +529,7 @@
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(op->type(), kCowClusterOp);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
@@ -546,7 +546,7 @@
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(op->type(), kCowClusterOp);
 
     iter->Next();
     ASSERT_TRUE(iter->AtEnd());
@@ -580,7 +580,7 @@
     std::string sink(options.block_size, '\0');
 
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_EQ(op->new_block, 51);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
 }
@@ -601,14 +601,14 @@
     ASSERT_TRUE(writer.AddCopy(10, 20));
     ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size()));
     ASSERT_TRUE(writer.AddZeroBlocks(51, 2));
-    auto size_before = writer.GetCowSize();
+    auto size_before = writer.GetCowSizeInfo().cow_size;
     ASSERT_TRUE(writer.Finalize());
-    auto size_after = writer.GetCowSize();
+    auto size_after = writer.GetCowSizeInfo().cow_size;
     ASSERT_EQ(size_before, size_after);
     struct stat buf;
 
     ASSERT_GE(fstat(cow_->fd, &buf), 0) << strerror(errno);
-    ASSERT_EQ(buf.st_size, writer.GetCowSize());
+    ASSERT_EQ(buf.st_size, writer.GetCowSizeInfo().cow_size);
 }
 
 TEST_F(CowTest, AppendLabelSmall) {
@@ -637,7 +637,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back both operations, and label.
     CowReader reader;
@@ -653,7 +653,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
@@ -663,14 +663,14 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 3);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 3);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
@@ -690,7 +690,7 @@
     ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
     ASSERT_TRUE(writer->AddLabel(1));
     // Drop the tail end of the last op header, corrupting it.
-    ftruncate(cow_->fd, writer->GetCowSize() - sizeof(CowFooter) - 3);
+    ftruncate(cow_->fd, writer->GetCowSizeInfo().cow_size - sizeof(CowFooter) - 3);
 
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
@@ -705,7 +705,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back both operations.
     CowReader reader;
@@ -716,14 +716,14 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 0);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
 
     iter->Next();
 
@@ -763,7 +763,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back all valid operations
     CowReader reader;
@@ -774,8 +774,8 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 5);
 
     iter->Next();
     ASSERT_TRUE(iter->AtEnd());
@@ -812,7 +812,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back all ops
     CowReader reader;
@@ -825,7 +825,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(0, options.block_size));
 
@@ -835,7 +835,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(options.block_size, 2 * options.block_size));
 
@@ -843,26 +843,26 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 4);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 4);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 5);
 
     iter->Next();
 
@@ -906,7 +906,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(0, options.block_size));
 
@@ -914,52 +914,52 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 4);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 4);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(op->type(), kCowClusterOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->type(), kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 5);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowCopyOp);
+    ASSERT_EQ(op->type(), kCowCopyOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(op->type(), kCowClusterOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 6);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 6);
 
     iter->Next();
 
@@ -989,7 +989,7 @@
 
     struct stat buf;
     ASSERT_EQ(fstat(cow_->fd, &buf), 0);
-    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+    ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
 
     // Read back both operations, plus cluster op at end
     CowReader reader;
@@ -1005,14 +1005,14 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(GetCowOpSourceInfoData(*op), 50);
+    ASSERT_EQ(op->type(), kCowLabelOp);
+    ASSERT_EQ(op->source(), 50);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->type(), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
@@ -1020,7 +1020,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(op->type(), kCowClusterOp);
 
     iter->Next();
 
@@ -1117,12 +1117,12 @@
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op->type == kCowReplaceOp) {
+        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) {
+        } else if (op->type() == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1178,12 +1178,12 @@
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op->type == kCowReplaceOp) {
+        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) {
+        } else if (op->type() == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1229,12 +1229,12 @@
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
-        if (op->type == kCowReplaceOp) {
+        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) {
+        } else if (op->type() == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1522,6 +1522,37 @@
     ASSERT_TRUE(reader.GetFooter(&footer));
 }
 
+TEST_F(CowTest, DecompressIncompressibleBlock) {
+    auto fd = OpenTestFile("incompressible_block", O_RDONLY);
+    ASSERT_GE(fd, 0);
+
+    std::string original;
+    ASSERT_TRUE(android::base::ReadFdToString(fd, &original)) << strerror(errno);
+    ASSERT_EQ(original.size(), 4096);
+
+    CowOptions options;
+    options.compression = "gz";
+    auto writer = CreateCowWriter(2, options, GetCowFd());
+    ASSERT_NE(writer, nullptr);
+    ASSERT_TRUE(writer->AddRawBlocks(0, original.data(), original.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    std::string block(original.size(), '\0');
+    ASSERT_EQ(iter->Get()->data_length, 4096);
+    ASSERT_TRUE(ReadData(reader, iter->Get(), block.data(), block.size()));
+
+    for (size_t i = 0; i < block.size(); i++) {
+        ASSERT_EQ(block[i], original[i]) << "mismatch at byte " << i;
+    }
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index cc8dd83..8cf46f4 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -1,10 +1,3 @@
-// Copyright (C) 2023 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,
@@ -15,17 +8,16 @@
 #include <sys/stat.h>
 
 #include <cstdio>
-#include <iostream>
+#include <limits>
 #include <memory>
-#include <string_view>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
+#include <libsnapshot/cow_format.h>
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
-#include "cow_decompress.h"
-#include "libsnapshot/cow_format.h"
 #include "writer_v2.h"
 #include "writer_v3.h"
 
@@ -37,7 +29,7 @@
 namespace android {
 namespace snapshot {
 
-class CowOperationV3Test : public ::testing::Test {
+class CowTestV3 : public ::testing::Test {
   protected:
     virtual void SetUp() override {
         cow_ = std::make_unique<TemporaryFile>();
@@ -51,7 +43,12 @@
     std::unique_ptr<TemporaryFile> cow_;
 };
 
-TEST_F(CowOperationV3Test, CowHeaderV2Test) {
+// Helper to check read sizes.
+static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) {
+    return reader.ReadData(op, buffer, size) == size;
+}
+
+TEST_F(CowTestV3, CowHeaderV2Test) {
     CowOptions options;
     options.cluster_ops = 5;
     options.num_merge_ops = 1;
@@ -67,11 +64,639 @@
 
     const auto& header = reader.GetHeader();
     ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
-    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.prefix.major_version, 2);
+    ASSERT_EQ(header.prefix.minor_version, 0);
     ASSERT_EQ(header.block_size, options.block_size);
     ASSERT_EQ(header.cluster_ops, options.cluster_ops);
 }
 
+TEST_F(CowTestV3, Header) {
+    CowOptions options;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.GetHeader();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, 0);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.cluster_ops, 0);
+}
+
+TEST_F(CowTestV3, MaxOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_FALSE(writer->AddZeroBlocks(1, 21));
+    ASSERT_TRUE(writer->AddZeroBlocks(1, 20));
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_FALSE(writer->AddRawBlocks(5, data.data(), data.size()));
+
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_EQ(reader.header_v3().op_count, 20);
+}
+
+TEST_F(CowTestV3, ZeroOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->AddZeroBlocks(1, 2));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_EQ(reader.header_v3().op_count, 2);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    auto op = iter->Get();
+    ASSERT_EQ(op->type(), kCowZeroOp);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 1);
+    ASSERT_EQ(op->source(), 0);
+
+    iter->Next();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
+
+    ASSERT_EQ(op->type(), kCowZeroOp);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 2);
+    ASSERT_EQ(op->source(), 0);
+}
+
+TEST_F(CowTestV3, ReplaceOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.scratch_space = false;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.op_count, 1);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    auto op = iter->Get();
+    std::string sink(data.size(), '\0');
+
+    ASSERT_EQ(op->type(), kCowReplaceOp);
+    ASSERT_EQ(op->data_length, 4096);
+    ASSERT_EQ(op->new_block, 5);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
+}
+
+TEST_F(CowTestV3, ConsecutiveReplaceOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.scratch_space = false;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    std::string data;
+    data.resize(options.block_size * 5);
+    for (int i = 0; i < data.size(); i++) {
+        data[i] = static_cast<char>('A' + i / options.block_size);
+    }
+
+    ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.op_count, 5);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    size_t i = 0;
+
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+        std::string sink(options.block_size, '\0');
+        ASSERT_EQ(op->type(), kCowReplaceOp);
+        ASSERT_EQ(op->data_length, options.block_size);
+        ASSERT_EQ(op->new_block, 5 + i);
+        ASSERT_TRUE(ReadData(reader, op, sink.data(), options.block_size));
+        ASSERT_EQ(std::string_view(sink),
+                  std::string_view(data).substr(i * options.block_size, options.block_size))
+                << " readback data for " << i << "th block does not match";
+        iter->Next();
+        i++;
+    }
+
+    ASSERT_EQ(i, 5);
+}
+
+TEST_F(CowTestV3, CopyOp) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    ASSERT_TRUE(writer->AddCopy(10, 1000, 100));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    size_t i = 0;
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type(), kCowCopyOp);
+        ASSERT_EQ(op->data_length, 0);
+        ASSERT_EQ(op->new_block, 10 + i);
+        ASSERT_EQ(op->source(), 1000 + i);
+        iter->Next();
+        i += 1;
+    }
+
+    ASSERT_EQ(i, 100);
+}
+
+TEST_F(CowTestV3, XorOp) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    std::string data = "This is test data-1. Testing xor";
+    data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.op_count, 1);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
+    std::string sink(data.size(), '\0');
+
+    ASSERT_EQ(op->type(), kCowXorOp);
+    ASSERT_EQ(op->data_length, 4096);
+    ASSERT_EQ(op->new_block, 50);
+    ASSERT_EQ(op->source(), 98314);  // 4096 * 24 + 10
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
+}
+
+TEST_F(CowTestV3, ConsecutiveXorOp) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    std::string data;
+    data.resize(options.block_size * 5);
+    for (int i = 0; i < data.size(); i++) {
+        data[i] = char(rand() % 256);
+    }
+
+    ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.op_count, 5);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    std::string sink(data.size(), '\0');
+    size_t i = 0;
+
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type(), kCowXorOp);
+        ASSERT_EQ(op->data_length, 4096);
+        ASSERT_EQ(op->new_block, 50 + i);
+        ASSERT_EQ(op->source(), 98314 + (i * options.block_size));  // 4096 * 24 + 10
+        ASSERT_TRUE(
+                ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+        iter->Next();
+        i++;
+    }
+    ASSERT_EQ(sink, data);
+
+    ASSERT_EQ(i, 5);
+}
+
+TEST_F(CowTestV3, AllOpsWithCompression) {
+    CowOptions options;
+    options.compression = "gz";
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    std::string data;
+    data.resize(options.block_size * 5);
+    for (int i = 0; i < data.size(); i++) {
+        data[i] = char(rand() % 4);
+    }
+
+    ASSERT_TRUE(writer->AddZeroBlocks(10, 5));
+    ASSERT_TRUE(writer->AddCopy(15, 3, 5));
+    ASSERT_TRUE(writer->AddRawBlocks(18, data.data(), data.size()));
+    ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.buffer_size, BUFFER_REGION_DEFAULT_SIZE);
+    ASSERT_EQ(header.op_count, 20);
+    ASSERT_EQ(header.op_count_max, 100);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    for (size_t i = 0; i < 5; i++) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type(), kCowZeroOp);
+        ASSERT_EQ(op->new_block, 10 + i);
+        iter->Next();
+    }
+    for (size_t i = 0; i < 5; i++) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type(), kCowCopyOp);
+        ASSERT_EQ(op->new_block, 15 + i);
+        ASSERT_EQ(op->source(), 3 + i);
+        iter->Next();
+    }
+    std::string sink(data.size(), '\0');
+
+    for (size_t i = 0; i < 5; i++) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type(), kCowReplaceOp);
+        ASSERT_EQ(op->new_block, 18 + i);
+        ASSERT_EQ(reader.ReadData(op, sink.data() + (i * options.block_size), options.block_size),
+                  options.block_size);
+        iter->Next();
+    }
+    ASSERT_EQ(sink, data);
+
+    std::fill(sink.begin(), sink.end(), '\0');
+    for (size_t i = 0; i < 5; i++) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type(), kCowXorOp);
+        ASSERT_EQ(op->new_block, 50 + i);
+        ASSERT_EQ(op->source(), 98314 + (i * options.block_size));  // 4096 * 24 + 10
+        ASSERT_TRUE(
+                ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+        iter->Next();
+    }
+    ASSERT_EQ(sink, data);
+}
+
+TEST_F(CowTestV3, GzCompression) {
+    CowOptions options;
+    options.op_count_max = 100;
+    options.compression = "gz";
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto header = reader.header_v3();
+    ASSERT_EQ(header.compression_algorithm, kCowCompressGz);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
+
+    std::string sink(data.size(), '\0');
+
+    ASSERT_EQ(op->type(), kCowReplaceOp);
+    ASSERT_EQ(op->data_length, 56);  // compressed!
+    ASSERT_EQ(op->new_block, 50);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
+
+    iter->Next();
+    ASSERT_TRUE(iter->AtEnd());
+}
+
+TEST_F(CowTestV3, ResumePointTest) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    ASSERT_TRUE(writer->AddZeroBlocks(0, 15));
+    ASSERT_TRUE(writer->AddLabel(0));
+    ASSERT_TRUE(writer->AddZeroBlocks(15, 15));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto header = reader.header_v3();
+    ASSERT_EQ(header.op_count, 30);
+
+    CowWriterV3 second_writer(options, GetCowFd());
+    ASSERT_TRUE(second_writer.Initialize(0));
+    ASSERT_TRUE(second_writer.Finalize());
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    header = reader.header_v3();
+    ASSERT_EQ(header.op_count, 15);
+}
+
+TEST_F(CowTestV3, BufferMetadataSyncTest) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    /*
+    Header metadafields
+    sequence_data_count = 0;
+    resume_point_count = 0;
+    resume_point_max = 4;
+    */
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto header = reader.header_v3();
+    ASSERT_EQ(header.sequence_data_count, 0);
+    ASSERT_EQ(header.resume_point_count, 0);
+    ASSERT_EQ(header.resume_point_max, 4);
+
+    writer->AddLabel(0);
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    header = reader.header_v3();
+    ASSERT_EQ(header.sequence_data_count, 0);
+    ASSERT_EQ(header.resume_point_count, 1);
+    ASSERT_EQ(header.resume_point_max, 4);
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    header = reader.header_v3();
+
+    /*
+    Header metadafields
+    sequence_data_count = 1;
+    resume_point_count = 0;
+    resume_point_max = 4;
+    */
+}
+
+TEST_F(CowTestV3, SequenceTest) {
+    CowOptions options;
+    constexpr int seq_len = std::numeric_limits<uint16_t>::max() / sizeof(uint32_t) + 1;
+    options.op_count_max = seq_len;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    // sequence data. This just an arbitrary set of integers that specify the merge order. The
+    // actual calculation is done by update_engine and passed to writer. All we care about here is
+    // writing that data correctly
+    uint32_t sequence[seq_len];
+    for (int i = 0; i < seq_len; i++) {
+        sequence[i] = i + 1;
+    }
+
+    ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
+    ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len - 1));
+    std::vector<uint8_t> data(writer->GetBlockSize());
+    for (size_t i = 0; i < data.size(); i++) {
+        data[i] = static_cast<uint8_t>(i & 0xFF);
+    }
+    ASSERT_TRUE(writer->AddRawBlocks(seq_len, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    auto iter = reader.GetRevMergeOpIter();
+
+    for (int i = 0; i < seq_len; i++) {
+        ASSERT_TRUE(!iter->AtEnd());
+        const auto& op = iter->Get();
+
+        ASSERT_EQ(op->new_block, seq_len - i);
+        if (op->new_block == seq_len) {
+            std::vector<uint8_t> read_back(writer->GetBlockSize());
+            ASSERT_EQ(reader.ReadData(op, read_back.data(), read_back.size()),
+                      static_cast<ssize_t>(read_back.size()));
+            ASSERT_EQ(read_back, data);
+        }
+
+        iter->Next();
+    }
+    ASSERT_TRUE(iter->AtEnd());
+}
+
+TEST_F(CowTestV3, MissingSeqOp) {
+    CowOptions options;
+    options.op_count_max = std::numeric_limits<uint32_t>::max();
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    const int seq_len = 10;
+    uint32_t sequence[seq_len];
+    for (int i = 0; i < seq_len; i++) {
+        sequence[i] = i + 1;
+    }
+    ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
+    ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len - 1));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_FALSE(reader.Parse(cow_->fd));
+}
+
+TEST_F(CowTestV3, ResumeSeqOp) {
+    CowOptions options;
+    options.op_count_max = std::numeric_limits<uint32_t>::max();
+    auto writer = std::make_unique<CowWriterV3>(options, GetCowFd());
+    const int seq_len = 10;
+    uint32_t sequence[seq_len];
+    for (int i = 0; i < seq_len; i++) {
+        sequence[i] = i + 1;
+    }
+    ASSERT_TRUE(writer->Initialize());
+
+    ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
+    ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len / 2));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, 1));
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+    auto reader = std::make_unique<CowReader>();
+    ASSERT_TRUE(reader->Parse(cow_->fd, 1));
+    auto itr = reader->GetRevMergeOpIter();
+    ASSERT_TRUE(itr->AtEnd());
+
+    writer = std::make_unique<CowWriterV3>(options, GetCowFd());
+    ASSERT_TRUE(writer->Initialize({1}));
+    ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, seq_len / 2));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    reader = std::make_unique<CowReader>();
+    ASSERT_TRUE(reader->Parse(cow_->fd));
+
+    auto iter = reader->GetRevMergeOpIter();
+
+    uint64_t expected_block = 10;
+    while (!iter->AtEnd() && expected_block > 0) {
+        ASSERT_FALSE(iter->AtEnd());
+        const auto& op = iter->Get();
+
+        ASSERT_EQ(op->new_block, expected_block);
+
+        iter->Next();
+        expected_block--;
+    }
+    ASSERT_EQ(expected_block, 0);
+    ASSERT_TRUE(iter->AtEnd());
+}
+
+TEST_F(CowTestV3, SetSourceManyTimes) {
+    CowOperationV3 op{};
+    op.set_source(1);
+    ASSERT_EQ(op.source(), 1);
+    op.set_source(2);
+    ASSERT_EQ(op.source(), 2);
+    op.set_source(4);
+    ASSERT_EQ(op.source(), 4);
+    op.set_source(8);
+    ASSERT_EQ(op.source(), 8);
+}
+
+TEST_F(CowTestV3, SetTypeManyTimes) {
+    CowOperationV3 op{};
+    op.set_type(kCowCopyOp);
+    ASSERT_EQ(op.type(), kCowCopyOp);
+    op.set_type(kCowReplaceOp);
+    ASSERT_EQ(op.type(), kCowReplaceOp);
+    op.set_type(kCowZeroOp);
+    ASSERT_EQ(op.type(), kCowZeroOp);
+    op.set_type(kCowXorOp);
+    ASSERT_EQ(op.type(), kCowXorOp);
+}
+
+TEST_F(CowTestV3, SetTypeSourceInverleave) {
+    CowOperationV3 op{};
+    op.set_type(kCowCopyOp);
+    ASSERT_EQ(op.type(), kCowCopyOp);
+    op.set_source(0x010203040506);
+    ASSERT_EQ(op.source(), 0x010203040506);
+    ASSERT_EQ(op.type(), kCowCopyOp);
+    op.set_type(kCowReplaceOp);
+    ASSERT_EQ(op.source(), 0x010203040506);
+    ASSERT_EQ(op.type(), kCowReplaceOp);
+}
+
+TEST_F(CowTestV3, CowSizeEstimate) {
+    CowOptions options{};
+    options.compression = "none";
+    auto estimator = android::snapshot::CreateCowEstimator(3, options);
+    ASSERT_TRUE(estimator->AddZeroBlocks(0, 1024 * 1024));
+    const auto cow_size = estimator->GetCowSizeInfo().cow_size;
+    options.op_count_max = 1024 * 1024;
+    options.max_blocks = 1024 * 1024;
+    CowWriterV3 writer(options, GetCowFd());
+    ASSERT_TRUE(writer.Initialize());
+    ASSERT_TRUE(writer.AddZeroBlocks(0, 1024 * 1024));
+
+    ASSERT_LE(writer.GetCowSizeInfo().cow_size, cow_size);
+}
+
+TEST_F(CowTestV3, CopyOpMany) {
+    CowOptions options;
+    options.op_count_max = 100;
+    CowWriterV3 writer(options, GetCowFd());
+    writer.Initialize();
+    ASSERT_TRUE(writer.AddCopy(100, 50, 50));
+    ASSERT_TRUE(writer.AddCopy(150, 100, 50));
+    ASSERT_TRUE(writer.Finalize());
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(GetCowFd()));
+    auto it = reader.GetOpIter();
+    for (size_t i = 0; i < 100; i++) {
+        ASSERT_FALSE(it->AtEnd()) << " op iterator ended at " << i;
+        const auto op = *it->Get();
+        ASSERT_EQ(op.type(), kCowCopyOp);
+        ASSERT_EQ(op.new_block, 100 + i);
+        it->Next();
+    }
+}
+
+TEST_F(CowTestV3, CheckOpCount) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.batch_write = true;
+    options.cluster_ops = 200;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->AddZeroBlocks(0, 19));
+    ASSERT_FALSE(writer->AddZeroBlocks(0, 19));
+}
+
 }  // namespace snapshot
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
index 709b248..5274456 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
@@ -62,6 +62,8 @@
     bool InitFd();
     bool ValidateNewBlock(uint64_t new_block);
 
+    bool IsEstimating() const { return is_dev_null_; }
+
     CowOptions options_;
 
     android::base::unique_fd fd_;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
index 83a9b1b..75cd111 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
@@ -273,10 +273,11 @@
     if (!ReadCowHeader(fd_, &header_v3)) {
         return false;
     }
+
     header_ = header_v3;
 
     CowParserV2 parser;
-    if (!parser.Parse(fd_, header_, {label})) {
+    if (!parser.Parse(fd_, header_v3, {label})) {
         return false;
     }
     if (header_.prefix.major_version > 2) {
@@ -292,7 +293,7 @@
     footer_.op.num_ops = 0;
     InitPos();
 
-    for (const auto& op : *parser.ops()) {
+    for (const auto& op : *parser.get_v2ops()) {
         AddOperation(op);
     }
 
@@ -368,7 +369,7 @@
 }
 
 bool CowWriterV2::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
-                             uint64_t old_block, uint16_t offset, uint8_t type) {
+                             uint64_t old_block, uint16_t offset, CowOperationType type) {
     CHECK(!merge_in_progress_);
     const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
 
@@ -575,12 +576,14 @@
     return Sync();
 }
 
-uint64_t CowWriterV2::GetCowSize() {
+CowSizeInfo CowWriterV2::GetCowSizeInfo() const {
+    CowSizeInfo info;
     if (current_data_size_ > 0) {
-        return next_data_pos_ + sizeof(footer_);
+        info.cow_size = next_data_pos_ + sizeof(footer_);
     } else {
-        return next_op_pos_ + sizeof(footer_);
+        info.cow_size = next_op_pos_ + sizeof(footer_);
     }
+    return info;
 }
 
 bool CowWriterV2::GetDataPos(uint64_t* pos) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
index 24170eb..05de2ad 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
@@ -27,7 +27,7 @@
 
     bool Initialize(std::optional<uint64_t> label = {}) override;
     bool Finalize() override;
-    uint64_t GetCowSize() override;
+    CowSizeInfo GetCowSizeInfo() const override;
 
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
@@ -42,7 +42,7 @@
     bool EmitCluster();
     bool EmitClusterIfNeeded();
     bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
-                    uint16_t offset, uint8_t type);
+                    uint16_t offset, CowOperationType type);
     void SetupHeaders();
     void SetupWriteOptions();
     bool ParseOptions();
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index 5ae5f19..be6b6da 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -34,9 +34,12 @@
 #include <zlib.h>
 
 #include <fcntl.h>
+#include <libsnapshot/cow_compress.h>
+#include <libsnapshot_cow/parser_v3.h>
 #include <linux/fs.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
+#include <numeric>
 
 // The info messages here are spammy, but as useful for update_engine. Disable
 // them when running on the host.
@@ -53,11 +56,35 @@
 
 using android::base::unique_fd;
 
+// Divide |x| by |y| and round up to the nearest integer.
+constexpr uint64_t DivRoundUp(uint64_t x, uint64_t y) {
+    return (x + y - 1) / y;
+}
+
 CowWriterV3::CowWriterV3(const CowOptions& options, unique_fd&& fd)
-    : CowWriterBase(options, std::move(fd)) {
+    : CowWriterBase(options, std::move(fd)), batch_size_(std::max<size_t>(options.cluster_ops, 1)) {
     SetupHeaders();
 }
 
+void CowWriterV3::InitWorkers() {
+    if (num_compress_threads_ <= 1) {
+        LOG_INFO << "Not creating new threads for compression.";
+        return;
+    }
+    compress_threads_.reserve(num_compress_threads_);
+    compress_threads_.clear();
+    threads_.reserve(num_compress_threads_);
+    threads_.clear();
+    for (size_t i = 0; i < num_compress_threads_; i++) {
+        std::unique_ptr<ICompressor> compressor =
+                ICompressor::Create(compression_, header_.block_size);
+        auto&& wt = compress_threads_.emplace_back(
+                std::make_unique<CompressWorker>(std::move(compressor), header_.block_size));
+        threads_.emplace_back(std::thread([wt = wt.get()]() { wt->RunThread(); }));
+    }
+    LOG(INFO) << num_compress_threads_ << " thread used for compression";
+}
+
 void CowWriterV3::SetupHeaders() {
     header_ = {};
     header_.prefix.magic = kCowMagicNumber;
@@ -76,9 +103,12 @@
     // v3 specific fields
     // WIP: not quite sure how some of these are calculated yet, assuming buffer_size is determined
     // during COW size estimation
-    header_.sequence_buffer_offset = 0;
-    header_.resume_buffer_size = 0;
-    header_.op_buffer_size = 0;
+    header_.sequence_data_count = 0;
+
+    header_.resume_point_count = 0;
+    header_.resume_point_max = kNumResumePoints;
+    header_.op_count = 0;
+    header_.op_count_max = 0;
     header_.compression_algorithm = kCowCompressNone;
     return;
 }
@@ -96,6 +126,9 @@
         LOG(ERROR) << "unrecognized compression: " << options_.compression;
         return false;
     }
+    header_.compression_algorithm = *algorithm;
+    header_.op_count_max = options_.op_count_max;
+
     if (parts.size() > 1) {
         if (!android::base::ParseUint(parts[1], &compression_.compression_level)) {
             LOG(ERROR) << "failed to parse compression level invalid type: " << parts[1];
@@ -107,20 +140,57 @@
     }
 
     compression_.algorithm = *algorithm;
+    if (compression_.algorithm != kCowCompressNone) {
+        compressor_ = ICompressor::Create(compression_, header_.block_size);
+        if (compressor_ == nullptr) {
+            LOG(ERROR) << "Failed to create compressor for " << compression_.algorithm;
+            return false;
+        }
+        if (options_.cluster_ops &&
+            (android::base::GetBoolProperty("ro.virtual_ab.batch_writes", false) ||
+             options_.batch_write)) {
+            batch_size_ = std::max<size_t>(options_.cluster_ops, 1);
+            data_vec_.reserve(batch_size_);
+            cached_data_.reserve(batch_size_);
+            cached_ops_.reserve(batch_size_);
+        }
+    }
+    if (batch_size_ > 1) {
+        LOG(INFO) << "Batch writes: enabled with batch size " << batch_size_;
+    } else {
+        LOG(INFO) << "Batch writes: disabled";
+    }
+    if (android::base::GetBoolProperty("ro.virtual_ab.compression.threads", false) &&
+        options_.num_compress_threads) {
+        num_compress_threads_ = options_.num_compress_threads;
+    }
+    InitWorkers();
     return true;
 }
 
-CowWriterV3::~CowWriterV3() {}
+CowWriterV3::~CowWriterV3() {
+    for (const auto& t : compress_threads_) {
+        t->Finalize();
+    }
+    for (auto& t : threads_) {
+        if (t.joinable()) {
+            t.join();
+        }
+    }
+}
 
 bool CowWriterV3::Initialize(std::optional<uint64_t> label) {
     if (!InitFd() || !ParseOptions()) {
         return false;
     }
-
-    CHECK(!label.has_value());
-
-    if (!OpenForWrite()) {
-        return false;
+    if (!label) {
+        if (!OpenForWrite()) {
+            return false;
+        }
+    } else {
+        if (!OpenForAppend(*label)) {
+            return false;
+        }
     }
 
     return true;
@@ -155,64 +225,363 @@
         }
     }
 
+    resume_points_ = std::make_shared<std::vector<ResumePoint>>();
+
     if (!Sync()) {
         LOG(ERROR) << "Header sync failed";
         return false;
     }
+    next_data_pos_ = GetDataOffset(header_);
+    return true;
+}
 
-    next_op_pos_ = 0;
-    next_data_pos_ = 0;
+bool CowWriterV3::OpenForAppend(uint64_t label) {
+    CowHeaderV3 header_v3{};
+    if (!ReadCowHeader(fd_, &header_v3)) {
+        LOG(ERROR) << "Couldn't read Cow Header";
+        return false;
+    }
+
+    header_ = header_v3;
+
+    CHECK(label >= 0);
+    CowParserV3 parser;
+    if (!parser.Parse(fd_, header_, label)) {
+        PLOG(ERROR) << "unable to parse with given label: " << label;
+        return false;
+    }
+
+    resume_points_ = parser.resume_points();
+    options_.block_size = header_.block_size;
+    next_data_pos_ = GetDataOffset(header_);
+
+    TranslatedCowOps ops;
+    parser.Translate(&ops);
+    header_.op_count = ops.ops->size();
+
+    for (const auto& op : *ops.ops) {
+        next_data_pos_ += op.data_length;
+    }
 
     return true;
 }
 
+bool CowWriterV3::CheckOpCount(size_t op_count) {
+    if (IsEstimating()) {
+        return true;
+    }
+    if (header_.op_count + cached_ops_.size() + op_count > header_.op_count_max) {
+        LOG(ERROR) << "Current number of ops on disk: " << header_.op_count
+                   << ", number of ops cached in memory: " << cached_ops_.size()
+                   << ", number of ops attempting to write: " << op_count
+                   << ", this will exceed max op count " << header_.op_count_max;
+        return false;
+    }
+    return true;
+}
+
 bool CowWriterV3::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (new_block || old_block || num_blocks) return false;
-    return false;
+    if (!CheckOpCount(num_blocks)) {
+        return false;
+    }
+    for (size_t i = 0; i < num_blocks; i++) {
+        CowOperationV3& op = cached_ops_.emplace_back();
+        op.set_type(kCowCopyOp);
+        op.new_block = new_block + i;
+        op.set_source(old_block + i);
+    }
+
+    if (NeedsFlush()) {
+        if (!FlushCacheOps()) {
+            return false;
+        }
+    }
+    return true;
 }
 
 bool CowWriterV3::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-
-    if (new_block_start || data || size) return false;
-    return false;
+    if (!CheckOpCount(size / header_.block_size)) {
+        return false;
+    }
+    return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp);
 }
 
 bool CowWriterV3::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
                                 uint32_t old_block, uint16_t offset) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (new_block_start || old_block || offset || data || size) return false;
-    return false;
+    if (!CheckOpCount(size / header_.block_size)) {
+        return false;
+    }
+    return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp);
 }
 
-bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (new_block_start && num_blocks) return false;
-    return false;
+bool CowWriterV3::NeedsFlush() const {
+    // Allow bigger batch sizes for ops without data. A single CowOperationV3
+    // struct uses 14 bytes of memory, even if we cache 200 * 16 ops in memory,
+    // it's only ~44K.
+    return cached_data_.size() >= batch_size_ || cached_ops_.size() >= batch_size_ * 16;
+}
+
+bool CowWriterV3::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
+                             uint64_t old_block, uint16_t offset, CowOperationType type) {
+    if (compression_.algorithm != kCowCompressNone && compressor_ == nullptr) {
+        LOG(ERROR) << "Compression algorithm is " << compression_.algorithm
+                   << " but compressor is uninitialized.";
+        return false;
+    }
+    const auto bytes = reinterpret_cast<const uint8_t*>(data);
+    const size_t num_blocks = (size / header_.block_size);
+
+    for (size_t i = 0; i < num_blocks;) {
+        const auto blocks_to_write =
+                std::min<size_t>(batch_size_ - cached_data_.size(), num_blocks - i);
+        size_t compressed_bytes = 0;
+        auto&& blocks = CompressBlocks(blocks_to_write, bytes + header_.block_size * i);
+        if (blocks.size() != blocks_to_write) {
+            LOG(ERROR) << "Failed to compress blocks " << new_block_start + i << ", "
+                       << blocks_to_write << ", actual number of blocks received from compressor "
+                       << blocks.size();
+            return false;
+        }
+        for (size_t j = 0; j < blocks_to_write; j++) {
+            CowOperation& op = cached_ops_.emplace_back();
+            auto& vec = data_vec_.emplace_back();
+            auto& compressed_data = cached_data_.emplace_back(std::move(blocks[j]));
+            op.new_block = new_block_start + i + j;
+
+            op.set_type(type);
+            if (type == kCowXorOp) {
+                op.set_source((old_block + i + j) * header_.block_size + offset);
+            } else {
+                op.set_source(next_data_pos_ + compressed_bytes);
+            }
+            vec = {.iov_base = compressed_data.data(), .iov_len = compressed_data.size()};
+            op.data_length = vec.iov_len;
+            compressed_bytes += op.data_length;
+        }
+        if (NeedsFlush() && !FlushCacheOps()) {
+            LOG(ERROR) << "EmitBlocks with compression: write failed. new block: "
+                       << new_block_start << " compression: " << compression_.algorithm
+                       << ", op type: " << type;
+            return false;
+        }
+        i += blocks_to_write;
+    }
+
+    return true;
+}
+
+bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, const uint64_t num_blocks) {
+    if (!CheckOpCount(num_blocks)) {
+        return false;
+    }
+    for (uint64_t i = 0; i < num_blocks; i++) {
+        auto& op = cached_ops_.emplace_back();
+        op.set_type(kCowZeroOp);
+        op.new_block = new_block_start + i;
+    }
+    if (NeedsFlush()) {
+        if (!FlushCacheOps()) {
+            return false;
+        }
+    }
+    return true;
 }
 
 bool CowWriterV3::EmitLabel(uint64_t label) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (label) return false;
-    return false;
+    // remove all labels greater than this current one. we want to avoid the situation of adding
+    // in
+    // duplicate labels with differing op values
+    if (!FlushCacheOps()) {
+        LOG(ERROR) << "Failed to flush cached ops before emitting label " << label;
+        return false;
+    }
+    auto remove_if_callback = [&](const auto& resume_point) -> bool {
+        if (resume_point.label >= label) return true;
+        return false;
+    };
+    resume_points_->erase(
+            std::remove_if(resume_points_->begin(), resume_points_->end(), remove_if_callback),
+            resume_points_->end());
+
+    resume_points_->push_back({label, header_.op_count});
+    header_.resume_point_count++;
+    // remove the oldest resume point if resume_buffer is full
+    while (resume_points_->size() > header_.resume_point_max) {
+        resume_points_->erase(resume_points_->begin());
+    }
+
+    CHECK_LE(resume_points_->size(), header_.resume_point_max);
+
+    if (!android::base::WriteFullyAtOffset(fd_, resume_points_->data(),
+                                           resume_points_->size() * sizeof(ResumePoint),
+                                           GetResumeOffset(header_))) {
+        PLOG(ERROR) << "writing resume buffer failed";
+        return false;
+    }
+    return Finalize();
 }
 
 bool CowWriterV3::EmitSequenceData(size_t num_ops, const uint32_t* data) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (num_ops && data) return false;
-    return false;
+    if (header_.op_count > 0 || !cached_ops_.empty()) {
+        LOG(ERROR) << "There's " << header_.op_count << " operations written to disk and "
+                   << cached_ops_.size()
+                   << " ops cached in memory. Writing sequence data is only allowed before all "
+                      "operation writes.";
+        return false;
+    }
+
+    header_.sequence_data_count = num_ops;
+
+    // Ensure next_data_pos_ is updated as previously initialized + the newly added sequence buffer.
+    CHECK_EQ(next_data_pos_ + header_.sequence_data_count * sizeof(uint32_t),
+             GetDataOffset(header_));
+    next_data_pos_ = GetDataOffset(header_);
+
+    if (!android::base::WriteFullyAtOffset(fd_, data, sizeof(data[0]) * num_ops,
+                                           GetSequenceOffset(header_))) {
+        PLOG(ERROR) << "writing sequence buffer failed";
+        return false;
+    }
+    return true;
+}
+
+bool CowWriterV3::FlushCacheOps() {
+    if (cached_ops_.empty()) {
+        if (!data_vec_.empty()) {
+            LOG(ERROR) << "Cached ops is empty, but data iovec has size: " << data_vec_.size()
+                       << " this is definitely a bug.";
+            return false;
+        }
+        return true;
+    }
+    size_t bytes_written = 0;
+
+    for (auto& op : cached_ops_) {
+        if (op.type() == kCowReplaceOp) {
+            op.set_source(next_data_pos_ + bytes_written);
+        }
+        bytes_written += op.data_length;
+    }
+    if (!WriteOperation({cached_ops_.data(), cached_ops_.size()},
+                        {data_vec_.data(), data_vec_.size()})) {
+        LOG(ERROR) << "Failed to flush " << cached_ops_.size() << " ops to disk";
+        return false;
+    }
+    cached_ops_.clear();
+    cached_data_.clear();
+    data_vec_.clear();
+    return true;
+}
+
+std::vector<std::basic_string<uint8_t>> CowWriterV3::CompressBlocks(const size_t num_blocks,
+                                                                    const void* data) {
+    const size_t num_threads = (num_blocks == 1) ? 1 : num_compress_threads_;
+    const size_t blocks_per_thread = DivRoundUp(num_blocks, num_threads);
+    std::vector<std::basic_string<uint8_t>> compressed_buf;
+    compressed_buf.clear();
+    const uint8_t* const iter = reinterpret_cast<const uint8_t*>(data);
+    if (compression_.algorithm == kCowCompressNone) {
+        for (size_t i = 0; i < num_blocks; i++) {
+            auto& buf = compressed_buf.emplace_back();
+            buf.resize(header_.block_size);
+            std::memcpy(buf.data(), iter + i * header_.block_size, header_.block_size);
+        }
+        return compressed_buf;
+    }
+    if (num_threads <= 1) {
+        if (!CompressWorker::CompressBlocks(compressor_.get(), header_.block_size, data, num_blocks,
+                                            &compressed_buf)) {
+            return {};
+        }
+    } else {
+        // Submit the blocks per thread. The retrieval of
+        // compressed buffers has to be done in the same order.
+        // We should not poll for completed buffers in a different order as the
+        // buffers are tightly coupled with block ordering.
+        for (size_t i = 0; i < num_threads; i++) {
+            CompressWorker* worker = compress_threads_[i].get();
+            const auto blocks_in_batch =
+                    std::min(num_blocks - i * blocks_per_thread, blocks_per_thread);
+            worker->EnqueueCompressBlocks(iter + i * blocks_per_thread * header_.block_size,
+                                          blocks_in_batch);
+        }
+
+        for (size_t i = 0; i < num_threads; i++) {
+            CompressWorker* worker = compress_threads_[i].get();
+            if (!worker->GetCompressedBuffers(&compressed_buf)) {
+                return {};
+            }
+        }
+    }
+    for (size_t i = 0; i < num_blocks; i++) {
+        auto& block = compressed_buf[i];
+        if (block.size() >= header_.block_size) {
+            block.resize(header_.block_size);
+            std::memcpy(block.data(), iter + header_.block_size * i, header_.block_size);
+        }
+    }
+
+    return compressed_buf;
+}
+
+bool CowWriterV3::WriteOperation(std::basic_string_view<CowOperationV3> ops,
+                                 std::basic_string_view<struct iovec> data) {
+    const auto total_data_size =
+            std::transform_reduce(data.begin(), data.end(), 0, std::plus<size_t>{},
+                                  [](const struct iovec& a) { return a.iov_len; });
+    if (IsEstimating()) {
+        header_.op_count += ops.size();
+        if (header_.op_count > header_.op_count_max) {
+            // If we increment op_count_max, the offset of data section would
+            // change. So need to update |next_data_pos_|
+            next_data_pos_ += (header_.op_count - header_.op_count_max) * sizeof(CowOperationV3);
+            header_.op_count_max = header_.op_count;
+        }
+        next_data_pos_ += total_data_size;
+        return true;
+    }
+
+    if (header_.op_count + ops.size() > header_.op_count_max) {
+        LOG(ERROR) << "Current op count " << header_.op_count << ", attempting to write "
+                   << ops.size() << " ops will exceed the max of " << header_.op_count_max;
+        return false;
+    }
+    const off_t offset = GetOpOffset(header_.op_count, header_);
+    if (!android::base::WriteFullyAtOffset(fd_, ops.data(), ops.size() * sizeof(ops[0]), offset)) {
+        PLOG(ERROR) << "Write failed for " << ops.size() << " ops at " << offset;
+        return false;
+    }
+    if (!data.empty()) {
+        const auto ret = pwritev(fd_, data.data(), data.size(), next_data_pos_);
+        if (ret != total_data_size) {
+            PLOG(ERROR) << "write failed for data of size: " << data.size()
+                        << " at offset: " << next_data_pos_ << " " << ret;
+            return false;
+        }
+    }
+    header_.op_count += ops.size();
+    next_data_pos_ += total_data_size;
+
+    return true;
 }
 
 bool CowWriterV3::Finalize() {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    return false;
+    CHECK_GE(header_.prefix.header_size, sizeof(CowHeaderV3));
+    CHECK_LE(header_.prefix.header_size, sizeof(header_));
+    if (!FlushCacheOps()) {
+        return false;
+    }
+    if (!android::base::WriteFullyAtOffset(fd_, &header_, header_.prefix.header_size, 0)) {
+        return false;
+    }
+    return Sync();
 }
 
-uint64_t CowWriterV3::GetCowSize() {
-    LOG(ERROR) << __LINE__ << " " << __FILE__
-               << " <- Get Cow Size function here should never be called";
-    return 0;
+CowSizeInfo CowWriterV3::GetCowSizeInfo() const {
+    CowSizeInfo info;
+    info.cow_size = next_data_pos_;
+    info.op_count_max = header_.op_count_max;
+    return info;
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 2a88a12..b19af60 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -14,7 +14,11 @@
 
 #pragma once
 
-#include <future>
+#include <android-base/logging.h>
+#include <string_view>
+#include <thread>
+#include <vector>
+
 #include "writer_base.h"
 
 namespace android {
@@ -27,7 +31,7 @@
 
     bool Initialize(std::optional<uint64_t> label = {}) override;
     bool Finalize() override;
-    uint64_t GetCowSize() override;
+    CowSizeInfo GetCowSizeInfo() const override;
 
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
@@ -40,19 +44,42 @@
 
   private:
     void SetupHeaders();
+    bool NeedsFlush() const;
     bool ParseOptions();
     bool OpenForWrite();
+    bool OpenForAppend(uint64_t label);
+    bool WriteOperation(std::basic_string_view<CowOperationV3> op,
+                        std::basic_string_view<struct iovec> data);
+    bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
+                    uint16_t offset, CowOperationType type);
+    bool CheckOpCount(size_t op_count);
 
   private:
+    std::vector<std::basic_string<uint8_t>> CompressBlocks(const size_t num_blocks,
+                                                           const void* data);
+    bool ReadBackVerification();
+    bool FlushCacheOps();
+    void InitWorkers();
     CowHeaderV3 header_{};
     CowCompression compression_;
     // in the case that we are using one thread for compression, we can store and re-use the same
     // compressor
+    std::unique_ptr<ICompressor> compressor_;
+    std::vector<std::unique_ptr<CompressWorker>> compress_threads_;
+    // Resume points contain a laebl + cow_op_index.
+    std::shared_ptr<std::vector<ResumePoint>> resume_points_;
 
-    uint64_t next_op_pos_ = 0;
     uint64_t next_data_pos_ = 0;
 
+    // in the case that we are using one thread for compression, we can store and re-use the same
+    // compressor
     int num_compress_threads_ = 1;
+    size_t batch_size_ = 1;
+    std::vector<CowOperationV3> cached_ops_;
+    std::vector<std::basic_string<uint8_t>> cached_data_;
+    std::vector<struct iovec> data_vec_;
+
+    std::vector<std::thread> threads_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 5bc7e65..c0d2073 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -217,6 +217,7 @@
 
     if (update && update->has_estimate_cow_size()) {
         ret.snapshot_status.set_estimated_cow_size(update->estimate_cow_size());
+        ret.snapshot_status.set_estimated_ops_buffer_size(update->estimate_op_count_max());
     }
 
     if (ret.snapshot_status.snapshot_size() == 0) {
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index eb4beb7..9eb41b2 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -30,6 +30,7 @@
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/properties.h>
+#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <cutils/sockets.h>
@@ -82,12 +83,28 @@
 using std::chrono::duration_cast;
 using namespace std::chrono_literals;
 using namespace std::string_literals;
+using android::base::Realpath;
+using android::base::StringPrintf;
 
 static constexpr char kBootSnapshotsWithoutSlotSwitch[] =
         "/metadata/ota/snapshot-boot-without-slot-switch";
 static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot";
 static constexpr char kRollbackIndicatorPath[] = "/metadata/ota/rollback-indicator";
 static constexpr auto kUpdateStateCheckInterval = 2s;
+/*
+ * The readahead size is set to 32kb so that
+ * there is no significant memory pressure (/proc/pressure/memory) during boot.
+ * After OTA, during boot, partitions are scanned before marking slot as successful.
+ * This scan will trigger readahead both on source and COW block device thereby
+ * leading to Inactive(file) pages to be very high.
+ *
+ * A lower value may help reduce memory pressure further, however, that will
+ * increase the boot time. Thus, for device which don't care about OTA boot
+ * time, they could use O_DIRECT functionality wherein the I/O to the source
+ * block device will be O_DIRECT.
+ */
+static constexpr auto kCowReadAheadSizeKb = 32;
+static constexpr auto kSourceReadAheadSizeKb = 32;
 
 // Note: IImageManager is an incomplete type in the header, so the default
 // destructor doesn't work.
@@ -1748,6 +1765,9 @@
                 snapuserd_argv->emplace_back(std::move(message));
             }
 
+            SetReadAheadSize(cow_image_device, kCowReadAheadSizeKb);
+            SetReadAheadSize(source_device, kSourceReadAheadSizeKb);
+
             // Do not attempt to connect to the new snapuserd yet, it hasn't
             // been started. We do however want to wait for the misc device
             // to have been created.
@@ -3310,7 +3330,7 @@
         // Terminate stale daemon if any
         std::unique_ptr<SnapuserdClient> snapuserd_client = std::move(snapuserd_client_);
         if (!snapuserd_client) {
-            snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
+            snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s);
         }
         if (snapuserd_client) {
             snapuserd_client->DetachSnapuserd();
@@ -3513,6 +3533,11 @@
             return Return::Error();
         }
 
+        if (!android::fs_mgr::WaitForFile(cow_path, 6s)) {
+            LOG(ERROR) << "Timed out waiting for device to appear: " << cow_path;
+            return Return::Error();
+        }
+
         if (it->second.using_snapuserd()) {
             unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
             if (fd < 0) {
@@ -3526,6 +3551,9 @@
                 options.scratch_space = false;
             }
             options.compression = it->second.compression_algorithm();
+            if (cow_version >= 3) {
+                options.op_count_max = it->second.estimated_ops_buffer_size();
+            }
 
             auto writer = CreateCowWriter(cow_version, options, std::move(fd));
             if (!writer->Finalize()) {
@@ -3636,7 +3664,8 @@
     cow_options.compression = status.compression_algorithm();
     cow_options.max_blocks = {status.device_size() / cow_options.block_size};
     cow_options.batch_write = status.batched_writes();
-    cow_options.num_compress_threads = status.enable_threading() ? 2 : 0;
+    cow_options.num_compress_threads = status.enable_threading() ? 2 : 1;
+    cow_options.op_count_max = status.estimated_ops_buffer_size();
     // Disable scratch space for vts tests
     if (device()->IsTestDevice()) {
         cow_options.scratch_space = false;
@@ -4334,32 +4363,30 @@
 }
 
 bool SnapshotManager::IsUserspaceSnapshotUpdateInProgress() {
-    auto slot = GetCurrentSlot();
-    if (slot == Slot::Target) {
-        // Merge in-progress
-        if (IsSnapuserdRequired()) {
+    // We cannot grab /metadata/ota lock here as this
+    // is in reboot path. See b/308900853
+    //
+    // Check if any of the partitions are mounted
+    // off dm-user block device. If so, then we are certain
+    // that OTA update in progress.
+    auto current_suffix = device_->GetSlotSuffix();
+    auto& dm = DeviceMapper::Instance();
+    auto dm_block_devices = dm.FindDmPartitions();
+    if (dm_block_devices.empty()) {
+        LOG(ERROR) << "No dm-enabled block device is found.";
+        return false;
+    }
+    for (auto& partition : dm_block_devices) {
+        std::string partition_name = partition.first + current_suffix;
+        DeviceMapper::TargetInfo snap_target;
+        if (!GetSingleTarget(partition_name, TableQuery::Status, &snap_target)) {
+            return false;
+        }
+        auto type = DeviceMapper::GetTargetType(snap_target.spec);
+        if (type == "user") {
             return true;
         }
     }
-
-    // Let's check more deeper to see if snapshots are mounted
-    auto lock = LockExclusive();
-    if (!lock) {
-        return false;
-    }
-
-    std::vector<std::string> snapshots;
-    if (!ListSnapshots(lock.get(), &snapshots)) {
-        return false;
-    }
-
-    for (const auto& snapshot : snapshots) {
-        // Active snapshot and daemon is alive
-        if (IsSnapshotDevice(snapshot) && EnsureSnapuserdConnected(2s)) {
-            return true;
-        }
-    }
-
     return false;
 }
 
@@ -4401,5 +4428,31 @@
     return true;
 }
 
+void SnapshotManager::SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb) {
+    std::string block_device;
+    if (!Realpath(entry_block_device, &block_device)) {
+        PLOG(ERROR) << "Failed to realpath " << entry_block_device;
+        return;
+    }
+
+    static constexpr std::string_view kDevBlockPrefix("/dev/block/");
+    if (!android::base::StartsWith(block_device, kDevBlockPrefix)) {
+        LOG(ERROR) << block_device << " is not a block device";
+        return;
+    }
+
+    std::string 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) {
+        block_name += "/..";
+    }
+    std::string sys_ra = android::base::StringPrintf("/sys/class/block/%s/queue/read_ahead_kb",
+                                                     block_name.c_str());
+    std::string size = std::to_string(size_kb);
+    android::base::WriteStringToFile(size, sys_ra.c_str());
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 4e6b5e1..e538d50 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -2362,8 +2362,10 @@
     auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
 
-    ASSERT_TRUE(init->EnsureSnapuserdConnected());
-    init->set_use_first_stage_snapuserd(true);
+    if (snapuserd_required_) {
+        ASSERT_TRUE(init->EnsureSnapuserdConnected());
+        init->set_use_first_stage_snapuserd(true);
+    }
 
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -2374,9 +2376,11 @@
         ASSERT_TRUE(IsPartitionUnchanged(name));
     }
 
-    ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
-    for (const auto& name : partitions) {
-        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init"));
+    if (snapuserd_required_) {
+        ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
+        for (const auto& name : partitions) {
+            ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init"));
+        }
     }
 
     // Initiate the merge and wait for it to be completed.
@@ -2860,15 +2864,23 @@
 }
 
 void KillSnapuserd() {
-    auto status = android::base::GetProperty("init.svc.snapuserd", "stopped");
-    if (status == "stopped") {
-        return;
+    // Detach the daemon if it's alive
+    auto snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s);
+    if (snapuserd_client) {
+        snapuserd_client->DetachSnapuserd();
     }
-    auto snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
-    if (!snapuserd_client) {
-        return;
+
+    // Now stop the service - Init will send a SIGKILL to the daemon. However,
+    // process state will move from "running" to "stopping". Only after the
+    // process is reaped by init, the service state is moved to "stopped".
+    //
+    // Since the tests involve starting the daemon immediately, wait for the
+    // process to completely stop (aka. wait until init reaps the terminated
+    // process).
+    android::base::SetProperty("ctl.stop", "snapuserd");
+    if (!android::base::WaitForProperty("init.svc.snapuserd", "stopped", 10s)) {
+        LOG(ERROR) << "Timed out waiting for snapuserd to stop.";
     }
-    snapuserd_client->DetachSnapuserd();
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index ebaca2d..0396a55 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -227,8 +227,12 @@
         if (file_offset >= dev_sz) {
             break;
         }
+
+        if (fsync(cfd.get()) < 0) {
+            PLOG(ERROR) << "Fsync failed at offset: " << file_offset << " size: " << to_read;
+            return false;
+        }
     }
-    fsync(cfd.get());
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 1b0c563..6b8e084 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -147,12 +147,6 @@
     // snapuserd, which would lead to deadlock if we had to handle page
     // faults for its code pages.
     static_executable: true,
-
-    // Snapuserd segfaults with ThinLTO
-    // http://b/208565717
-    lto: {
-         never: true,
-    },
 }
 
 cc_binary {
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 6dc082e..93bb0b2 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -406,9 +406,9 @@
             break;
         }
 
-        if (cow_op->type == kCowReplaceOp) {
+        if (cow_op->type() == kCowReplaceOp) {
             replace_ops++;
-        } else if (cow_op->type == kCowZeroOp) {
+        } else if (cow_op->type() == kCowZeroOp) {
             zero_ops++;
         }
 
@@ -508,7 +508,7 @@
             // the merge of operations are done based on the ops present
             // in the file.
             //===========================================================
-            uint64_t block_source = GetCowOpSourceInfoData(*cow_op);
+            uint64_t block_source = cow_op->source();
             if (prev_id.has_value()) {
                 if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source)) {
                     break;
@@ -540,7 +540,7 @@
             chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
             offset += sizeof(struct disk_exception);
             num_ops += 1;
-            if (cow_op->type == kCowCopyOp) {
+            if (cow_op->type() == kCowCopyOp) {
                 copy_ops++;
             }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index ab0b309..f1b9245 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -172,7 +172,7 @@
 }
 
 void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
-    uint64_t source_block = GetCowOpSourceInfoData(*cow_op);
+    uint64_t source_block = cow_op->source();
     if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block)) {
         overlap_ = true;
     }
@@ -191,8 +191,8 @@
         // Get the first block with offset
         const CowOperation* cow_op = GetRAOpIter();
         CHECK_NE(cow_op, nullptr);
-        *source_offset = GetCowOpSourceInfoData(*cow_op);
-        if (cow_op->type == kCowCopyOp) {
+        *source_offset = cow_op->source();
+        if (cow_op->type() == kCowCopyOp) {
             *source_offset *= BLOCK_SZ;
         }
         RAIterNext();
@@ -210,8 +210,8 @@
         while (!RAIterDone() && num_ops) {
             const CowOperation* op = GetRAOpIter();
             CHECK_NE(op, nullptr);
-            uint64_t next_offset = GetCowOpSourceInfoData(*op);
-            if (op->type == kCowCopyOp) {
+            uint64_t next_offset = op->source();
+            if (op->type() == kCowCopyOp) {
                 next_offset *= BLOCK_SZ;
             }
             if (next_offset + nr_consecutive * BLOCK_SZ != *source_offset) {
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 571b352..1f5d568 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -177,7 +177,7 @@
         return false;
     }
 
-    switch (cow_op->type) {
+    switch (cow_op->type()) {
         case kCowReplaceOp: {
             return ProcessReplaceOp(cow_op);
         }
@@ -191,7 +191,8 @@
         }
 
         default: {
-            SNAP_LOG(ERROR) << "Unsupported operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unsupported operation-type found: "
+                            << static_cast<uint8_t>(cow_op->type());
         }
     }
     return false;
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index 010beb3..ede92dd 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -17,11 +17,7 @@
 #include <unistd.h>
 
 #include <chrono>
-#include <cstring>
-#include <iostream>
 #include <string>
-#include <thread>
-#include <vector>
 
 #include <android-base/unique_fd.h>
 
@@ -53,9 +49,14 @@
     explicit SnapuserdClient(android::base::unique_fd&& sockfd);
     SnapuserdClient(){};
 
+    // Attempt to connect to snapsuerd, wait for the daemon to start if
+    // connection failed.
     static std::unique_ptr<SnapuserdClient> Connect(const std::string& socket_name,
                                                     std::chrono::milliseconds timeout_ms);
-
+    // Attempt to connect to snapsuerd, but does not wait for the daemon to
+    // start.
+    static std::unique_ptr<SnapuserdClient> TryConnect(const std::string& socket_name,
+                                                       std::chrono::milliseconds timeout_ms);
     bool StopSnapuserd();
 
     // Initializing a snapuserd handler is a three-step process:
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 3bed3a4..789c980 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -27,7 +27,7 @@
 #include <unistd.h>
 
 #include <chrono>
-#include <sstream>
+#include <thread>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -64,6 +64,40 @@
     return errno == ECONNREFUSED || errno == EINTR || errno == ENOENT;
 }
 
+std::unique_ptr<SnapuserdClient> SnapuserdClient::TryConnect(const std::string& socket_name,
+                                                             std::chrono::milliseconds timeout_ms) {
+    unique_fd fd;
+    const auto start = std::chrono::steady_clock::now();
+    while (true) {
+        fd.reset(TEMP_FAILURE_RETRY(socket_local_client(
+                socket_name.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)));
+        if (fd >= 0) {
+            auto client = std::make_unique<SnapuserdClient>(std::move(fd));
+            if (!client->ValidateConnection()) {
+                return nullptr;
+            }
+            return client;
+        }
+        if (errno == ENOENT) {
+            LOG(INFO) << "Daemon socket " << socket_name
+                      << " does not exist, return without waiting.";
+            return nullptr;
+        }
+        if (errno == ECONNREFUSED) {
+            const auto now = std::chrono::steady_clock::now();
+            const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+            if (elapsed >= timeout_ms) {
+                LOG(ERROR) << "Timed out connecting to snapuserd socket: " << socket_name;
+                return nullptr;
+            }
+            std::this_thread::sleep_for(10ms);
+        } else {
+            PLOG(ERROR) << "connect failed: " << socket_name;
+            return nullptr;
+        }
+    }
+}
+
 std::unique_ptr<SnapuserdClient> SnapuserdClient::Connect(const std::string& socket_name,
                                                           std::chrono::milliseconds timeout_ms) {
     unique_fd fd;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
index c5718d5..c85331b 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
@@ -41,7 +41,7 @@
 bool Extractor::Init() {
     auto opener = factory_.CreateTestOpener(control_name_);
     handler_ = std::make_shared<SnapshotHandler>(control_name_, cow_path_, base_path_, base_path_,
-                                                 opener, 1, false, false);
+                                                 opener, 1, false, false, false);
     if (!handler_->InitCowDevice()) {
         return false;
     }
@@ -50,7 +50,7 @@
     }
 
     read_worker_ = std::make_unique<ReadWorker>(cow_path_, base_path_, control_name_, base_path_,
-                                                handler_->GetSharedPtr(), opener);
+                                                handler_->GetSharedPtr(), opener, false);
     if (!read_worker_->Init()) {
         return false;
     }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
index d979e20..711e704 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -52,10 +52,10 @@
         const std::string& misc_name, const std::string& cow_device_path,
         const std::string& backing_device, const std::string& base_path_merge,
         std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads, bool use_iouring,
-        bool perform_verification) {
-    auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
-                                                       base_path_merge, opener, num_worker_threads,
-                                                       use_iouring, perform_verification);
+        bool o_direct) {
+    auto snapuserd = std::make_shared<SnapshotHandler>(
+            misc_name, cow_device_path, backing_device, base_path_merge, opener, num_worker_threads,
+            use_iouring, perform_verification_, o_direct);
     if (!snapuserd->InitCowDevice()) {
         LOG(ERROR) << "Failed to initialize Snapuserd";
         return nullptr;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
index b1605f0..f23f07e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
@@ -58,7 +58,7 @@
                                                       const std::string& base_path_merge,
                                                       std::shared_ptr<IBlockServerOpener> opener,
                                                       int num_worker_threads, bool use_iouring,
-                                                      bool perform_verification) = 0;
+                                                      bool o_direct) = 0;
 
     // Start serving requests on a snapshot handler.
     virtual bool StartHandler(const std::string& misc_name) = 0;
@@ -84,6 +84,9 @@
 
     // Returns whether all snapshots have verified.
     virtual bool GetVerificationStatus() = 0;
+
+    // Disable partition verification
+    virtual void DisableVerification() = 0;
 };
 
 class SnapshotHandlerManager final : public ISnapshotHandlerManager {
@@ -95,7 +98,7 @@
                                               const std::string& base_path_merge,
                                               std::shared_ptr<IBlockServerOpener> opener,
                                               int num_worker_threads, bool use_iouring,
-                                              bool perform_verification) override;
+                                              bool o_direct) override;
     bool StartHandler(const std::string& misc_name) override;
     bool DeleteHandler(const std::string& misc_name) override;
     bool InitiateMerge(const std::string& misc_name) override;
@@ -104,6 +107,7 @@
     void TerminateMergeThreads() override;
     double GetMergePercentage() override;
     bool GetVerificationStatus() override;
+    void DisableVerification() override { perform_verification_ = false; }
 
   private:
     bool StartHandler(const std::shared_ptr<HandlerThread>& handler);
@@ -128,6 +132,7 @@
     int num_partitions_merge_complete_ = 0;
     std::queue<std::shared_ptr<HandlerThread>> merge_handlers_;
     android::base::unique_fd monitor_merge_event_fd_;
+    bool perform_verification_ = true;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
index 11b8d7c..bcf9aab 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
@@ -114,13 +114,13 @@
                 SNAP_LOG(ERROR) << "AcquireBuffer failed in MergeReplaceOps";
                 return false;
             }
-            if (cow_op->type == kCowReplaceOp) {
+            if (cow_op->type() == kCowReplaceOp) {
                 if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
                     SNAP_LOG(ERROR) << "Failed to read COW in merge";
                     return false;
                 }
             } else {
-                CHECK(cow_op->type == kCowZeroOp);
+                CHECK(cow_op->type() == kCowZeroOp);
                 memset(buffer, 0, BLOCK_SZ);
             }
         }
@@ -557,7 +557,7 @@
         return true;
     }
 
-    if (!SetThreadPriority(kNiceValueForMergeThreads)) {
+    if (!SetThreadPriority(ANDROID_PRIORITY_BACKGROUND)) {
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
index 5cb13e8..f1d4065 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
@@ -31,16 +31,19 @@
 void ReadWorker::CloseFds() {
     block_server_ = {};
     backing_store_fd_ = {};
+    backing_store_direct_fd_ = {};
     Worker::CloseFds();
 }
 
 ReadWorker::ReadWorker(const std::string& cow_device, const std::string& backing_device,
                        const std::string& misc_name, const std::string& base_path_merge,
                        std::shared_ptr<SnapshotHandler> snapuserd,
-                       std::shared_ptr<IBlockServerOpener> opener)
+                       std::shared_ptr<IBlockServerOpener> opener, bool direct_read)
     : Worker(cow_device, misc_name, base_path_merge, snapuserd),
       backing_store_device_(backing_device),
-      block_server_opener_(opener) {}
+      direct_read_(direct_read),
+      block_server_opener_(opener),
+      aligned_buffer_(std::unique_ptr<void, decltype(&::free)>(nullptr, &::free)) {}
 
 // Start the replace operation. This will read the
 // internal COW format and if the block is compressed,
@@ -61,9 +64,20 @@
     }
     SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
                     << " Op: " << *cow_op;
+
+    if (direct_read_ && IsBlockAligned(offset)) {
+        if (!android::base::ReadFullyAtOffset(backing_store_direct_fd_, aligned_buffer_.get(),
+                                              BLOCK_SZ, offset)) {
+            SNAP_PLOG(ERROR) << "O_DIRECT Read failed at offset: " << offset;
+            return false;
+        }
+        std::memcpy(buffer, aligned_buffer_.get(), BLOCK_SZ);
+        return true;
+    }
+
     if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
         std::string op;
-        if (cow_op->type == kCowCopyOp)
+        if (cow_op->type() == kCowCopyOp)
             op = "Copy-op";
         else {
             op = "Xor-op";
@@ -133,7 +147,7 @@
         }
         case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
             bool ret;
-            if (cow_op->type == kCowCopyOp) {
+            if (cow_op->type() == kCowCopyOp) {
                 ret = ProcessCopyOp(cow_op, buffer);
             } else {
                 ret = ProcessXorOp(cow_op, buffer);
@@ -167,7 +181,7 @@
         return false;
     }
 
-    switch (cow_op->type) {
+    switch (cow_op->type()) {
         case kCowReplaceOp: {
             return ProcessReplaceOp(cow_op, buffer);
         }
@@ -183,7 +197,8 @@
         }
 
         default: {
-            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unknown operation-type found: "
+                            << static_cast<uint8_t>(cow_op->type());
         }
     }
     return false;
@@ -200,6 +215,24 @@
         return false;
     }
 
+    if (direct_read_) {
+        backing_store_direct_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY | O_DIRECT));
+        if (backing_store_direct_fd_ < 0) {
+            SNAP_PLOG(ERROR) << "Open Failed with O_DIRECT: " << backing_store_direct_fd_;
+            direct_read_ = false;
+        } else {
+            void* aligned_addr;
+            ssize_t page_size = getpagesize();
+            if (posix_memalign(&aligned_addr, page_size, page_size) < 0) {
+                direct_read_ = false;
+                SNAP_PLOG(ERROR) << "posix_memalign failed "
+                                 << " page_size: " << page_size << " read_sz: " << page_size;
+            } else {
+                aligned_buffer_.reset(aligned_addr);
+            }
+        }
+    }
+
     block_server_ = block_server_opener_->Open(this, PAYLOAD_BUFFER_SZ);
     if (!block_server_) {
         SNAP_PLOG(ERROR) << "Unable to open block server";
@@ -213,7 +246,7 @@
 
     pthread_setname_np(pthread_self(), "ReadWorker");
 
-    if (!SetThreadPriority(kNiceValueForMergeThreads)) {
+    if (!SetThreadPriority(ANDROID_PRIORITY_NORMAL)) {
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
index 6dbae81..1aff50c 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
@@ -28,7 +28,7 @@
     ReadWorker(const std::string& cow_device, const std::string& backing_device,
                const std::string& misc_name, const std::string& base_path_merge,
                std::shared_ptr<SnapshotHandler> snapuserd,
-               std::shared_ptr<IBlockServerOpener> opener);
+               std::shared_ptr<IBlockServerOpener> opener, bool direct_read = false);
 
     bool Run();
     bool Init() override;
@@ -59,11 +59,14 @@
 
     std::string backing_store_device_;
     unique_fd backing_store_fd_;
+    unique_fd backing_store_direct_fd_;
+    bool direct_read_ = false;
 
     std::shared_ptr<IBlockServerOpener> block_server_opener_;
     std::unique_ptr<IBlockServer> block_server_;
 
     std::basic_string<uint8_t> xor_buffer_;
+    std::unique_ptr<void, decltype(&::free)> aligned_buffer_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 8208d67..05ba047 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -36,7 +36,7 @@
 SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
                                  std::string backing_device, std::string base_path_merge,
                                  std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads,
-                                 bool use_iouring, bool perform_verification) {
+                                 bool use_iouring, bool perform_verification, bool o_direct) {
     misc_name_ = std::move(misc_name);
     cow_device_ = std::move(cow_device);
     backing_store_device_ = std::move(backing_device);
@@ -45,13 +45,14 @@
     num_worker_threads_ = num_worker_threads;
     is_io_uring_enabled_ = use_iouring;
     perform_verification_ = perform_verification;
+    o_direct_ = o_direct;
 }
 
 bool SnapshotHandler::InitializeWorkers() {
     for (int i = 0; i < num_worker_threads_; i++) {
         auto wt = std::make_unique<ReadWorker>(cow_device_, backing_store_device_, misc_name_,
                                                base_path_merge_, GetSharedPtr(),
-                                               block_server_opener_);
+                                               block_server_opener_, o_direct_);
         if (!wt->Init()) {
             SNAP_LOG(ERROR) << "Thread initialization failed";
             return false;
@@ -112,7 +113,7 @@
             return false;
         }
 
-        if (!android::base::WriteFully(cow_fd_, &header, sizeof(CowHeader))) {
+        if (!android::base::WriteFully(cow_fd_, &header, header.prefix.header_size)) {
             SNAP_PLOG(ERROR) << "Write to header failed";
             return false;
         }
@@ -199,13 +200,13 @@
     while (!cowop_iter->AtEnd()) {
         const CowOperation* cow_op = cowop_iter->Get();
 
-        if (cow_op->type == kCowCopyOp) {
+        if (cow_op->type() == kCowCopyOp) {
             copy_ops += 1;
-        } else if (cow_op->type == kCowReplaceOp) {
+        } else if (cow_op->type() == kCowReplaceOp) {
             replace_ops += 1;
-        } else if (cow_op->type == kCowZeroOp) {
+        } else if (cow_op->type() == kCowZeroOp) {
             zero_ops += 1;
-        } else if (cow_op->type == kCowXorOp) {
+        } else if (cow_op->type() == kCowXorOp) {
             xor_ops += 1;
         }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index fa1e7a0..9b7238a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -47,6 +47,7 @@
 #include <snapuserd/snapuserd_buffer.h>
 #include <snapuserd/snapuserd_kernel.h>
 #include <storage_literals/storage_literals.h>
+#include <system/thread_defs.h>
 #include "snapuserd_readahead.h"
 #include "snapuserd_verify.h"
 
@@ -62,8 +63,6 @@
 
 static constexpr int kNumWorkerThreads = 4;
 
-static constexpr int kNiceValueForMergeThreads = -5;
-
 #define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
 #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
 
@@ -105,7 +104,7 @@
   public:
     SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device,
                     std::string base_path_merge, std::shared_ptr<IBlockServerOpener> opener,
-                    int num_workers, bool use_iouring, bool perform_verification);
+                    int num_workers, bool use_iouring, bool perform_verification, bool o_direct);
     bool InitCowDevice();
     bool Start();
 
@@ -247,6 +246,7 @@
     bool perform_verification_ = true;
     bool resume_merge_ = false;
     bool merge_complete_ = false;
+    bool o_direct_ = false;
 
     std::unique_ptr<UpdateVerify> update_verify_;
     std::shared_ptr<IBlockServerOpener> block_server_opener_;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index 998d233..c08c1b1 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -77,7 +77,7 @@
         SNAP_LOG(ERROR) << "PrepareNextReadAhead operation has no source offset: " << *cow_op;
         return nr_consecutive;
     }
-    if (cow_op->type == kCowXorOp) {
+    if (cow_op->type() == kCowXorOp) {
         xor_op_vec.push_back(cow_op);
     }
 
@@ -106,7 +106,7 @@
             break;
         }
 
-        if (op->type == kCowXorOp) {
+        if (op->type() == kCowXorOp) {
             xor_op_vec.push_back(op);
         }
 
@@ -778,7 +778,7 @@
 
     InitializeIouring();
 
-    if (!SetThreadPriority(kNiceValueForMergeThreads)) {
+    if (!SetThreadPriority(ANDROID_PRIORITY_BACKGROUND)) {
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index 13b9a00..0b881b6 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -346,7 +346,8 @@
 std::shared_ptr<HandlerThread> UserSnapshotServer::AddHandler(const std::string& misc_name,
                                                               const std::string& cow_device_path,
                                                               const std::string& backing_device,
-                                                              const std::string& base_path_merge) {
+                                                              const std::string& base_path_merge,
+                                                              const bool o_direct) {
     // We will need multiple worker threads only during
     // device boot after OTA. For all other purposes,
     // one thread is sufficient. We don't want to consume
@@ -360,16 +361,15 @@
         num_worker_threads = 1;
     }
 
-    bool perform_verification = true;
-    if (android::base::EndsWith(misc_name, "-init") || is_socket_present_) {
-        perform_verification = false;
+    if (android::base::EndsWith(misc_name, "-init") || is_socket_present_ ||
+        (access(kBootSnapshotsWithoutSlotSwitch, F_OK) == 0)) {
+        handlers_->DisableVerification();
     }
 
     auto opener = block_server_factory_->CreateOpener(misc_name);
 
     return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge,
-                                 opener, num_worker_threads, io_uring_enabled_,
-                                 perform_verification);
+                                 opener, num_worker_threads, io_uring_enabled_, o_direct);
 }
 
 bool UserSnapshotServer::WaitForSocket() {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index be28541..3013c47 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -40,6 +40,8 @@
 
 static constexpr uint32_t kMaxPacketSize = 512;
 static constexpr uint8_t kMaxMergeThreads = 2;
+static constexpr char kBootSnapshotsWithoutSlotSwitch[] =
+        "/metadata/ota/snapshot-boot-without-slot-switch";
 
 class UserSnapshotServer {
   private:
@@ -84,7 +86,8 @@
     std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
                                               const std::string& cow_device_path,
                                               const std::string& backing_device,
-                                              const std::string& base_path_merge);
+                                              const std::string& base_path_merge,
+                                              bool o_direct = false);
     bool StartHandler(const std::string& misc_name);
 
     void SetTerminating() { terminating_ = true; }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index bed71cf..8ddb0f4 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -59,10 +59,16 @@
 using testing::AssertionFailure;
 using testing::AssertionResult;
 using testing::AssertionSuccess;
+using ::testing::TestWithParam;
 
-class SnapuserdTestBase : public ::testing::TestWithParam<bool> {
+struct TestParam {
+    bool io_uring;
+    bool o_direct;
+};
+
+class SnapuserdTestBase : public ::testing::TestWithParam<TestParam> {
   protected:
-    void SetUp() override;
+    virtual void SetUp() override;
     void TearDown() override;
     void CreateBaseDevice();
     void CreateCowDevice();
@@ -627,9 +633,11 @@
 void SnapuserdTest::InitCowDevice() {
     auto factory = harness_->GetBlockServerFactory();
     auto opener = factory->CreateOpener(system_device_ctrl_name_);
-    auto handler =
-            handlers_->AddHandler(system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(),
-                                  base_dev_->GetPath(), opener, 1, GetParam(), false);
+    handlers_->DisableVerification();
+    const TestParam params = GetParam();
+    auto handler = handlers_->AddHandler(system_device_ctrl_name_, cow_system_->path,
+                                         base_dev_->GetPath(), base_dev_->GetPath(), opener, 1,
+                                         params.io_uring, params.o_direct);
     ASSERT_NE(handler, nullptr);
     ASSERT_NE(handler->snapuserd(), nullptr);
 #ifdef __ANDROID__
@@ -897,9 +905,10 @@
     opener_ = factory_.CreateTestOpener(system_device_ctrl_name_);
     ASSERT_NE(opener_, nullptr);
 
+    const TestParam params = GetParam();
     handler_ = std::make_shared<SnapshotHandler>(system_device_ctrl_name_, cow_system_->path,
                                                  base_dev_->GetPath(), base_dev_->GetPath(),
-                                                 opener_, 1, false, false);
+                                                 opener_, 1, false, false, params.o_direct);
     ASSERT_TRUE(handler_->InitCowDevice());
     ASSERT_TRUE(handler_->InitializeWorkers());
 
@@ -989,14 +998,28 @@
     return {false, true};
 }
 
-std::string IoUringConfigName(const testing::TestParamInfo<SnapuserdTest::ParamType>& info) {
-    return info.param ? "io_uring" : "sync";
+std::vector<TestParam> GetTestConfigs() {
+    std::vector<TestParam> testParams;
+    std::vector<bool> uring_configs = GetIoUringConfigs();
+
+    for (bool config : uring_configs) {
+        TestParam param;
+        param.io_uring = config;
+        param.o_direct = false;
+        testParams.push_back(std::move(param));
+    }
+
+    for (bool config : uring_configs) {
+        TestParam param;
+        param.io_uring = config;
+        param.o_direct = true;
+        testParams.push_back(std::move(param));
+    }
+    return testParams;
 }
 
-INSTANTIATE_TEST_SUITE_P(Io, SnapuserdTest, ::testing::ValuesIn(GetIoUringConfigs()),
-                         IoUringConfigName);
-INSTANTIATE_TEST_SUITE_P(Io, HandlerTest, ::testing::ValuesIn(GetIoUringConfigs()),
-                         IoUringConfigName);
+INSTANTIATE_TEST_SUITE_P(Io, SnapuserdTest, ::testing::ValuesIn(GetTestConfigs()));
+INSTANTIATE_TEST_SUITE_P(Io, HandlerTest, ::testing::ValuesIn(GetTestConfigs()));
 
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h
index d07d2f8..7c99085 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h
@@ -48,10 +48,27 @@
     std::mutex m_lock_;
     std::condition_variable m_cv_;
 
+    /*
+     * Scanning of partitions is an expensive operation both in terms of memory
+     * and CPU usage. The goal here is to scan the partitions fast enough without
+     * significant increase in the boot time.
+     *
+     * Partitions such as system, product which may be huge and may need multiple
+     * threads to speed up the verification process. Using multiple threads for
+     * all partitions may increase CPU usage significantly. Hence, limit that to
+     * 1 thread per partition.
+     *
+     * These numbers were derived by monitoring the memory and CPU pressure
+     * (/proc/pressure/{cpu,memory}; and monitoring the Inactive(file) and
+     * Active(file) pages from /proc/meminfo.
+     *
+     * Additionally, for low memory devices, it is advisible to use O_DIRECT
+     * fucntionality for source block device.
+     */
     int kMinThreadsToVerify = 1;
-    int kMaxThreadsToVerify = 4;
-    uint64_t kThresholdSize = 512_MiB;
-    uint64_t kBlockSizeVerify = 1_MiB;
+    int kMaxThreadsToVerify = 3;
+    uint64_t kThresholdSize = 750_MiB;
+    uint64_t kBlockSizeVerify = 2_MiB;
 
     bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
     void UpdatePartitionVerificationState(UpdateVerifyState state);
diff --git a/fs_mgr/libsnapshot/tools/testdata/incompressible_block b/fs_mgr/libsnapshot/tools/testdata/incompressible_block
new file mode 100644
index 0000000..cc45cd0
--- /dev/null
+++ b/fs_mgr/libsnapshot/tools/testdata/incompressible_block
Binary files differ
diff --git a/fs_mgr/tests/src/com/android/tests/vendoroverlay/VendorOverlayHostTest.java b/fs_mgr/tests/src/com/android/tests/vendoroverlay/VendorOverlayHostTest.java
index f08cab2..91f235c 100644
--- a/fs_mgr/tests/src/com/android/tests/vendoroverlay/VendorOverlayHostTest.java
+++ b/fs_mgr/tests/src/com/android/tests/vendoroverlay/VendorOverlayHostTest.java
@@ -21,10 +21,8 @@
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
-
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
@@ -38,9 +36,15 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class VendorOverlayHostTest extends BaseHostJUnit4Test {
   boolean wasRoot = false;
+  String vndkVersion = null;
 
   @Before
   public void setup() throws DeviceNotAvailableException {
+    vndkVersion = getDevice().executeShellV2Command("getprop ro.vndk.version").getStdout();
+    Assume.assumeTrue(
+        "Vendor Overlay is disabled for VNDK deprecated devices",
+        vndkVersion != null && !vndkVersion.trim().isEmpty());
+
     wasRoot = getDevice().isAdbRoot();
     if (!wasRoot) {
       Assume.assumeTrue("Test requires root", getDevice().enableAdbRoot());
@@ -74,8 +78,6 @@
    */
   @Test
   public void testVendorOverlay() throws DeviceNotAvailableException {
-    String vndkVersion = getDevice().executeShellV2Command("getprop ro.vndk.version").getStdout();
-
     // Create files and modify policy
     CommandResult result = getDevice().executeShellV2Command(
         "echo '/(product|system/product)/vendor_overlay/" + vndkVersion +
diff --git a/fs_mgr/tests/vts_fs_test.cpp b/fs_mgr/tests/vts_fs_test.cpp
index 32947b5..9503072 100644
--- a/fs_mgr/tests/vts_fs_test.cpp
+++ b/fs_mgr/tests/vts_fs_test.cpp
@@ -32,6 +32,24 @@
     return android::base::GetIntProperty("ro.vendor.api_level", -1);
 }
 
+// Returns true iff the device has the specified feature.
+bool DeviceSupportsFeature(const char* feature) {
+    bool device_supports_feature = false;
+    FILE* p = popen("pm list features", "re");
+    if (p) {
+        char* line = NULL;
+        size_t len = 0;
+        while (getline(&line, &len, p) > 0) {
+            if (strstr(line, feature)) {
+                device_supports_feature = true;
+                break;
+            }
+        }
+        pclose(p);
+    }
+    return device_supports_feature;
+}
+
 TEST(fs, ErofsSupported) {
     // T-launch GKI kernels and higher must support EROFS.
     if (GetVsrLevel() < __ANDROID_API_T__) {
@@ -81,11 +99,7 @@
     ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/super", &super_bdev));
     ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/userdata", &userdata_bdev));
 
-    std::vector<std::string> must_be_f2fs = {"/data"};
-    if (vsr_level >= __ANDROID_API_U__) {
-        must_be_f2fs.emplace_back("/metadata");
-    }
-
+    std::vector<std::string> data_fs = {"/data", "/metadata"};
     for (const auto& entry : fstab) {
         std::string parent_bdev = entry.blk_device;
         while (true) {
@@ -119,11 +133,10 @@
             std::vector<std::string> allowed = {"erofs", "ext4", "f2fs"};
             EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end())
                     << entry.mount_point;
-        } else {
-            if (std::find(must_be_f2fs.begin(), must_be_f2fs.end(), entry.mount_point) !=
-                must_be_f2fs.end()) {
-                EXPECT_EQ(entry.fs_type, "f2fs") << entry.mount_point;
-            }
+        } else if (std::find(data_fs.begin(), data_fs.end(), entry.mount_point) != data_fs.end()) {
+            std::vector<std::string> allowed = {"ext4", "f2fs"};
+            EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end())
+                    << entry.mount_point << ", " << entry.fs_type;
         }
     }
 }
diff --git a/healthd/Android.bp b/healthd/Android.bp
index eb366a5..e158e07 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -76,7 +76,7 @@
     defaults: ["libbatterymonitor_defaults"],
     srcs: ["BatteryMonitor.cpp"],
     static_libs: [
-        "android.hardware.health-V2-ndk",
+        "android.hardware.health-V3-ndk",
     ],
     whole_static_libs: [
         // Need to translate HIDL to AIDL to support legacy APIs in
@@ -165,12 +165,12 @@
     defaults: ["libhealthd_charger_ui_defaults"],
 
     static_libs: [
-        "android.hardware.health-V2-ndk",
+        "android.hardware.health-V3-ndk",
         "android.hardware.health-translate-ndk",
     ],
 
     export_static_lib_headers: [
-        "android.hardware.health-V2-ndk",
+        "android.hardware.health-V3-ndk",
     ],
 }
 
@@ -242,7 +242,7 @@
     static_libs: [
         // common
         "android.hardware.health@1.0-convert",
-        "android.hardware.health-V2-ndk",
+        "android.hardware.health-V3-ndk",
         "libbatterymonitor",
         "libcharger_sysprop",
         "libhealthd_charger_nops",
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 0c97632..b8bb586 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -59,6 +59,7 @@
 using aidl::android::hardware::health::BatteryChargingState;
 using aidl::android::hardware::health::BatteryHealth;
 using aidl::android::hardware::health::BatteryHealthData;
+using aidl::android::hardware::health::BatteryPartStatus;
 using aidl::android::hardware::health::BatteryStatus;
 using aidl::android::hardware::health::HealthInfo;
 
@@ -219,6 +220,7 @@
             {"Warm", BatteryHealth::GOOD},
             {"Cool", BatteryHealth::GOOD},
             {"Hot", BatteryHealth::OVERHEAT},
+            {"Calibration required", BatteryHealth::INCONSISTENT},
             {NULL, BatteryHealth::UNKNOWN},
     };
 
@@ -596,6 +598,9 @@
         if (!mHealthdConfig->batteryStateOfHealthPath.empty())
             return getIntField(mHealthdConfig->batteryStateOfHealthPath);
     }
+    if (id == BATTERY_PROP_PART_STATUS) {
+        return static_cast<int>(BatteryPartStatus::UNSUPPORTED);
+    }
     return 0;
 }
 
@@ -679,6 +684,11 @@
         ret = OK;
         break;
 
+    case BATTERY_PROP_PART_STATUS:
+        val->valueInt64 = getBatteryHealthData(BATTERY_PROP_PART_STATUS);
+        ret = OK;
+        break;
+
     default:
         break;
     }
@@ -686,6 +696,11 @@
     return ret;
 }
 
+status_t BatteryMonitor::getSerialNumber(std::optional<std::string>* out) {
+    *out = std::nullopt;
+    return OK;
+}
+
 void BatteryMonitor::dumpState(int fd) {
     int v;
     char vs[128];
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index e9998ba..b30458d 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -18,6 +18,7 @@
 #define HEALTHD_BATTERYMONITOR_H
 
 #include <memory>
+#include <optional>
 
 #include <batteryservice/BatteryService.h>
 #include <utils/String8.h>
@@ -86,6 +87,8 @@
     int getChargingPolicy();
     int getBatteryHealthData(int id);
 
+    status_t getSerialNumber(std::optional<std::string>* out);
+
     static void logValues(const android::hardware::health::V2_1::HealthInfo& health_info,
                           const struct healthd_config& healthd_config);
 
diff --git a/init/Android.bp b/init/Android.bp
index e5512e6..a781d8b 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -188,7 +188,6 @@
         "libfs_mgr",
         "libgsi",
         "libhidl-gen-utils",
-        "libkeyutils",
         "liblog",
         "liblogwrap",
         "liblp",
diff --git a/init/builtins.cpp b/init/builtins.cpp
index a95a4a3..606ea8c 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -592,9 +592,6 @@
     } else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED ||
                code == FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED ||
                code == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
-        if (!FscryptInstallKeyring()) {
-            return Error() << "FscryptInstallKeyring() failed";
-        }
         SetProperty("ro.crypto.state", "encrypted");
 
         // Although encrypted, vold has already set the device up, so we do not need to
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index e48fa15..c4d0f75 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -30,6 +30,7 @@
 #include <chrono>
 #include <filesystem>
 #include <string>
+#include <thread>
 #include <vector>
 
 #include <android-base/chrono_utils.h>
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index d0f68a8..c0b9281 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -732,6 +732,15 @@
     return true;
 }
 
+bool IsHashtreeDisabled(const AvbHandle& vbmeta, const std::string& mount_point) {
+    if (vbmeta.status() == AvbHandleStatus::kHashtreeDisabled ||
+        vbmeta.status() == AvbHandleStatus::kVerificationDisabled) {
+        LOG(ERROR) << "Top-level vbmeta is disabled, skip Hashtree setup for " << mount_point;
+        return true;  // Returns true to mount the partition directly.
+    }
+    return false;
+}
+
 bool FirstStageMountVBootV2::SetUpDmVerity(FstabEntry* fstab_entry) {
     AvbHashtreeResult hashtree_result;
 
@@ -740,34 +749,46 @@
     if (!fstab_entry->avb_keys.empty()) {
         if (!InitAvbHandle()) return false;
         // Checks if hashtree should be disabled from the top-level /vbmeta.
-        if (avb_handle_->status() == AvbHandleStatus::kHashtreeDisabled ||
-            avb_handle_->status() == AvbHandleStatus::kVerificationDisabled) {
-            LOG(ERROR) << "Top-level vbmeta is disabled, skip Hashtree setup for "
-                       << fstab_entry->mount_point;
-            return true;  // Returns true to mount the partition directly.
+        if (IsHashtreeDisabled(*avb_handle_, fstab_entry->mount_point)) {
+            return true;
+        }
+        auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta(
+                *fstab_entry, preload_avb_key_blobs_[fstab_entry->avb_keys]);
+        if (!avb_standalone_handle) {
+            LOG(ERROR) << "Failed to load offline vbmeta for " << fstab_entry->mount_point;
+            // Fallbacks to built-in hashtree if fs_mgr_flags.avb is set.
+            if (!fstab_entry->fs_mgr_flags.avb) return false;
+            LOG(INFO) << "Fallback to built-in hashtree for " << fstab_entry->mount_point;
+            hashtree_result =
+                    avb_handle_->SetUpAvbHashtree(fstab_entry, false /* wait_for_verity_dev */);
         } else {
-            auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta(
-                    *fstab_entry, preload_avb_key_blobs_[fstab_entry->avb_keys]);
-            if (!avb_standalone_handle) {
-                LOG(ERROR) << "Failed to load offline vbmeta for " << fstab_entry->mount_point;
-                // Fallbacks to built-in hashtree if fs_mgr_flags.avb is set.
-                if (!fstab_entry->fs_mgr_flags.avb) return false;
-                LOG(INFO) << "Fallback to built-in hashtree for " << fstab_entry->mount_point;
-                hashtree_result =
-                        avb_handle_->SetUpAvbHashtree(fstab_entry, false /* wait_for_verity_dev */);
-            } else {
-                // Sets up hashtree via the standalone handle.
-                if (IsStandaloneImageRollback(*avb_handle_, *avb_standalone_handle, *fstab_entry)) {
-                    return false;
-                }
-                hashtree_result = avb_standalone_handle->SetUpAvbHashtree(
-                        fstab_entry, false /* wait_for_verity_dev */);
+            // Sets up hashtree via the standalone handle.
+            if (IsStandaloneImageRollback(*avb_handle_, *avb_standalone_handle, *fstab_entry)) {
+                return false;
             }
+            hashtree_result = avb_standalone_handle->SetUpAvbHashtree(
+                    fstab_entry, false /* wait_for_verity_dev */);
         }
     } else if (fstab_entry->fs_mgr_flags.avb) {
         if (!InitAvbHandle()) return false;
         hashtree_result =
                 avb_handle_->SetUpAvbHashtree(fstab_entry, false /* wait_for_verity_dev */);
+    } else if (!fstab_entry->avb_hashtree_digest.empty()) {
+        // When fstab_entry has neither avb_keys nor avb flag, try using
+        // avb_hashtree_digest.
+        if (!InitAvbHandle()) return false;
+        // Checks if hashtree should be disabled from the top-level /vbmeta.
+        if (IsHashtreeDisabled(*avb_handle_, fstab_entry->mount_point)) {
+            return true;
+        }
+        auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta(*fstab_entry);
+        if (!avb_standalone_handle) {
+            LOG(ERROR) << "Failed to load vbmeta based on hashtree descriptor root digest for "
+                       << fstab_entry->mount_point;
+            return false;
+        }
+        hashtree_result = avb_standalone_handle->SetUpAvbHashtree(fstab_entry,
+                                                                  false /* wait_for_verity_dev */);
     } else {
         return true;  // No need AVB, returns true to mount the partition directly.
     }
diff --git a/init/fscrypt_init_extensions.cpp b/init/fscrypt_init_extensions.cpp
index fbd8189..6a561e5 100644
--- a/init/fscrypt_init_extensions.cpp
+++ b/init/fscrypt_init_extensions.cpp
@@ -34,28 +34,12 @@
 #include <cutils/properties.h>
 #include <cutils/sockets.h>
 #include <fscrypt/fscrypt.h>
-#include <keyutils.h>
 #include <logwrap/logwrap.h>
 
 #define TAG "fscrypt"
 
 using namespace android::fscrypt;
 
-bool FscryptInstallKeyring() {
-    if (keyctl_search(KEY_SPEC_SESSION_KEYRING, "keyring", "fscrypt", 0) != -1) {
-        LOG(INFO) << "Keyring is already created";
-        return true;
-    }
-    key_serial_t device_keyring = add_key("keyring", "fscrypt", 0, 0, KEY_SPEC_SESSION_KEYRING);
-
-    if (device_keyring == -1) {
-        PLOG(ERROR) << "Failed to create keyring";
-        return false;
-    }
-    LOG(INFO) << "Keyring created with id " << device_keyring << " in process " << getpid();
-    return true;
-}
-
 // TODO(b/139378601): use a single central implementation of this.
 static void delete_dir_contents(const std::string& dir) {
     char* const paths[2] = {const_cast<char*>(dir.c_str()), nullptr};
diff --git a/init/fscrypt_init_extensions.h b/init/fscrypt_init_extensions.h
index d357bb2..5e0269a 100644
--- a/init/fscrypt_init_extensions.h
+++ b/init/fscrypt_init_extensions.h
@@ -25,6 +25,5 @@
     kDeleteIfNecessary,
 };
 
-bool FscryptInstallKeyring();
 bool FscryptSetDirectoryPolicy(const std::string& ref_basename, FscryptAction action,
                                const std::string& dir);
diff --git a/init/fuzzer/Android.bp b/init/fuzzer/Android.bp
index 856ca8c..9916246 100644
--- a/init/fuzzer/Android.bp
+++ b/init/fuzzer/Android.bp
@@ -32,7 +32,6 @@
         "libbase",
         "libfs_mgr",
         "libhidl-gen-utils",
-        "libkeyutils",
         "liblog",
         "libprocessgroup",
         "libselinux",
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index f070776..662185c 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -108,9 +108,9 @@
     static passwd static_passwd = {
         .pw_name = static_name,
         .pw_dir = static_dir,
-        .pw_shell = static_shell,
         .pw_uid = 0,
         .pw_gid = 0,
+        .pw_shell = static_shell,
     };
 
     for (size_t n = 0; n < android_id_count; ++n) {
diff --git a/init/init.cpp b/init/init.cpp
index 40e2169..19e909f 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -54,7 +54,6 @@
 #include <android-base/thread_annotations.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr_vendor_overlay.h>
-#include <keyutils.h>
 #include <libavb/libavb.h>
 #include <libgsi/libgsi.h>
 #include <libsnapshot/snapshot.h>
@@ -108,6 +107,7 @@
 using android::base::StringPrintf;
 using android::base::Timer;
 using android::base::Trim;
+using android::base::unique_fd;
 using android::fs_mgr::AvbHandle;
 using android::snapshot::SnapshotManager;
 
@@ -116,7 +116,7 @@
 
 static int property_triggers_enabled = 0;
 
-static int signal_fd = -1;
+static int sigterm_fd = -1;
 static int property_fd = -1;
 
 struct PendingControlMessage {
@@ -713,8 +713,9 @@
     HandlePowerctlMessage("shutdown,container");
 }
 
-static void HandleSignalFd() {
+static void HandleSignalFd(int signal) {
     signalfd_siginfo siginfo;
+    const int signal_fd = signal == SIGCHLD ? Service::GetSigchldFd() : sigterm_fd;
     ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
     if (bytes_read != sizeof(siginfo)) {
         PLOG(ERROR) << "Failed to read siginfo from signal_fd";
@@ -748,40 +749,51 @@
     }
 }
 
+static Result<void> RegisterSignalFd(Epoll* epoll, int signal, int fd) {
+    return epoll->RegisterHandler(
+            fd, [signal]() { HandleSignalFd(signal); }, EPOLLIN | EPOLLPRI);
+}
+
+static Result<int> CreateAndRegisterSignalFd(Epoll* epoll, int signal) {
+    sigset_t mask;
+    sigemptyset(&mask);
+    sigaddset(&mask, signal);
+    if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
+        return ErrnoError() << "failed to block signal " << signal;
+    }
+
+    unique_fd signal_fd(signalfd(-1, &mask, SFD_CLOEXEC));
+    if (signal_fd.get() < 0) {
+        return ErrnoError() << "failed to create signalfd for signal " << signal;
+    }
+    OR_RETURN(RegisterSignalFd(epoll, signal, signal_fd.get()));
+
+    return signal_fd.release();
+}
+
 static void InstallSignalFdHandler(Epoll* epoll) {
     // Applying SA_NOCLDSTOP to a defaulted SIGCHLD handler prevents the signalfd from receiving
     // SIGCHLD when a child process stops or continues (b/77867680#comment9).
-    const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };
+    const struct sigaction act { .sa_flags = SA_NOCLDSTOP, .sa_handler = SIG_DFL };
     sigaction(SIGCHLD, &act, nullptr);
 
-    sigset_t mask;
-    sigemptyset(&mask);
-    sigaddset(&mask, SIGCHLD);
-
-    if (!IsRebootCapable()) {
-        // If init does not have the CAP_SYS_BOOT capability, it is running in a container.
-        // In that case, receiving SIGTERM will cause the system to shut down.
-        sigaddset(&mask, SIGTERM);
-    }
-
-    if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
-        PLOG(FATAL) << "failed to block signals";
-    }
-
     // Register a handler to unblock signals in the child processes.
     const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);
     if (result != 0) {
         LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);
     }
 
-    signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
-    if (signal_fd == -1) {
-        PLOG(FATAL) << "failed to create signalfd";
+    Result<void> cs_result = RegisterSignalFd(epoll, SIGCHLD, Service::GetSigchldFd());
+    if (!cs_result.ok()) {
+        PLOG(FATAL) << cs_result.error();
     }
 
-    constexpr int flags = EPOLLIN | EPOLLPRI;
-    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd, flags); !result.ok()) {
-        LOG(FATAL) << result.error();
+    if (!IsRebootCapable()) {
+        Result<int> cs_result = CreateAndRegisterSignalFd(epoll, SIGTERM);
+        if (!cs_result.ok()) {
+            PLOG(FATAL) << cs_result.error();
+        }
+        sigterm_fd = cs_result.value();
     }
 }
 
@@ -958,11 +970,6 @@
                    << " to /proc/1/oom_score_adj: " << result.error();
     }
 
-    // Set up a session keyring that all processes will have access to. It
-    // will hold things like FBE encryption keys. No process should override
-    // its session keyring.
-    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
-
     // Indicate that booting is in progress to background fw loaders, etc.
     close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
 
@@ -1059,8 +1066,8 @@
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
     am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
-    am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd");
     am.QueueEventTrigger("early-init");
+    am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd");
 
     // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
     am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
diff --git a/init/persistent_properties.cpp b/init/persistent_properties.cpp
index e89244f..6f8a4de 100644
--- a/init/persistent_properties.cpp
+++ b/init/persistent_properties.cpp
@@ -46,6 +46,13 @@
 
 constexpr const char kLegacyPersistentPropertyDir[] = "/data/property";
 
+void AddPersistentProperty(const std::string& name, const std::string& value,
+                           PersistentProperties* persistent_properties) {
+    auto persistent_property_record = persistent_properties->add_properties();
+    persistent_property_record->set_name(name);
+    persistent_property_record->set_value(value);
+}
+
 Result<PersistentProperties> LoadLegacyPersistentProperties() {
     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(kLegacyPersistentPropertyDir), closedir);
     if (!dir) {
@@ -146,13 +153,6 @@
 
 }  // namespace
 
-void AddPersistentProperty(const std::string& name, const std::string& value,
-                           PersistentProperties* persistent_properties) {
-    auto persistent_property_record = persistent_properties->add_properties();
-    persistent_property_record->set_name(name);
-    persistent_property_record->set_value(value);
-}
-
 Result<PersistentProperties> LoadPersistentPropertyFile() {
     auto file_contents = ReadPersistentPropertyFile();
     if (!file_contents.ok()) return file_contents.error();
@@ -266,8 +266,57 @@
         }
     }
 
-    return *persistent_properties;
+    // loop over to find all staged props
+    auto const staged_prefix = std::string_view("next_boot.");
+    auto staged_props = std::unordered_map<std::string, std::string>();
+    for (const auto& property_record : persistent_properties->properties()) {
+        auto const& prop_name = property_record.name();
+        auto const& prop_value = property_record.value();
+        if (StartsWith(prop_name, staged_prefix)) {
+            auto actual_prop_name = prop_name.substr(staged_prefix.size());
+            staged_props[actual_prop_name] = prop_value;
+        }
+    }
+
+    if (staged_props.empty()) {
+        return *persistent_properties;
+    }
+
+    // if has staging, apply staging and perserve the original prop order
+    PersistentProperties updated_persistent_properties;
+    for (const auto& property_record : persistent_properties->properties()) {
+        auto const& prop_name = property_record.name();
+        auto const& prop_value = property_record.value();
+
+        // don't include staged props anymore
+        if (StartsWith(prop_name, staged_prefix)) {
+            continue;
+        }
+
+        auto iter = staged_props.find(prop_name);
+        if (iter != staged_props.end()) {
+            AddPersistentProperty(prop_name, iter->second, &updated_persistent_properties);
+            staged_props.erase(iter);
+        } else {
+            AddPersistentProperty(prop_name, prop_value, &updated_persistent_properties);
+        }
+    }
+
+    // add any additional staged props
+    for (auto const& [prop_name, prop_value] : staged_props) {
+        AddPersistentProperty(prop_name, prop_value, &updated_persistent_properties);
+    }
+
+    // write current updated persist prop file
+    auto result = WritePersistentPropertyFile(updated_persistent_properties);
+    if (!result.ok()) {
+        LOG(ERROR) << "Could not store persistent property: " << result.error();
+    }
+
+    return updated_persistent_properties;
 }
 
+
+
 }  // namespace init
 }  // namespace android
diff --git a/init/persistent_properties.h b/init/persistent_properties.h
index 7e9d438..11083da 100644
--- a/init/persistent_properties.h
+++ b/init/persistent_properties.h
@@ -25,8 +25,6 @@
 namespace android {
 namespace init {
 
-void AddPersistentProperty(const std::string& name, const std::string& value,
-                           PersistentProperties* persistent_properties);
 PersistentProperties LoadPersistentProperties();
 void WritePersistentProperty(const std::string& name, const std::string& value);
 PersistentProperties LoadPersistentPropertiesFromMemory();
diff --git a/init/persistent_properties_test.cpp b/init/persistent_properties_test.cpp
index e5d26db..5763050 100644
--- a/init/persistent_properties_test.cpp
+++ b/init/persistent_properties_test.cpp
@@ -178,5 +178,37 @@
     EXPECT_FALSE(it == read_back_properties.properties().end());
 }
 
+TEST(persistent_properties, StagedPersistProperty) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    persistent_property_filename = tf.path;
+
+    std::vector<std::pair<std::string, std::string>> persistent_properties = {
+        {"persist.sys.locale", "en-US"},
+        {"next_boot.persist.test.numbers", "54321"},
+        {"persist.sys.timezone", "America/Los_Angeles"},
+        {"persist.test.numbers", "12345"},
+        {"next_boot.persist.test.extra", "abc"},
+    };
+
+    ASSERT_RESULT_OK(
+            WritePersistentPropertyFile(VectorToPersistentProperties(persistent_properties)));
+
+    std::vector<std::pair<std::string, std::string>> expected_persistent_properties = {
+        {"persist.sys.locale", "en-US"},
+        {"persist.sys.timezone", "America/Los_Angeles"},
+        {"persist.test.numbers", "54321"},
+        {"persist.test.extra", "abc"},
+    };
+
+    // lock down that staged props are applied
+    auto first_read_back_properties = LoadPersistentProperties();
+    CheckPropertiesEqual(expected_persistent_properties, first_read_back_properties);
+
+    // lock down that other props are not overwritten
+    auto second_read_back_properties = LoadPersistentProperties();
+    CheckPropertiesEqual(expected_persistent_properties, second_read_back_properties);
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index b08a904..e2cff95 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -76,10 +76,15 @@
 #include "system/core/init/property_service.pb.h"
 #include "util.h"
 
+static constexpr char APPCOMPAT_OVERRIDE_PROP_FOLDERNAME[] =
+        "/dev/__properties__/appcompat_override";
+static constexpr char APPCOMPAT_OVERRIDE_PROP_TREE_FILE[] =
+        "/dev/__properties__/appcompat_override/property_info";
 using namespace std::literals;
 
 using android::base::ErrnoError;
 using android::base::Error;
+using android::base::GetIntProperty;
 using android::base::GetProperty;
 using android::base::ParseInt;
 using android::base::ReadFileToString;
@@ -108,7 +113,7 @@
 constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
 constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
 constexpr auto DIGEST_SIZE_USED = 8;
-constexpr auto API_LEVEL_CURRENT = 10000;
+constexpr auto MAX_VENDOR_API_LEVEL = 1000000;
 
 static bool persistent_properties_loaded = false;
 
@@ -1080,15 +1085,16 @@
     }
 }
 
-static int read_api_level_props(const std::vector<std::string>& api_level_props) {
-    int api_level = API_LEVEL_CURRENT;
-    for (const auto& api_level_prop : api_level_props) {
-        api_level = android::base::GetIntProperty(api_level_prop, API_LEVEL_CURRENT);
-        if (api_level != API_LEVEL_CURRENT) {
-            break;
-        }
+static int vendor_api_level_of(int sdk_api_level) {
+    if (sdk_api_level < __ANDROID_API_V__) {
+        return sdk_api_level;
     }
-    return api_level;
+    // In Android V, vendor API level started with version 202404.
+    // The calculation assumes that the SDK api level bumps once a year.
+    if (sdk_api_level < __ANDROID_API_FUTURE__) {
+        return 202404 + ((sdk_api_level - __ANDROID_API_V__) * 100);
+    }
+    return MAX_VENDOR_API_LEVEL;
 }
 
 static void property_initialize_ro_vendor_api_level() {
@@ -1096,20 +1102,27 @@
     // required to support.
     constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
 
-    // Api level properties of the board. The order of the properties must be kept.
-    std::vector<std::string> BOARD_API_LEVEL_PROPS = {"ro.board.api_level",
-                                                      "ro.board.first_api_level"};
-    // Api level properties of the device. The order of the properties must be kept.
-    std::vector<std::string> DEVICE_API_LEVEL_PROPS = {"ro.product.first_api_level",
-                                                       "ro.build.version.sdk"};
+    auto vendor_api_level = GetIntProperty("ro.board.first_api_level", MAX_VENDOR_API_LEVEL);
+    if (vendor_api_level != MAX_VENDOR_API_LEVEL) {
+        // Update the vendor_api_level with "ro.board.api_level" only if both "ro.board.api_level"
+        // and "ro.board.first_api_level" are defined.
+        vendor_api_level = GetIntProperty("ro.board.api_level", vendor_api_level);
+    }
 
-    int api_level = std::min(read_api_level_props(BOARD_API_LEVEL_PROPS),
-                             read_api_level_props(DEVICE_API_LEVEL_PROPS));
+    auto product_first_api_level =
+            GetIntProperty("ro.product.first_api_level", __ANDROID_API_FUTURE__);
+    if (product_first_api_level == __ANDROID_API_FUTURE__) {
+        // Fallback to "ro.build.version.sdk" if the "ro.product.first_api_level" is not defined.
+        product_first_api_level = GetIntProperty("ro.build.version.sdk", __ANDROID_API_FUTURE__);
+    }
+
+    vendor_api_level = std::min(vendor_api_level_of(product_first_api_level), vendor_api_level);
+
     std::string error;
-    auto res = PropertySetNoSocket(VENDOR_API_LEVEL_PROP, std::to_string(api_level), &error);
+    auto res = PropertySetNoSocket(VENDOR_API_LEVEL_PROP, std::to_string(vendor_api_level), &error);
     if (res != PROP_SUCCESS) {
-        LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << api_level << ": "
-                   << error << "(" << res << ")";
+        LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << vendor_api_level
+                   << ": " << error << "(" << res << ")";
     }
 }
 
@@ -1279,11 +1292,17 @@
         return;
     }
 
-    constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
-    if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
+    if (!WriteStringToFile(serialized_contexts, PROP_TREE_FILE, 0444, 0, 0, false)) {
         PLOG(ERROR) << "Unable to write serialized property infos to file";
     }
-    selinux_android_restorecon(kPropertyInfosPath, 0);
+    selinux_android_restorecon(PROP_TREE_FILE, 0);
+
+    mkdir(APPCOMPAT_OVERRIDE_PROP_FOLDERNAME, S_IRWXU | S_IXGRP | S_IXOTH);
+    if (!WriteStringToFile(serialized_contexts, APPCOMPAT_OVERRIDE_PROP_TREE_FILE, 0444, 0, 0,
+                           false)) {
+        PLOG(ERROR) << "Unable to write vendor overrides to file";
+    }
+    selinux_android_restorecon(APPCOMPAT_OVERRIDE_PROP_TREE_FILE, 0);
 }
 
 static void ExportKernelBootProps() {
@@ -1396,33 +1415,12 @@
     switch (init_message.msg_case()) {
         case InitMessage::kLoadPersistentProperties: {
             load_override_properties();
-            // Read persistent properties after all default values have been loaded.
-            // Apply staged and persistent properties
-            bool has_staged_prop = false;
-            auto const staged_prefix = std::string_view("next_boot.");
 
             auto persistent_properties = LoadPersistentProperties();
             for (const auto& property_record : persistent_properties.properties()) {
                 auto const& prop_name = property_record.name();
                 auto const& prop_value = property_record.value();
-
-                if (StartsWith(prop_name, staged_prefix)) {
-                  has_staged_prop = true;
-                  auto actual_prop_name = prop_name.substr(staged_prefix.size());
-                  InitPropertySet(actual_prop_name, prop_value);
-                } else {
-                  InitPropertySet(prop_name, prop_value);
-                }
-            }
-
-            // Update persist prop file if there are staged props
-            if (has_staged_prop) {
-                PersistentProperties props = LoadPersistentPropertiesFromMemory();
-                // write current updated persist prop file
-                auto result = WritePersistentPropertyFile(props);
-                if (!result.ok()) {
-                    LOG(ERROR) << "Could not store persistent property: " << result.error();
-                }
+                InitPropertySet(prop_name, prop_value);
             }
 
             // Apply debug ramdisk special settings after persistent properties are loaded.
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 3351c4c..1a26c4d 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -563,7 +563,7 @@
         }
     }
     if (timeout > 0ms) {
-        WaitToBeReaped(pids, timeout);
+        WaitToBeReaped(Service::GetSigchldFd(), pids, timeout);
     } else {
         // Even if we don't to wait for services to stop, we still optimistically reap zombies.
         ReapAnyOutstandingChildren();
diff --git a/init/security.cpp b/init/security.cpp
index 0c73fae..3e15447 100644
--- a/init/security.cpp
+++ b/init/security.cpp
@@ -20,6 +20,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/perf_event.h>
+#include <math.h>
 #include <selinux/selinux.h>
 #include <sys/ioctl.h>
 #include <sys/syscall.h>
@@ -121,8 +122,12 @@
     }
 #elif defined(__x86_64__)
     // x86_64 supports 28 - 32 rnd bits, but Android wants to ensure that the
-    // theoretical maximum of 32 bits is always supported and used.
-    if (SetMmapRndBitsMin(32, 32, false) && (!Has32BitAbi() || SetMmapRndBitsMin(16, 16, true))) {
+    // theoretical maximum of 32 bits is always supported and used; except in
+    // the case of the x86 page size emulator which supports a maximum
+    // of 30 bits for 16k page size, or 28 bits for 64k page size.
+    int max_bits = 32 - (static_cast<int>(log2(getpagesize())) - 12);
+    if (SetMmapRndBitsMin(max_bits, max_bits, false) &&
+        (!Has32BitAbi() || SetMmapRndBitsMin(16, 16, true))) {
         return {};
     }
 #elif defined(__arm__) || defined(__i386__)
diff --git a/init/service.cpp b/init/service.cpp
index 5e900ee..eb24dd5 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -37,6 +37,7 @@
 #include <cutils/sockets.h>
 #include <processgroup/processgroup.h>
 #include <selinux/selinux.h>
+#include <sys/signalfd.h>
 
 #include <string>
 
@@ -68,6 +69,7 @@
 using android::base::SetProperty;
 using android::base::StartsWith;
 using android::base::StringPrintf;
+using android::base::unique_fd;
 using android::base::WriteStringToFile;
 
 namespace android {
@@ -194,28 +196,20 @@
     }
 }
 
-void Service::KillProcessGroup(int signal, bool report_oneshot) {
-    // If we've already seen a successful result from killProcessGroup*(), then we have removed
-    // the cgroup already and calling these functions a second time will simply result in an error.
-    // This is true regardless of which signal was sent.
-    // These functions handle their own logging, so no additional logging is needed.
-    if (!process_cgroup_empty_) {
+void Service::KillProcessGroup(int signal) {
+    // Always attempt the process kill if process is still running.
+    // Cgroup clean up routines are idempotent. It's safe to call
+    // killProcessGroup repeatedly. During shutdown, `init` will
+    // call this function to send SIGTERM/SIGKILL to all processes.
+    // These signals must be sent for a successful shutdown.
+    if (!process_cgroup_empty_ || IsRunning()) {
         LOG(INFO) << "Sending signal " << signal << " to service '" << name_ << "' (pid " << pid_
                   << ") process group...";
-        int max_processes = 0;
         int r;
         if (signal == SIGTERM) {
-            r = killProcessGroupOnce(uid(), pid_, signal, &max_processes);
+            r = killProcessGroupOnce(uid(), pid_, signal);
         } else {
-            r = killProcessGroup(uid(), pid_, signal, &max_processes);
-        }
-
-        if (report_oneshot && max_processes > 0) {
-            LOG(WARNING)
-                    << "Killed " << max_processes
-                    << " additional processes from a oneshot process group for service '" << name_
-                    << "'. This is new behavior, previously child processes would not be killed in "
-                       "this case.";
+            r = killProcessGroup(uid(), pid_, signal);
         }
 
         if (r == 0) process_cgroup_empty_ = true;
@@ -265,7 +259,7 @@
 
 void Service::Reap(const siginfo_t& siginfo) {
     if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
-        KillProcessGroup(SIGKILL, false);
+        KillProcessGroup(SIGKILL);
     } else {
         // Legacy behavior from ~2007 until Android R: this else branch did not exist and we did not
         // kill the process group in this case.
@@ -273,7 +267,7 @@
             // The new behavior in Android R is to kill these process groups in all cases.  The
             // 'true' parameter instructions KillProcessGroup() to report a warning message where it
             // detects a difference in behavior has occurred.
-            KillProcessGroup(SIGKILL, true);
+            KillProcessGroup(SIGKILL);
         }
     }
 
@@ -799,6 +793,35 @@
     mount_namespace_ = IsDefaultMountNamespaceReady() ? NS_DEFAULT : NS_BOOTSTRAP;
 }
 
+static int ThreadCount() {
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/proc/self/task"), closedir);
+    if (!dir) {
+        return -1;
+    }
+
+    int count = 0;
+    dirent* entry;
+    while ((entry = readdir(dir.get())) != nullptr) {
+        if (entry->d_name[0] != '.') {
+            count++;
+        }
+    }
+    return count;
+}
+
+// Must be called BEFORE any threads are created. See also the sigprocmask() man page.
+unique_fd Service::CreateSigchldFd() {
+    CHECK_EQ(ThreadCount(), 1);
+    sigset_t mask;
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGCHLD);
+    if (sigprocmask(SIG_BLOCK, &mask, nullptr) < 0) {
+        PLOG(FATAL) << "Failed to block SIGCHLD";
+    }
+
+    return unique_fd(signalfd(-1, &mask, SFD_CLOEXEC));
+}
+
 void Service::SetStartedInFirstStage(pid_t pid) {
     LOG(INFO) << "adding first-stage service '" << name_ << "'...";
 
diff --git a/init/service.h b/init/service.h
index 9f09cef..5e9af25 100644
--- a/init/service.h
+++ b/init/service.h
@@ -156,11 +156,15 @@
     const Subcontext* subcontext() const { return subcontext_; }
     const std::string& filename() const { return filename_; }
     void set_filename(const std::string& name) { filename_ = name; }
+    static int GetSigchldFd() {
+        static int sigchld_fd = CreateSigchldFd().release();
+        return sigchld_fd;
+    }
 
   private:
     void NotifyStateChange(const std::string& new_state) const;
     void StopOrReset(int how);
-    void KillProcessGroup(int signal, bool report_oneshot = false);
+    void KillProcessGroup(int signal);
     void SetProcessAttributesAndCaps(InterprocessFifo setsid_finished);
     void ResetFlagsForStart();
     Result<void> CheckConsole();
@@ -168,6 +172,8 @@
     void RunService(const std::vector<Descriptor>& descriptors, InterprocessFifo cgroups_activated,
                     InterprocessFifo setsid_finished);
     void SetMountNamespace();
+    static ::android::base::unique_fd CreateSigchldFd();
+
     static unsigned long next_start_order_;
     static bool is_exec_service_running_;
 
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index a1b2cc5..92e350b 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -179,8 +179,9 @@
     if (!ParseInt(args[1], &service_->proc_attr_.priority,
                   static_cast<int>(ANDROID_PRIORITY_HIGHEST),  // highest is negative
                   static_cast<int>(ANDROID_PRIORITY_LOWEST))) {
-        return Errorf("process priority value must be range {} - {}", ANDROID_PRIORITY_HIGHEST,
-                      ANDROID_PRIORITY_LOWEST);
+        return Errorf("process priority value must be range {} - {}",
+                      static_cast<int>(ANDROID_PRIORITY_HIGHEST),
+                      static_cast<int>(ANDROID_PRIORITY_LOWEST));
     }
     return {};
 }
diff --git a/init/service_test.cpp b/init/service_test.cpp
index 87a2ce5..a3590b5 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -17,18 +17,45 @@
 #include "service.h"
 
 #include <algorithm>
+#include <fstream>
 #include <memory>
 #include <type_traits>
 #include <vector>
 
 #include <gtest/gtest.h>
 
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <selinux/selinux.h>
+#include <sys/signalfd.h>
 #include "lmkd_service.h"
+#include "reboot.h"
+#include "service.h"
+#include "service_list.h"
+#include "service_parser.h"
 #include "util.h"
 
+using ::android::base::ReadFileToString;
+using ::android::base::StringPrintf;
+using ::android::base::StringReplace;
+using ::android::base::unique_fd;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+
 namespace android {
 namespace init {
 
+static std::string GetSecurityContext() {
+    char* ctx;
+    if (getcon(&ctx) == -1) {
+        ADD_FAILURE() << "Failed to call getcon : " << strerror(errno);
+    }
+    std::string result{ctx};
+    freecon(ctx);
+    return result;
+}
+
 TEST(service, pod_initialized) {
     constexpr auto memory_size = sizeof(Service);
     alignas(alignof(Service)) unsigned char old_memory[memory_size];
@@ -190,5 +217,69 @@
     Test_make_temporary_oneshot_service(false, false, false, false, false);
 }
 
+// Returns the path in the v2 cgroup hierarchy for a given process in the format /uid_%d/pid_%d.
+static std::string CgroupPath(pid_t pid) {
+    std::string cgroup_path = StringPrintf("/proc/%d/cgroup", pid);
+    std::ifstream is(cgroup_path, std::ios::in);
+    std::string line;
+    while (std::getline(is, line)) {
+        if (line.substr(0, 3) == "0::") {
+            return line.substr(3);
+        }
+    }
+    return {};
+}
+
+class ServiceStopTest : public testing::TestWithParam<bool> {};
+
+// Before November 2023, processes that were migrated to another v2 cgroup were ignored by
+// Service::Stop() if their uid_%d/pid_%d cgroup directory got removed. This test, if run with the
+// parameter set to 'true', verifies that such services are stopped.
+TEST_P(ServiceStopTest, stop) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Must be run as root.";
+        return;
+    }
+
+    static constexpr std::string_view kServiceName = "ServiceA";
+    static constexpr std::string_view kScriptTemplate = R"init(
+service $name /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+
+    std::string script = StringReplace(StringReplace(kScriptTemplate, "$name", kServiceName, false),
+                                       "$selabel", GetSecurityContext(), false);
+    ServiceList& service_list = ServiceList::GetInstance();
+    Parser parser;
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+    TemporaryFile tf;
+    ASSERT_GE(tf.fd, 0);
+    ASSERT_TRUE(WriteStringToFd(script, tf.fd));
+    ASSERT_TRUE(parser.ParseConfig(tf.path));
+
+    Service* const service = ServiceList::GetInstance().FindService(kServiceName);
+    ASSERT_NE(service, nullptr);
+    ASSERT_RESULT_OK(service->Start());
+    ASSERT_TRUE(service->IsRunning());
+    if (GetParam()) {
+        const pid_t pid = service->pid();
+        const std::string cgroup_path = CgroupPath(pid);
+        EXPECT_NE(cgroup_path, "");
+        EXPECT_NE(cgroup_path, "/");
+        const std::string pid_str = std::to_string(pid);
+        EXPECT_TRUE(WriteStringToFile(pid_str, "/sys/fs/cgroup/cgroup.procs"));
+        EXPECT_EQ(CgroupPath(pid), "/");
+        EXPECT_EQ(rmdir(("/sys/fs/cgroup" + cgroup_path).c_str()), 0);
+    }
+    EXPECT_EQ(0, StopServicesAndLogViolations({service->name()}, 10s, /*terminate=*/true));
+    ServiceList::GetInstance().RemoveService(*service);
+}
+
+INSTANTIATE_TEST_SUITE_P(service, ServiceStopTest, testing::Values(false, true));
+
 }  // namespace init
 }  // namespace android
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index f8c501f..8e9e713 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -18,6 +18,7 @@
 
 #include <signal.h>
 #include <string.h>
+#include <sys/signalfd.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -31,6 +32,7 @@
 
 #include <thread>
 
+#include "epoll.h"
 #include "init.h"
 #include "service.h"
 #include "service_list.h"
@@ -116,33 +118,76 @@
     return pid;
 }
 
-void ReapAnyOutstandingChildren() {
-    while (ReapOneProcess() != 0) {
+std::set<pid_t> ReapAnyOutstandingChildren() {
+    std::set<pid_t> reaped_pids;
+    for (;;) {
+        const pid_t pid = ReapOneProcess();
+        if (pid <= 0) {
+            return reaped_pids;
+        }
+        reaped_pids.emplace(pid);
     }
 }
 
-void WaitToBeReaped(const std::vector<pid_t>& pids, std::chrono::milliseconds timeout) {
+static void ReapAndRemove(std::vector<pid_t>& alive_pids) {
+    for (auto pid : ReapAnyOutstandingChildren()) {
+        const auto it = std::find(alive_pids.begin(), alive_pids.end(), pid);
+        if (it != alive_pids.end()) {
+            alive_pids.erase(it);
+        }
+    }
+}
+
+static void HandleSignal(int signal_fd) {
+    signalfd_siginfo siginfo;
+    ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
+    if (bytes_read != sizeof(siginfo)) {
+        LOG(WARNING) << "Unexpected: " << __func__ << " read " << bytes_read << " bytes instead of "
+                     << sizeof(siginfo);
+    }
+}
+
+void WaitToBeReaped(int sigchld_fd, const std::vector<pid_t>& pids,
+                    std::chrono::milliseconds timeout) {
     Timer t;
-    std::vector<pid_t> alive_pids(pids.begin(), pids.end());
+    Epoll epoll;
+    if (sigchld_fd >= 0) {
+        if (auto result = epoll.Open(); result.ok()) {
+            result =
+                    epoll.RegisterHandler(sigchld_fd, [sigchld_fd]() { HandleSignal(sigchld_fd); });
+            if (!result.ok()) {
+                LOG(WARNING) << __func__
+                             << " RegisterHandler() failed. Falling back to sleep_for(): "
+                             << result.error();
+                sigchld_fd = -1;
+            }
+        } else {
+            LOG(WARNING) << __func__ << " Epoll::Open() failed. Falling back to sleep_for(): "
+                         << result.error();
+            sigchld_fd = -1;
+        }
+    }
+    std::vector<pid_t> alive_pids(pids);
+    ReapAndRemove(alive_pids);
     while (!alive_pids.empty() && t.duration() < timeout) {
-        pid_t pid;
-        while ((pid = ReapOneProcess()) != 0) {
-            auto it = std::find(alive_pids.begin(), alive_pids.end(), pid);
-            if (it != alive_pids.end()) {
-                alive_pids.erase(it);
+        if (sigchld_fd >= 0) {
+            auto result = epoll.Wait(std::max(timeout - t.duration(), 0ms));
+            if (result.ok()) {
+                ReapAndRemove(alive_pids);
+                continue;
+            } else {
+                LOG(WARNING) << "Epoll::Wait() failed " << result.error();
             }
         }
-        if (alive_pids.empty()) {
-            break;
-        }
         std::this_thread::sleep_for(50ms);
+        ReapAndRemove(alive_pids);
     }
     LOG(INFO) << "Waiting for " << pids.size() << " pids to be reaped took " << t << " with "
               << alive_pids.size() << " of them still running";
-    for (pid_t pid : pids) {
+    for (pid_t pid : alive_pids) {
         std::string status = "(no-such-pid)";
         ReadFileToString(StringPrintf("/proc/%d/status", pid), &status);
-        LOG(INFO) << "Still running: " << pid << ' ' << status;
+        LOG(INFO) << "Still running: " << pid << '\n' << status;
     }
 }
 
diff --git a/init/sigchld_handler.h b/init/sigchld_handler.h
index fac1020..5351302 100644
--- a/init/sigchld_handler.h
+++ b/init/sigchld_handler.h
@@ -18,14 +18,16 @@
 #define _INIT_SIGCHLD_HANDLER_H_
 
 #include <chrono>
+#include <set>
 #include <vector>
 
 namespace android {
 namespace init {
 
-void ReapAnyOutstandingChildren();
+std::set<pid_t> ReapAnyOutstandingChildren();
 
-void WaitToBeReaped(const std::vector<pid_t>& pids, std::chrono::milliseconds timeout);
+void WaitToBeReaped(int sigchld_fd, const std::vector<pid_t>& pids,
+                    std::chrono::milliseconds timeout);
 
 }  // namespace init
 }  // namespace android
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
index 3a9ff5b..3a78343 100644
--- a/init/snapuserd_transition.cpp
+++ b/init/snapuserd_transition.cpp
@@ -25,6 +25,7 @@
 #include <filesystem>
 #include <string>
 #include <string_view>
+#include <thread>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 586e2cf..3f0d0e9 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -297,6 +297,10 @@
 }
 
 static UeventdConfiguration GetConfiguration() {
+    if (IsMicrodroid()) {
+        return ParseConfig({"/system/etc/ueventd.rc", "/vendor/etc/ueventd.rc"});
+    }
+
     auto hardware = android::base::GetProperty("ro.hardware", "");
 
     struct LegacyPathInfo {
diff --git a/janitors/OWNERS b/janitors/OWNERS
index d871201..a28737e 100644
--- a/janitors/OWNERS
+++ b/janitors/OWNERS
@@ -3,5 +3,4 @@
 cferris@google.com
 dwillemsen@google.com
 enh@google.com
-narayan@google.com
 sadafebrahimi@google.com
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index dbaeb93..ca6868c 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -26,7 +26,8 @@
 
 __BEGIN_DECLS
 
-static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
+static constexpr const char* CGROUPV2_HIERARCHY_NAME = "cgroup2";
+[[deprecated]] static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
 
 bool CgroupsAvailable();
 bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
@@ -64,22 +65,19 @@
 // should be active again. E.g. Zygote specialization for child process.
 void DropTaskProfilesResourceCaching();
 
-// Return 0 and removes the cgroup if there are no longer any processes in it.
-// Returns -1 in the case of an error occurring or if there are processes still running
-// even after retrying for up to 200ms.
-// If max_processes is not nullptr, it returns the maximum number of processes seen in the cgroup
-// during the killing process.  Note that this can be 0 if all processes from the process group have
-// already been terminated.
-int killProcessGroup(uid_t uid, int initialPid, int signal, int* max_processes = nullptr);
+// Return 0 if all processes were killed and the cgroup was successfully removed.
+// Returns -1 in the case of an error occurring or if there are processes still running.
+int killProcessGroup(uid_t uid, int initialPid, int signal);
 
 // Returns the same as killProcessGroup(), however it does not retry, which means
 // that it only returns 0 in the case that the cgroup exists and it contains no processes.
-int killProcessGroupOnce(uid_t uid, int initialPid, int signal, int* max_processes = nullptr);
+int killProcessGroupOnce(uid_t uid, int initialPid, int signal);
 
 // Sends the provided signal to all members of a process group, but does not wait for processes to
 // exit, or for the cgroup to be removed. Callers should also ensure that killProcessGroup is called
-// later to ensure the cgroup is fully removed, otherwise system resources may leak.
-int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal);
+// later to ensure the cgroup is fully removed, otherwise system resources will leak.
+// Returns true if no errors are encountered sending signals, otherwise false.
+bool sendSignalToProcessGroup(uid_t uid, int initialPid, int signal);
 
 int createProcessGroup(uid_t uid, int initialPid, bool memControl = false);
 
@@ -89,7 +87,6 @@
 bool setProcessGroupSoftLimit(uid_t uid, int initialPid, int64_t softLimitInBytes);
 bool setProcessGroupLimit(uid_t uid, int initialPid, int64_t limitInBytes);
 
-void removeAllProcessGroups(void);
 void removeAllEmptyProcessGroups(void);
 
 // Provides the path for an attribute in a specific process group
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index cc2565f..3209adf 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -22,6 +22,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <poll.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -30,6 +31,7 @@
 #include <unistd.h>
 
 #include <chrono>
+#include <cstring>
 #include <map>
 #include <memory>
 #include <mutex>
@@ -53,7 +55,9 @@
 
 using namespace std::chrono_literals;
 
-#define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"
+#define PROCESSGROUP_CGROUP_PROCS_FILE "cgroup.procs"
+#define PROCESSGROUP_CGROUP_KILL_FILE "cgroup.kill"
+#define PROCESSGROUP_CGROUP_EVENTS_FILE "cgroup.events"
 
 bool CgroupsAvailable() {
     static bool cgroups_available = access("/proc/cgroups", F_OK) == 0;
@@ -74,6 +78,29 @@
     return true;
 }
 
+static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
+    return StringPrintf("%s/uid_%u", cgroup, uid);
+}
+
+static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) {
+    return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid);
+}
+
+static bool CgroupKillAvailable() {
+    static std::once_flag f;
+    static bool cgroup_kill_available = false;
+    std::call_once(f, []() {
+        std::string cg_kill;
+        CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cg_kill);
+        // cgroup.kill is not on the root cgroup, so check a non-root cgroup that should always
+        // exist
+        cg_kill = ConvertUidToPath(cg_kill.c_str(), AID_ROOT) + '/' + PROCESSGROUP_CGROUP_KILL_FILE;
+        cgroup_kill_available = access(cg_kill.c_str(), F_OK) == 0;
+    });
+
+    return cgroup_kill_available;
+}
+
 static bool CgroupGetMemcgAppsPath(std::string* path) {
     CgroupController controller = CgroupMap::GetInstance().FindController("memory");
 
@@ -129,7 +156,7 @@
     }
 
     if (!attr->GetPathForTask(tid, path)) {
-        PLOG(ERROR) << "Failed to find cgroup for tid " << tid;
+        LOG(ERROR) << "Failed to find cgroup for tid " << tid;
         return false;
     }
 
@@ -205,35 +232,27 @@
                                                        false);
 }
 
-static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
-    return StringPrintf("%s/uid_%u", cgroup, uid);
-}
-
-static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) {
-    return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid);
-}
-
-static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
-    int ret = 0;
-    auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
-
-    while (retries--) {
-        ret = rmdir(uid_pid_path.c_str());
-        if (!ret || errno != EBUSY || !retries) break;
-        std::this_thread::sleep_for(5ms);
-    }
+static int RemoveCgroup(const char* cgroup, uid_t uid, int pid) {
+    auto path = ConvertUidPidToPath(cgroup, uid, pid);
+    int ret = TEMP_FAILURE_RETRY(rmdir(path.c_str()));
 
     if (!ret && uid >= AID_ISOLATED_START && uid <= AID_ISOLATED_END) {
         // Isolated UIDs are unlikely to be reused soon after removal,
         // so free up the kernel resources for the UID level cgroup.
-        const auto uid_path = ConvertUidToPath(cgroup, uid);
-        ret = rmdir(uid_path.c_str());
+        path = ConvertUidToPath(cgroup, uid);
+        ret = TEMP_FAILURE_RETRY(rmdir(path.c_str()));
+    }
+
+    if (ret < 0 && errno == ENOENT) {
+        // This function is idempoetent, but still warn here.
+        LOG(WARNING) << "RemoveCgroup: " << path << " does not exist.";
+        ret = 0;
     }
 
     return ret;
 }
 
-static bool RemoveUidProcessGroups(const std::string& uid_path, bool empty_only) {
+static bool RemoveEmptyUidCgroups(const std::string& uid_path) {
     std::unique_ptr<DIR, decltype(&closedir)> uid(opendir(uid_path.c_str()), closedir);
     bool empty = true;
     if (uid != NULL) {
@@ -248,21 +267,6 @@
             }
 
             auto path = StringPrintf("%s/%s", uid_path.c_str(), dir->d_name);
-            if (empty_only) {
-                struct stat st;
-                auto procs_file = StringPrintf("%s/%s", path.c_str(),
-                                               PROCESSGROUP_CGROUP_PROCS_FILE);
-                if (stat(procs_file.c_str(), &st) == -1) {
-                    PLOG(ERROR) << "Failed to get stats for " << procs_file;
-                    continue;
-                }
-                if (st.st_size > 0) {
-                    // skip non-empty groups
-                    LOG(VERBOSE) << "Skipping non-empty group " << path;
-                    empty = false;
-                    continue;
-                }
-            }
             LOG(VERBOSE) << "Removing " << path;
             if (rmdir(path.c_str()) == -1) {
                 if (errno != EBUSY) {
@@ -275,11 +279,13 @@
     return empty;
 }
 
-void removeAllProcessGroupsInternal(bool empty_only) {
+void removeAllEmptyProcessGroups() {
+    LOG(VERBOSE) << "removeAllEmptyProcessGroups()";
+
     std::vector<std::string> cgroups;
     std::string path, memcg_apps_path;
 
-    if (CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &path)) {
+    if (CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &path)) {
         cgroups.push_back(path);
     }
     if (CgroupGetMemcgAppsPath(&memcg_apps_path) && memcg_apps_path != path) {
@@ -302,7 +308,7 @@
                 }
 
                 auto path = StringPrintf("%s/%s", cgroup_root_path.c_str(), dir->d_name);
-                if (!RemoveUidProcessGroups(path, empty_only)) {
+                if (!RemoveEmptyUidCgroups(path)) {
                     LOG(VERBOSE) << "Skip removing " << path;
                     continue;
                 }
@@ -315,16 +321,6 @@
     }
 }
 
-void removeAllProcessGroups() {
-    LOG(VERBOSE) << "removeAllProcessGroups()";
-    removeAllProcessGroupsInternal(false);
-}
-
-void removeAllEmptyProcessGroups() {
-    LOG(VERBOSE) << "removeAllEmptyProcessGroups()";
-    removeAllProcessGroupsInternal(true);
-}
-
 /**
  * Process groups are primarily created by the Zygote, meaning that uid/pid groups are created by
  * the user root. Ownership for the newly created cgroup and all of its files must thus be
@@ -374,35 +370,55 @@
     return false;
 }
 
-// Returns number of processes killed on success
-// Returns 0 if there are no processes in the process cgroup left to kill
-// Returns -1 on error
-static int DoKillProcessGroupOnce(const char* cgroup, uid_t uid, int initialPid, int signal) {
-    // We separate all of the pids in the cgroup into those pids that are also the leaders of
-    // process groups (stored in the pgids set) and those that are not (stored in the pids set).
-    std::set<pid_t> pgids;
-    pgids.emplace(initialPid);
-    std::set<pid_t> pids;
-    int processes = 0;
-
-    std::unique_ptr<FILE, decltype(&fclose)> fd(nullptr, fclose);
+bool sendSignalToProcessGroup(uid_t uid, int initialPid, int signal) {
+    std::set<pid_t> pgids, pids;
 
     if (CgroupsAvailable()) {
-        auto path = ConvertUidPidToPath(cgroup, uid, initialPid) + PROCESSGROUP_CGROUP_PROCS_FILE;
-        fd.reset(fopen(path.c_str(), "re"));
-        if (!fd) {
-            if (errno == ENOENT) {
-                // This happens when process is already dead
-                return 0;
+        std::string hierarchy_root_path, cgroup_v2_path;
+        CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
+        cgroup_v2_path = ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid);
+
+        if (signal == SIGKILL && CgroupKillAvailable()) {
+            LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_KILL_FILE << " to SIGKILL "
+                         << cgroup_v2_path;
+
+            // We need to kill the process group in addition to the cgroup. For normal apps they
+            // should completely overlap, but system_server kills depend on process group kills to
+            // take down apps which are in their own cgroups and not individually targeted.
+            if (kill(-initialPid, signal) == -1 && errno != ESRCH) {
+                PLOG(WARNING) << "kill(" << -initialPid << ", " << signal << ") failed";
             }
-            PLOG(WARNING) << __func__ << " failed to open process cgroup uid " << uid << " pid "
-                          << initialPid;
-            return -1;
+
+            const std::string killfilepath = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_KILL_FILE;
+            if (WriteStringToFile("1", killfilepath)) {
+                return true;
+            } else {
+                PLOG(ERROR) << "Failed to write 1 to " << killfilepath;
+                // Fallback to cgroup.procs below
+            }
         }
+
+        // Since cgroup.kill only sends SIGKILLs, we read cgroup.procs to find each process to
+        // signal individually. This is more costly than using cgroup.kill for SIGKILLs.
+        LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_PROCS_FILE << " to signal (" << signal
+                     << ") " << cgroup_v2_path;
+
+        // We separate all of the pids in the cgroup into those pids that are also the leaders of
+        // process groups (stored in the pgids set) and those that are not (stored in the pids set).
+        const auto procsfilepath = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_PROCS_FILE;
+        std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(procsfilepath.c_str(), "re"), fclose);
+        if (!fp) {
+            // This should only happen if the cgroup has already been removed with a successful call
+            // to killProcessGroup. Callers should only retry sendSignalToProcessGroup or
+            // killProcessGroup calls if they fail without ENOENT.
+            PLOG(ERROR) << "Failed to open " << procsfilepath;
+            kill(-initialPid, signal);
+            return false;
+        }
+
         pid_t pid;
         bool file_is_empty = true;
-        while (fscanf(fd.get(), "%d\n", &pid) == 1 && pid >= 0) {
-            processes++;
+        while (fscanf(fp.get(), "%d\n", &pid) == 1 && pid >= 0) {
             file_is_empty = false;
             if (pid == 0) {
                 // Should never happen...  but if it does, trying to kill this
@@ -432,6 +448,8 @@
         }
     }
 
+    pgids.emplace(initialPid);
+
     // Kill all process groups.
     for (const auto pgid : pgids) {
         LOG(VERBOSE) << "Killing process group " << -pgid << " in uid " << uid
@@ -452,109 +470,174 @@
         }
     }
 
-    return (!fd || feof(fd.get())) ? processes : -1;
+    return true;
 }
 
-static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries,
-                            int* max_processes) {
+template <typename T>
+static std::chrono::milliseconds toMillisec(T&& duration) {
+    return std::chrono::duration_cast<std::chrono::milliseconds>(duration);
+}
+
+enum class populated_status
+{
+    populated,
+    not_populated,
+    error
+};
+
+static populated_status cgroupIsPopulated(int events_fd) {
+    const std::string POPULATED_KEY("populated ");
+    const std::string::size_type MAX_EVENTS_FILE_SIZE = 32;
+
+    std::string buf;
+    buf.resize(MAX_EVENTS_FILE_SIZE);
+    ssize_t len = TEMP_FAILURE_RETRY(pread(events_fd, buf.data(), buf.size(), 0));
+    if (len == -1) {
+        PLOG(ERROR) << "Could not read cgroup.events: ";
+        // Potentially ENODEV if the cgroup has been removed since we opened this file, but that
+        // shouldn't have happened yet.
+        return populated_status::error;
+    }
+
+    if (len == 0) {
+        LOG(ERROR) << "cgroup.events EOF";
+        return populated_status::error;
+    }
+
+    buf.resize(len);
+
+    const std::string::size_type pos = buf.find(POPULATED_KEY);
+    if (pos == std::string::npos) {
+        LOG(ERROR) << "Could not find populated key in cgroup.events";
+        return populated_status::error;
+    }
+
+    if (pos + POPULATED_KEY.size() + 1 > len) {
+        LOG(ERROR) << "Partial read of cgroup.events";
+        return populated_status::error;
+    }
+
+    return buf[pos + POPULATED_KEY.size()] == '1' ?
+        populated_status::populated : populated_status::not_populated;
+}
+
+// The default timeout of 2200ms comes from the default number of retries in a previous
+// implementation of this function. The default retry value was 40 for killing and 400 for cgroup
+// removal with 5ms sleeps between each retry.
+static int KillProcessGroup(
+        uid_t uid, int initialPid, int signal, bool once = false,
+        std::chrono::steady_clock::time_point until = std::chrono::steady_clock::now() + 2200ms) {
     CHECK_GE(uid, 0);
     CHECK_GT(initialPid, 0);
 
+    // Always attempt to send a kill signal to at least the initialPid, at least once, regardless of
+    // whether its cgroup exists or not. This should only be necessary if a bug results in the
+    // migration of the targeted process out of its cgroup, which we will also attempt to kill.
+    const bool signal_ret = sendSignalToProcessGroup(uid, initialPid, signal);
+
+    if (!CgroupsAvailable() || !signal_ret) return signal_ret ? 0 : -1;
+
     std::string hierarchy_root_path;
-    if (CgroupsAvailable()) {
-        CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
-    }
-    const char* cgroup = hierarchy_root_path.c_str();
+    CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
 
-    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
+    const std::string cgroup_v2_path =
+            ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid);
 
-    if (max_processes != nullptr) {
-        *max_processes = 0;
-    }
-
-    int retry = retries;
-    int processes;
-    while ((processes = DoKillProcessGroupOnce(cgroup, uid, initialPid, signal)) > 0) {
-        if (max_processes != nullptr && processes > *max_processes) {
-            *max_processes = processes;
-        }
-        LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid;
-        if (!CgroupsAvailable()) {
-            // makes no sense to retry, because there are no cgroup_procs file
-            processes = 0;  // no remaining processes
-            break;
-        }
-        if (retry > 0) {
-            std::this_thread::sleep_for(5ms);
-            --retry;
-        } else {
-            break;
-        }
-    }
-
-    if (processes < 0) {
-        PLOG(ERROR) << "Error encountered killing process cgroup uid " << uid << " pid "
-                    << initialPid;
+    const std::string eventsfile = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_EVENTS_FILE;
+    android::base::unique_fd events_fd(open(eventsfile.c_str(), O_RDONLY));
+    if (events_fd.get() == -1) {
+        PLOG(WARNING) << "Error opening " << eventsfile << " for KillProcessGroup";
         return -1;
     }
 
-    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
-    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+    struct pollfd fds = {
+        .fd = events_fd,
+        .events = POLLPRI,
+    };
 
-    // We only calculate the number of 'processes' when killing the processes.
-    // In the retries == 0 case, we only kill the processes once and therefore
-    // will not have waited then recalculated how many processes are remaining
-    // after the first signals have been sent.
-    // Logging anything regarding the number of 'processes' here does not make sense.
+    const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
 
-    if (processes == 0) {
-        if (retries > 0) {
-            LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid
-                      << " in " << static_cast<int>(ms) << "ms";
+    // The primary reason to loop here is to capture any new forks or migrations that could occur
+    // after we send signals to the original set of processes, but before all of those processes
+    // exit and the cgroup becomes unpopulated, or before we remove the cgroup. We try hard to
+    // ensure this completes successfully to avoid permanent memory leaks, but we still place a
+    // large default upper bound on the amount of time we spend in this loop. The amount of CPU
+    // contention, and the amount of work that needs to be done in do_exit for each process
+    // determines how long this will take.
+    int ret;
+    do {
+        populated_status populated;
+        while ((populated = cgroupIsPopulated(events_fd.get())) == populated_status::populated &&
+               std::chrono::steady_clock::now() < until) {
+
+            sendSignalToProcessGroup(uid, initialPid, signal);
+            if (once) {
+                populated = cgroupIsPopulated(events_fd.get());
+                break;
+            }
+
+            const std::chrono::steady_clock::time_point poll_start =
+                    std::chrono::steady_clock::now();
+
+            if (poll_start < until)
+                ret = TEMP_FAILURE_RETRY(poll(&fds, 1, toMillisec(until - poll_start).count()));
+
+            if (ret == -1) {
+                // Fallback to 5ms sleeps if poll fails
+                PLOG(ERROR) << "Poll on " << eventsfile << "failed";
+                const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
+                if (now < until)
+                    std::this_thread::sleep_for(std::min(5ms, toMillisec(until - now)));
+            }
+
+            LOG(VERBOSE) << "Waited "
+                         << toMillisec(std::chrono::steady_clock::now() - poll_start).count()
+                         << " ms for " << eventsfile << " poll";
         }
 
-        if (!CgroupsAvailable()) {
-            // nothing to do here, if cgroups isn't available
-            return 0;
+        const std::chrono::milliseconds kill_duration =
+                toMillisec(std::chrono::steady_clock::now() - start);
+
+        if (populated == populated_status::populated) {
+            LOG(WARNING) << "Still waiting on process(es) to exit for cgroup " << cgroup_v2_path
+                         << " after " << kill_duration.count() << " ms";
+            // We'll still try the cgroup removal below which we expect to log an error.
+        } else if (populated == populated_status::not_populated) {
+            LOG(VERBOSE) << "Killed all processes under cgroup " << cgroup_v2_path
+                         << " after " << kill_duration.count() << " ms";
         }
 
-        // 400 retries correspond to 2 secs max timeout
-        int err = RemoveProcessGroup(cgroup, uid, initialPid, 400);
+        ret = RemoveCgroup(hierarchy_root_path.c_str(), uid, initialPid);
+        if (ret)
+            PLOG(ERROR) << "Unable to remove cgroup " << cgroup_v2_path;
+        else
+            LOG(INFO) << "Removed cgroup " << cgroup_v2_path;
 
         if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
+            // This per-application memcg v1 case should eventually be removed after migration to
+            // memcg v2.
             std::string memcg_apps_path;
             if (CgroupGetMemcgAppsPath(&memcg_apps_path) &&
-                RemoveProcessGroup(memcg_apps_path.c_str(), uid, initialPid, 400) < 0) {
-                return -1;
+                (ret = RemoveCgroup(memcg_apps_path.c_str(), uid, initialPid)) < 0) {
+                const auto memcg_v1_cgroup_path =
+                        ConvertUidPidToPath(memcg_apps_path.c_str(), uid, initialPid);
+                PLOG(ERROR) << "Unable to remove memcg v1 cgroup " << memcg_v1_cgroup_path;
             }
         }
 
-        return err;
-    } else {
-        if (retries > 0) {
-            LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid
-                       << " in " << static_cast<int>(ms) << "ms, " << processes
-                       << " processes remain";
-        }
-        return -1;
-    }
+        if (once) break;
+        if (std::chrono::steady_clock::now() >= until) break;
+    } while (ret && errno == EBUSY);
+
+    return ret;
 }
 
-int killProcessGroup(uid_t uid, int initialPid, int signal, int* max_processes) {
-    return KillProcessGroup(uid, initialPid, signal, 40 /*retries*/, max_processes);
+int killProcessGroup(uid_t uid, int initialPid, int signal) {
+    return KillProcessGroup(uid, initialPid, signal);
 }
 
-int killProcessGroupOnce(uid_t uid, int initialPid, int signal, int* max_processes) {
-    return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/, max_processes);
-}
-
-int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal) {
-    std::string hierarchy_root_path;
-    if (CgroupsAvailable()) {
-        CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
-    }
-    const char* cgroup = hierarchy_root_path.c_str();
-    return DoKillProcessGroupOnce(cgroup, uid, initialPid, signal);
+int killProcessGroupOnce(uid_t uid, int initialPid, int signal) {
+    return KillProcessGroup(uid, initialPid, signal, true);
 }
 
 static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup,
@@ -594,7 +677,7 @@
         return -errno;
     }
 
-    auto uid_pid_procs_file = uid_pid_path + PROCESSGROUP_CGROUP_PROCS_FILE;
+    auto uid_pid_procs_file = uid_pid_path + '/' + PROCESSGROUP_CGROUP_PROCS_FILE;
 
     if (!WriteStringToFile(std::to_string(initialPid), uid_pid_procs_file)) {
         ret = -errno;
@@ -609,7 +692,7 @@
     CHECK_GT(initialPid, 0);
 
     if (memControl && !UsePerAppMemcg()) {
-        PLOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
+        LOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
         return -EINVAL;
     }
 
@@ -625,19 +708,19 @@
     }
 
     std::string cgroup;
-    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
+    CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cgroup);
     return createProcessGroupInternal(uid, initialPid, cgroup, true);
 }
 
 static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
     if (!isMemoryCgroupSupported()) {
-        PLOG(ERROR) << "Memcg is not mounted.";
+        LOG(ERROR) << "Memcg is not mounted.";
         return false;
     }
 
     std::string path;
     if (!CgroupGetAttributePathForTask(attr_name, tid, &path)) {
-        PLOG(ERROR) << "Failed to find attribute '" << attr_name << "'";
+        LOG(ERROR) << "Failed to find attribute '" << attr_name << "'";
         return false;
     }
 
@@ -672,4 +755,4 @@
     }
 
     return tp->IsValidForProcess(uid, pid);
-}
\ No newline at end of file
+}
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 3e4393d..d013ec8 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -1,13 +1,6 @@
 {
   "Cgroups": [
     {
-      "Controller": "blkio",
-      "Path": "/dev/blkio",
-      "Mode": "0775",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
       "Controller": "cpu",
       "Path": "/dev/cpuctl",
       "Mode": "0755",
@@ -39,6 +32,12 @@
       {
         "Controller": "freezer",
         "Path": "."
+      },
+      {
+        "Controller": "io",
+        "Path": ".",
+        "NeedsActivation": true,
+        "Optional": true
       }
     ]
   }
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 1fc66ba..f2ef316 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -76,6 +76,26 @@
       "Name": "FreezerState",
       "Controller": "freezer",
       "File": "cgroup.freeze"
+    },
+    {
+      "Name": "BfqWeight",
+      "Controller": "io",
+      "File": "io.bfq.weight"
+    },
+    {
+      "Name": "CfqGroupIdle",
+      "Controller": "io",
+      "File": "io.group_idle"
+    },
+    {
+      "Name": "CfqWeight",
+      "Controller": "io",
+      "File": "io.weight"
+    },
+    {
+      "Name": "IoPrioClass",
+      "Controller": "io",
+      "File": "io.prio.class"
     }
   ],
 
@@ -439,11 +459,39 @@
       "Name": "LowIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "blkio",
-            "Path": "background"
+            "Name": "BfqWeight",
+            "Value": "10",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "200",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "IoPrioClass",
+            "Value": "restrict-to-be",
+            "Optional": "true"
           }
         }
       ]
@@ -452,11 +500,39 @@
       "Name": "NormalIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "blkio",
-            "Path": ""
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "IoPrioClass",
+            "Value": "restrict-to-be",
+            "Optional": "true"
           }
         }
       ]
@@ -465,11 +541,39 @@
       "Name": "HighIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "blkio",
-            "Path": ""
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "IoPrioClass",
+            "Value": "promote-to-rt",
+            "Optional": "true"
           }
         }
       ]
@@ -478,11 +582,39 @@
       "Name": "MaxIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "blkio",
-            "Path": ""
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "IoPrioClass",
+            "Value": "promote-to-rt",
+            "Optional": "true"
           }
         }
       ]
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index fbeedf9..4e44c91 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -212,7 +212,7 @@
     if (root.isMember("Cgroups2")) {
         const Json::Value& cgroups2 = root["Cgroups2"];
         std::string root_path = cgroups2["Path"].asString();
-        MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_CONTROLLER_NAME, "", 2);
+        MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME, "", 2);
 
         const Json::Value& childGroups = cgroups2["Controllers"];
         for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
@@ -358,7 +358,7 @@
     const format::CgroupController* controller = descriptor.controller();
 
     if (controller->version() == 2) {
-        if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
+        if (!strcmp(controller->name(), CGROUPV2_HIERARCHY_NAME)) {
             return MountV2CgroupController(descriptor);
         } else {
             return ActivateV2CgroupController(descriptor);
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index f51b076..d5bd47c 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -316,7 +316,7 @@
     FdCacheHelper::Init(controller_.GetProcsFilePath(path_, 0, 0), fd_[ProfileAction::RCT_PROCESS]);
 }
 
-bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
+bool SetCgroupAction::AddTidToCgroup(int tid, int fd, ResourceCacheType cache_type) const {
     if (tid <= 0) {
         return true;
     }
@@ -332,6 +332,7 @@
         return true;
     }
 
+    const char* controller_name = controller()->name();
     // ENOSPC is returned when cpuset cgroup that we are joining has no online cpus
     if (errno == ENOSPC && !strcmp(controller_name, "cpuset")) {
         // This is an abnormal case happening only in testing, so report it only once
@@ -345,7 +346,8 @@
                    << "' into cpuset because all cpus in that cpuset are offline";
         empty_cpuset_reported = true;
     } else {
-        PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
+        PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; path=" << path_ << "; "
+                    << (cache_type == RCT_TASK ? "task" : "process");
     }
 
     return false;
@@ -356,7 +358,7 @@
     std::lock_guard<std::mutex> lock(fd_mutex_);
     if (FdCacheHelper::IsCached(fd_[cache_type])) {
         // fd is cached, reuse it
-        if (!AddTidToCgroup(id, fd_[cache_type], controller()->name())) {
+        if (!AddTidToCgroup(id, fd_[cache_type], cache_type)) {
             LOG(ERROR) << "Failed to add task into cgroup";
             return ProfileAction::FAIL;
         }
@@ -391,7 +393,7 @@
         PLOG(WARNING) << Name() << "::" << __func__ << ": failed to open " << procs_path;
         return false;
     }
-    if (!AddTidToCgroup(pid, tmp_fd, controller()->name())) {
+    if (!AddTidToCgroup(pid, tmp_fd, RCT_PROCESS)) {
         LOG(ERROR) << "Failed to add task into cgroup";
         return false;
     }
@@ -412,7 +414,7 @@
         PLOG(WARNING) << Name() << "::" << __func__ << ": failed to open " << tasks_path;
         return false;
     }
-    if (!AddTidToCgroup(tid, tmp_fd, controller()->name())) {
+    if (!AddTidToCgroup(tid, tmp_fd, RCT_TASK)) {
         LOG(ERROR) << "Failed to add task into cgroup";
         return false;
     }
@@ -866,7 +868,13 @@
 
                 auto controller = cg_map.FindController(controller_name);
                 if (controller.HasValue()) {
-                    profile->Add(std::make_unique<SetCgroupAction>(controller, path));
+                    if (controller.version() == 1) {
+                        profile->Add(std::make_unique<SetCgroupAction>(controller, path));
+                    } else {
+                        LOG(WARNING) << "A JoinCgroup action in the " << profile_name
+                                     << " profile is used for controller " << controller_name
+                                     << " in the cgroup v2 hierarchy and will be ignored";
+                    }
                 } else {
                     LOG(WARNING) << "JoinCgroup: controller " << controller_name << " is not found";
                 }
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 4663f64..16ffe63 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -160,7 +160,7 @@
     android::base::unique_fd fd_[ProfileAction::RCT_COUNT];
     mutable std::mutex fd_mutex_;
 
-    static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
+    bool AddTidToCgroup(int tid, int fd, ResourceCacheType cache_type) const;
     CacheUseResult UseCachedFd(ResourceCacheType cache_type, int id) const;
 };
 
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index 99d819a..b17e695 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -116,7 +116,7 @@
     }
     bool GetPathForTask(int tid, std::string* path) const override {
 #ifdef __ANDROID__
-        CHECK(CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, path));
+        CHECK(CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, path));
         CHECK_GT(path->length(), 0);
         if (path->rbegin()[0] != '/') {
             *path += "/";
diff --git a/libutils/Android.bp b/libutils/Android.bp
index b3ddda3..1c622ff 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -56,7 +56,7 @@
 }
 
 cc_defaults {
-    name: "libutils_defaults",
+    name: "libutils_defaults_nodeps",
     vendor_available: true,
     product_available: true,
     recovery_available: true,
@@ -68,18 +68,7 @@
         "-Wno-exit-time-destructors",
         "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
     ],
-    header_libs: [
-        "libbase_headers",
-        "libutils_headers",
-    ],
-    export_header_lib_headers: [
-        "libutils_headers",
-    ],
 
-    shared_libs: [
-        "libcutils",
-        "liblog",
-    ],
     sanitize: {
         misc_undefined: ["integer"],
     },
@@ -125,6 +114,18 @@
 }
 
 cc_defaults {
+    name: "libutils_defaults",
+    defaults: [
+        "libutils_defaults_nodeps",
+    ],
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+    ],
+}
+
+cc_defaults {
     name: "libutils_impl_defaults",
     defaults: [
         "libutils_defaults",
@@ -134,6 +135,14 @@
 
     whole_static_libs: ["libutils_binder"],
 
+    header_libs: [
+        "libbase_headers",
+        "libutils_headers",
+    ],
+    export_header_lib_headers: [
+        "libutils_headers",
+    ],
+
     srcs: [
         "FileMap.cpp",
         "JenkinsHash.cpp",
@@ -202,6 +211,7 @@
     defaults: ["libutils_impl_defaults"],
 
     cflags: [
+        "-DDEBUG_CALLBACKS=1",
         "-DDEBUG_POLL_AND_WAKE=1",
         "-DDEBUG_REFS=1",
         "-DDEBUG_TOKENIZER=1",
@@ -221,6 +231,14 @@
         support_system_process: true,
     },
 
+    header_libs: [
+        "libbase_headers",
+        "libutils_headers",
+    ],
+    export_header_lib_headers: [
+        "libutils_headers",
+    ],
+
     srcs: [
         "CallStack.cpp",
     ],
diff --git a/libutils/Looper.cpp b/libutils/Looper.cpp
index 402e43c..576c61d 100644
--- a/libutils/Looper.cpp
+++ b/libutils/Looper.cpp
@@ -534,7 +534,7 @@
 
 int Looper::removeSequenceNumberLocked(SequenceNumber seq) {
 #if DEBUG_CALLBACKS
-    ALOGD("%p ~ removeFd - fd=%d, seq=%u", this, fd, seq);
+    ALOGD("%p ~ removeFd - seq=%" PRIu64, this, seq);
 #endif
 
     const auto& request_it = mRequests.find(seq);
diff --git a/libutils/binder/Android.bp b/libutils/binder/Android.bp
index e2eddb3..60b0cb6 100644
--- a/libutils/binder/Android.bp
+++ b/libutils/binder/Android.bp
@@ -3,13 +3,14 @@
 }
 
 cc_defaults {
-    name: "libutils_binder_impl_defaults",
+    name: "libutils_binder_impl_defaults_nodeps",
     defaults: [
-        "libutils_defaults",
+        "libutils_defaults_nodeps",
         "apex-lowest-min-sdk-version",
     ],
     native_bridge_supported: true,
 
+    export_include_dirs: ["include"],
     srcs: [
         "Errors.cpp",
         "RefBase.cpp",
@@ -29,11 +30,33 @@
     afdo: true,
 }
 
+cc_defaults {
+    name: "libutils_binder_impl_defaults",
+    defaults: [
+        "libutils_defaults",
+        "libutils_binder_impl_defaults_nodeps",
+    ],
+}
+
 cc_library {
     name: "libutils_binder",
     defaults: ["libutils_binder_impl_defaults"],
 }
 
+cc_library_shared {
+    name: "libutils_binder_sdk",
+    defaults: ["libutils_binder_impl_defaults_nodeps"],
+
+    header_libs: [
+        "liblog_stub",
+    ],
+
+    cflags: [
+        "-DANDROID_LOG_STUB_WEAK_PRINT",
+        "-DANDROID_UTILS_CALLSTACK_ENABLED=0",
+    ],
+}
+
 cc_library {
     name: "libutils_binder_test_compile",
     defaults: ["libutils_binder_impl_defaults"],
diff --git a/libutils/binder/RefBase.cpp b/libutils/binder/RefBase.cpp
index c7055fb..2d2e40b 100644
--- a/libutils/binder/RefBase.cpp
+++ b/libutils/binder/RefBase.cpp
@@ -20,8 +20,6 @@
 #include <memory>
 #include <mutex>
 
-#include <android-base/macros.h>
-
 #include <fcntl.h>
 #include <log/log.h>
 
@@ -57,15 +55,17 @@
 // log all reference counting operations
 #define PRINT_REFS 0
 
+#if !defined(ANDROID_UTILS_CALLSTACK_ENABLED)
 #if defined(__linux__)
 // CallStack is only supported on linux type platforms.
-#define CALLSTACK_ENABLED 1
+#define ANDROID_UTILS_CALLSTACK_ENABLED 1
 #else
-#define CALLSTACK_ENABLED 0
-#endif
+#define ANDROID_UTILS_CALLSTACK_ENABLED 0
+#endif  // defined(__linux__)
+#endif  // !defined(ANDROID_UTILS_CALLSTACK_ENABLED)
 
-#if CALLSTACK_ENABLED
-#include <utils/CallStack.h>
+#if ANDROID_UTILS_CALLSTACK_ENABLED
+#include "../../include/utils/CallStack.h"
 #endif
 
 // ---------------------------------------------------------------------------
@@ -232,7 +232,7 @@
             while (refs) {
                 char inc = refs->ref >= 0 ? '+' : '-';
                 ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref);
-#if DEBUG_REFS_CALLSTACK_ENABLED && CALLSTACK_ENABLED
+#if DEBUG_REFS_CALLSTACK_ENABLED && ANDROID_UTILS_CALLSTACK_ENABLED
                 CallStack::logStack(LOG_TAG, refs->stack.get());
 #endif
                 refs = refs->next;
@@ -246,7 +246,7 @@
             while (refs) {
                 char inc = refs->ref >= 0 ? '+' : '-';
                 ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref);
-#if DEBUG_REFS_CALLSTACK_ENABLED && CALLSTACK_ENABLED
+#if DEBUG_REFS_CALLSTACK_ENABLED && ANDROID_UTILS_CALLSTACK_ENABLED
                 CallStack::logStack(LOG_TAG, refs->stack.get());
 #endif
                 refs = refs->next;
@@ -254,7 +254,7 @@
         }
         if (dumpStack) {
             ALOGE("above errors at:");
-#if CALLSTACK_ENABLED
+#if ANDROID_UTILS_CALLSTACK_ENABLED
             CallStack::logStack(LOG_TAG);
 #endif
         }
@@ -343,7 +343,7 @@
     {
         ref_entry* next;
         const void* id;
-#if DEBUG_REFS_CALLSTACK_ENABLED && CALLSTACK_ENABLED
+#if DEBUG_REFS_CALLSTACK_ENABLED && ANDROID_UTILS_CALLSTACK_ENABLED
         CallStack::CallStackUPtr stack;
 #endif
         int32_t ref;
@@ -360,7 +360,7 @@
             // decrement the reference count.
             ref->ref = mRef;
             ref->id = id;
-#if DEBUG_REFS_CALLSTACK_ENABLED && CALLSTACK_ENABLED
+#if DEBUG_REFS_CALLSTACK_ENABLED && ANDROID_UTILS_CALLSTACK_ENABLED
             ref->stack = CallStack::getCurrent(2);
 #endif
             ref->next = *refs;
@@ -396,7 +396,7 @@
                 ref = ref->next;
             }
 
-#if CALLSTACK_ENABLED
+#if ANDROID_UTILS_CALLSTACK_ENABLED
             CallStack::logStack(LOG_TAG);
 #endif
         }
@@ -424,7 +424,7 @@
             snprintf(buf, sizeof(buf), "\t%c ID %p (ref %d):\n",
                      inc, refs->id, refs->ref);
             out->append(buf);
-#if DEBUG_REFS_CALLSTACK_ENABLED && CALLSTACK_ENABLED
+#if DEBUG_REFS_CALLSTACK_ENABLED && ANDROID_UTILS_CALLSTACK_ENABLED
             out->append(CallStack::stackToString("\t\t", refs->stack.get()));
 #else
             out->append("\t\t(call stacks disabled)");
@@ -536,7 +536,7 @@
     case INITIAL_STRONG_VALUE:
         refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE,
                 std::memory_order_relaxed);
-        FALLTHROUGH_INTENDED;
+        [[fallthrough]];
     case 0:
         refs->mBase->onFirstRef();
     }
@@ -790,7 +790,7 @@
                   "object.",
                   mRefs->mWeak.load(), this);
 
-#if CALLSTACK_ENABLED
+#if ANDROID_UTILS_CALLSTACK_ENABLED
             CallStack::logStack(LOG_TAG);
 #endif
         } else if (strongs != 0) {
diff --git a/libutils/binder/String8.cpp b/libutils/binder/String8.cpp
index 6a75484..749bfcb 100644
--- a/libutils/binder/String8.cpp
+++ b/libutils/binder/String8.cpp
@@ -19,7 +19,6 @@
 
 #include <utils/String8.h>
 
-#include <utils/Compat.h>
 #include <log/log.h>
 #include <utils/String16.h>
 
@@ -430,6 +429,13 @@
 // ---------------------------------------------------------------------------
 // Path functions
 
+// TODO: we should remove all the path functions from String8
+#if defined(_WIN32)
+#define OS_PATH_SEPARATOR '\\'
+#else
+#define OS_PATH_SEPARATOR '/'
+#endif
+
 String8 String8::getPathDir(void) const
 {
     const char* cp;
diff --git a/libutils/binder/StrongPointer_test.cpp b/libutils/binder/StrongPointer_test.cpp
index f27c1f1..aa993c3 100644
--- a/libutils/binder/StrongPointer_test.cpp
+++ b/libutils/binder/StrongPointer_test.cpp
@@ -106,3 +106,17 @@
     EXPECT_DEATH(sp<TypeParam>::fromExisting(foo), "");
     delete foo;
 }
+
+TYPED_TEST(StrongPointer, release) {
+    bool isDeleted = false;
+    TypeParam* foo = nullptr;
+    {
+        sp<TypeParam> sp1 = sp<TypeParam>::make(&isDeleted);
+        ASSERT_EQ(1, sp1->getStrongCount());
+        foo = sp1.release();
+    }
+    ASSERT_FALSE(isDeleted) << "release failed, deleted anyway when sp left scope";
+    ASSERT_EQ(1, foo->getStrongCount()) << "release mismanaged refcount";
+    foo->decStrong(nullptr);
+    ASSERT_TRUE(isDeleted) << "foo was leaked!";
+}
diff --git a/libutils/binder/Unicode.cpp b/libutils/binder/Unicode.cpp
index 364a177..2ed2d4f 100644
--- a/libutils/binder/Unicode.cpp
+++ b/libutils/binder/Unicode.cpp
@@ -16,7 +16,6 @@
 
 #define LOG_TAG "unicode"
 
-#include <android-base/macros.h>
 #include <limits.h>
 #include <utils/Unicode.h>
 
@@ -92,11 +91,11 @@
     switch (bytes)
     {   /* note: everything falls through. */
         case 4: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6;
-            FALLTHROUGH_INTENDED;
+            [[fallthrough]];
         case 3: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6;
-            FALLTHROUGH_INTENDED;
+            [[fallthrough]];
         case 2: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6;
-            FALLTHROUGH_INTENDED;
+            [[fallthrough]];
         case 1: *--dstP = (uint8_t)(srcChar | kFirstByteMark[bytes]);
     }
 }
@@ -304,15 +303,15 @@
 
     while (in < end) {
         char16_t w = *in++;
-        if (LIKELY(w < 0x0080)) {
+        if (w < 0x0080) [[likely]] {
             utf8_len += 1;
             continue;
         }
-        if (LIKELY(w < 0x0800)) {
+        if (w < 0x0800) [[likely]] {
             utf8_len += 2;
             continue;
         }
-        if (LIKELY(!is_any_surrogate(w))) {
+        if (!is_any_surrogate(w)) [[likely]] {
             utf8_len += 3;
             continue;
         }
@@ -345,20 +344,20 @@
 
     while (in < in_end) {
         char16_t w = *in++;
-        if (LIKELY(w < 0x0080)) {
+        if (w < 0x0080) [[likely]] {
             if (out + 1 > out_end)
                 return err_out();
             *out++ = (char)(w & 0xff);
             continue;
         }
-        if (LIKELY(w < 0x0800)) {
+        if (w < 0x0800) [[likely]] {
             if (out + 2 > out_end)
                 return err_out();
             *out++ = (char)(0xc0 | ((w >> 6) & 0x1f));
             *out++ = (char)(0x80 | ((w >> 0) & 0x3f));
             continue;
         }
-        if (LIKELY(!is_any_surrogate(w))) {
+        if (!is_any_surrogate(w)) [[likely]] {
             if (out + 3 > out_end)
                 return err_out();
             *out++ = (char)(0xe0 | ((w >> 12) & 0xf));
@@ -420,25 +419,25 @@
     while (in < in_end) {
         uint8_t c = *in;
         utf16_len++;
-        if (LIKELY((c & 0x80) == 0)) {
+        if ((c & 0x80) == 0) [[likely]] {
             in++;
             continue;
         }
-        if (UNLIKELY(c < 0xc0)) {
+        if (c < 0xc0) [[unlikely]] {
             ALOGW("Invalid UTF-8 leading byte: 0x%02x", c);
             in++;
             continue;
         }
-        if (LIKELY(c < 0xe0)) {
+        if (c < 0xe0) [[likely]] {
             in += 2;
             continue;
         }
-        if (LIKELY(c < 0xf0)) {
+        if (c < 0xf0) [[likely]] {
             in += 3;
             continue;
         } else {
             uint8_t c2, c3, c4;
-            if (UNLIKELY(c >= 0xf8)) {
+            if (c >= 0xf8) [[unlikely]] {
                 ALOGW("Invalid UTF-8 leading byte: 0x%02x", c);
             }
             c2 = in[1]; c3 = in[2]; c4 = in[3];
@@ -487,25 +486,25 @@
 
     while (in < in_end && out < out_end) {
         c = *in++;
-        if (LIKELY((c & 0x80) == 0)) {
+        if ((c & 0x80) == 0) [[likely]] {
             *out++ = (char16_t)(c);
             continue;
         }
-        if (UNLIKELY(c < 0xc0)) {
+        if (c < 0xc0) [[unlikely]] {
             ALOGW("Invalid UTF-8 leading byte: 0x%02x", c);
             *out++ = (char16_t)(c);
             continue;
         }
-        if (LIKELY(c < 0xe0)) {
-            if (UNLIKELY(in + 1 > in_end)) {
+        if (c < 0xe0) [[likely]] {
+            if (in + 1 > in_end) [[unlikely]] {
                 return err_in();
             }
             c2 = *in++;
             *out++ = (char16_t)(((c & 0x1f) << 6) | (c2 & 0x3f));
             continue;
         }
-        if (LIKELY(c < 0xf0)) {
-            if (UNLIKELY(in + 2 > in_end)) {
+        if (c < 0xf0) [[likely]] {
+            if (in + 2 > in_end) [[unlikely]] {
                 return err_in();
             }
             c2 = *in++; c3 = *in++;
@@ -513,19 +512,19 @@
                                 ((c2 & 0x3f) << 6) | (c3 & 0x3f));
             continue;
         } else {
-            if (UNLIKELY(in + 3 > in_end)) {
+            if (in + 3 > in_end) [[unlikely]] {
                 return err_in();
             }
-            if (UNLIKELY(c >= 0xf8)) {
+            if (c >= 0xf8) [[unlikely]] {
                 ALOGW("Invalid UTF-8 leading byte: 0x%02x", c);
             }
             // Multiple UTF16 characters with surrogates
             c2 = *in++; c3 = *in++; c4 = *in++;
             w = utf8_4b_to_utf32(c, c2, c3, c4);
-            if (UNLIKELY(w < 0x10000)) {
+            if (w < 0x10000) [[unlikely]] {
                 *out++ = (char16_t)(w);
             } else {
-                if (UNLIKELY(out + 2 > out_end)) {
+                if (out + 2 > out_end) [[unlikely]] {
                     // Ooops.... not enough room for this surrogate pair.
                     return out;
                 }
diff --git a/libutils/include/utils/LightRefBase.h b/libutils/binder/include/utils/LightRefBase.h
similarity index 100%
rename from libutils/include/utils/LightRefBase.h
rename to libutils/binder/include/utils/LightRefBase.h
diff --git a/libutils/binder/include/utils/StrongPointer.h b/libutils/binder/include/utils/StrongPointer.h
index 54aa691..43c00c9 100644
--- a/libutils/binder/include/utils/StrongPointer.h
+++ b/libutils/binder/include/utils/StrongPointer.h
@@ -98,6 +98,15 @@
 
     void clear();
 
+    // Releases the ownership of the object managed by this instance of sp, if any.
+    // The caller is now responsible for managing it. That is, the caller must ensure
+    // decStrong() is called when the pointer is no longer used.
+    [[nodiscard]] inline T* release() noexcept {
+        auto ret = m_ptr;
+        m_ptr = nullptr;
+        return ret;
+    }
+
     // Accessors
 
     inline T&       operator* () const     { return *m_ptr; }
diff --git a/libutils/include/utils/TypeHelpers.h b/libutils/binder/include/utils/TypeHelpers.h
similarity index 100%
rename from libutils/include/utils/TypeHelpers.h
rename to libutils/binder/include/utils/TypeHelpers.h
diff --git a/libutils/include/utils/LightRefBase.h b/libutils/include/utils/LightRefBase.h
new file mode 120000
index 0000000..9da2cf0
--- /dev/null
+++ b/libutils/include/utils/LightRefBase.h
@@ -0,0 +1 @@
+../../binder/include/utils/LightRefBase.h
\ No newline at end of file
diff --git a/libutils/include/utils/TypeHelpers.h b/libutils/include/utils/TypeHelpers.h
new file mode 120000
index 0000000..848f6d7
--- /dev/null
+++ b/libutils/include/utils/TypeHelpers.h
@@ -0,0 +1 @@
+../../binder/include/utils/TypeHelpers.h
\ No newline at end of file
diff --git a/libvendorsupport/Android.bp b/libvendorsupport/Android.bp
new file mode 100644
index 0000000..16a4c4c
--- /dev/null
+++ b/libvendorsupport/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2024 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: "libvendorsupport",
+    native_bridge_supported: true,
+    llndk: {
+        symbol_file: "libvendorsupport.map.txt",
+    },
+    srcs: ["version_props.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    local_include_dirs: ["include/vendorsupport"],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "liblog",
+    ],
+}
diff --git a/libvendorsupport/OWNERS b/libvendorsupport/OWNERS
new file mode 100644
index 0000000..2ab18eb
--- /dev/null
+++ b/libvendorsupport/OWNERS
@@ -0,0 +1,2 @@
+jiyong@google.com
+justinyun@google.com
diff --git a/libvendorsupport/TEST_MAPPING b/libvendorsupport/TEST_MAPPING
new file mode 100644
index 0000000..5bd09ba
--- /dev/null
+++ b/libvendorsupport/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "libvendorsupport-tests"
+    }
+  ]
+}
diff --git a/libvendorsupport/include/vendorsupport/api_level.h b/libvendorsupport/include/vendorsupport/api_level.h
new file mode 100644
index 0000000..ba1a6b8
--- /dev/null
+++ b/libvendorsupport/include/vendorsupport/api_level.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <android/api-level.h>
+
+#define __ANDROID_VENDOR_API_MAX__ 1000000
+#define __INVALID_API_LEVEL -1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Find corresponding vendor API level from an SDK API version.
+ *
+ * @details
+ * SDK API versions and vendor API levels are not compatible and not
+ * convertible. However, this function can be used to compare the two versions
+ * to know which one is newer than the other.
+ *
+ * @param sdk_api_level The SDK version int. This must be less than 10000.
+ * @return The corresponding vendor API level of the SDK version. -1 if the SDK
+ * version is invalid or 10000.
+ */
+int vendor_api_level_of(int sdk_api_level);
+
+/**
+ * @brief Find corresponding SDK API version from a vendor API level.
+ *
+ * @param vendor_api_level The vendor API level int.
+ * @return The corresponding SDK API version of the vendor API level. -1 if the
+ * vendor API level is invalid.
+ */
+int sdk_api_level_of(int vendor_api_level);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libvendorsupport/libvendorsupport.map.txt b/libvendorsupport/libvendorsupport.map.txt
new file mode 100644
index 0000000..9a23b94
--- /dev/null
+++ b/libvendorsupport/libvendorsupport.map.txt
@@ -0,0 +1,7 @@
+LIBVENDORSUPPORT {
+  global:
+    vendor_api_level_of; # llndk systemapi
+    sdk_api_level_of; # llndk systemapi
+  local:
+    *;
+};
diff --git a/libvendorsupport/tests/Android.bp b/libvendorsupport/tests/Android.bp
new file mode 100644
index 0000000..42e3371
--- /dev/null
+++ b/libvendorsupport/tests/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 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_test {
+    name: "libvendorsupport-tests",
+    srcs: [
+        "version_props_test.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libvendorsupport",
+    ],
+    test_suites: ["general-tests"],
+}
+
diff --git a/libvendorsupport/tests/version_props_test.cpp b/libvendorsupport/tests/version_props_test.cpp
new file mode 100644
index 0000000..538a2e2
--- /dev/null
+++ b/libvendorsupport/tests/version_props_test.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <gtest/gtest.h>
+
+#include <vendorsupport/api_level.h>
+
+using namespace std;
+
+namespace {
+
+TEST(vendorsupport, get_corresponding_vendor_api_level) {
+    ASSERT_EQ(__ANDROID_API_U__, vendor_api_level_of(__ANDROID_API_U__));
+    ASSERT_EQ(202404, vendor_api_level_of(__ANDROID_API_V__));
+    ASSERT_EQ(__INVALID_API_LEVEL, vendor_api_level_of(__ANDROID_API_FUTURE__));
+}
+
+TEST(vendorsupport, get_corresponding_sdk_api_level) {
+    ASSERT_EQ(__ANDROID_API_U__, sdk_api_level_of(__ANDROID_API_U__));
+    ASSERT_EQ(__ANDROID_API_V__, sdk_api_level_of(202404));
+    ASSERT_EQ(__INVALID_API_LEVEL, sdk_api_level_of(__ANDROID_VENDOR_API_MAX__));
+    ASSERT_EQ(__INVALID_API_LEVEL, sdk_api_level_of(35));
+}
+
+}  // namespace
\ No newline at end of file
diff --git a/libvendorsupport/version_props.c b/libvendorsupport/version_props.c
new file mode 100644
index 0000000..4d0e45e
--- /dev/null
+++ b/libvendorsupport/version_props.c
@@ -0,0 +1,41 @@
+// Copyright (C) 2024 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 "api_level.h"
+
+#include <log/log.h>
+
+int vendor_api_level_of(int sdk_api_level) {
+    if (sdk_api_level < __ANDROID_API_V__) {
+        return sdk_api_level;
+    }
+    // In Android V, vendor API level started with version 202404.
+    // The calculation assumes that the SDK api level bumps once a year.
+    if (sdk_api_level < __ANDROID_API_FUTURE__) {
+        return 202404 + ((sdk_api_level - __ANDROID_API_V__) * 100);
+    }
+    ALOGE("The SDK version must be less than 10000: %d", sdk_api_level);
+    return __INVALID_API_LEVEL;
+}
+
+int sdk_api_level_of(int vendor_api_level) {
+    if (vendor_api_level < __ANDROID_API_V__) {
+        return vendor_api_level;
+    }
+    if (vendor_api_level >= 202404 && vendor_api_level < __ANDROID_VENDOR_API_MAX__) {
+        return (vendor_api_level - 202404) / 100 + __ANDROID_API_V__;
+    }
+    ALOGE("Unexpected vendor api level: %d", vendor_api_level);
+    return __INVALID_API_LEVEL;
+}
diff --git a/libvndksupport/include/vndksupport/linker.h b/libvndksupport/include/vndksupport/linker.h
index 5f48c39..6845135 100644
--- a/libvndksupport/include/vndksupport/linker.h
+++ b/libvndksupport/include/vndksupport/linker.h
@@ -20,15 +20,8 @@
 extern "C" {
 #endif
 
-/*
- * Returns whether the current process is a vendor process.
- *
- * Note that this is only checking what process is running and has nothing to
- * do with what namespace the caller is loaded at.  For example, a VNDK-SP
- * library loaded by SP-HAL calling this function may still get a 'false',
- * because it is running in a system process.
- */
-int android_is_in_vendor_process();
+int android_is_in_vendor_process() __attribute__((
+        deprecated("This function would not give exact result if VNDK is deprecated.")));
 
 void* android_load_sphal_library(const char* name, int flag);
 
diff --git a/libvndksupport/libvndksupport.map.txt b/libvndksupport/libvndksupport.map.txt
index 1d94b9d..325505d 100644
--- a/libvndksupport/libvndksupport.map.txt
+++ b/libvndksupport/libvndksupport.map.txt
@@ -1,6 +1,6 @@
 LIBVNDKSUPPORT {
   global:
-    android_is_in_vendor_process; # llndk systemapi
+    android_is_in_vendor_process; # llndk-deprecated=35 systemapi
     android_load_sphal_library; # llndk systemapi
     android_unload_sphal_library; # llndk systemapi
   local:
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 87646f9..b4a16d3 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -25,4 +25,8 @@
         },
     },
     export_include_dirs: ["include"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.runtime",
+    ],
 }
diff --git a/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h b/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
index 0548021..65705ac 100644
--- a/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
+++ b/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
@@ -20,6 +20,8 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+static constexpr char PROP_TREE_FILE[] = "/dev/__properties__/property_info";
+
 namespace android {
 namespace properties {
 
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index cc6b64a..7444f96 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -72,6 +72,11 @@
   endif
 endif
 
+EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE :=
+ifneq ($(PRODUCT_SCUDO_ALLOCATION_RING_BUFFER_SIZE),)
+  EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE := export SCUDO_ALLOCATION_RING_BUFFER_SIZE $(PRODUCT_SCUDO_ALLOCATION_RING_BUFFER_SIZE)
+endif
+
 EXPORT_GLOBAL_GCOV_OPTIONS :=
 ifeq ($(NATIVE_COVERAGE),true)
   EXPORT_GLOBAL_GCOV_OPTIONS := export GCOV_PREFIX /data/misc/trace
@@ -92,7 +97,7 @@
 # create some directories (some are mount points) and symlinks
 LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
     dev proc sys system data data_mirror odm oem acct config storage mnt apex bootstrap-apex debug_ramdisk \
-    linkerconfig second_stage_resources postinstall $(BOARD_ROOT_EXTRA_FOLDERS)); \
+    linkerconfig second_stage_resources postinstall tmp $(BOARD_ROOT_EXTRA_FOLDERS)); \
     ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
     ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
     ln -sf /data/user_de/0/com.android.shell/files/bugreports $(TARGET_ROOT_OUT)/bugreports; \
@@ -216,6 +221,7 @@
 	$(hide) sed -i -e 's?%EXPORT_GLOBAL_GCOV_OPTIONS%?$(EXPORT_GLOBAL_GCOV_OPTIONS)?g' $@
 	$(hide) sed -i -e 's?%EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%?$(EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS)?g' $@
 	$(hide) sed -i -e 's?%EXPORT_GLOBAL_HWASAN_OPTIONS%?$(EXPORT_GLOBAL_HWASAN_OPTIONS)?g' $@
+	$(hide) sed -i -e 's?%EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE%?$(EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE)?g' $@
 
 # Append PLATFORM_VNDK_VERSION to base name.
 define append_vndk_version
diff --git a/rootdir/init.environ.rc.in b/rootdir/init.environ.rc.in
index bf6e986..7ba1f46 100644
--- a/rootdir/init.environ.rc.in
+++ b/rootdir/init.environ.rc.in
@@ -14,3 +14,4 @@
     %EXPORT_GLOBAL_GCOV_OPTIONS%
     %EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%
     %EXPORT_GLOBAL_HWASAN_OPTIONS%
+    %EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE%
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 317f809..12c46eb 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -92,6 +92,12 @@
     # checker programs.
     mkdir /dev/fscklogs 0770 root system
 
+    # Create tmpfs for use by the shell user.
+    mount tmpfs tmpfs /tmp
+    restorecon /tmp
+    chown shell shell /tmp
+    chmod 0771 /tmp
+
 on init
     sysclktz 0
 
@@ -219,26 +225,6 @@
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
-    # Create blkio group and apply initial settings.
-    # This feature needs kernel to support it, and the
-    # device's init.rc must actually set the correct values.
-    mkdir /dev/blkio/background
-    chown system system /dev/blkio
-    chown system system /dev/blkio/background
-    chown system system /dev/blkio/tasks
-    chown system system /dev/blkio/background/tasks
-    chown system system /dev/blkio/cgroup.procs
-    chown system system /dev/blkio/background/cgroup.procs
-    chmod 0664 /dev/blkio/tasks
-    chmod 0664 /dev/blkio/background/tasks
-    chmod 0664 /dev/blkio/cgroup.procs
-    chmod 0664 /dev/blkio/background/cgroup.procs
-    write /dev/blkio/blkio.weight 1000
-    write /dev/blkio/background/blkio.weight 200
-    write /dev/blkio/background/blkio.bfq.weight 10
-    write /dev/blkio/blkio.group_idle 0
-    write /dev/blkio/background/blkio.group_idle 0
-
     restorecon_recursive /mnt
 
     mount configfs none /config nodev noexec nosuid
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 60dcc2a..3927501 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -71,6 +71,7 @@
 /dev/mtp_usb              0660   root       mtp
 /dev/usb_accessory        0660   root       usb
 /dev/tun                  0660   system     vpn
+/dev/hidraw*              0660   system     system
 
 # CDMA radio interface MUX
 /dev/ppp                  0660   radio      vpn
diff --git a/run-as/run-as.cpp b/run-as/run-as.cpp
index cc92c68..32057b4 100644
--- a/run-as/run-as.cpp
+++ b/run-as/run-as.cpp
@@ -76,9 +76,6 @@
     error(1, errno, "couldn't stat %s", path);
   }
 
-  // /data/user/0 is a known safe symlink.
-  if (strcmp("/data/user/0", path) == 0) return;
-
   // Must be a real directory, not a symlink.
   if (!S_ISDIR(st.st_mode)) {
     error(1, 0, "%s not a directory: %o", path, st.st_mode);
@@ -191,27 +188,17 @@
   }
 
   // Retrieve package information from system, switching egid so we can read the file.
+  pkg_info info = {.name = pkgname};
   gid_t old_egid = getegid();
   if (setegid(AID_PACKAGE_INFO) == -1) error(1, errno, "setegid(AID_PACKAGE_INFO) failed");
-  pkg_info info;
-  memset(&info, 0, sizeof(info));
-  info.name = pkgname;
   if (!packagelist_parse(packagelist_parse_callback, &info)) {
     error(1, errno, "packagelist_parse failed");
   }
-
-  // Handle a multi-user data path
-  if (userId > 0) {
-    free(info.data_dir);
-    if (asprintf(&info.data_dir, "/data/user/%d/%s", userId, pkgname) == -1) {
-      error(1, errno, "asprintf failed");
-    }
-  }
+  if (setegid(old_egid) == -1) error(1, errno, "couldn't restore egid");
 
   if (info.uid == 0) {
     error(1, 0, "unknown package: %s", pkgname);
   }
-  if (setegid(old_egid) == -1) error(1, errno, "couldn't restore egid");
 
   // Verify that user id is not too big.
   if ((UID_MAX - info.uid) / AID_USER_OFFSET < (uid_t)userId) {
@@ -231,6 +218,12 @@
     error(1, 0, "package not debuggable: %s", pkgname);
   }
 
+  // Ensure we have the right data path for the specific user.
+  free(info.data_dir);
+  if (asprintf(&info.data_dir, "/data/user/%d/%s", userId, pkgname) == -1) {
+    error(1, errno, "asprintf failed");
+  }
+
   // Check that the data directory path is valid.
   check_data_path(pkgname, info.data_dir, userAppId);
 
diff --git a/storaged/Android.bp b/storaged/Android.bp
index fe8c1f3..357c0e6 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -24,7 +24,7 @@
     shared_libs: [
         "android.hardware.health@1.0",
         "android.hardware.health@2.0",
-        "android.hardware.health-V2-ndk",
+        "android.hardware.health-V3-ndk",
         "libbase",
         "libbinder",
         "libbinder_ndk",
diff --git a/trusty/OWNERS b/trusty/OWNERS
index bf16912..4016792 100644
--- a/trusty/OWNERS
+++ b/trusty/OWNERS
@@ -2,7 +2,6 @@
 arve@android.com
 danielangell@google.com
 gmar@google.com
-marcone@google.com
 mikemcternan@google.com
 mmaurer@google.com
 ncbray@google.com
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index f782d2a..0915eab 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -107,7 +107,11 @@
         return {};
     }
 
-    assert(st.st_size >= 0);
+    if (st.st_size == 0) {
+        LOG(ERROR) << "Zero length file '" << file_name << "'";
+        return {};
+    }
+
     file_size = st.st_size;
 
     /* The dmabuf size needs to be a multiple of the page size */
@@ -123,7 +127,8 @@
     BufferAllocator alloc;
     unique_fd dmabuf_fd(alloc.Alloc(kDmabufSystemHeapName, file_page_size));
     if (!dmabuf_fd.ok()) {
-        LOG(ERROR) << "Error creating dmabuf: " << dmabuf_fd.get();
+        LOG(ERROR) << "Error creating dmabuf for " << file_page_size
+                   << " bytes: " << dmabuf_fd.get();
         return dmabuf_fd;
     }
 
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index 2299481..8c8edb7 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -353,7 +353,6 @@
     if (open_flags & O_CREAT) {
         sync_parent(path, watcher);
     }
-    free(path);
 
     /* at this point rc contains storage file fd */
     msg->result = STORAGE_NO_ERROR;
@@ -361,6 +360,9 @@
     ALOGV("%s: \"%s\": fd = %u: handle = %d\n",
           __func__, path, rc, resp.handle);
 
+    free(path);
+    path = NULL;
+
     /* a backing file has been opened, notify any waiting init steps */
     if (!fs_ready_initialized) {
         rc = property_set(FS_READY_PROPERTY, "1");