Merge "Snap for 8253222 from d07d694175ceb9eeba1d8faaedc91c7716889a5a to sdk-release" into sdk-release
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index edcea44..a541f6e 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -391,6 +391,9 @@
     apex_available: [
         "com.android.runtime",
     ],
+
+    // Required for tests.
+    required: ["crash_dump.policy"],
 }
 
 cc_binary {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 0737612..85adbea 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -1409,6 +1409,16 @@
   return true;
 }
 
+extern "C" void foo() {
+  LOG(INFO) << "foo";
+  std::this_thread::sleep_for(1s);
+}
+
+extern "C" void bar() {
+  LOG(INFO) << "bar";
+  std::this_thread::sleep_for(1s);
+}
+
 TEST_F(CrasherTest, seccomp_tombstone) {
   int intercept_result;
   unique_fd output_fd;
@@ -1416,6 +1426,11 @@
   static const auto dump_type = kDebuggerdTombstone;
   StartProcess(
       []() {
+        std::thread a(foo);
+        std::thread b(bar);
+
+        std::this_thread::sleep_for(100ms);
+
         raise_debugger_signal(dump_type);
         _exit(0);
       },
@@ -1430,16 +1445,8 @@
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
   ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal");
-}
-
-extern "C" void foo() {
-  LOG(INFO) << "foo";
-  std::this_thread::sleep_for(1s);
-}
-
-extern "C" void bar() {
-  LOG(INFO) << "bar";
-  std::this_thread::sleep_for(1s);
+  ASSERT_BACKTRACE_FRAME(result, "foo");
+  ASSERT_BACKTRACE_FRAME(result, "bar");
 }
 
 TEST_F(CrasherTest, seccomp_backtrace) {
diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp
index baf994f..4c1f9eb 100644
--- a/debuggerd/handler/debuggerd_fallback.cpp
+++ b/debuggerd/handler/debuggerd_fallback.cpp
@@ -98,32 +98,6 @@
   __linker_disable_fallback_allocator();
 }
 
-static void iterate_siblings(bool (*callback)(pid_t, int), int output_fd) {
-  pid_t current_tid = gettid();
-  char buf[BUFSIZ];
-  snprintf(buf, sizeof(buf), "/proc/%d/task", current_tid);
-  DIR* dir = opendir(buf);
-
-  if (!dir) {
-    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to open %s: %s", buf, strerror(errno));
-    return;
-  }
-
-  struct dirent* ent;
-  while ((ent = readdir(dir))) {
-    char* end;
-    long tid = strtol(ent->d_name, &end, 10);
-    if (end == ent->d_name || *end != '\0') {
-      continue;
-    }
-
-    if (tid != current_tid) {
-      callback(tid, output_fd);
-    }
-  }
-  closedir(dir);
-}
-
 static bool forward_output(int src_fd, int dst_fd, pid_t expected_tid) {
   // Make sure the thread actually got the signal.
   struct pollfd pfd = {
@@ -216,21 +190,21 @@
   }
 
   // Only allow one thread to perform a trace at a time.
-  static pthread_mutex_t trace_mutex = PTHREAD_MUTEX_INITIALIZER;
-  int ret = pthread_mutex_trylock(&trace_mutex);
-  if (ret != 0) {
-    async_safe_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_try_lock failed: %s",
-                          strerror(ret));
+  static std::mutex trace_mutex;
+  if (!trace_mutex.try_lock()) {
+    async_safe_format_log(ANDROID_LOG_INFO, "libc", "trace lock failed");
     return;
   }
 
+  std::lock_guard<std::mutex> scoped_lock(trace_mutex, std::adopt_lock);
+
   // Fetch output fd from tombstoned.
   unique_fd tombstone_socket, output_fd;
   if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd, nullptr,
                           kDebuggerdNativeBacktrace)) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                           "missing crash_dump_fallback() in selinux policy?");
-    goto exit;
+    return;
   }
 
   dump_backtrace_header(output_fd.get());
@@ -239,15 +213,15 @@
   debuggerd_fallback_trace(output_fd.get(), ucontext);
 
   // Send a signal to all of our siblings, asking them to dump their stack.
-  iterate_siblings(
-      [](pid_t tid, int output_fd) {
+  pid_t current_tid = gettid();
+  if (!iterate_tids(current_tid, [&output_fd](pid_t tid) {
         // Use a pipe, to be able to detect situations where the thread gracefully exits before
         // receiving our signal.
         unique_fd pipe_read, pipe_write;
         if (!Pipe(&pipe_read, &pipe_write)) {
           async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to create pipe: %s",
                                 strerror(errno));
-          return false;
+          return;
         }
 
         uint64_t expected = pack_thread_fd(-1, -1);
@@ -257,7 +231,7 @@
           async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                                 "thread %d is already outputting to fd %d?", tid, fd);
           close(sent_fd);
-          return false;
+          return;
         }
 
         siginfo_t siginfo = {};
@@ -269,10 +243,10 @@
         if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, BIONIC_SIGNAL_DEBUGGER, &siginfo) != 0) {
           async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s",
                                 tid, strerror(errno));
-          return false;
+          return;
         }
 
-        bool success = forward_output(pipe_read.get(), output_fd, tid);
+        bool success = forward_output(pipe_read.get(), output_fd.get(), tid);
         if (!success) {
           async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                                 "timeout expired while waiting for thread %d to dump", tid);
@@ -288,15 +262,14 @@
           }
         }
 
-        return true;
-      },
-      output_fd.get());
+        return;
+      })) {
+    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to open /proc/%d/task: %s",
+                          current_tid, strerror(errno));
+  }
 
   dump_backtrace_footer(output_fd.get());
   tombstoned_notify_completion(tombstone_socket.get());
-
-exit:
-  pthread_mutex_unlock(&trace_mutex);
 }
 
 static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_message) {
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index f21a203..14caaf6 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -23,6 +23,7 @@
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/prctl.h>
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -73,22 +74,40 @@
 
   std::map<pid_t, ThreadInfo> threads;
   threads[tid] = ThreadInfo{
-      .registers = std::move(regs),
-      .uid = uid,
-      .tid = tid,
-      .thread_name = std::move(thread_name),
-      .pid = pid,
-      .command_line = std::move(command_line),
-      .selinux_label = std::move(selinux_label),
-      .siginfo = siginfo,
+    .registers = std::move(regs), .uid = uid, .tid = tid, .thread_name = std::move(thread_name),
+    .pid = pid, .command_line = std::move(command_line), .selinux_label = std::move(selinux_label),
+    .siginfo = siginfo,
+#if defined(__aarch64__)
+    // Only supported on aarch64 for now.
+        .tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0),
+    .pac_enabled_keys = prctl(PR_PAC_GET_ENABLED_KEYS, 0, 0, 0, 0),
+#endif
   };
+  if (pid == tid) {
+    const ThreadInfo& thread = threads[pid];
+    if (!iterate_tids(pid, [&threads, &thread](pid_t tid) {
+          threads[tid] = ThreadInfo{
+              .uid = thread.uid,
+              .tid = tid,
+              .pid = thread.pid,
+              .command_line = thread.command_line,
+              .thread_name = get_thread_name(tid),
+              .tagged_addr_ctrl = thread.tagged_addr_ctrl,
+              .pac_enabled_keys = thread.pac_enabled_keys,
+          };
+        })) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to open /proc/%d/task: %s", pid,
+                            strerror(errno));
+    }
+  }
 
   unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch());
   auto process_memory =
       unwindstack::Memory::CreateProcessMemoryCached(getpid());
   unwinder.SetProcessMemory(process_memory);
   if (!unwinder.Init()) {
-    async_safe_fatal("failed to init unwinder object");
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to init unwinder object");
+    return;
   }
 
   ProcessInfo process_info;
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index b7d5bc4..3e31bb7 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -48,6 +48,7 @@
 
 #include <android/log.h>
 #include <bionic/macros.h>
+#include <bionic/reserved_signals.h>
 #include <log/log.h>
 #include <log/log_read.h>
 #include <log/logprint.h>
@@ -346,6 +347,93 @@
   f->set_build_id(frame.map_info->GetPrintableBuildID());
 }
 
+static void dump_registers(unwindstack::Unwinder* unwinder,
+                           const std::unique_ptr<unwindstack::Regs>& regs, Thread& thread,
+                           bool memory_dump) {
+  if (regs == nullptr) {
+    return;
+  }
+
+  unwindstack::Maps* maps = unwinder->GetMaps();
+  unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
+
+  regs->IterateRegisters([&thread, memory_dump, maps, memory](const char* name, uint64_t value) {
+    Register r;
+    r.set_name(name);
+    r.set_u64(value);
+    *thread.add_registers() = r;
+
+    if (memory_dump) {
+      MemoryDump dump;
+
+      dump.set_register_name(name);
+      std::shared_ptr<unwindstack::MapInfo> map_info = maps->Find(untag_address(value));
+      if (map_info) {
+        dump.set_mapping_name(map_info->name());
+      }
+
+      constexpr size_t kNumBytesAroundRegister = 256;
+      constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize;
+      char buf[kNumBytesAroundRegister];
+      uint8_t tags[kNumTagsAroundRegister];
+      ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
+      if (bytes == -1) {
+        return;
+      }
+      dump.set_begin_address(value);
+      dump.set_memory(buf, bytes);
+
+      bool has_tags = false;
+#if defined(__aarch64__)
+      for (size_t i = 0; i < kNumTagsAroundRegister; ++i) {
+        if (tags[i] != 0) {
+          has_tags = true;
+        }
+      }
+#endif  // defined(__aarch64__)
+
+      if (has_tags) {
+        dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister);
+      }
+
+      *thread.add_memory_dump() = std::move(dump);
+    }
+  });
+}
+
+static void log_unwinder_error(unwindstack::Unwinder* unwinder) {
+  if (unwinder->LastErrorCode() == unwindstack::ERROR_NONE) {
+    return;
+  }
+
+  async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error code: %s",
+                        unwinder->LastErrorCodeString());
+  async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error address: 0x%" PRIx64,
+                        unwinder->LastErrorAddress());
+}
+
+static void dump_thread_backtrace(unwindstack::Unwinder* unwinder, Thread& thread) {
+  if (unwinder->NumFrames() == 0) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to unwind");
+    log_unwinder_error(unwinder);
+    return;
+  }
+
+  if (unwinder->elf_from_memory_not_file()) {
+    auto backtrace_note = thread.mutable_backtrace_note();
+    *backtrace_note->Add() =
+        "Function names and BuildId information is missing for some frames due";
+    *backtrace_note->Add() = "to unreadable libraries. For unwinds of apps, only shared libraries";
+    *backtrace_note->Add() = "found under the lib/ directory are readable.";
+    *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable.";
+  }
+  unwinder->SetDisplayBuildID(true);
+  for (const auto& frame : unwinder->frames()) {
+    BacktraceFrame* f = thread.add_current_backtrace();
+    fill_in_backtrace_frame(f, frame);
+  }
+}
+
 static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
                         const ThreadInfo& thread_info, bool memory_dump = false) {
   Thread thread;
@@ -355,97 +443,32 @@
   thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl);
   thread.set_pac_enabled_keys(thread_info.pac_enabled_keys);
 
-  unwindstack::Maps* maps = unwinder->GetMaps();
-  unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
-
-  thread_info.registers->IterateRegisters(
-      [&thread, memory_dump, maps, memory](const char* name, uint64_t value) {
-        Register r;
-        r.set_name(name);
-        r.set_u64(value);
-        *thread.add_registers() = r;
-
-        if (memory_dump) {
-          MemoryDump dump;
-
-          dump.set_register_name(name);
-          std::shared_ptr<unwindstack::MapInfo> map_info = maps->Find(untag_address(value));
-          if (map_info) {
-            dump.set_mapping_name(map_info->name());
-          }
-
-          constexpr size_t kNumBytesAroundRegister = 256;
-          constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize;
-          char buf[kNumBytesAroundRegister];
-          uint8_t tags[kNumTagsAroundRegister];
-          size_t start_offset = 0;
-          ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
-          if (bytes == -1) {
-            return;
-          }
-          dump.set_begin_address(value);
-
-          if (start_offset + bytes > sizeof(buf)) {
-            async_safe_fatal("dump_memory overflowed? start offset = %zu, bytes read = %zd",
-                             start_offset, bytes);
-          }
-
-          dump.set_memory(buf, bytes);
-
-          bool has_tags = false;
-#if defined(__aarch64__)
-          for (size_t i = 0; i < kNumTagsAroundRegister; ++i) {
-            if (tags[i] != 0) {
-              has_tags = true;
-            }
-          }
-#endif  // defined(__aarch64__)
-
-          if (has_tags) {
-            dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister);
-          }
-
-          *thread.add_memory_dump() = std::move(dump);
-        }
-      });
-
-  std::unique_ptr<unwindstack::Regs> regs_copy(thread_info.registers->Clone());
-  unwinder->SetRegs(regs_copy.get());
-  unwinder->Unwind();
-  if (unwinder->NumFrames() == 0) {
-    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to unwind");
-    if (unwinder->LastErrorCode() != unwindstack::ERROR_NONE) {
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error code: %s",
-                            unwinder->LastErrorCodeString());
-      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error address: 0x%" PRIx64,
-                            unwinder->LastErrorAddress());
+  if (thread_info.pid == getpid() && thread_info.pid != thread_info.tid) {
+    // Fallback path for non-main thread, doing unwind from running process.
+    unwindstack::ThreadUnwinder thread_unwinder(kMaxFrames, unwinder->GetMaps());
+    if (!thread_unwinder.Init()) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                            "Unable to initialize ThreadUnwinder object.");
+      log_unwinder_error(&thread_unwinder);
+      return;
     }
+
+    std::unique_ptr<unwindstack::Regs> initial_regs;
+    thread_unwinder.UnwindWithSignal(BIONIC_SIGNAL_BACKTRACE, thread_info.tid, &initial_regs);
+    dump_registers(&thread_unwinder, initial_regs, thread, memory_dump);
+    dump_thread_backtrace(&thread_unwinder, thread);
   } else {
-    if (unwinder->elf_from_memory_not_file()) {
-      auto backtrace_note = thread.mutable_backtrace_note();
-      *backtrace_note->Add() =
-          "Function names and BuildId information is missing for some frames due";
-      *backtrace_note->Add() =
-          "to unreadable libraries. For unwinds of apps, only shared libraries";
-      *backtrace_note->Add() = "found under the lib/ directory are readable.";
-      *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable.";
-    }
-    unwinder->SetDisplayBuildID(true);
-    for (const auto& frame : unwinder->frames()) {
-      BacktraceFrame* f = thread.add_current_backtrace();
-      fill_in_backtrace_frame(f, frame);
-    }
+    dump_registers(unwinder, thread_info.registers, thread, memory_dump);
+    std::unique_ptr<unwindstack::Regs> regs_copy(thread_info.registers->Clone());
+    unwinder->SetRegs(regs_copy.get());
+    unwinder->Unwind();
+    dump_thread_backtrace(unwinder, thread);
   }
 
   auto& threads = *tombstone->mutable_threads();
   threads[thread_info.tid] = thread;
 }
 
-static void dump_main_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
-                             const ThreadInfo& thread_info) {
-  dump_thread(tombstone, unwinder, thread_info, true);
-}
-
 static void dump_mappings(Tombstone* tombstone, unwindstack::Unwinder* unwinder) {
   unwindstack::Maps* maps = unwinder->GetMaps();
   std::shared_ptr<unwindstack::Memory> process_memory = unwinder->GetProcessMemory();
@@ -663,7 +686,8 @@
 
   dump_abort_message(&result, unwinder, process_info);
 
-  dump_main_thread(&result, unwinder, main_thread);
+  // Dump the main thread, but save the memory around the registers.
+  dump_thread(&result, unwinder, main_thread, /* memory_dump */ true);
 
   for (const auto& [tid, thread_info] : threads) {
     if (tid != target_thread) {
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index 858a338..4e8fdf9 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -25,7 +25,7 @@
 rt_sigprocmask: 1
 rt_sigaction: 1
 rt_tgsigqueueinfo: 1
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS || arg0 == PR_GET_TAGGED_ADDR_CTRL || arg0 == PR_PAC_GET_ENABLED_KEYS
 madvise: 1
 mprotect: arg2 in 0x1|0x2
 munmap: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index 152697c..4eb996e 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -37,7 +37,7 @@
 #define PR_SET_VMA 0x53564d41
 #if defined(__aarch64__)
 // PR_PAC_RESET_KEYS happens on aarch64 in pthread_create path.
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS || arg0 == PR_GET_TAGGED_ADDR_CTRL || arg0 == PR_PAC_GET_ENABLED_KEYS
 #else
 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA
 #endif
diff --git a/debuggerd/util.cpp b/debuggerd/util.cpp
index ce0fd30..5c6abc9 100644
--- a/debuggerd/util.cpp
+++ b/debuggerd/util.cpp
@@ -18,6 +18,7 @@
 
 #include <time.h>
 
+#include <functional>
 #include <string>
 #include <utility>
 
@@ -74,3 +75,24 @@
   n = strftime(s, sz, "%z", &tm), s += n, sz -= n;
   return buf;
 }
+
+bool iterate_tids(pid_t pid, std::function<void(pid_t)> callback) {
+  char buf[BUFSIZ];
+  snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
+  std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(buf), closedir);
+  if (dir == nullptr) {
+    return false;
+  }
+
+  struct dirent* entry;
+  while ((entry = readdir(dir.get())) != nullptr) {
+    pid_t tid = atoi(entry->d_name);
+    if (tid == 0) {
+      continue;
+    }
+    if (pid != tid) {
+      callback(tid);
+    }
+  }
+  return true;
+}
diff --git a/debuggerd/util.h b/debuggerd/util.h
index ec2862a..4375870 100644
--- a/debuggerd/util.h
+++ b/debuggerd/util.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <functional>
 #include <string>
 #include <vector>
 
@@ -27,3 +28,4 @@
 std::string get_thread_name(pid_t tid);
 
 std::string get_timestamp();
+bool iterate_tids(pid_t, std::function<void(pid_t)>);
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 44dc81f..22f8363 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -186,6 +186,11 @@
     return result;
 }
 
+static void RemoveScratchPartition() {
+    AutoMountMetadata mount_metadata;
+    android::fs_mgr::TeardownAllOverlayForMountPoint();
+}
+
 bool UpdateSuper(FastbootDevice* device, const std::string& super_name, bool wipe) {
     std::vector<char> data = std::move(device->download_data());
     if (data.empty()) {
@@ -218,7 +223,7 @@
         if (!FlashPartitionTable(super_name, *new_metadata.get())) {
             return device->WriteFail("Unable to flash new partition table");
         }
-        android::fs_mgr::TeardownAllOverlayForMountPoint();
+        RemoveScratchPartition();
         sync();
         return device->WriteOkay("Successfully flashed partition table");
     }
@@ -262,7 +267,7 @@
     if (!UpdateAllPartitionMetadata(device, super_name, *new_metadata.get())) {
         return device->WriteFail("Unable to write new partition table");
     }
-    android::fs_mgr::TeardownAllOverlayForMountPoint();
+    RemoveScratchPartition();
     sync();
     return device->WriteOkay("Successfully updated partition table");
 }
diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp
index fcd3bd6..1b59e4a 100644
--- a/fastboot/fuzzer/Android.bp
+++ b/fastboot/fuzzer/Android.bp
@@ -15,6 +15,11 @@
  *
  */
 
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_fuzz {
     name: "fastboot_fuzzer",
     host_supported: true,
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 0ca1946..143e980 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -657,7 +657,12 @@
             if (partition_ext4 == fstab->end()) {
                 auto new_entry = *GetEntryForMountPoint(fstab, mount_point);
                 new_entry.fs_type = "ext4";
-                fstab->emplace_back(new_entry);
+                auto it = std::find_if(fstab->rbegin(), fstab->rend(),
+                                       [&mount_point](const auto& entry) {
+                                           return entry.mount_point == mount_point;
+                                       });
+                auto end_of_mount_point_group = fstab->begin() + std::distance(it, fstab->rend());
+                fstab->insert(end_of_mount_point_group, new_entry);
             }
         }
     }
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 2da5b0f..82b5275 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -33,6 +33,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -125,8 +126,12 @@
 
 bool fs_mgr_in_recovery() {
     // Check the existence of recovery binary instead of using the compile time
-    // macro, because first-stage-init is compiled with __ANDROID_RECOVERY__
-    // defined, albeit not in recovery. More details: system/core/init/README.md
+    // __ANDROID_RECOVERY__ macro.
+    // If BOARD_USES_RECOVERY_AS_BOOT is true, both normal and recovery boot
+    // mode would use the same init binary, which would mean during normal boot
+    // the '/init' binary is actually a symlink pointing to
+    // init_second_stage.recovery, which would be compiled with
+    // __ANDROID_RECOVERY__ defined.
     return fs_mgr_access("/system/bin/recovery");
 }
 
@@ -1121,6 +1126,23 @@
     return true;
 }
 
+static inline uint64_t GetIdealDataScratchSize() {
+    BlockDeviceInfo super_info;
+    PartitionOpener opener;
+    if (!opener.GetInfo(fs_mgr_get_super_partition_name(), &super_info)) {
+        LERROR << "could not get block device info for super";
+        return 0;
+    }
+
+    struct statvfs s;
+    if (statvfs("/data", &s) < 0) {
+        PERROR << "could not statfs /data";
+        return 0;
+    }
+
+    return std::min(super_info.size, (uint64_t(s.f_frsize) * s.f_bfree) / 2);
+}
+
 static bool CreateScratchOnData(std::string* scratch_device, bool* partition_exists, bool* change) {
     *partition_exists = false;
     if (change) *change = false;
@@ -1136,13 +1158,6 @@
         return true;
     }
 
-    BlockDeviceInfo info;
-    PartitionOpener opener;
-    if (!opener.GetInfo(fs_mgr_get_super_partition_name(), &info)) {
-        LERROR << "could not get block device info for super";
-        return false;
-    }
-
     if (change) *change = true;
 
     // Note: calling RemoveDisabledImages here ensures that we do not race with
@@ -1152,10 +1167,11 @@
         return false;
     }
     if (!images->BackingImageExists(partition_name)) {
-        static constexpr uint64_t kMinimumSize = 64_MiB;
-        static constexpr uint64_t kMaximumSize = 2_GiB;
+        uint64_t size = GetIdealDataScratchSize();
+        if (!size) {
+            size = 2_GiB;
+        }
 
-        uint64_t size = std::clamp(info.size / 2, kMinimumSize, kMaximumSize);
         auto flags = IImageManager::CREATE_IMAGE_DEFAULT;
 
         if (!images->CreateBackingImage(partition_name, size, flags)) {
@@ -1396,18 +1412,35 @@
     return ret;
 }
 
+struct MapInfo {
+    // If set, partition is owned by ImageManager.
+    std::unique_ptr<IImageManager> images;
+    // If set, and images is null, this is a DAP partition.
+    std::string name;
+    // If set, and images and name are empty, this is a non-dynamic partition.
+    std::string device;
+
+    MapInfo() = default;
+    MapInfo(MapInfo&&) = default;
+    ~MapInfo() {
+        if (images) {
+            images->UnmapImageDevice(name);
+        } else if (!name.empty()) {
+            DestroyLogicalPartition(name);
+        }
+    }
+};
+
 // Note: This function never returns the DSU scratch device in recovery or fastbootd,
 // because the DSU scratch is created in the first-stage-mount, which is not run in recovery.
-static bool EnsureScratchMapped(std::string* device, bool* mapped) {
-    *mapped = false;
-    *device = GetBootScratchDevice();
-    if (!device->empty()) {
-        return true;
+static std::optional<MapInfo> EnsureScratchMapped() {
+    MapInfo info;
+    info.device = GetBootScratchDevice();
+    if (!info.device.empty()) {
+        return {std::move(info)};
     }
-
     if (!fs_mgr_in_recovery()) {
-        errno = EINVAL;
-        return false;
+        return {};
     }
 
     auto partition_name = android::base::Basename(kScratchMountPoint);
@@ -1417,11 +1450,15 @@
     // would otherwise always be mapped.
     auto images = IImageManager::Open("remount", 10s);
     if (images && images->BackingImageExists(partition_name)) {
-        if (!images->MapImageDevice(partition_name, 10s, device)) {
-            return false;
+        if (images->IsImageDisabled(partition_name)) {
+            return {};
         }
-        *mapped = true;
-        return true;
+        if (!images->MapImageDevice(partition_name, 10s, &info.device)) {
+            return {};
+        }
+        info.name = partition_name;
+        info.images = std::move(images);
+        return {std::move(info)};
     }
 
     // Avoid uart spam by first checking for a scratch partition.
@@ -1429,12 +1466,12 @@
     auto super_device = fs_mgr_overlayfs_super_device(metadata_slot);
     auto metadata = ReadCurrentMetadata(super_device);
     if (!metadata) {
-        return false;
+        return {};
     }
 
     auto partition = FindPartition(*metadata.get(), partition_name);
     if (!partition) {
-        return false;
+        return {};
     }
 
     CreateLogicalPartitionParams params = {
@@ -1444,11 +1481,11 @@
             .force_writable = true,
             .timeout_ms = 10s,
     };
-    if (!CreateLogicalPartition(params, device)) {
-        return false;
+    if (!CreateLogicalPartition(params, &info.device)) {
+        return {};
     }
-    *mapped = true;
-    return true;
+    info.name = partition_name;
+    return {std::move(info)};
 }
 
 // This should only be reachable in recovery, where DSU scratch is not
@@ -1602,26 +1639,35 @@
         fs_mgr_overlayfs_teardown_one(overlay_mount_point, teardown_dir, ignore_change);
     }
 
-    // Map scratch device, mount kScratchMountPoint and teardown kScratchMountPoint.
-    bool mapped = false;
-    std::string scratch_device;
-    if (EnsureScratchMapped(&scratch_device, &mapped)) {
+    if (mount_point.empty()) {
+        // Throw away the entire partition.
+        auto partition_name = android::base::Basename(kScratchMountPoint);
+        auto images = IImageManager::Open("remount", 10s);
+        if (images && images->BackingImageExists(partition_name)) {
+            if (images->DisableImage(partition_name)) {
+                LOG(INFO) << "Disabled scratch partition for: " << kScratchMountPoint;
+            } else {
+                LOG(ERROR) << "Unable to disable scratch partition for " << kScratchMountPoint;
+            }
+        }
+    }
+
+    if (auto info = EnsureScratchMapped(); info.has_value()) {
+        // Map scratch device, mount kScratchMountPoint and teardown kScratchMountPoint.
         fs_mgr_overlayfs_umount_scratch();
-        if (fs_mgr_overlayfs_mount_scratch(scratch_device, fs_mgr_overlayfs_scratch_mount_type())) {
+        if (fs_mgr_overlayfs_mount_scratch(info->device, fs_mgr_overlayfs_scratch_mount_type())) {
             bool should_destroy_scratch = false;
             fs_mgr_overlayfs_teardown_one(kScratchMountPoint, teardown_dir, ignore_change,
                                           &should_destroy_scratch);
+            fs_mgr_overlayfs_umount_scratch();
             if (should_destroy_scratch) {
                 fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr);
             }
-            fs_mgr_overlayfs_umount_scratch();
-        }
-        if (mapped) {
-            DestroyLogicalPartition(android::base::Basename(kScratchMountPoint));
         }
     }
 
     // Teardown DSU overlay if present.
+    std::string scratch_device;
     if (MapDsuScratchDevice(&scratch_device)) {
         fs_mgr_overlayfs_umount_scratch();
         if (fs_mgr_overlayfs_mount_scratch(scratch_device, fs_mgr_overlayfs_scratch_mount_type())) {
diff --git a/fs_mgr/fuzz/Android.bp b/fs_mgr/fuzz/Android.bp
index f0afd28..b2b9be8 100644
--- a/fs_mgr/fuzz/Android.bp
+++ b/fs_mgr/fuzz/Android.bp
@@ -14,6 +14,11 @@
 // limitations under the License.
 //
 
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_fuzz {
   name: "libfstab_fuzzer",
   srcs: [
diff --git a/fs_mgr/libfiemap/binder.cpp b/fs_mgr/libfiemap/binder.cpp
index 31a57a8..003e6ed 100644
--- a/fs_mgr/libfiemap/binder.cpp
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -66,6 +66,7 @@
     bool RemoveDisabledImages() override;
     bool GetMappedImageDevice(const std::string& name, std::string* device) override;
     bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override;
+    bool IsImageDisabled(const std::string& name) override;
 
     std::vector<std::string> GetAllBackingImages() override;
 
@@ -219,6 +220,17 @@
     return !device->empty();
 }
 
+bool ImageManagerBinder::IsImageDisabled(const std::string& name) {
+    bool retval;
+    auto status = manager_->isImageDisabled(name, &retval);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return retval;
+}
+
 bool ImageManagerBinder::MapAllImages(const std::function<bool(std::set<std::string>)>&) {
     LOG(ERROR) << __PRETTY_FUNCTION__ << " not available over binder";
     return false;
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index e3f5716..c416f4d 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -79,7 +79,7 @@
     partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
 
     // Allow overriding whether ImageManager thinks it's in recovery, for testing.
-#ifdef __ANDROID_RECOVERY__
+#ifdef __ANDROID_RAMDISK__
     device_info_.is_recovery = {true};
 #else
     if (!device_info_.is_recovery.has_value()) {
@@ -523,7 +523,7 @@
 
     auto image_header = GetImageHeaderPath(name);
 
-#if !defined __ANDROID_RECOVERY__
+#ifndef __ANDROID_RAMDISK__
     // If there is a device-mapper node wrapping the block device, then we're
     // able to create another node around it; the dm layer does not carry the
     // exclusion lock down the stack when a mount occurs.
@@ -854,6 +854,24 @@
     return true;
 }
 
+bool ImageManager::IsImageDisabled(const std::string& name) {
+    if (!MetadataExists(metadata_dir_)) {
+        return true;
+    }
+
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+
+    auto partition = FindPartition(*metadata.get(), name);
+    if (!partition) {
+        return false;
+    }
+
+    return !!(partition->attributes & LP_PARTITION_ATTR_DISABLED);
+}
+
 std::unique_ptr<MappedDevice> MappedDevice::Open(IImageManager* manager,
                                                  const std::chrono::milliseconds& timeout_ms,
                                                  const std::string& name) {
diff --git a/fs_mgr/libfiemap/image_test.cpp b/fs_mgr/libfiemap/image_test.cpp
index 6d09751..7472949 100644
--- a/fs_mgr/libfiemap/image_test.cpp
+++ b/fs_mgr/libfiemap/image_test.cpp
@@ -119,6 +119,7 @@
     ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr));
     ASSERT_TRUE(manager_->BackingImageExists(base_name_));
     ASSERT_TRUE(manager_->DisableImage(base_name_));
+    ASSERT_TRUE(manager_->IsImageDisabled(base_name_));
     ASSERT_TRUE(manager_->RemoveDisabledImages());
     ASSERT_TRUE(!manager_->BackingImageExists(base_name_));
 }
diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
index b23a7f7..00dd661 100644
--- a/fs_mgr/libfiemap/include/libfiemap/image_manager.h
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -131,6 +131,9 @@
     virtual bool RemoveAllImages() = 0;
 
     virtual bool UnmapImageIfExists(const std::string& name);
+
+    // Returns whether DisableImage() was called.
+    virtual bool IsImageDisabled(const std::string& name) = 0;
 };
 
 class ImageManager final : public IImageManager {
@@ -162,6 +165,7 @@
     bool RemoveDisabledImages() override;
     bool GetMappedImageDevice(const std::string& name, std::string* device) override;
     bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override;
+    bool IsImageDisabled(const std::string& name) override;
 
     std::vector<std::string> GetAllBackingImages();
 
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 8b269cd..6db8f13 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -275,6 +275,24 @@
     ],
 }
 
+cc_test {
+    name: "vts_ota_config_test",
+    srcs: [
+        "vts_ota_config_test.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+    test_suites: [
+        "vts",
+    ],
+    test_options: {
+        min_shipping_api_level: 33,
+    },
+    auto_gen_config: true,
+    require_root: true,
+}
+
 cc_binary {
     name: "snapshotctl",
     srcs: [
diff --git a/fs_mgr/libsnapshot/OWNERS b/fs_mgr/libsnapshot/OWNERS
index 37319fe..9d2b877 100644
--- a/fs_mgr/libsnapshot/OWNERS
+++ b/fs_mgr/libsnapshot/OWNERS
@@ -2,3 +2,4 @@
 balsini@google.com
 dvander@google.com
 elsk@google.com
+akailash@google.com
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 532f66d..5daa84d 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -197,6 +197,9 @@
 
     // user-space snapshots
     bool userspace_snapshots = 9;
+
+    // io_uring support
+    bool io_uring_enabled = 10;
 }
 
 // Next: 10
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 9b5fd2a..746feeb 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -550,6 +550,9 @@
     const CowOperation& Get() override;
     void Next() override;
 
+    void Prev() override;
+    bool RDone() override;
+
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
     std::vector<CowOperation>::iterator op_iter_;
@@ -560,6 +563,15 @@
     op_iter_ = ops_->begin();
 }
 
+bool CowOpIter::RDone() {
+    return op_iter_ == ops_->begin();
+}
+
+void CowOpIter::Prev() {
+    CHECK(!RDone());
+    op_iter_--;
+}
+
 bool CowOpIter::Done() {
     return op_iter_ == ops_->end();
 }
@@ -585,6 +597,9 @@
     const CowOperation& Get() override;
     void Next() override;
 
+    void Prev() override;
+    bool RDone() override;
+
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
@@ -603,6 +618,9 @@
     const CowOperation& Get() override;
     void Next() override;
 
+    void Prev() override;
+    bool RDone() override;
+
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
@@ -623,6 +641,15 @@
     block_iter_ = merge_op_blocks->begin() + start;
 }
 
+bool CowMergeOpIter::RDone() {
+    return block_iter_ == merge_op_blocks_->begin();
+}
+
+void CowMergeOpIter::Prev() {
+    CHECK(!RDone());
+    block_iter_--;
+}
+
 bool CowMergeOpIter::Done() {
     return block_iter_ == merge_op_blocks_->end();
 }
@@ -649,6 +676,15 @@
     block_riter_ = merge_op_blocks->rbegin();
 }
 
+bool CowRevMergeOpIter::RDone() {
+    return block_riter_ == merge_op_blocks_->rbegin();
+}
+
+void CowRevMergeOpIter::Prev() {
+    CHECK(!RDone());
+    block_riter_--;
+}
+
 bool CowRevMergeOpIter::Done() {
     return block_riter_ == merge_op_blocks_->rend() - start_;
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index d5b4335..8e6bbd9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -92,7 +92,7 @@
   public:
     virtual ~ICowOpIter() {}
 
-    // True if there are more items to read, false otherwise.
+    // True if there are no more items to read forward, false otherwise.
     virtual bool Done() = 0;
 
     // Read the current operation.
@@ -100,6 +100,12 @@
 
     // Advance to the next item.
     virtual void Next() = 0;
+
+    // Advance to the previous item.
+    virtual void Prev() = 0;
+
+    // True if there are no more items to read backwards, false otherwise
+    virtual bool RDone() = 0;
 };
 
 class CowReader final : public ICowReader {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 120f95b..38b47d5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -809,6 +809,9 @@
     // userspace snapshots.
     bool UpdateUsesUserSnapshots(LockedFile* lock);
 
+    // Check if io_uring API's need to be used
+    bool UpdateUsesIouring(LockedFile* lock);
+
     // Wrapper around libdm, with diagnostics.
     bool DeleteDeviceIfExists(const std::string& name,
                               const std::chrono::milliseconds& timeout_ms = {});
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index 548ba00..167ff8c 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -155,6 +155,7 @@
     }
     StringSink sink;
     bool success = true;
+    uint64_t xor_ops = 0, copy_ops = 0, replace_ops = 0, zero_ops = 0;
     while (!iter->Done()) {
         const CowOperation& op = iter->Get();
 
@@ -187,9 +188,26 @@
             }
         }
 
+        if (op.type == kCowCopyOp) {
+            copy_ops++;
+        } else if (op.type == kCowReplaceOp) {
+            replace_ops++;
+        } else if (op.type == kCowZeroOp) {
+            zero_ops++;
+        } else if (op.type == kCowXorOp) {
+            xor_ops++;
+        }
+
         iter->Next();
     }
 
+    if (!opt.silent) {
+        auto total_ops = replace_ops + zero_ops + copy_ops + xor_ops;
+        std::cout << "Total-data-ops: " << total_ops << "Replace-ops: " << replace_ops
+                  << " Zero-ops: " << zero_ops << " Copy-ops: " << copy_ops
+                  << " Xor_ops: " << xor_ops << std::endl;
+    }
+
     return success;
 }
 
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index f3de2b4..797d627 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -1685,6 +1685,9 @@
 
     if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) {
         snapuserd_argv->emplace_back("-user_snapshot");
+        if (UpdateUsesIouring(lock.get())) {
+            snapuserd_argv->emplace_back("-io_uring");
+        }
     }
 
     size_t num_cows = 0;
@@ -2062,6 +2065,11 @@
     return update_status.compression_enabled();
 }
 
+bool SnapshotManager::UpdateUsesIouring(LockedFile* lock) {
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    return update_status.io_uring_enabled();
+}
+
 bool SnapshotManager::UpdateUsesUserSnapshots() {
     // This and the following function is constantly
     // invoked during snapshot merge. We want to avoid
@@ -2877,6 +2885,7 @@
         status.set_source_build_fingerprint(old_status.source_build_fingerprint());
         status.set_merge_phase(old_status.merge_phase());
         status.set_userspace_snapshots(old_status.userspace_snapshots());
+        status.set_io_uring_enabled(old_status.io_uring_enabled());
     }
     return WriteSnapshotUpdateStatus(lock, status);
 }
@@ -3200,6 +3209,7 @@
             status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
             if (IsUserspaceSnapshotsEnabled()) {
                 is_snapshot_userspace_ = true;
+                status.set_io_uring_enabled(IsIouringEnabled());
                 LOG(INFO) << "User-space snapshots enabled";
             } else {
                 is_snapshot_userspace_ = false;
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index c1a5af7..a648384 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -199,6 +199,7 @@
     bool UnmapImageIfExists(const std::string& name) override {
         return impl_->UnmapImageIfExists(name);
     }
+    bool IsImageDisabled(const std::string& name) override { return impl_->IsImageDisabled(name); }
 
   private:
     std::unique_ptr<android::fiemap::IImageManager> impl_;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index d76558b..04d228d 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -46,8 +46,6 @@
 #include "partition_cow_creator.h"
 #include "utility.h"
 
-#include <android-base/properties.h>
-
 // Mock classes are not used. Header included to ensure mocked class definition aligns with the
 // class itself.
 #include <libsnapshot/mock_device_info.h>
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index a082742..0b88567 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -28,6 +28,7 @@
 DEFINE_bool(socket_handoff, false,
             "If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
 DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
+DEFINE_bool(io_uring, false, "If true, io_uring feature is enabled");
 
 namespace android {
 namespace snapshot {
@@ -51,7 +52,12 @@
     // is applied will check for the property. This is ok as the system
     // properties are valid at this point. We can't do this during first
     // stage init and hence use the command line flags to get the information.
-    if (!IsDmSnapshotTestingEnabled() && (FLAGS_user_snapshot || IsUserspaceSnapshotsEnabled())) {
+    bool user_snapshots = FLAGS_user_snapshot;
+    if (!user_snapshots) {
+        user_snapshots = (!IsDmSnapshotTestingEnabled() && IsUserspaceSnapshotsEnabled());
+    }
+
+    if (user_snapshots) {
         LOG(INFO) << "Starting daemon for user-space snapshots.....";
         return StartServerForUserspaceSnapshots(arg_start, argc, argv);
     } else {
@@ -75,6 +81,11 @@
 
     MaskAllSignalsExceptIntAndTerm();
 
+    user_server_.SetServerRunning();
+    if (FLAGS_io_uring) {
+        user_server_.SetIouringEnabled();
+    }
+
     if (FLAGS_socket_handoff) {
         return user_server_.RunForSocketHandoff();
     }
@@ -165,7 +176,10 @@
 }
 
 void Daemon::Interrupt() {
-    if (IsUserspaceSnapshotsEnabled()) {
+    // TODO: We cannot access system property during first stage init.
+    // Until we remove the dm-snapshot code, we will have this check
+    // and verify it through a temp variable.
+    if (user_server_.IsServerRunning()) {
         user_server_.Interrupt();
     } else {
         server_.Interrupt();
@@ -173,7 +187,7 @@
 }
 
 void Daemon::ReceivedSocketSignal() {
-    if (IsUserspaceSnapshotsEnabled()) {
+    if (user_server_.IsServerRunning()) {
         user_server_.ReceivedSocketSignal();
     } else {
         server_.ReceivedSocketSignal();
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 5109d82..692cb74 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -39,7 +39,21 @@
 }
 
 bool SnapshotHandler::InitializeWorkers() {
-    for (int i = 0; i < kNumWorkerThreads; i++) {
+    int num_worker_threads = kNumWorkerThreads;
+
+    // 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
+    // unnecessary memory especially during OTA install phase
+    // when daemon will be up during entire post install phase.
+    //
+    // During boot up, we need multiple threads primarily for
+    // update-verification.
+    if (is_socket_present_) {
+        num_worker_threads = 1;
+    }
+
+    for (int i = 0; i < num_worker_threads; i++) {
         std::unique_ptr<Worker> wt =
                 std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
                                          misc_name_, base_path_merge_, GetSharedPtr());
@@ -478,22 +492,17 @@
         return;
     }
 
-    if (IsIouringSupported()) {
-        std::async(std::launch::async, &SnapshotHandler::ReadBlocksAsync, this, dm_block_device,
-                   partition_name, dev_sz);
-    } else {
-        int num_threads = 2;
-        size_t num_blocks = dev_sz >> BLOCK_SHIFT;
-        size_t num_blocks_per_thread = num_blocks / num_threads;
-        size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
-        off_t offset = 0;
+    int num_threads = 2;
+    size_t num_blocks = dev_sz >> BLOCK_SHIFT;
+    size_t num_blocks_per_thread = num_blocks / num_threads;
+    size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
+    off_t offset = 0;
 
-        for (int i = 0; i < num_threads; i++) {
-            std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this,
-                       dm_block_device, partition_name, offset, read_sz_per_thread);
+    for (int i = 0; i < num_threads; i++) {
+        std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this, dm_block_device,
+                   partition_name, offset, read_sz_per_thread);
 
-            offset += read_sz_per_thread;
-        }
+        offset += read_sz_per_thread;
     }
 }
 
@@ -677,6 +686,14 @@
         return false;
     }
 
+    // During selinux init transition, libsnapshot will propagate the
+    // status of io_uring enablement. As properties are not initialized,
+    // we cannot query system property.
+    if (is_io_uring_enabled_) {
+        return true;
+    }
+
+    // Finally check the system property
     return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
 }
 
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 b0f2d65..83d40f6 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -99,6 +99,7 @@
     void InitializeRAIter();
     bool RAIterDone();
     void RAIterNext();
+    void RAResetIter(uint64_t num_blocks);
     const CowOperation* GetRAOpIter();
 
     void InitializeBuffer();
@@ -151,12 +152,16 @@
     std::unique_ptr<uint8_t[]> ra_temp_meta_buffer_;
     BufferSink bufsink_;
 
+    uint64_t total_ra_blocks_completed_ = 0;
     bool read_ahead_async_ = false;
-    // Queue depth of 32 seems optimal. We don't want
+    // Queue depth of 8 seems optimal. We don't want
     // to have a huge depth as it may put more memory pressure
     // on the kernel worker threads given that we use
-    // IOSQE_ASYNC flag.
-    int queue_depth_ = 32;
+    // IOSQE_ASYNC flag - ASYNC flags can potentially
+    // result in EINTR; Since we don't restart
+    // syscalls and fallback to synchronous I/O, we
+    // don't want huge queue depth
+    int queue_depth_ = 8;
     std::unique_ptr<struct io_uring> ring_;
 };
 
@@ -210,11 +215,12 @@
 
     // Merge related ops
     bool Merge();
-    bool MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
-    bool MergeOrderedOpsAsync(const std::unique_ptr<ICowOpIter>& cowop_iter);
-    bool MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+    bool AsyncMerge();
+    bool SyncMerge();
+    bool MergeOrderedOps();
+    bool MergeOrderedOpsAsync();
+    bool MergeReplaceZeroOps();
     int PrepareMerge(uint64_t* source_offset, int* pending_ops,
-                     const std::unique_ptr<ICowOpIter>& cowop_iter,
                      std::vector<const CowOperation*>* replace_zero_vec = nullptr);
 
     sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
@@ -238,12 +244,18 @@
     unique_fd base_path_merge_fd_;
     unique_fd ctrl_fd_;
 
+    std::unique_ptr<ICowOpIter> cowop_iter_;
+    size_t ra_block_index_ = 0;
+    uint64_t blocks_merged_in_group_ = 0;
     bool merge_async_ = false;
-    // Queue depth of 32 seems optimal. We don't want
+    // Queue depth of 8 seems optimal. We don't want
     // to have a huge depth as it may put more memory pressure
     // on the kernel worker threads given that we use
-    // IOSQE_ASYNC flag.
-    int queue_depth_ = 32;
+    // IOSQE_ASYNC flag - ASYNC flags can potentially
+    // result in EINTR; Since we don't restart
+    // syscalls and fallback to synchronous I/O, we
+    // don't want huge queue depth
+    int queue_depth_ = 8;
     std::unique_ptr<struct io_uring> ring_;
 
     std::shared_ptr<SnapshotHandler> snapuserd_;
@@ -319,6 +331,7 @@
     void SetMergedBlockCountForNextCommit(int x) { total_ra_blocks_merged_ = x; }
     int GetTotalBlocksToMerge() { return total_ra_blocks_merged_; }
     void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
+    void SetIouringEnabled(bool io_uring_enabled) { is_io_uring_enabled_ = io_uring_enabled; }
     bool MergeInitiated() { return merge_initiated_; }
     double GetMergePercentage() { return merge_completion_percentage_; }
 
@@ -396,6 +409,7 @@
     bool merge_initiated_ = false;
     bool attached_ = false;
     bool is_socket_present_;
+    bool is_io_uring_enabled_ = false;
     bool scratch_space_ = false;
 
     std::unique_ptr<struct io_uring> ring_;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index d4d4efe..0cb41d3 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -24,15 +24,14 @@
 using android::base::unique_fd;
 
 int Worker::PrepareMerge(uint64_t* source_offset, int* pending_ops,
-                         const std::unique_ptr<ICowOpIter>& cowop_iter,
                          std::vector<const CowOperation*>* replace_zero_vec) {
     int num_ops = *pending_ops;
     int nr_consecutive = 0;
     bool checkOrderedOp = (replace_zero_vec == nullptr);
 
     do {
-        if (!cowop_iter->Done() && num_ops) {
-            const CowOperation* cow_op = &cowop_iter->Get();
+        if (!cowop_iter_->Done() && num_ops) {
+            const CowOperation* cow_op = &cowop_iter_->Get();
             if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
                 break;
             }
@@ -42,12 +41,12 @@
                 replace_zero_vec->push_back(cow_op);
             }
 
-            cowop_iter->Next();
+            cowop_iter_->Next();
             num_ops -= 1;
             nr_consecutive = 1;
 
-            while (!cowop_iter->Done() && num_ops) {
-                const CowOperation* op = &cowop_iter->Get();
+            while (!cowop_iter_->Done() && num_ops) {
+                const CowOperation* op = &cowop_iter_->Get();
                 if (checkOrderedOp && !IsOrderedOp(*op)) {
                     break;
                 }
@@ -63,7 +62,7 @@
 
                 nr_consecutive += 1;
                 num_ops -= 1;
-                cowop_iter->Next();
+                cowop_iter_->Next();
             }
         }
     } while (0);
@@ -71,7 +70,7 @@
     return nr_consecutive;
 }
 
-bool Worker::MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+bool Worker::MergeReplaceZeroOps() {
     // Flush every 8192 ops. Since all ops are independent and there is no
     // dependency between COW ops, we will flush the data and the number
     // of ops merged in COW file for every 8192 ops. If there is a crash,
@@ -84,15 +83,17 @@
     int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 32;
     int num_ops_merged = 0;
 
-    while (!cowop_iter->Done()) {
+    SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
+
+    while (!cowop_iter_->Done()) {
         int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
         std::vector<const CowOperation*> replace_zero_vec;
         uint64_t source_offset;
 
-        int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter, &replace_zero_vec);
+        int linear_blocks = PrepareMerge(&source_offset, &num_ops, &replace_zero_vec);
         if (linear_blocks == 0) {
             // Merge complete
-            CHECK(cowop_iter->Done());
+            CHECK(cowop_iter_->Done());
             break;
         }
 
@@ -117,8 +118,8 @@
         size_t io_size = linear_blocks * BLOCK_SZ;
 
         // Merge - Write the contents back to base device
-        int ret = pwrite(base_path_merge_fd_.get(), bufsink_.GetPayloadBufPtr(), io_size,
-                         source_offset);
+        int ret = TEMP_FAILURE_RETRY(pwrite(base_path_merge_fd_.get(), bufsink_.GetPayloadBufPtr(),
+                                            io_size, source_offset));
         if (ret < 0 || ret != io_size) {
             SNAP_LOG(ERROR)
                     << "Merge: ReplaceZeroOps: Failed to write to backing device while merging "
@@ -172,16 +173,15 @@
     return true;
 }
 
-bool Worker::MergeOrderedOpsAsync(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+bool Worker::MergeOrderedOpsAsync() {
     void* mapped_addr = snapuserd_->GetMappedAddr();
     void* read_ahead_buffer =
             static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
-    size_t block_index = 0;
 
     SNAP_LOG(INFO) << "MergeOrderedOpsAsync started....";
 
-    while (!cowop_iter->Done()) {
-        const CowOperation* cow_op = &cowop_iter->Get();
+    while (!cowop_iter_->Done()) {
+        const CowOperation* cow_op = &cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -190,11 +190,10 @@
         // Wait for RA thread to notify that the merge window
         // is ready for merging.
         if (!snapuserd_->WaitForMergeBegin()) {
-            snapuserd_->SetMergeFailed(block_index);
             return false;
         }
 
-        snapuserd_->SetMergeInProgress(block_index);
+        snapuserd_->SetMergeInProgress(ra_block_index_);
 
         loff_t offset = 0;
         int num_ops = snapuserd_->GetTotalBlocksToMerge();
@@ -202,12 +201,13 @@
         int pending_sqe = queue_depth_;
         int pending_ios_to_submit = 0;
         bool flush_required = false;
+        blocks_merged_in_group_ = 0;
 
         SNAP_LOG(DEBUG) << "Merging copy-ops of size: " << num_ops;
         while (num_ops) {
             uint64_t source_offset;
 
-            int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter);
+            int linear_blocks = PrepareMerge(&source_offset, &num_ops);
 
             if (linear_blocks != 0) {
                 size_t io_size = (linear_blocks * BLOCK_SZ);
@@ -216,7 +216,6 @@
                 struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get());
                 if (!sqe) {
                     SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during merge-ordered ops";
-                    snapuserd_->SetMergeFailed(block_index);
                     return false;
                 }
 
@@ -225,10 +224,18 @@
 
                 offset += io_size;
                 num_ops -= linear_blocks;
+                blocks_merged_in_group_ += linear_blocks;
 
                 pending_sqe -= 1;
                 pending_ios_to_submit += 1;
-                sqe->flags |= IOSQE_ASYNC;
+                // These flags are important - We need to make sure that the
+                // blocks are linked and are written in the same order as
+                // populated. This is because of overlapping block writes.
+                //
+                // If there are no dependency, we can optimize this further by
+                // allowing parallel writes; but for now, just link all the SQ
+                // entries.
+                sqe->flags |= (IOSQE_IO_LINK | IOSQE_ASYNC);
             }
 
             // Ring is full or no more COW ops to be merged in this batch
@@ -256,7 +263,7 @@
                             pending_sqe -= 1;
                             flush_required = false;
                             pending_ios_to_submit += 1;
-                            sqe->flags |= IOSQE_ASYNC;
+                            sqe->flags |= (IOSQE_IO_LINK | IOSQE_ASYNC);
                         }
                     } else {
                         flush_required = true;
@@ -269,35 +276,45 @@
                     SNAP_PLOG(ERROR)
                             << "io_uring_submit failed for read-ahead: "
                             << " io submit: " << ret << " expected: " << pending_ios_to_submit;
-                    snapuserd_->SetMergeFailed(block_index);
                     return false;
                 }
 
                 int pending_ios_to_complete = pending_ios_to_submit;
                 pending_ios_to_submit = 0;
 
+                bool status = true;
+
                 // Reap I/O completions
                 while (pending_ios_to_complete) {
                     struct io_uring_cqe* cqe;
 
+                    // We need to make sure to reap all the I/O's submitted
+                    // even if there are any errors observed.
+                    //
+                    // io_uring_wait_cqe can potentially return -EAGAIN or -EINTR;
+                    // these error codes are not truly I/O errors; we can retry them
+                    // by re-populating the SQE entries and submitting the I/O
+                    // request back. However, we don't do that now; instead we
+                    // will fallback to synchronous I/O.
                     ret = io_uring_wait_cqe(ring_.get(), &cqe);
                     if (ret) {
-                        SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << ret;
-                        snapuserd_->SetMergeFailed(block_index);
-                        return false;
+                        SNAP_LOG(ERROR) << "Merge: io_uring_wait_cqe failed: " << ret;
+                        status = false;
                     }
 
                     if (cqe->res < 0) {
-                        SNAP_LOG(ERROR)
-                                << "Read-ahead - io_uring_Wait_cqe failed with res: " << cqe->res;
-                        snapuserd_->SetMergeFailed(block_index);
-                        return false;
+                        SNAP_LOG(ERROR) << "Merge: io_uring_wait_cqe failed with res: " << cqe->res;
+                        status = false;
                     }
 
                     io_uring_cqe_seen(ring_.get(), cqe);
                     pending_ios_to_complete -= 1;
                 }
 
+                if (!status) {
+                    return false;
+                }
+
                 pending_sqe = queue_depth_;
             }
 
@@ -312,7 +329,6 @@
         // Flush the data
         if (flush_required && (fsync(base_path_merge_fd_.get()) < 0)) {
             SNAP_LOG(ERROR) << " Failed to fsync merged data";
-            snapuserd_->SetMergeFailed(block_index);
             return false;
         }
 
@@ -320,35 +336,34 @@
         // the merge completion
         if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) {
             SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
-            snapuserd_->SetMergeFailed(block_index);
             return false;
         }
 
         SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge();
+
         // Mark the block as merge complete
-        snapuserd_->SetMergeCompleted(block_index);
+        snapuserd_->SetMergeCompleted(ra_block_index_);
 
         // Notify RA thread that the merge thread is ready to merge the next
         // window
         snapuserd_->NotifyRAForMergeReady();
 
         // Get the next block
-        block_index += 1;
+        ra_block_index_ += 1;
     }
 
     return true;
 }
 
-bool Worker::MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+bool Worker::MergeOrderedOps() {
     void* mapped_addr = snapuserd_->GetMappedAddr();
     void* read_ahead_buffer =
             static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
-    size_t block_index = 0;
 
     SNAP_LOG(INFO) << "MergeOrderedOps started....";
 
-    while (!cowop_iter->Done()) {
-        const CowOperation* cow_op = &cowop_iter->Get();
+    while (!cowop_iter_->Done()) {
+        const CowOperation* cow_op = &cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -357,11 +372,11 @@
         // Wait for RA thread to notify that the merge window
         // is ready for merging.
         if (!snapuserd_->WaitForMergeBegin()) {
-            snapuserd_->SetMergeFailed(block_index);
+            snapuserd_->SetMergeFailed(ra_block_index_);
             return false;
         }
 
-        snapuserd_->SetMergeInProgress(block_index);
+        snapuserd_->SetMergeInProgress(ra_block_index_);
 
         loff_t offset = 0;
         int num_ops = snapuserd_->GetTotalBlocksToMerge();
@@ -369,7 +384,7 @@
         while (num_ops) {
             uint64_t source_offset;
 
-            int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter);
+            int linear_blocks = PrepareMerge(&source_offset, &num_ops);
             if (linear_blocks == 0) {
                 break;
             }
@@ -378,12 +393,13 @@
             // Write to the base device. Data is already in the RA buffer. Note
             // that XOR ops is already handled by the RA thread. We just write
             // the contents out.
-            int ret = pwrite(base_path_merge_fd_.get(), (char*)read_ahead_buffer + offset, io_size,
-                             source_offset);
+            int ret = TEMP_FAILURE_RETRY(pwrite(base_path_merge_fd_.get(),
+                                                (char*)read_ahead_buffer + offset, io_size,
+                                                source_offset));
             if (ret < 0 || ret != io_size) {
                 SNAP_LOG(ERROR) << "Failed to write to backing device while merging "
                                 << " at offset: " << source_offset << " io_size: " << io_size;
-                snapuserd_->SetMergeFailed(block_index);
+                snapuserd_->SetMergeFailed(ra_block_index_);
                 return false;
             }
 
@@ -397,7 +413,7 @@
         // Flush the data
         if (fsync(base_path_merge_fd_.get()) < 0) {
             SNAP_LOG(ERROR) << " Failed to fsync merged data";
-            snapuserd_->SetMergeFailed(block_index);
+            snapuserd_->SetMergeFailed(ra_block_index_);
             return false;
         }
 
@@ -405,47 +421,87 @@
         // the merge completion
         if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) {
             SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
-            snapuserd_->SetMergeFailed(block_index);
+            snapuserd_->SetMergeFailed(ra_block_index_);
             return false;
         }
 
         SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge();
         // Mark the block as merge complete
-        snapuserd_->SetMergeCompleted(block_index);
+        snapuserd_->SetMergeCompleted(ra_block_index_);
 
         // Notify RA thread that the merge thread is ready to merge the next
         // window
         snapuserd_->NotifyRAForMergeReady();
 
         // Get the next block
-        block_index += 1;
+        ra_block_index_ += 1;
     }
 
     return true;
 }
 
-bool Worker::Merge() {
-    std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+bool Worker::AsyncMerge() {
+    if (!MergeOrderedOpsAsync()) {
+        SNAP_LOG(ERROR) << "MergeOrderedOpsAsync failed - Falling back to synchronous I/O";
+        // Reset the iter so that we retry the merge
+        while (blocks_merged_in_group_ && !cowop_iter_->RDone()) {
+            cowop_iter_->Prev();
+            blocks_merged_in_group_ -= 1;
+        }
 
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "MergeOrderedOpsAsync completed";
+    return true;
+}
+
+bool Worker::SyncMerge() {
+    if (!MergeOrderedOps()) {
+        SNAP_LOG(ERROR) << "Merge failed for ordered ops";
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "MergeOrderedOps completed";
+    return true;
+}
+
+bool Worker::Merge() {
+    cowop_iter_ = reader_->GetMergeOpIter();
+
+    bool retry = false;
+    bool ordered_ops_merge_status;
+
+    // Start Async Merge
     if (merge_async_) {
-        if (!MergeOrderedOpsAsync(cowop_iter)) {
+        ordered_ops_merge_status = AsyncMerge();
+        if (!ordered_ops_merge_status) {
+            FinalizeIouring();
+            retry = true;
+            merge_async_ = false;
+        }
+    }
+
+    // Check if we need to fallback and retry the merge
+    //
+    // If the device doesn't support async merge, we
+    // will directly enter here (aka devices with 4.x kernels)
+    const bool sync_merge_required = (retry || !merge_async_);
+
+    if (sync_merge_required) {
+        ordered_ops_merge_status = SyncMerge();
+        if (!ordered_ops_merge_status) {
+            // Merge failed. Device will continue to be mounted
+            // off snapshots; merge will be retried during
+            // next reboot
             SNAP_LOG(ERROR) << "Merge failed for ordered ops";
             snapuserd_->MergeFailed();
             return false;
         }
-        SNAP_LOG(INFO) << "MergeOrderedOpsAsync completed.....";
-    } else {
-        // Start with Copy and Xor ops
-        if (!MergeOrderedOps(cowop_iter)) {
-            SNAP_LOG(ERROR) << "Merge failed for ordered ops";
-            snapuserd_->MergeFailed();
-            return false;
-        }
-        SNAP_LOG(INFO) << "MergeOrderedOps completed.....";
     }
 
     // Replace and Zero ops
-    if (!MergeReplaceZeroOps(cowop_iter)) {
+    if (!MergeReplaceZeroOps()) {
         SNAP_LOG(ERROR) << "Merge failed for replace/zero ops";
         snapuserd_->MergeFailed();
         return false;
@@ -506,7 +562,7 @@
     CloseFds();
     reader_->CloseCowFd();
 
-    SNAP_LOG(INFO) << "Merge finish";
+    SNAP_LOG(INFO) << "Snapshot-Merge completed";
 
     return true;
 }
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 26c5f19..7d9d392 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -279,7 +279,6 @@
             sqe = io_uring_get_sqe(ring_.get());
             if (!sqe) {
                 SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during read-ahead";
-                snapuserd_->ReadAheadIOFailed();
                 return false;
             }
 
@@ -309,7 +308,6 @@
             if (ret != pending_ios_to_submit) {
                 SNAP_PLOG(ERROR) << "io_uring_submit failed for read-ahead: "
                                  << " io submit: " << ret << " expected: " << pending_ios_to_submit;
-                snapuserd_->ReadAheadIOFailed();
                 return false;
             }
 
@@ -321,14 +319,12 @@
             // Read XOR data from COW file in parallel when I/O's are in-flight
             if (xor_processing_required && !ReadXorData(block_index, xor_op_index, xor_op_vec)) {
                 SNAP_LOG(ERROR) << "ReadXorData failed";
-                snapuserd_->ReadAheadIOFailed();
                 return false;
             }
 
             // Fetch I/O completions
             if (!ReapIoCompletions(pending_ios_to_complete)) {
                 SNAP_LOG(ERROR) << "ReapIoCompletions failed";
-                snapuserd_->ReadAheadIOFailed();
                 return false;
             }
 
@@ -393,26 +389,36 @@
 }
 
 bool ReadAhead::ReapIoCompletions(int pending_ios_to_complete) {
+    bool status = true;
+
     // Reap I/O completions
     while (pending_ios_to_complete) {
         struct io_uring_cqe* cqe;
 
+        // We need to make sure to reap all the I/O's submitted
+        // even if there are any errors observed.
+        //
+        // io_uring_wait_cqe can potentially return -EAGAIN or -EINTR;
+        // these error codes are not truly I/O errors; we can retry them
+        // by re-populating the SQE entries and submitting the I/O
+        // request back. However, we don't do that now; instead we
+        // will fallback to synchronous I/O.
         int ret = io_uring_wait_cqe(ring_.get(), &cqe);
         if (ret) {
             SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << ret;
-            return false;
+            status = false;
         }
 
         if (cqe->res < 0) {
             SNAP_LOG(ERROR) << "Read-ahead - io_uring_Wait_cqe failed with res: " << cqe->res;
-            return false;
+            status = false;
         }
 
         io_uring_cqe_seen(ring_.get(), cqe);
         pending_ios_to_complete -= 1;
     }
 
-    return true;
+    return status;
 }
 
 void ReadAhead::ProcessXorData(size_t& block_xor_index, size_t& xor_index,
@@ -610,18 +616,38 @@
         return ReconstructDataFromCow();
     }
 
+    bool retry = false;
+    bool ra_status;
+
+    // Start Async read-ahead
     if (read_ahead_async_) {
-        if (!ReadAheadAsyncIO()) {
-            SNAP_LOG(ERROR) << "ReadAheadAsyncIO failed - io_uring processing failure.";
-            return false;
+        ra_status = ReadAheadAsyncIO();
+        if (!ra_status) {
+            SNAP_LOG(ERROR) << "ReadAheadAsyncIO failed - Falling back synchronous I/O";
+            FinalizeIouring();
+            RAResetIter(total_blocks_merged_);
+            retry = true;
+            read_ahead_async_ = false;
         }
-    } else {
-        if (!ReadAheadSyncIO()) {
+    }
+
+    // Check if we need to fallback and retry the merge
+    //
+    // If the device doesn't support async operations, we
+    // will directly enter here (aka devices with 4.x kernels)
+
+    const bool ra_sync_required = (retry || !read_ahead_async_);
+
+    if (ra_sync_required) {
+        ra_status = ReadAheadSyncIO();
+        if (!ra_status) {
             SNAP_LOG(ERROR) << "ReadAheadSyncIO failed";
             return false;
         }
     }
 
+    SNAP_LOG(DEBUG) << "Read-ahead: total_ra_blocks_merged: " << total_ra_blocks_completed_;
+
     // Wait for the merge to finish for the previous RA window. We shouldn't
     // be touching the scratch space until merge is complete of previous RA
     // window. If there is a crash during this time frame, merge should resume
@@ -646,6 +672,7 @@
         offset += BLOCK_SZ;
     }
 
+    total_ra_blocks_completed_ += total_blocks_merged_;
     snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged_);
 
     // Flush the data only if we have a overlapping blocks in the region
@@ -763,6 +790,13 @@
     cowop_iter_->Next();
 }
 
+void ReadAhead::RAResetIter(uint64_t num_blocks) {
+    while (num_blocks && !cowop_iter_->RDone()) {
+        cowop_iter_->Prev();
+        num_blocks -= 1;
+    }
+}
+
 const CowOperation* ReadAhead::GetRAOpIter() {
     const CowOperation* cow_op = &cowop_iter_->Get();
     return cow_op;
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 eb64704..82b2b25 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -293,7 +293,6 @@
 void UserSnapshotServer::RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler) {
     LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
-    handler->snapuserd()->SetSocketPresent(is_socket_present_);
     if (!handler->snapuserd()->Start()) {
         LOG(ERROR) << " Failed to launch all worker threads";
     }
@@ -471,6 +470,9 @@
         return nullptr;
     }
 
+    snapuserd->SetSocketPresent(is_socket_present_);
+    snapuserd->SetIouringEnabled(io_uring_enabled_);
+
     if (!snapuserd->InitializeWorkers()) {
         LOG(ERROR) << "Failed to initialize workers";
         return nullptr;
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 c645456..34e7941 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -84,6 +84,8 @@
     std::vector<struct pollfd> watched_fds_;
     bool is_socket_present_ = false;
     int num_partitions_merge_complete_ = 0;
+    bool is_server_running_ = false;
+    bool io_uring_enabled_ = false;
 
     std::mutex lock_;
 
@@ -136,6 +138,10 @@
 
     void SetTerminating() { terminating_ = true; }
     void ReceivedSocketSignal() { received_socket_signal_ = true; }
+    void SetServerRunning() { is_server_running_ = true; }
+    bool IsServerRunning() { return is_server_running_; }
+    void SetIouringEnabled() { io_uring_enabled_ = true; }
+    bool IsIouringEnabled() { return io_uring_enabled_; }
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
index 6dec1e2..d4e1d7c 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -531,6 +531,13 @@
     {
         std::unique_lock<std::mutex> lock(blk_state->m_lock);
 
+        // We may have fallback from Async-merge to synchronous merging
+        // on the existing block. There is no need to reset as the
+        // merge is already in progress.
+        if (blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS) {
+            return;
+        }
+
         CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
 
         // First set the state to RA_READY so that in-flight I/O will drain
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 89d6145..f01bec9 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -192,6 +192,10 @@
     return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
 }
 
+bool IsIouringEnabled() {
+    return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
+}
+
 std::string GetOtherPartitionName(const std::string& name) {
     auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
     CHECK(suffix == "_a" || suffix == "_b");
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index a032b68..0ef3234 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -135,6 +135,8 @@
 
 bool IsDmSnapshotTestingEnabled();
 
+bool IsIouringEnabled();
+
 // Swap the suffix of a partition name.
 std::string GetOtherPartitionName(const std::string& name);
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/vts_ota_config_test.cpp b/fs_mgr/libsnapshot/vts_ota_config_test.cpp
new file mode 100644
index 0000000..afc2d81
--- /dev/null
+++ b/fs_mgr/libsnapshot/vts_ota_config_test.cpp
@@ -0,0 +1,23 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <android-base/properties.h>
+#include <gtest/gtest.h>
+
+TEST(VAB, Enabled) {
+    ASSERT_TRUE(android::base::GetBoolProperty("ro.virtual_ab.enabled", false));
+    ASSERT_TRUE(android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false));
+}
diff --git a/fs_mgr/tests/Android.bp b/fs_mgr/tests/Android.bp
index 262beaa..d82d566 100644
--- a/fs_mgr/tests/Android.bp
+++ b/fs_mgr/tests/Android.bp
@@ -96,7 +96,7 @@
         "device-tests",
     ],
     test_options: {
-        min_shipping_api_level: 33,
+        min_shipping_api_level: 29,
     },
     require_root: true,
     auto_gen_config: true,
@@ -109,9 +109,9 @@
     ],
     shared_libs: [
         "libbase",
-        "libfs_mgr",
     ],
     static_libs: [
+        "libfs_mgr",
         "libfstab",
         "libgmock",
         "libgtest",
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index eccb902..1dbee75 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -1104,3 +1104,76 @@
     EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->readahead_size_kb);
 }
+
+TEST(fs_mgr, TransformFstabForDsu) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    std::string fstab_contents = R"fs(
+system /system      erofs   ro  wait,logical,first_stage_mount
+system /system      ext4    ro  wait,logical,first_stage_mount
+vendor /vendor      ext4    ro  wait,logical,first_stage_mount
+data   /data        f2fs    noatime     wait
+)fs";
+
+    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+    Fstab fstab;
+    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
+    TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
+    ASSERT_EQ(4U, fstab.size());
+
+    auto entry = fstab.begin();
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/vendor", entry->mount_point);
+    EXPECT_EQ("vendor", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/data", entry->mount_point);
+    EXPECT_EQ("userdata_gsi", entry->blk_device);
+    entry++;
+}
+
+TEST(fs_mgr, TransformFstabForDsu_synthesisExt4Entry) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    std::string fstab_contents = R"fs(
+system /system      erofs   ro  wait,logical,first_stage_mount
+vendor /vendor      ext4    ro  wait,logical,first_stage_mount
+data   /data        f2fs    noatime     wait
+)fs";
+
+    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+    Fstab fstab;
+    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
+    TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
+    ASSERT_EQ(4U, fstab.size());
+
+    auto entry = fstab.begin();
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("erofs", entry->fs_type);
+    entry++;
+
+    EXPECT_EQ("/system", entry->mount_point);
+    EXPECT_EQ("system_gsi", entry->blk_device);
+    EXPECT_EQ("ext4", entry->fs_type);
+    entry++;
+
+    EXPECT_EQ("/vendor", entry->mount_point);
+    EXPECT_EQ("vendor", entry->blk_device);
+    entry++;
+
+    EXPECT_EQ("/data", entry->mount_point);
+    EXPECT_EQ("userdata_gsi", entry->blk_device);
+    entry++;
+}
diff --git a/fs_mgr/tests/vts_fs_test.cpp b/fs_mgr/tests/vts_fs_test.cpp
index 77900df..b5fac53 100644
--- a/fs_mgr/tests/vts_fs_test.cpp
+++ b/fs_mgr/tests/vts_fs_test.cpp
@@ -29,7 +29,7 @@
 
 TEST(fs, ErofsSupported) {
     // S and higher for this test.
-    if (GetVsrLevel() < 31) {
+    if (GetVsrLevel() < __ANDROID_API_S__) {
         GTEST_SKIP();
     }
 
@@ -85,10 +85,10 @@
             continue;
         }
 
-        if (vsr_level <= 32) {
+        if (vsr_level < __ANDROID_API_T__) {
             continue;
         }
-        if (vsr_level == 33 && parent_bdev != super_bdev) {
+        if (vsr_level == __ANDROID_API_T__ && parent_bdev != super_bdev) {
             // Only check for dynamic partitions at this VSR level.
             continue;
         }
@@ -100,3 +100,12 @@
         }
     }
 }
+
+TEST(fs, NoDtFstab) {
+    if (GetVsrLevel() < __ANDROID_API_Q__) {
+        GTEST_SKIP();
+    }
+
+    android::fs_mgr::Fstab fstab;
+    EXPECT_FALSE(android::fs_mgr::ReadFstabFromDt(&fstab, false));
+}
diff --git a/healthd/Android.bp b/healthd/Android.bp
index 24777c8..f180006 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -22,9 +22,15 @@
         "libutils",
         "libbase",
 
-        // Need latest HealthInfo definition from headers of this shared
-        // library. Clients don't need to link to this.
+        // Need HealthInfo definition from headers of these shared
+        // libraries. Clients don't need to link to these.
         "android.hardware.health@2.1",
+        "android.hardware.health-V1-ndk",
+    ],
+    whole_static_libs: [
+        // Need to translate HIDL to AIDL to support legacy APIs in
+        // BatteryMonitor.
+        "android.hardware.health-translate-ndk",
     ],
     header_libs: ["libhealthd_headers"],
     export_header_lib_headers: ["libhealthd_headers"],
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 5890f9a..a7571a2 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -31,10 +31,12 @@
 #include <memory>
 #include <optional>
 
+#include <aidl/android/hardware/health/HealthInfo.h>
 #include <android-base/file.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 #include <android/hardware/health/2.1/types.h>
+#include <android/hardware/health/translate-ndk.h>
 #include <batteryservice/BatteryService.h>
 #include <cutils/klog.h>
 #include <cutils/properties.h>
@@ -52,10 +54,54 @@
 using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
 using HealthInfo_2_0 = android::hardware::health::V2_0::HealthInfo;
 using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
-using android::hardware::health::V1_0::BatteryHealth;
-using android::hardware::health::V1_0::BatteryStatus;
-using android::hardware::health::V2_1::BatteryCapacityLevel;
-using android::hardware::health::V2_1::Constants;
+using aidl::android::hardware::health::BatteryCapacityLevel;
+using aidl::android::hardware::health::BatteryHealth;
+using aidl::android::hardware::health::BatteryStatus;
+using aidl::android::hardware::health::HealthInfo;
+
+namespace {
+
+// Translate from AIDL back to HIDL definition for getHealthInfo_*_* calls.
+// Skips storageInfo and diskStats.
+void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in,
+                     ::android::hardware::health::V1_0::HealthInfo* out) {
+    out->chargerAcOnline = in.chargerAcOnline;
+    out->chargerUsbOnline = in.chargerUsbOnline;
+    out->chargerWirelessOnline = in.chargerWirelessOnline;
+    out->maxChargingCurrent = in.maxChargingCurrentMicroamps;
+    out->maxChargingVoltage = in.maxChargingVoltageMicrovolts;
+    out->batteryStatus =
+            static_cast<::android::hardware::health::V1_0::BatteryStatus>(in.batteryStatus);
+    out->batteryHealth =
+            static_cast<::android::hardware::health::V1_0::BatteryHealth>(in.batteryHealth);
+    out->batteryPresent = in.batteryPresent;
+    out->batteryLevel = in.batteryLevel;
+    out->batteryVoltage = in.batteryVoltageMillivolts;
+    out->batteryTemperature = in.batteryTemperatureTenthsCelsius;
+    out->batteryCurrent = in.batteryCurrentMicroamps;
+    out->batteryCycleCount = in.batteryCycleCount;
+    out->batteryFullCharge = in.batteryFullChargeUah;
+    out->batteryChargeCounter = in.batteryChargeCounterUah;
+    out->batteryTechnology = in.batteryTechnology;
+}
+
+void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in,
+                     ::android::hardware::health::V2_0::HealthInfo* out) {
+    translateToHidl(in, &out->legacy);
+    out->batteryCurrentAverage = in.batteryCurrentAverageMicroamps;
+    // Skip storageInfo and diskStats
+}
+
+void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in,
+                     ::android::hardware::health::V2_1::HealthInfo* out) {
+    translateToHidl(in, &out->legacy);
+    out->batteryCapacityLevel = static_cast<android::hardware::health::V2_1::BatteryCapacityLevel>(
+            in.batteryCapacityLevel);
+    out->batteryChargeTimeToFullNowSeconds = in.batteryChargeTimeToFullNowSeconds;
+    out->batteryFullChargeDesignCapacityUah = in.batteryFullChargeDesignCapacityUah;
+}
+
+}  // namespace
 
 namespace android {
 
@@ -74,17 +120,14 @@
     return std::nullopt;
 }
 
-static void initHealthInfo(HealthInfo_2_1* health_info_2_1) {
-    *health_info_2_1 = HealthInfo_2_1{};
-
-    // HIDL enum values are zero initialized, so they need to be initialized
-    // properly.
-    health_info_2_1->batteryCapacityLevel = BatteryCapacityLevel::UNSUPPORTED;
-    health_info_2_1->batteryChargeTimeToFullNowSeconds =
-            (int64_t)Constants::BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED;
-    auto* props = &health_info_2_1->legacy.legacy;
-    props->batteryStatus = BatteryStatus::UNKNOWN;
-    props->batteryHealth = BatteryHealth::UNKNOWN;
+static void initHealthInfo(HealthInfo* health_info) {
+    *health_info = {
+            .batteryCapacityLevel = BatteryCapacityLevel::UNSUPPORTED,
+            .batteryChargeTimeToFullNowSeconds =
+                    (int64_t)HealthInfo::BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED,
+            .batteryStatus = BatteryStatus::UNKNOWN,
+            .batteryHealth = BatteryHealth::UNKNOWN,
+    };
 }
 
 BatteryMonitor::BatteryMonitor()
@@ -92,22 +135,31 @@
       mBatteryDevicePresent(false),
       mBatteryFixedCapacity(0),
       mBatteryFixedTemperature(0),
-      mChargerDockOnline(false),
-      mHealthInfo(std::make_unique<HealthInfo_2_1>()) {
+      mHealthInfo(std::make_unique<HealthInfo>()) {
     initHealthInfo(mHealthInfo.get());
 }
 
 BatteryMonitor::~BatteryMonitor() {}
 
-const HealthInfo_1_0& BatteryMonitor::getHealthInfo_1_0() const {
-    return getHealthInfo_2_0().legacy;
+HealthInfo_1_0 BatteryMonitor::getHealthInfo_1_0() const {
+    HealthInfo_1_0 health_info_1_0;
+    translateToHidl(*mHealthInfo, &health_info_1_0);
+    return health_info_1_0;
 }
 
-const HealthInfo_2_0& BatteryMonitor::getHealthInfo_2_0() const {
-    return getHealthInfo_2_1().legacy;
+HealthInfo_2_0 BatteryMonitor::getHealthInfo_2_0() const {
+    HealthInfo_2_0 health_info_2_0;
+    translateToHidl(*mHealthInfo, &health_info_2_0);
+    return health_info_2_0;
 }
 
-const HealthInfo_2_1& BatteryMonitor::getHealthInfo_2_1() const {
+HealthInfo_2_1 BatteryMonitor::getHealthInfo_2_1() const {
+    HealthInfo_2_1 health_info_2_1;
+    translateToHidl(*mHealthInfo, &health_info_2_1);
+    return health_info_2_1;
+}
+
+const HealthInfo& BatteryMonitor::getHealthInfo() const {
     return *mHealthInfo;
 }
 
@@ -175,46 +227,48 @@
     return *ret;
 }
 
-int BatteryMonitor::readFromFile(const String8& path, std::string* buf) {
+static int readFromFile(const String8& path, std::string* buf) {
+    buf->clear();
     if (android::base::ReadFileToString(path.c_str(), buf)) {
         *buf = android::base::Trim(*buf);
     }
     return buf->length();
 }
 
-BatteryMonitor::PowerSupplyType BatteryMonitor::readPowerSupplyType(const String8& path) {
+static BatteryMonitor::PowerSupplyType readPowerSupplyType(const String8& path) {
     static SysfsStringEnumMap<int> supplyTypeMap[] = {
-            {"Unknown", ANDROID_POWER_SUPPLY_TYPE_UNKNOWN},
-            {"Battery", ANDROID_POWER_SUPPLY_TYPE_BATTERY},
-            {"UPS", ANDROID_POWER_SUPPLY_TYPE_AC},
-            {"Mains", ANDROID_POWER_SUPPLY_TYPE_AC},
-            {"USB", ANDROID_POWER_SUPPLY_TYPE_USB},
-            {"USB_DCP", ANDROID_POWER_SUPPLY_TYPE_AC},
-            {"USB_HVDCP", ANDROID_POWER_SUPPLY_TYPE_AC},
-            {"USB_CDP", ANDROID_POWER_SUPPLY_TYPE_AC},
-            {"USB_ACA", ANDROID_POWER_SUPPLY_TYPE_AC},
-            {"USB_C", ANDROID_POWER_SUPPLY_TYPE_AC},
-            {"USB_PD", ANDROID_POWER_SUPPLY_TYPE_AC},
-            {"USB_PD_DRP", ANDROID_POWER_SUPPLY_TYPE_USB},
-            {"Wireless", ANDROID_POWER_SUPPLY_TYPE_WIRELESS},
-            {"Dock", ANDROID_POWER_SUPPLY_TYPE_DOCK},
+            {"Unknown", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN},
+            {"Battery", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_BATTERY},
+            {"UPS", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"Mains", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_USB},
+            {"USB_DCP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_HVDCP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_CDP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_ACA", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_C", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_PD", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_PD_DRP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_USB},
+            {"Wireless", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_WIRELESS},
+            {"Dock", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_DOCK},
             {NULL, 0},
     };
     std::string buf;
 
-    if (readFromFile(path, &buf) <= 0)
-        return ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
+    if (readFromFile(path, &buf) <= 0) {
+        return BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
+    }
 
     auto ret = mapSysfsString(buf.c_str(), supplyTypeMap);
     if (!ret) {
         KLOG_WARNING(LOG_TAG, "Unknown power supply type '%s'\n", buf.c_str());
-        *ret = ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
+        *ret = BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
     }
 
     return static_cast<BatteryMonitor::PowerSupplyType>(*ret);
 }
 
-bool BatteryMonitor::getBooleanField(const String8& path) {
+static bool getBooleanField(const String8& path) {
     std::string buf;
     bool value = false;
 
@@ -225,7 +279,7 @@
     return value;
 }
 
-int BatteryMonitor::getIntField(const String8& path) {
+static int getIntField(const String8& path) {
     std::string buf;
     int value = 0;
 
@@ -235,7 +289,7 @@
     return value;
 }
 
-bool BatteryMonitor::isScopedPowerSupply(const char* name) {
+static bool isScopedPowerSupply(const char* name) {
     constexpr char kScopeDevice[] = "Device";
 
     String8 path;
@@ -247,32 +301,31 @@
 void BatteryMonitor::updateValues(void) {
     initHealthInfo(mHealthInfo.get());
 
-    HealthInfo_1_0& props = mHealthInfo->legacy.legacy;
-
     if (!mHealthdConfig->batteryPresentPath.isEmpty())
-        props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
+        mHealthInfo->batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
     else
-        props.batteryPresent = mBatteryDevicePresent;
+        mHealthInfo->batteryPresent = mBatteryDevicePresent;
 
-    props.batteryLevel = mBatteryFixedCapacity ?
-        mBatteryFixedCapacity :
-        getIntField(mHealthdConfig->batteryCapacityPath);
-    props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
+    mHealthInfo->batteryLevel = mBatteryFixedCapacity
+                                        ? mBatteryFixedCapacity
+                                        : getIntField(mHealthdConfig->batteryCapacityPath);
+    mHealthInfo->batteryVoltageMillivolts = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
 
     if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
-        props.batteryCurrent = getIntField(mHealthdConfig->batteryCurrentNowPath);
+        mHealthInfo->batteryCurrentMicroamps = getIntField(mHealthdConfig->batteryCurrentNowPath);
 
     if (!mHealthdConfig->batteryFullChargePath.isEmpty())
-        props.batteryFullCharge = getIntField(mHealthdConfig->batteryFullChargePath);
+        mHealthInfo->batteryFullChargeUah = getIntField(mHealthdConfig->batteryFullChargePath);
 
     if (!mHealthdConfig->batteryCycleCountPath.isEmpty())
-        props.batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath);
+        mHealthInfo->batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath);
 
     if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
-        props.batteryChargeCounter = getIntField(mHealthdConfig->batteryChargeCounterPath);
+        mHealthInfo->batteryChargeCounterUah =
+                getIntField(mHealthdConfig->batteryChargeCounterPath);
 
     if (!mHealthdConfig->batteryCurrentAvgPath.isEmpty())
-        mHealthInfo->legacy.batteryCurrentAverage =
+        mHealthInfo->batteryCurrentAverageMicroamps =
                 getIntField(mHealthdConfig->batteryCurrentAvgPath);
 
     if (!mHealthdConfig->batteryChargeTimeToFullNowPath.isEmpty())
@@ -283,9 +336,9 @@
         mHealthInfo->batteryFullChargeDesignCapacityUah =
                 getIntField(mHealthdConfig->batteryFullChargeDesignCapacityUahPath);
 
-    props.batteryTemperature = mBatteryFixedTemperature ?
-        mBatteryFixedTemperature :
-        getIntField(mHealthdConfig->batteryTemperaturePath);
+    mHealthInfo->batteryTemperatureTenthsCelsius =
+            mBatteryFixedTemperature ? mBatteryFixedTemperature
+                                     : getIntField(mHealthdConfig->batteryTemperaturePath);
 
     std::string buf;
 
@@ -293,13 +346,13 @@
         mHealthInfo->batteryCapacityLevel = getBatteryCapacityLevel(buf.c_str());
 
     if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
-        props.batteryStatus = getBatteryStatus(buf.c_str());
+        mHealthInfo->batteryStatus = getBatteryStatus(buf.c_str());
 
     if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
-        props.batteryHealth = getBatteryHealth(buf.c_str());
+        mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str());
 
     if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0)
-        props.batteryTechnology = String8(buf.c_str());
+        mHealthInfo->batteryTechnology = String8(buf.c_str());
 
     double MaxPower = 0;
 
@@ -313,29 +366,26 @@
                               mChargerNames[i].string());
             switch(readPowerSupplyType(path)) {
             case ANDROID_POWER_SUPPLY_TYPE_AC:
-                props.chargerAcOnline = true;
+                mHealthInfo->chargerAcOnline = true;
                 break;
             case ANDROID_POWER_SUPPLY_TYPE_USB:
-                props.chargerUsbOnline = true;
+                mHealthInfo->chargerUsbOnline = true;
                 break;
             case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
-                props.chargerWirelessOnline = true;
+                mHealthInfo->chargerWirelessOnline = true;
                 break;
             case ANDROID_POWER_SUPPLY_TYPE_DOCK:
-                mChargerDockOnline = true;
+                mHealthInfo->chargerDockOnline = true;
                 break;
             default:
                 path.clear();
                 path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH,
                                   mChargerNames[i].string());
-                if (access(path.string(), R_OK) == 0) {
-                    mChargerDockOnline = true;
-                    KLOG_INFO(LOG_TAG, "%s: online\n",
-                              mChargerNames[i].string());
-                } else {
+                if (access(path.string(), R_OK) == 0)
+                    mHealthInfo->chargerDockOnline = true;
+                else
                     KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
                                  mChargerNames[i].string());
-                }
             }
             path.clear();
             path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH,
@@ -354,38 +404,34 @@
             double power = ((double)ChargingCurrent / MILLION) *
                            ((double)ChargingVoltage / MILLION);
             if (MaxPower < power) {
-                props.maxChargingCurrent = ChargingCurrent;
-                props.maxChargingVoltage = ChargingVoltage;
+                mHealthInfo->maxChargingCurrentMicroamps = ChargingCurrent;
+                mHealthInfo->maxChargingVoltageMicrovolts = ChargingVoltage;
                 MaxPower = power;
             }
         }
     }
 }
 
-void BatteryMonitor::logValues(void) {
-    logValues(*mHealthInfo, *mHealthdConfig);
-}
-
-void BatteryMonitor::logValues(const android::hardware::health::V2_1::HealthInfo& health_info,
-                               const struct healthd_config& healthd_config) {
+static void doLogValues(const HealthInfo& props, const struct healthd_config& healthd_config) {
     char dmesgline[256];
     size_t len;
-    const HealthInfo_1_0& props = health_info.legacy.legacy;
     if (props.batteryPresent) {
         snprintf(dmesgline, sizeof(dmesgline), "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
-                 props.batteryLevel, props.batteryVoltage, props.batteryTemperature < 0 ? "-" : "",
-                 abs(props.batteryTemperature / 10), abs(props.batteryTemperature % 10),
-                 props.batteryHealth, props.batteryStatus);
+                 props.batteryLevel, props.batteryVoltageMillivolts,
+                 props.batteryTemperatureTenthsCelsius < 0 ? "-" : "",
+                 abs(props.batteryTemperatureTenthsCelsius / 10),
+                 abs(props.batteryTemperatureTenthsCelsius % 10), props.batteryHealth,
+                 props.batteryStatus);
 
         len = strlen(dmesgline);
         if (!healthd_config.batteryCurrentNowPath.isEmpty()) {
             len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " c=%d",
-                            props.batteryCurrent);
+                            props.batteryCurrentMicroamps);
         }
 
         if (!healthd_config.batteryFullChargePath.isEmpty()) {
             len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " fc=%d",
-                            props.batteryFullCharge);
+                            props.batteryFullChargeUah);
         }
 
         if (!healthd_config.batteryCycleCountPath.isEmpty()) {
@@ -396,17 +442,28 @@
         len = snprintf(dmesgline, sizeof(dmesgline), "battery none");
     }
 
-    snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s",
+    snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s%s",
              props.chargerAcOnline ? "a" : "", props.chargerUsbOnline ? "u" : "",
-             props.chargerWirelessOnline ? "w" : "");
+             props.chargerWirelessOnline ? "w" : "", props.chargerDockOnline ? "d" : "");
 
     KLOG_WARNING(LOG_TAG, "%s\n", dmesgline);
 }
 
+void BatteryMonitor::logValues(const HealthInfo_2_1& health_info,
+                               const struct healthd_config& healthd_config) {
+    HealthInfo aidl_health_info;
+    (void)android::h2a::translate(health_info, &aidl_health_info);
+    doLogValues(aidl_health_info, healthd_config);
+}
+
+void BatteryMonitor::logValues(void) {
+    doLogValues(*mHealthInfo, *mHealthdConfig);
+}
+
 bool BatteryMonitor::isChargerOnline() {
-    const HealthInfo_1_0& props = mHealthInfo->legacy.legacy;
+    const HealthInfo& props = *mHealthInfo;
     return props.chargerAcOnline | props.chargerUsbOnline | props.chargerWirelessOnline |
-           mChargerDockOnline;
+           props.chargerDockOnline;
 }
 
 int BatteryMonitor::getChargeStatus() {
@@ -489,19 +546,19 @@
 void BatteryMonitor::dumpState(int fd) {
     int v;
     char vs[128];
-    const HealthInfo_1_0& props = mHealthInfo->legacy.legacy;
+    const HealthInfo& props = *mHealthInfo;
 
     snprintf(vs, sizeof(vs),
              "ac: %d usb: %d wireless: %d dock: %d current_max: %d voltage_max: %d\n",
              props.chargerAcOnline, props.chargerUsbOnline, props.chargerWirelessOnline,
-             mChargerDockOnline, props.maxChargingCurrent, props.maxChargingVoltage);
+             props.chargerDockOnline, props.maxChargingCurrentMicroamps,
+             props.maxChargingVoltageMicrovolts);
     write(fd, vs, strlen(vs));
     snprintf(vs, sizeof(vs), "status: %d health: %d present: %d\n",
              props.batteryStatus, props.batteryHealth, props.batteryPresent);
     write(fd, vs, strlen(vs));
-    snprintf(vs, sizeof(vs), "level: %d voltage: %d temp: %d\n",
-             props.batteryLevel, props.batteryVoltage,
-             props.batteryTemperature);
+    snprintf(vs, sizeof(vs), "level: %d voltage: %d temp: %d\n", props.batteryLevel,
+             props.batteryVoltageMillivolts, props.batteryTemperatureTenthsCelsius);
     write(fd, vs, strlen(vs));
 
     if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
@@ -523,7 +580,7 @@
     }
 
     if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
-        snprintf(vs, sizeof(vs), "current now: %d\n", props.batteryCurrent);
+        snprintf(vs, sizeof(vs), "current now: %d\n", props.batteryCurrentMicroamps);
         write(fd, vs, strlen(vs));
     }
 
@@ -533,7 +590,7 @@
     }
 
     if (!mHealthdConfig->batteryFullChargePath.isEmpty()) {
-        snprintf(vs, sizeof(vs), "Full charge: %d\n", props.batteryFullCharge);
+        snprintf(vs, sizeof(vs), "Full charge: %d\n", props.batteryFullChargeUah);
         write(fd, vs, strlen(vs));
     }
 }
@@ -551,13 +608,13 @@
 
         while ((entry = readdir(dir.get()))) {
             const char* name = entry->d_name;
-            std::vector<String8>::iterator itIgnoreName;
 
             if (!strcmp(name, ".") || !strcmp(name, ".."))
                 continue;
 
-            itIgnoreName = find(hc->ignorePowerSupplyNames.begin(),
-                                hc->ignorePowerSupplyNames.end(), String8(name));
+            std::vector<String8>::iterator itIgnoreName =
+                    find(hc->ignorePowerSupplyNames.begin(), hc->ignorePowerSupplyNames.end(),
+                         String8(name));
             if (itIgnoreName != hc->ignorePowerSupplyNames.end())
                 continue;
 
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index 89c2e25..8cbf5ea 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -25,6 +25,10 @@
 
 #include <healthd/healthd.h>
 
+namespace aidl::android::hardware::health {
+class HealthInfo;
+}  // namespace aidl::android::hardware::health
+
 namespace android {
 namespace hardware {
 namespace health {
@@ -59,9 +63,10 @@
     status_t getProperty(int id, struct BatteryProperty *val);
     void dumpState(int fd);
 
-    const android::hardware::health::V1_0::HealthInfo& getHealthInfo_1_0() const;
-    const android::hardware::health::V2_0::HealthInfo& getHealthInfo_2_0() const;
-    const android::hardware::health::V2_1::HealthInfo& getHealthInfo_2_1() const;
+    android::hardware::health::V1_0::HealthInfo getHealthInfo_1_0() const;
+    android::hardware::health::V2_0::HealthInfo getHealthInfo_2_0() const;
+    android::hardware::health::V2_1::HealthInfo getHealthInfo_2_1() const;
+    const aidl::android::hardware::health::HealthInfo& getHealthInfo() const;
 
     void updateValues(void);
     void logValues(void);
@@ -76,15 +81,7 @@
     bool mBatteryDevicePresent;
     int mBatteryFixedCapacity;
     int mBatteryFixedTemperature;
-    // TODO(b/214126090): to migrate to AIDL HealthInfo
-    bool mChargerDockOnline;
-    std::unique_ptr<android::hardware::health::V2_1::HealthInfo> mHealthInfo;
-
-    int readFromFile(const String8& path, std::string* buf);
-    PowerSupplyType readPowerSupplyType(const String8& path);
-    bool getBooleanField(const String8& path);
-    int getIntField(const String8& path);
-    bool isScopedPowerSupply(const char* name);
+    std::unique_ptr<aidl::android::hardware::health::HealthInfo> mHealthInfo;
 };
 
 }; // namespace android
diff --git a/init/README.md b/init/README.md
index c102b1f..c82dbfb 100644
--- a/init/README.md
+++ b/init/README.md
@@ -1034,7 +1034,7 @@
 /first_stage_ramdisk to remove the recovery components from the environment, then proceed the same
 as 2). Note that the decision to boot normally into Android instead of booting
 into recovery mode is made if androidboot.force_normal_boot=1 is present in the
-kernel commandline.
+kernel commandline, or in bootconfig with Android S and later.
 
 Once first stage init finishes it execs /system/bin/init with the "selinux_setup" argument. This
 phase is where SELinux is optionally compiled and loaded onto the system. selinux.cpp contains more
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
index 2401da3..3c7107a 100644
--- a/init/README.ueventd.md
+++ b/init/README.ueventd.md
@@ -147,6 +147,12 @@
 Ueventd will additionally log all messages sent to stderr from the external program to the serial
 console after the external program has exited.
 
+If the kernel command-line argument `firmware_class.path` is set, this path
+will be used first by the kernel to search for the firmware files. If found,
+ueventd will not be called at all. See the
+[kernel documentation](https://www.kernel.org/doc/html/v5.10/driver-api/firmware/fw_search_path.html)
+for more details on this feature.
+
 ## Coldboot
 --------
 Ueventd must create devices in `/dev` for all devices that have already sent their uevents before
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 7a34849..9f7c215 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -1035,8 +1035,8 @@
     constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
 
     // Api level properties of the board. The order of the properties must be kept.
-    std::vector<std::string> BOARD_API_LEVEL_PROPS = {
-            "ro.board.api_level", "ro.board.first_api_level", "ro.vendor.build.version.sdk"};
+    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"};
diff --git a/init/service.cpp b/init/service.cpp
index f7318cb..8a9cc0a 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -397,6 +397,117 @@
     return {};
 }
 
+static void ClosePipe(const std::array<int, 2>* pipe) {
+    for (const auto fd : *pipe) {
+        if (fd >= 0) {
+            close(fd);
+        }
+    }
+}
+
+Result<void> Service::CheckConsole() {
+    if (!(flags_ & SVC_CONSOLE)) {
+        return {};
+    }
+
+    if (proc_attr_.console.empty()) {
+        proc_attr_.console = "/dev/" + GetProperty("ro.boot.console", "console");
+    }
+
+    // Make sure that open call succeeds to ensure a console driver is
+    // properly registered for the device node
+    int console_fd = open(proc_attr_.console.c_str(), O_RDWR | O_CLOEXEC);
+    if (console_fd < 0) {
+        flags_ |= SVC_DISABLED;
+        return ErrnoError() << "Couldn't open console '" << proc_attr_.console << "'";
+    }
+    close(console_fd);
+    return {};
+}
+
+// Configures the memory cgroup properties for the service.
+void Service::ConfigureMemcg() {
+    if (swappiness_ != -1) {
+        if (!setProcessGroupSwappiness(proc_attr_.uid, pid_, swappiness_)) {
+            PLOG(ERROR) << "setProcessGroupSwappiness failed";
+        }
+    }
+
+    if (soft_limit_in_bytes_ != -1) {
+        if (!setProcessGroupSoftLimit(proc_attr_.uid, pid_, soft_limit_in_bytes_)) {
+            PLOG(ERROR) << "setProcessGroupSoftLimit failed";
+        }
+    }
+
+    size_t computed_limit_in_bytes = limit_in_bytes_;
+    if (limit_percent_ != -1) {
+        long page_size = sysconf(_SC_PAGESIZE);
+        long num_pages = sysconf(_SC_PHYS_PAGES);
+        if (page_size > 0 && num_pages > 0) {
+            size_t max_mem = SIZE_MAX;
+            if (size_t(num_pages) < SIZE_MAX / size_t(page_size)) {
+                max_mem = size_t(num_pages) * size_t(page_size);
+            }
+            computed_limit_in_bytes =
+                    std::min(computed_limit_in_bytes, max_mem / 100 * limit_percent_);
+        }
+    }
+
+    if (!limit_property_.empty()) {
+        // This ends up overwriting computed_limit_in_bytes but only if the
+        // property is defined.
+        computed_limit_in_bytes =
+                android::base::GetUintProperty(limit_property_, computed_limit_in_bytes, SIZE_MAX);
+    }
+
+    if (computed_limit_in_bytes != size_t(-1)) {
+        if (!setProcessGroupLimit(proc_attr_.uid, pid_, computed_limit_in_bytes)) {
+            PLOG(ERROR) << "setProcessGroupLimit failed";
+        }
+    }
+}
+
+// Enters namespaces, sets environment variables, writes PID files and runs the service executable.
+void Service::RunService(const std::optional<MountNamespace>& override_mount_namespace,
+                         const std::vector<Descriptor>& descriptors,
+                         std::unique_ptr<std::array<int, 2>, decltype(&ClosePipe)> pipefd) {
+    if (auto result = EnterNamespaces(namespaces_, name_, override_mount_namespace); !result.ok()) {
+        LOG(FATAL) << "Service '" << name_ << "' failed to set up namespaces: " << result.error();
+    }
+
+    for (const auto& [key, value] : environment_vars_) {
+        setenv(key.c_str(), value.c_str(), 1);
+    }
+
+    for (const auto& descriptor : descriptors) {
+        descriptor.Publish();
+    }
+
+    if (auto result = WritePidToFiles(&writepid_files_); !result.ok()) {
+        LOG(ERROR) << "failed to write pid to files: " << result.error();
+    }
+
+    // Wait until the cgroups have been created and until the cgroup controllers have been
+    // activated.
+    if (std::byte byte; read((*pipefd)[0], &byte, 1) < 0) {
+        PLOG(ERROR) << "failed to read from notification channel";
+    }
+    pipefd.reset();
+
+    if (task_profiles_.size() > 0 && !SetTaskProfiles(getpid(), task_profiles_)) {
+        LOG(ERROR) << "failed to set task profiles";
+    }
+
+    // As requested, set our gid, supplemental gids, uid, context, and
+    // priority. Aborts on failure.
+    SetProcessAttributesAndCaps();
+
+    if (!ExpandArgsAndExecv(args_, sigstop_)) {
+        PLOG(ERROR) << "cannot execv('" << args_[0]
+                    << "'). See the 'Debugging init' section of init's README.md for tips";
+    }
+}
+
 Result<void> Service::Start() {
     auto reboot_on_failure = make_scope_guard([this] {
         if (on_failure_reboot_target_) {
@@ -428,20 +539,14 @@
         return {};
     }
 
-    bool needs_console = (flags_ & SVC_CONSOLE);
-    if (needs_console) {
-        if (proc_attr_.console.empty()) {
-            proc_attr_.console = "/dev/" + GetProperty("ro.boot.console", "console");
-        }
+    std::unique_ptr<std::array<int, 2>, decltype(&ClosePipe)> pipefd(new std::array<int, 2>{-1, -1},
+                                                                     ClosePipe);
+    if (pipe(pipefd->data()) < 0) {
+        return ErrnoError() << "pipe()";
+    }
 
-        // Make sure that open call succeeds to ensure a console driver is
-        // properly registered for the device node
-        int console_fd = open(proc_attr_.console.c_str(), O_RDWR | O_CLOEXEC);
-        if (console_fd < 0) {
-            flags_ |= SVC_DISABLED;
-            return ErrnoError() << "Couldn't open console '" << proc_attr_.console << "'";
-        }
-        close(console_fd);
+    if (Result<void> result = CheckConsole(); !result.ok()) {
+        return result;
     }
 
     struct stat sb;
@@ -513,38 +618,7 @@
 
     if (pid == 0) {
         umask(077);
-
-        if (auto result = EnterNamespaces(namespaces_, name_, override_mount_namespace);
-            !result.ok()) {
-            LOG(FATAL) << "Service '" << name_
-                       << "' failed to set up namespaces: " << result.error();
-        }
-
-        for (const auto& [key, value] : environment_vars_) {
-            setenv(key.c_str(), value.c_str(), 1);
-        }
-
-        for (const auto& descriptor : descriptors) {
-            descriptor.Publish();
-        }
-
-        if (auto result = WritePidToFiles(&writepid_files_); !result.ok()) {
-            LOG(ERROR) << "failed to write pid to files: " << result.error();
-        }
-
-        if (task_profiles_.size() > 0 && !SetTaskProfiles(getpid(), task_profiles_)) {
-            LOG(ERROR) << "failed to set task profiles";
-        }
-
-        // As requested, set our gid, supplemental gids, uid, context, and
-        // priority. Aborts on failure.
-        SetProcessAttributesAndCaps();
-
-        if (!ExpandArgsAndExecv(args_, sigstop_)) {
-            PLOG(ERROR) << "cannot execv('" << args_[0]
-                        << "'). See the 'Debugging init' section of init's README.md for tips";
-        }
-
+        RunService(override_mount_namespace, descriptors, std::move(pipefd));
         _exit(127);
     }
 
@@ -574,50 +648,17 @@
         PLOG(ERROR) << "createProcessGroup(" << proc_attr_.uid << ", " << pid_
                     << ") failed for service '" << name_ << "'";
     } else if (use_memcg) {
-        if (swappiness_ != -1) {
-            if (!setProcessGroupSwappiness(proc_attr_.uid, pid_, swappiness_)) {
-                PLOG(ERROR) << "setProcessGroupSwappiness failed";
-            }
-        }
-
-        if (soft_limit_in_bytes_ != -1) {
-            if (!setProcessGroupSoftLimit(proc_attr_.uid, pid_, soft_limit_in_bytes_)) {
-                PLOG(ERROR) << "setProcessGroupSoftLimit failed";
-            }
-        }
-
-        size_t computed_limit_in_bytes = limit_in_bytes_;
-        if (limit_percent_ != -1) {
-            long page_size = sysconf(_SC_PAGESIZE);
-            long num_pages = sysconf(_SC_PHYS_PAGES);
-            if (page_size > 0 && num_pages > 0) {
-                size_t max_mem = SIZE_MAX;
-                if (size_t(num_pages) < SIZE_MAX / size_t(page_size)) {
-                    max_mem = size_t(num_pages) * size_t(page_size);
-                }
-                computed_limit_in_bytes =
-                        std::min(computed_limit_in_bytes, max_mem / 100 * limit_percent_);
-            }
-        }
-
-        if (!limit_property_.empty()) {
-            // This ends up overwriting computed_limit_in_bytes but only if the
-            // property is defined.
-            computed_limit_in_bytes = android::base::GetUintProperty(
-                    limit_property_, computed_limit_in_bytes, SIZE_MAX);
-        }
-
-        if (computed_limit_in_bytes != size_t(-1)) {
-            if (!setProcessGroupLimit(proc_attr_.uid, pid_, computed_limit_in_bytes)) {
-                PLOG(ERROR) << "setProcessGroupLimit failed";
-            }
-        }
+        ConfigureMemcg();
     }
 
     if (oom_score_adjust_ != DEFAULT_OOM_SCORE_ADJUST) {
         LmkdRegister(name_, proc_attr_.uid, pid_, oom_score_adjust_);
     }
 
+    if (write((*pipefd)[1], "", 1) < 0) {
+        return ErrnoError() << "sending notification failed";
+    }
+
     NotifyStateChange("running");
     reboot_on_failure.Disable();
     return {};
diff --git a/init/service.h b/init/service.h
index 3289f54..3f12aa2 100644
--- a/init/service.h
+++ b/init/service.h
@@ -145,6 +145,12 @@
     void KillProcessGroup(int signal, bool report_oneshot = false);
     void SetProcessAttributesAndCaps();
     void ResetFlagsForStart();
+    Result<void> CheckConsole();
+    void ConfigureMemcg();
+    void RunService(
+            const std::optional<MountNamespace>& override_mount_namespace,
+            const std::vector<Descriptor>& descriptors,
+            std::unique_ptr<std::array<int, 2>, void (*)(const std::array<int, 2>* pipe)> pipefd);
 
     static unsigned long next_start_order_;
     static bool is_exec_service_running_;
diff --git a/libcutils/include/cutils/multiuser.h b/libcutils/include/cutils/multiuser.h
index 9a2305c..229ee3a 100644
--- a/libcutils/include/cutils/multiuser.h
+++ b/libcutils/include/cutils/multiuser.h
@@ -30,6 +30,7 @@
 extern appid_t multiuser_get_app_id(uid_t uid);
 
 extern uid_t multiuser_get_uid(userid_t user_id, appid_t app_id);
+extern uid_t multiuser_get_sdk_sandbox_uid(userid_t user_id, appid_t app_id);
 
 extern gid_t multiuser_get_cache_gid(userid_t user_id, appid_t app_id);
 extern gid_t multiuser_get_ext_gid(userid_t user_id, appid_t app_id);
diff --git a/libcutils/include/cutils/qtaguid.h b/libcutils/include/cutils/qtaguid.h
index 3f5e41f..a5ffb03 100644
--- a/libcutils/include/cutils/qtaguid.h
+++ b/libcutils/include/cutils/qtaguid.h
@@ -34,24 +34,6 @@
 extern int qtaguid_untagSocket(int sockfd);
 
 /*
- * For the given uid, switch counter sets.
- * The kernel only keeps a limited number of sets.
- * 2 for now.
- */
-extern int qtaguid_setCounterSet(int counterSetNum, uid_t uid);
-
-/*
- * Delete all tag info that relates to the given tag an uid.
- * If the tag is 0, then ALL info about the uid is freed.
- * The delete data also affects active tagged sockets, which are
- * then untagged.
- * The calling process can only operate on its own tags.
- * Unless it is part of the happy AID_NET_BW_ACCT group.
- * In which case it can clobber everything.
- */
-extern int qtaguid_deleteTagData(int tag, uid_t uid);
-
-/*
  * Enable/disable qtaguid functionnality at a lower level.
  * When pacified, the kernel will accept commands but do nothing.
  */
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index 97e93f8..17a0070 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -75,7 +75,8 @@
 #define ATRACE_TAG_AIDL             (1<<24)
 #define ATRACE_TAG_NNAPI            (1<<25)
 #define ATRACE_TAG_RRO              (1<<26)
-#define ATRACE_TAG_LAST             ATRACE_TAG_RRO
+#define ATRACE_TAG_THERMAL          (1 << 27)
+#define ATRACE_TAG_LAST             ATRACE_TAG_THERMAL
 
 // Reserved for initialization.
 #define ATRACE_TAG_NOT_READY        (1ULL<<63)
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 8e6b81c..b765be5 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -133,6 +133,9 @@
 #define AID_THREAD_NETWORK 1084   /* Thread Network subsystem */
 #define AID_DICED 1085            /* Android's DICE daemon */
 #define AID_DMESGD 1086           /* dmesg parsing daemon for kernel report collection */
+#define AID_JC_WEAVER 1087        /* Javacard Weaver HAL - to manage omapi ARA rules */
+#define AID_JC_STRONGBOX 1088     /* Javacard Strongbox HAL - to manage omapi ARA rules */
+#define AID_JC_IDENTITYCRED 1089  /* Javacard Identity Cred HAL - to manage omapi ARA rules */
 /* Changes to this file must be made in AOSP, *not* in internal branches. */
 
 #define AID_SHELL 2000 /* adb and debug shell user */
@@ -210,6 +213,10 @@
  */
 #define AID_OVERFLOWUID 65534 /* unmapped user in the user namespace */
 
+/* use the ranges below to determine whether a process is sdk sandbox */
+#define AID_SDK_SANDBOX_PROCESS_START 20000 /* start of uids allocated to sdk sandbox processes */
+#define AID_SDK_SANDBOX_PROCESS_END 29999   /* end of uids allocated to sdk sandbox processes */
+
 /* use the ranges below to determine whether a process is isolated */
 #define AID_ISOLATED_START 90000 /* start of uids for fully isolated sandboxed processes */
 #define AID_ISOLATED_END 99999   /* end of uids for fully isolated sandboxed processes */
diff --git a/libcutils/multiuser.cpp b/libcutils/multiuser.cpp
index 0fd3d0c..979cbf4 100644
--- a/libcutils/multiuser.cpp
+++ b/libcutils/multiuser.cpp
@@ -29,6 +29,15 @@
     return (user_id * AID_USER_OFFSET) + (app_id % AID_USER_OFFSET);
 }
 
+uid_t multiuser_get_sdk_sandbox_uid(userid_t user_id, appid_t app_id) {
+    int sdk_sandbox_offset = AID_SDK_SANDBOX_PROCESS_START - AID_APP_START;
+    if (app_id >= AID_APP_START && app_id <= AID_APP_END) {
+        return (user_id * AID_USER_OFFSET) + (app_id % AID_USER_OFFSET) + sdk_sandbox_offset;
+    } else {
+        return -1;
+    }
+}
+
 gid_t multiuser_get_cache_gid(userid_t user_id, appid_t app_id) {
     if (app_id >= AID_APP_START && app_id <= AID_APP_END) {
         return multiuser_get_uid(user_id, (app_id - AID_APP_START) + AID_CACHE_GID_START);
diff --git a/libcutils/multiuser_test.cpp b/libcutils/multiuser_test.cpp
index 4b0fd13..62dd5e0 100644
--- a/libcutils/multiuser_test.cpp
+++ b/libcutils/multiuser_test.cpp
@@ -18,6 +18,7 @@
 #include <gtest/gtest.h>
 
 static constexpr auto ERR_GID = static_cast<gid_t>(-1);
+static constexpr auto ERR_UID = static_cast<uid_t>(-1);
 
 TEST(MultiuserTest, TestMerge) {
     EXPECT_EQ(0U, multiuser_get_uid(0, 0));
@@ -30,6 +31,22 @@
     EXPECT_EQ(1050000U, multiuser_get_uid(10, 50000));
 }
 
+TEST(MultiuserTest, TestSdkSandboxUid) {
+    EXPECT_EQ(ERR_UID, multiuser_get_sdk_sandbox_uid(0, 0));
+    EXPECT_EQ(ERR_UID, multiuser_get_sdk_sandbox_uid(0, 1000));
+    EXPECT_EQ(20000U, multiuser_get_sdk_sandbox_uid(0, 10000));
+    EXPECT_EQ(25000U, multiuser_get_sdk_sandbox_uid(0, 15000));
+    EXPECT_EQ(29999U, multiuser_get_sdk_sandbox_uid(0, 19999));
+    EXPECT_EQ(ERR_UID, multiuser_get_sdk_sandbox_uid(0, 50000));
+
+    EXPECT_EQ(ERR_UID, multiuser_get_sdk_sandbox_uid(10, 0));
+    EXPECT_EQ(ERR_UID, multiuser_get_sdk_sandbox_uid(10, 1000));
+    EXPECT_EQ(1020000U, multiuser_get_sdk_sandbox_uid(10, 10000));
+    EXPECT_EQ(1025000U, multiuser_get_sdk_sandbox_uid(10, 15000));
+    EXPECT_EQ(ERR_UID, multiuser_get_sdk_sandbox_uid(10, 20000));
+    EXPECT_EQ(ERR_UID, multiuser_get_sdk_sandbox_uid(10, 50000));
+}
+
 TEST(MultiuserTest, TestSplitUser) {
     EXPECT_EQ(0U, multiuser_get_user_id(0));
     EXPECT_EQ(0U, multiuser_get_user_id(1000));
diff --git a/libcutils/qtaguid.cpp b/libcutils/qtaguid.cpp
index 2fe877c..a987b85 100644
--- a/libcutils/qtaguid.cpp
+++ b/libcutils/qtaguid.cpp
@@ -34,8 +34,6 @@
   public:
     int (*netdTagSocket)(int, uint32_t, uid_t);
     int (*netdUntagSocket)(int);
-    int (*netdSetCounterSet)(uint32_t, uid_t);
-    int (*netdDeleteTagData)(uint32_t, uid_t);
 };
 
 int stubTagSocket(int, uint32_t, uid_t) {
@@ -46,16 +44,8 @@
     return -EREMOTEIO;
 }
 
-int stubSetCounterSet(uint32_t, uid_t) {
-    return -EREMOTEIO;
-}
-
-int stubDeleteTagData(uint32_t, uid_t) {
-    return -EREMOTEIO;
-}
-
 netdHandler initHandler(void) {
-    netdHandler handler = {stubTagSocket, stubUntagSocket, stubSetCounterSet, stubDeleteTagData};
+    netdHandler handler = {stubTagSocket, stubUntagSocket};
 
     void* netdClientHandle = dlopen("libnetd_client.so", RTLD_NOW);
     if (!netdClientHandle) {
@@ -73,15 +63,6 @@
         ALOGE("load netdUntagSocket handler failed: %s", dlerror());
     }
 
-    handler.netdSetCounterSet = (int (*)(uint32_t, uid_t))dlsym(netdClientHandle, "setCounterSet");
-    if (!handler.netdSetCounterSet) {
-        ALOGE("load netdSetCounterSet handler failed: %s", dlerror());
-    }
-
-    handler.netdDeleteTagData = (int (*)(uint32_t, uid_t))dlsym(netdClientHandle, "deleteTagData");
-    if (!handler.netdDeleteTagData) {
-        ALOGE("load netdDeleteTagData handler failed: %s", dlerror());
-    }
     return handler;
 }
 
@@ -114,13 +95,3 @@
     ALOGV("Untagging socket %d", sockfd);
     return getHandler().netdUntagSocket(sockfd);
 }
-
-int qtaguid_setCounterSet(int counterSetNum, uid_t uid) {
-    ALOGV("Setting counters to set %d for uid %d", counterSetNum, uid);
-    return getHandler().netdSetCounterSet(counterSetNum, uid);
-}
-
-int qtaguid_deleteTagData(int tag, uid_t uid) {
-    ALOGV("Deleting tag data with tag %u for uid %d", tag, uid);
-    return getHandler().netdDeleteTagData(tag, uid);
-}
diff --git a/libgrallocusage/Android.bp b/libgrallocusage/Android.bp
index f31b5f1..16103d2 100644
--- a/libgrallocusage/Android.bp
+++ b/libgrallocusage/Android.bp
@@ -44,4 +44,9 @@
     shared_libs: ["android.hardware.graphics.allocator@2.0"],
     header_libs: ["libhardware_headers"],
     min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+        "test_com.android.media.swcodec",
+    ],
 }
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 352847a..8c00326 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -231,7 +231,8 @@
             const ACgroupController* controller = ACgroupFile_getController(i);
             if (ACgroupController_getFlags(controller) &
                 CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
-                std::string str = std::string("+") + ACgroupController_getName(controller);
+                std::string str("+");
+                str.append(ACgroupController_getName(controller));
                 if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) {
                     return -errno;
                 }
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index cb2fe0a..76d5e13 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -85,7 +85,7 @@
 
 bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) {
     const TaskProfiles& tp = TaskProfiles::GetInstance();
-    const ProfileAttribute* attr = tp.GetAttribute(attr_name);
+    const IProfileAttribute* attr = tp.GetAttribute(attr_name);
 
     if (attr == nullptr) {
         return false;
@@ -100,7 +100,7 @@
 
 bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
     const TaskProfiles& tp = TaskProfiles::GetInstance();
-    const ProfileAttribute* attr = tp.GetAttribute(attr_name);
+    const IProfileAttribute* attr = tp.GetAttribute(attr_name);
 
     if (attr == nullptr) {
         return false;
@@ -211,7 +211,7 @@
     for (std::string cgroup_root_path : cgroups) {
         std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path.c_str()), closedir);
         if (root == NULL) {
-            PLOG(ERROR) << "Failed to open " << cgroup_root_path;
+            PLOG(ERROR) << __func__ << " failed to open " << cgroup_root_path;
         } else {
             dirent* dir;
             while ((dir = readdir(root.get())) != nullptr) {
@@ -297,7 +297,8 @@
             // This happens when process is already dead
             return 0;
         }
-        PLOG(WARNING) << "Failed to open process cgroup uid " << uid << " pid " << initialPid;
+        PLOG(WARNING) << __func__ << " failed to open process cgroup uid " << uid << " pid "
+                      << initialPid;
         return -1;
     }
 
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 74ba7f6..dc7c368 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -112,6 +112,8 @@
     return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
 }
 
+IProfileAttribute::~IProfileAttribute() = default;
+
 void ProfileAttribute::Reset(const CgroupController& controller, const std::string& file_name) {
     controller_ = controller;
     file_name_ = file_name;
@@ -183,6 +185,12 @@
     return true;
 }
 
+#else
+
+bool SetTimerSlackAction::ExecuteForTask(int) const {
+    return true;
+};
+
 #endif
 
 bool SetAttributeAction::ExecuteForProcess(uid_t, pid_t pid) const {
@@ -342,14 +350,33 @@
     FdCacheHelper::Drop(fd_[cache_type]);
 }
 
-WriteFileAction::WriteFileAction(const std::string& path, const std::string& value,
-                                 bool logfailures)
-    : path_(path), value_(value), logfailures_(logfailures) {
-    FdCacheHelper::Init(path_, fd_);
+WriteFileAction::WriteFileAction(const std::string& task_path, const std::string& proc_path,
+                                 const std::string& value, bool logfailures)
+    : task_path_(task_path), proc_path_(proc_path), value_(value), logfailures_(logfailures) {
+    FdCacheHelper::Init(task_path_, fd_[ProfileAction::RCT_TASK]);
+    if (!proc_path_.empty()) FdCacheHelper::Init(proc_path_, fd_[ProfileAction::RCT_PROCESS]);
 }
 
-bool WriteFileAction::WriteValueToFile(const std::string& value, const std::string& path,
-                                       bool logfailures) {
+bool WriteFileAction::WriteValueToFile(const std::string& value_, ResourceCacheType cache_type,
+                                       int uid, int pid, bool logfailures) const {
+    std::string value(value_);
+
+    value = StringReplace(value, "<uid>", std::to_string(uid), true);
+    value = StringReplace(value, "<pid>", std::to_string(pid), true);
+
+    CacheUseResult result = UseCachedFd(cache_type, value);
+
+    if (result != ProfileAction::UNUSED) {
+        return result == ProfileAction::SUCCESS;
+    }
+
+    std::string path;
+    if (cache_type == ProfileAction::RCT_TASK || proc_path_.empty()) {
+        path = task_path_;
+    } else {
+        path = proc_path_;
+    }
+
     // Use WriteStringToFd instead of WriteStringToFile because the latter will open file with
     // O_TRUNC which causes kernfs_mutex contention
     unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_WRONLY | O_CLOEXEC)));
@@ -370,21 +397,27 @@
 ProfileAction::CacheUseResult WriteFileAction::UseCachedFd(ResourceCacheType cache_type,
                                                            const std::string& value) const {
     std::lock_guard<std::mutex> lock(fd_mutex_);
-    if (FdCacheHelper::IsCached(fd_)) {
+    if (FdCacheHelper::IsCached(fd_[cache_type])) {
         // fd is cached, reuse it
-        if (!WriteStringToFd(value, fd_)) {
-            if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << path_;
-            return ProfileAction::FAIL;
+        bool ret = WriteStringToFd(value, fd_[cache_type]);
+
+        if (!ret && logfailures_) {
+            if (cache_type == ProfileAction::RCT_TASK || proc_path_.empty()) {
+                PLOG(ERROR) << "Failed to write '" << value << "' to " << task_path_;
+            } else {
+                PLOG(ERROR) << "Failed to write '" << value << "' to " << proc_path_;
+            }
         }
-        return ProfileAction::SUCCESS;
+        return ret ? ProfileAction::SUCCESS : ProfileAction::FAIL;
     }
 
-    if (fd_ == FdCacheHelper::FDS_INACCESSIBLE) {
+    if (fd_[cache_type] == FdCacheHelper::FDS_INACCESSIBLE) {
         // no permissions to access the file, ignore
         return ProfileAction::SUCCESS;
     }
 
-    if (cache_type == ResourceCacheType::RCT_TASK && fd_ == FdCacheHelper::FDS_APP_DEPENDENT) {
+    if (cache_type == ResourceCacheType::RCT_TASK &&
+        fd_[cache_type] == FdCacheHelper::FDS_APP_DEPENDENT) {
         // application-dependent path can't be used with tid
         PLOG(ERROR) << "Application profile can't be applied to a thread";
         return ProfileAction::FAIL;
@@ -393,46 +426,64 @@
 }
 
 bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
-    std::string value(value_);
-
-    value = StringReplace(value, "<uid>", std::to_string(uid), true);
-    value = StringReplace(value, "<pid>", std::to_string(pid), true);
-
-    CacheUseResult result = UseCachedFd(ProfileAction::RCT_PROCESS, value);
-    if (result != ProfileAction::UNUSED) {
-        return result == ProfileAction::SUCCESS;
+    if (!proc_path_.empty()) {
+        return WriteValueToFile(value_, ProfileAction::RCT_PROCESS, uid, pid, logfailures_);
     }
 
-    std::string path(path_);
-    path = StringReplace(path, "<uid>", std::to_string(uid), true);
-    path = StringReplace(path, "<pid>", std::to_string(pid), true);
+    DIR* d;
+    struct dirent* de;
+    char proc_path[255];
+    int t_pid;
 
-    return WriteValueToFile(value, path, logfailures_);
+    sprintf(proc_path, "/proc/%d/task", pid);
+    if (!(d = opendir(proc_path))) {
+        return false;
+    }
+
+    while ((de = readdir(d))) {
+        if (de->d_name[0] == '.') {
+            continue;
+        }
+
+        t_pid = atoi(de->d_name);
+
+        if (!t_pid) {
+            continue;
+        }
+
+        WriteValueToFile(value_, ProfileAction::RCT_TASK, uid, t_pid, logfailures_);
+    }
+
+    closedir(d);
+
+    return true;
 }
 
 bool WriteFileAction::ExecuteForTask(int tid) const {
-    std::string value(value_);
-    int uid = getuid();
+    return WriteValueToFile(value_, ProfileAction::RCT_TASK, getuid(), tid, logfailures_);
+}
 
-    value = StringReplace(value, "<uid>", std::to_string(uid), true);
-    value = StringReplace(value, "<pid>", std::to_string(tid), true);
-
-    CacheUseResult result = UseCachedFd(ProfileAction::RCT_TASK, value);
-    if (result != ProfileAction::UNUSED) {
-        return result == ProfileAction::SUCCESS;
+void WriteFileAction::EnableResourceCaching(ResourceCacheType cache_type) {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (fd_[cache_type] != FdCacheHelper::FDS_NOT_CACHED) {
+        return;
     }
-
-    return WriteValueToFile(value, path_, logfailures_);
+    switch (cache_type) {
+        case (ProfileAction::RCT_TASK):
+            FdCacheHelper::Cache(task_path_, fd_[cache_type]);
+            break;
+        case (ProfileAction::RCT_PROCESS):
+            if (!proc_path_.empty()) FdCacheHelper::Cache(proc_path_, fd_[cache_type]);
+            break;
+        default:
+            LOG(ERROR) << "Invalid cache type is specified!";
+            break;
+    }
 }
 
-void WriteFileAction::EnableResourceCaching(ResourceCacheType) {
+void WriteFileAction::DropResourceCaching(ResourceCacheType cache_type) {
     std::lock_guard<std::mutex> lock(fd_mutex_);
-    FdCacheHelper::Cache(path_, fd_);
-}
-
-void WriteFileAction::DropResourceCaching(ResourceCacheType) {
-    std::lock_guard<std::mutex> lock(fd_mutex_);
-    FdCacheHelper::Drop(fd_);
+    FdCacheHelper::Drop(fd_[cache_type]);
 }
 
 bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
@@ -469,6 +520,7 @@
 bool TaskProfile::ExecuteForProcess(uid_t uid, pid_t pid) const {
     for (const auto& element : elements_) {
         if (!element->ExecuteForProcess(uid, pid)) {
+            LOG(VERBOSE) << "Applying profile action " << element->Name() << " failed";
             return false;
         }
     }
@@ -481,6 +533,7 @@
     }
     for (const auto& element : elements_) {
         if (!element->ExecuteForTask(tid)) {
+            LOG(VERBOSE) << "Applying profile action " << element->Name() << " failed";
             return false;
         }
     }
@@ -592,7 +645,7 @@
 
         std::string profile_name = profile_val["Name"].asString();
         const Json::Value& actions = profile_val["Actions"];
-        auto profile = std::make_shared<TaskProfile>();
+        auto profile = std::make_shared<TaskProfile>(profile_name);
 
         for (Json::Value::ArrayIndex act_idx = 0; act_idx < actions.size(); ++act_idx) {
             const Json::Value& action_val = actions[act_idx];
@@ -649,12 +702,14 @@
                 }
             } else if (action_name == "WriteFile") {
                 std::string attr_filepath = params_val["FilePath"].asString();
+                std::string attr_procfilepath = params_val["ProcFilePath"].asString();
                 std::string attr_value = params_val["Value"].asString();
+                // FilePath and Value are mandatory
                 if (!attr_filepath.empty() && !attr_value.empty()) {
                     std::string attr_logfailures = params_val["LogFailures"].asString();
                     bool logfailures = attr_logfailures.empty() || attr_logfailures == "true";
-                    profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value,
-                                                                   logfailures));
+                    profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_procfilepath,
+                                                                   attr_value, logfailures));
                 } else if (attr_filepath.empty()) {
                     LOG(WARNING) << "WriteFile: invalid parameter: "
                                  << "empty filepath";
@@ -702,7 +757,7 @@
             }
         }
         if (ret) {
-            auto profile = std::make_shared<TaskProfile>();
+            auto profile = std::make_shared<TaskProfile>(aggregateprofile_name);
             profile->Add(std::make_unique<ApplyProfileAction>(profiles));
             profiles_[aggregateprofile_name] = profile;
         }
@@ -720,7 +775,7 @@
     return nullptr;
 }
 
-const ProfileAttribute* TaskProfiles::GetAttribute(const std::string& name) const {
+const IProfileAttribute* TaskProfiles::GetAttribute(const std::string& name) const {
     auto iter = attributes_.find(name);
 
     if (iter != attributes_.end()) {
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 1aaa196..9ee3781 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -26,16 +26,26 @@
 #include <android-base/unique_fd.h>
 #include <cgroup_map.h>
 
-class ProfileAttribute {
+class IProfileAttribute {
+  public:
+    virtual ~IProfileAttribute() = 0;
+    virtual void Reset(const CgroupController& controller, const std::string& file_name) = 0;
+    virtual const CgroupController* controller() const = 0;
+    virtual const std::string& file_name() const = 0;
+    virtual bool GetPathForTask(int tid, std::string* path) const = 0;
+};
+
+class ProfileAttribute : public IProfileAttribute {
   public:
     ProfileAttribute(const CgroupController& controller, const std::string& file_name)
         : controller_(controller), file_name_(file_name) {}
+    ~ProfileAttribute() = default;
 
-    const CgroupController* controller() const { return &controller_; }
-    const std::string& file_name() const { return file_name_; }
-    void Reset(const CgroupController& controller, const std::string& file_name);
+    const CgroupController* controller() const override { return &controller_; }
+    const std::string& file_name() const override { return file_name_; }
+    void Reset(const CgroupController& controller, const std::string& file_name) override;
 
-    bool GetPathForTask(int tid, std::string* path) const;
+    bool GetPathForTask(int tid, std::string* path) const override;
 
   private:
     CgroupController controller_;
@@ -49,6 +59,8 @@
 
     virtual ~ProfileAction() {}
 
+    virtual const char* Name() const = 0;
+
     // Default implementations will fail
     virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
     virtual bool ExecuteForTask(int) const { return false; };
@@ -65,22 +77,21 @@
   public:
     SetClampsAction(int boost, int clamp) noexcept : boost_(boost), clamp_(clamp) {}
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
+    const char* Name() const override { return "SetClamps"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
 
   protected:
     int boost_;
     int clamp_;
 };
 
-// To avoid issues in sdk_mac build
-#if defined(__ANDROID__)
-
 class SetTimerSlackAction : public ProfileAction {
   public:
     SetTimerSlackAction(unsigned long slack) noexcept : slack_(slack) {}
 
-    virtual bool ExecuteForTask(int tid) const;
+    const char* Name() const override { return "SetTimerSlack"; }
+    bool ExecuteForTask(int tid) const override;
 
   private:
     unsigned long slack_;
@@ -88,28 +99,18 @@
     static bool IsTimerSlackSupported(int tid);
 };
 
-#else
-
-class SetTimerSlackAction : public ProfileAction {
-  public:
-    SetTimerSlackAction(unsigned long) noexcept {}
-
-    virtual bool ExecuteForTask(int) const { return true; }
-};
-
-#endif
-
 // Set attribute profile element
 class SetAttributeAction : public ProfileAction {
   public:
-    SetAttributeAction(const ProfileAttribute* attribute, const std::string& value)
+    SetAttributeAction(const IProfileAttribute* attribute, const std::string& value)
         : attribute_(attribute), value_(value) {}
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
+    const char* Name() const override { return "SetAttribute"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
 
   private:
-    const ProfileAttribute* attribute_;
+    const IProfileAttribute* attribute_;
     std::string value_;
 };
 
@@ -118,10 +119,11 @@
   public:
     SetCgroupAction(const CgroupController& c, const std::string& p);
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
-    virtual void EnableResourceCaching(ResourceCacheType cache_type);
-    virtual void DropResourceCaching(ResourceCacheType cache_type);
+    const char* Name() const override { return "SetCgroup"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
+    void EnableResourceCaching(ResourceCacheType cache_type) override;
+    void DropResourceCaching(ResourceCacheType cache_type) override;
 
     const CgroupController* controller() const { return &controller_; }
 
@@ -138,28 +140,31 @@
 // Write to file action
 class WriteFileAction : public ProfileAction {
   public:
-    WriteFileAction(const std::string& path, const std::string& value, bool logfailures);
+    WriteFileAction(const std::string& task_path, const std::string& proc_path,
+                    const std::string& value, bool logfailures);
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
-    virtual void EnableResourceCaching(ResourceCacheType cache_type);
-    virtual void DropResourceCaching(ResourceCacheType cache_type);
+    const char* Name() const override { return "WriteFile"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
+    void EnableResourceCaching(ResourceCacheType cache_type) override;
+    void DropResourceCaching(ResourceCacheType cache_type) override;
 
   private:
-    std::string path_, value_;
+    std::string task_path_, proc_path_, value_;
     bool logfailures_;
-    android::base::unique_fd fd_;
+    android::base::unique_fd fd_[ProfileAction::RCT_COUNT];
     mutable std::mutex fd_mutex_;
 
-    static bool WriteValueToFile(const std::string& value, const std::string& path,
-                                 bool logfailures);
+    bool WriteValueToFile(const std::string& value, ResourceCacheType cache_type, int uid, int pid,
+                          bool logfailures) const;
     CacheUseResult UseCachedFd(ResourceCacheType cache_type, const std::string& value) const;
 };
 
 class TaskProfile {
   public:
-    TaskProfile() : res_cached_(false) {}
+    TaskProfile(const std::string& name) : name_(name), res_cached_(false) {}
 
+    const std::string& Name() const { return name_; }
     void Add(std::unique_ptr<ProfileAction> e) { elements_.push_back(std::move(e)); }
     void MoveTo(TaskProfile* profile);
 
@@ -169,6 +174,7 @@
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
 
   private:
+    const std::string name_;
     bool res_cached_;
     std::vector<std::unique_ptr<ProfileAction>> elements_;
 };
@@ -179,10 +185,11 @@
     ApplyProfileAction(const std::vector<std::shared_ptr<TaskProfile>>& profiles)
         : profiles_(profiles) {}
 
-    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
-    virtual bool ExecuteForTask(int tid) const;
-    virtual void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
-    virtual void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
+    const char* Name() const override { return "ApplyProfileAction"; }
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
+    bool ExecuteForTask(int tid) const override;
+    void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
+    void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
 
   private:
     std::vector<std::shared_ptr<TaskProfile>> profiles_;
@@ -194,7 +201,7 @@
     static TaskProfiles& GetInstance();
 
     TaskProfile* GetProfile(const std::string& name) const;
-    const ProfileAttribute* GetAttribute(const std::string& name) const;
+    const IProfileAttribute* GetAttribute(const std::string& name) const;
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const;
     bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles,
                             bool use_fd_cache);
@@ -202,7 +209,7 @@
 
   private:
     std::map<std::string, std::shared_ptr<TaskProfile>> profiles_;
-    std::map<std::string, std::unique_ptr<ProfileAttribute>> attributes_;
+    std::map<std::string, std::unique_ptr<IProfileAttribute>> attributes_;
 
     TaskProfiles();
 
diff --git a/libutils/Errors_test.cpp b/libutils/Errors_test.cpp
index 873c994..0d13bb0 100644
--- a/libutils/Errors_test.cpp
+++ b/libutils/Errors_test.cpp
@@ -108,3 +108,65 @@
     status_t b = g(false);
     EXPECT_EQ(PERMISSION_DENIED, b);
 }
+
+TEST(errors, conversion_promotion) {
+    constexpr size_t successVal = 10ull;
+    auto f = [&](bool success) -> Result<size_t, StatusT> {
+        OR_RETURN(success_or_fail(success));
+        return successVal;
+    };
+    auto s = f(true);
+    ASSERT_TRUE(s.ok());
+    EXPECT_EQ(s.value(), successVal);
+    auto r = f(false);
+    EXPECT_TRUE(!r.ok());
+    EXPECT_EQ(PERMISSION_DENIED, r.error().code());
+}
+
+TEST(errors, conversion_promotion_bool) {
+    constexpr size_t successVal = true;
+    auto f = [&](bool success) -> Result<bool, StatusT> {
+        OR_RETURN(success_or_fail(success));
+        return successVal;
+    };
+    auto s = f(true);
+    ASSERT_TRUE(s.ok());
+    EXPECT_EQ(s.value(), successVal);
+    auto r = f(false);
+    EXPECT_TRUE(!r.ok());
+    EXPECT_EQ(PERMISSION_DENIED, r.error().code());
+}
+
+TEST(errors, conversion_promotion_char) {
+    constexpr char successVal = 'a';
+    auto f = [&](bool success) -> Result<unsigned char, StatusT> {
+        OR_RETURN(success_or_fail(success));
+        return successVal;
+    };
+    auto s = f(true);
+    ASSERT_TRUE(s.ok());
+    EXPECT_EQ(s.value(), successVal);
+    auto r = f(false);
+    EXPECT_TRUE(!r.ok());
+    EXPECT_EQ(PERMISSION_DENIED, r.error().code());
+}
+
+struct IntContainer {
+  // Implicit conversion from int is desired
+  IntContainer(int val) : val_(val) {}
+  int val_;
+};
+
+TEST(errors, conversion_construct) {
+    constexpr int successVal = 10;
+    auto f = [&](bool success) -> Result<IntContainer, StatusT> {
+        OR_RETURN(success_or_fail(success));
+        return successVal;
+    };
+    auto s = f(true);
+    ASSERT_TRUE(s.ok());
+    EXPECT_EQ(s.value().val_, successVal);
+    auto r = f(false);
+    EXPECT_TRUE(!r.ok());
+    EXPECT_EQ(PERMISSION_DENIED, r.error().code());
+}
diff --git a/libutils/include/utils/ErrorsMacros.h b/libutils/include/utils/ErrorsMacros.h
index 048c538..fdc46e6 100644
--- a/libutils/include/utils/ErrorsMacros.h
+++ b/libutils/include/utils/ErrorsMacros.h
@@ -25,6 +25,7 @@
 // [1] build/soong/cc/config/global.go#commonGlobalIncludes
 #include <android-base/errors.h>
 #include <android-base/result.h>
+#include <log/log_main.h>
 
 #include <assert.h>
 
@@ -44,13 +45,58 @@
     status_t val_;
 };
 
+
 namespace base {
+// TODO(b/221235365) StatusT fulfill ResultError contract and cleanup.
+
+// Unlike typical ResultError types, the underlying code should be a status_t
+// instead of a StatusT. We also special-case message generation.
+template<>
+struct ResultError<StatusT, false> {
+    ResultError(status_t s) : val_(s) {
+        LOG_FATAL_IF(s == OK, "Result error should not hold success");
+    }
+
+    template <typename T>
+    operator expected<T, ResultError<StatusT, false>>() const {
+        return unexpected(*this);
+    }
+
+    std::string message() const { return statusToString(val_); }
+    status_t code() const { return val_; }
+
+ private:
+    const status_t val_;
+};
+
+template<>
+struct ResultError<StatusT, true> {
+    template <typename T>
+    ResultError(T&& message, status_t s) : val_(s), message_(std::forward<T>(message)) {
+        LOG_FATAL_IF(s == OK, "Result error should not hold success");
+    }
+
+    ResultError(status_t s) : val_(s) {}
+
+    template <typename T>
+    operator expected<T, ResultError<StatusT, true>>() const {
+        return unexpected(*this);
+    }
+
+    status_t code() const { return val_; }
+
+    std::string message() const { return statusToString(val_) + message_; }
+ private:
+    const status_t val_;
+    std::string message_;
+};
 
 // Specialization of android::base::OkOrFail<V> for V = status_t. This is used to use the OR_RETURN
 // and OR_FATAL macros with statements that yields a value of status_t. See android-base/errors.h
 // for the detailed contract.
 template <>
 struct OkOrFail<status_t> {
+    static_assert(std::is_same_v<status_t, int>);
     // Tests if status_t is a success value of not.
     static bool IsOk(const status_t& s) { return s == OK; }
 
@@ -71,16 +117,70 @@
 
     // Or converts into Result<T, StatusT>. This is used when OR_RETURN is used in a function whose
     // return type is Result<T, StatusT>.
-    template <typename T, typename = std::enable_if_t<!std::is_same_v<T, status_t>>>
+
+    template <typename T>
     operator Result<T, StatusT>() && {
-        return Error<StatusT>(std::move(val_));
+        return ResultError<StatusT>(std::move(val_));
     }
 
-    operator Result<int, StatusT>() && { return Error<StatusT>(std::move(val_)); }
+    template<typename T>
+    operator Result<T, StatusT, false>() && {
+        return ResultError<StatusT, false>(std::move(val_));
+    }
 
+    // Since user defined conversion can be followed by numeric conversion,
+    // we have to specialize all conversions to results holding numeric types to
+    // avoid conversion ambiguities with the constructor of expected.
+#pragma push_macro("SPECIALIZED_CONVERSION")
+#define SPECIALIZED_CONVERSION(type)\
+  operator Result<type, StatusT>() && { return ResultError<StatusT>(std::move(val_)); }\
+  operator Result<type, StatusT, false>() && { return ResultError<StatusT, false>(std::move(val_));}
+
+    SPECIALIZED_CONVERSION(int)
+    SPECIALIZED_CONVERSION(short int)
+    SPECIALIZED_CONVERSION(unsigned short int)
+    SPECIALIZED_CONVERSION(unsigned int)
+    SPECIALIZED_CONVERSION(long int)
+    SPECIALIZED_CONVERSION(unsigned long int)
+    SPECIALIZED_CONVERSION(long long int)
+    SPECIALIZED_CONVERSION(unsigned long long int)
+    SPECIALIZED_CONVERSION(bool)
+    SPECIALIZED_CONVERSION(char)
+    SPECIALIZED_CONVERSION(unsigned char)
+    SPECIALIZED_CONVERSION(signed char)
+    SPECIALIZED_CONVERSION(wchar_t)
+    SPECIALIZED_CONVERSION(char16_t)
+    SPECIALIZED_CONVERSION(char32_t)
+    SPECIALIZED_CONVERSION(float)
+    SPECIALIZED_CONVERSION(double)
+    SPECIALIZED_CONVERSION(long double)
+#undef SPECIALIZED_CONVERSION
+#pragma pop_macro("SPECIALIZED_CONVERSION")
     // String representation of the error value.
     static std::string ErrorMessage(const status_t& s) { return statusToString(s); }
 };
-
 }  // namespace base
+
+
+// These conversions make StatusT directly comparable to status_t in order to
+// avoid calling code whenever comparisons are desired.
+
+template <bool include_message>
+bool operator==(const base::ResultError<StatusT, include_message>& l, const status_t& r) {
+    return (l.code() == r);
+}
+template <bool include_message>
+bool operator==(const status_t& l, const base::ResultError<StatusT, include_message>& r) {
+    return (l == r.code());
+}
+
+template <bool include_message>
+bool operator!=(const base::ResultError<StatusT, include_message>& l, const status_t& r) {
+    return (l.code() != r);
+}
+template <bool include_message>
+bool operator!=(const status_t& l, const base::ResultError<StatusT, include_message>& r) {
+    return (l != r.code());
+}
+
 }  // namespace android
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index d592366..11f414f 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -1,5 +1,7 @@
 LOCAL_PATH:= $(call my-dir)
 
+$(eval $(call declare-1p-copy-files,system/core/rootdir,))
+
 #######################################
 # init-debug.rc
 include $(CLEAR_VARS)
@@ -197,15 +199,9 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
 LOCAL_MODULE_STEM := $(LOCAL_MODULE)
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_SANITIZER_RUNTIME_LIBRARIES := $(addsuffix .so,\
-  $(ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(HWADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(UBSAN_RUNTIME_LIBRARY) \
-  $(TSAN_RUNTIME_LIBRARY) \
-  $(2ND_ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(2ND_HWADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(2ND_UBSAN_RUNTIME_LIBRARY) \
-  $(2ND_TSAN_RUNTIME_LIBRARY))
+$(LOCAL_BUILT_MODULE): PRIVATE_SANITIZER_RUNTIME_LIBRARIES := \
+    $(SANITIZER_STEMS) \
+    $(2ND_SANITIZER_STEMS)
 $(LOCAL_BUILT_MODULE):
 	@echo "Generate: $@"
 	@mkdir -p $(dir $@)
diff --git a/rootdir/init.no_zygote.rc b/rootdir/init.no_zygote.rc
new file mode 100644
index 0000000..8e0afcb
--- /dev/null
+++ b/rootdir/init.no_zygote.rc
@@ -0,0 +1,2 @@
+# This is an empty file that does not specify any zygote services.
+# It exists to support targets that do not include a zygote.
diff --git a/shell_and_utilities/README.md b/shell_and_utilities/README.md
index f339553..26ae4e3 100644
--- a/shell_and_utilities/README.md
+++ b/shell_and_utilities/README.md
@@ -34,185 +34,39 @@
 full list for a release by running `toybox` directly.
 
 
-## Android 2.3 (Gingerbread)
+## Android ("S")
 
-BSD: cat dd newfs\_msdos
-
-toolbox: chmod chown cmp date df dmesg getevent getprop hd id ifconfig
-iftop insmod ioctl ionice kill ln log ls lsmod lsof mkdir mount mv
-nandread netstat notify printenv ps reboot renice rm rmdir rmmod route
-schedtop sendevent setconsole setprop sleep smd start stop sync top
-umount uptime vmstat watchprops wipe
-
-
-## Android 4.0 (IceCreamSandwich)
-
-BSD: cat dd newfs\_msdos
-
-toolbox: chmod chown cmp date df dmesg getevent getprop hd id ifconfig
-iftop insmod ioctl ionice kill ln log ls lsmod lsof mkdir mount mv
-nandread netstat notify printenv ps reboot renice rm rmdir rmmod route
-schedtop sendevent setconsole setprop sleep smd start stop sync top
-touch umount uptime vmstat watchprops wipe
-
-
-## Android 4.1-4.3 (JellyBean)
-
-BSD: cat cp dd du grep newfs\_msdos
-
-toolbox: chcon chmod chown clear cmp date df dmesg getenforce getevent
-getprop getsebool hd id ifconfig iftop insmod ioctl ionice kill ln
-load\_policy log ls lsmod lsof md5 mkdir mount mv nandread netstat notify
-printenv ps reboot renice restorecon rm rmdir rmmod route runcon schedtop
-sendevent setconsole setenforce setprop setsebool sleep smd start stop
-sync top touch umount uptime vmstat watchprops wipe
-
-
-## Android 4.4 (KitKat)
-
-BSD: cat cp dd du grep newfs\_msdos
-
-toolbox: chcon chmod chown clear cmp date df dmesg getenforce getevent
-getprop getsebool hd id ifconfig iftop insmod ioctl ionice kill ln
-load\_policy log ls lsmod lsof md5 mkdir mkswap mount mv nandread netstat
-notify printenv ps readlink renice restorecon rm rmdir rmmod route runcon
-schedtop sendevent setconsole setenforce setprop setsebool sleep smd start
-stop swapoff swapon sync top touch umount uptime vmstat watchprops wipe
-
-
-## Android 5.0 (Lollipop)
-
-BSD: cat chown cp dd du grep kill ln mv printenv rm rmdir sleep sync
-
-toolbox: chcon chmod clear cmp date df dmesg getenforce getevent getprop
-getsebool hd id ifconfig iftop insmod ioctl ionice load\_policy log ls
-lsmod lsof md5 mkdir mknod mkswap mount nandread netstat newfs\_msdos
-nohup notify ps readlink renice restorecon rmmod route runcon schedtop
-sendevent setenforce setprop setsebool smd start stop swapoff swapon
-top touch umount uptime vmstat watchprops wipe
-
-
-## Android 6.0 (Marshmallow)
-
-BSD: dd du grep
-
-toolbox: df getevent iftop ioctl ionice log ls lsof mount nandread
-newfs\_msdos ps prlimit renice sendevent start stop top uptime watchprops
-
-toybox (0.5.2-ish): acpi basename blockdev bzcat cal cat chcon chgrp chmod chown
-chroot cksum clear comm cmp cp cpio cut date dirname dmesg dos2unix echo
-env expand expr fallocate false find free getenforce getprop groups
-head hostname hwclock id ifconfig inotifyd insmod kill load\_policy ln
-logname losetup lsmod lsusb md5sum mkdir mknod mkswap mktemp modinfo
-more mountpoint mv netstat nice nl nohup od paste patch pgrep pidof
-pkill pmap printenv printf pwd readlink realpath restorecon rm rmdir
-rmmod route runcon sed seq setenforce setprop setsid sha1sum sleep sort
-split stat strings swapoff swapon sync sysctl tac tail tar taskset tee
-time timeout touch tr true truncate umount uname uniq unix2dos usleep
-vmstat wc which whoami xargs yes
-
-
-## Android 7.0 (Nougat)
-
-BSD: dd grep
-
-toolbox: getevent iftop ioctl log nandread newfs\_msdos ps prlimit
-sendevent start stop top
-
-toybox (0.7.0-ish): acpi **base64** basename blockdev bzcat cal cat chcon chgrp chmod
-chown chroot cksum clear comm cmp cp cpio cut date **df** dirname dmesg
-dos2unix **du** echo env expand expr fallocate false find **flock** free
-getenforce getprop groups head hostname hwclock id ifconfig inotifyd
-insmod **ionice** **iorenice** kill **killall** load\_policy ln logname losetup **ls**
-lsmod **lsof** lsusb md5sum mkdir mknod mkswap mktemp modinfo more *mount*
-mountpoint mv netstat nice nl nohup od paste patch pgrep pidof pkill
-pmap printenv printf pwd readlink realpath **renice** restorecon rm rmdir
-rmmod route runcon sed seq setenforce setprop setsid sha1sum sleep sort
-split stat strings swapoff swapon sync sysctl tac tail tar taskset tee
-time timeout touch tr true truncate **tty** **ulimit** umount uname uniq unix2dos
-**uptime** usleep vmstat wc which whoami xargs **xxd** yes
-
-
-## Android 8.0 (Oreo)
-
-BSD: dd grep
+BSD: fsck\_msdos newfs\_msdos
 
 bzip2: bzcat bzip2 bunzip2
 
-toolbox: getevent newfs\_msdos
-
-toybox (0.7.3-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
-chroot chrt cksum clear cmp comm cp cpio cut date df **diff** dirname dmesg
-dos2unix du echo env expand expr fallocate false **file** find flock free
-getenforce getprop groups **gunzip** **gzip** head hostname hwclock id ifconfig
-inotifyd insmod ionice iorenice kill killall ln load\_policy **log** logname
-losetup ls lsmod lsof **lspci** lsusb md5sum **microcom** mkdir **mkfifo** mknod
-mkswap mktemp modinfo **modprobe** more mount mountpoint mv netstat nice
-nl nohup od paste patch pgrep pidof pkill pmap printenv printf **ps** pwd
-readlink realpath renice restorecon rm rmdir rmmod runcon sed **sendevent**
-seq setenforce setprop setsid sha1sum **sha224sum** **sha256sum** **sha384sum**
-**sha512sum** sleep sort split start stat stop strings swapoff swapon sync
-sysctl tac tail tar taskset tee time timeout **top** touch tr true truncate
-tty ulimit umount uname uniq unix2dos uptime usleep **uudecode** **uuencode**
-vmstat wc which whoami xargs xxd yes **zcat**
-
-
-## Android 9.0 (Pie)
-
-BSD: dd grep
-
-bzip2: bzcat bzip2 bunzip2
+gavinhoward/bc: bc
 
 one-true-awk: awk
 
-toolbox: getevent getprop newfs\_msdos
+toolbox: getevent getprop setprop start stop
 
-toybox (0.7.6-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
-chroot chrt cksum clear cmp comm cp cpio cut date df diff dirname dmesg
-dos2unix du echo env expand expr fallocate false file find flock **fmt** free
-getenforce groups gunzip gzip head hostname hwclock id ifconfig inotifyd
-insmod ionice iorenice kill killall ln load\_policy log logname losetup ls
-lsmod lsof lspci lsusb md5sum microcom mkdir mkfifo mknod mkswap mktemp
-modinfo modprobe more mount mountpoint mv netstat nice nl nohup od paste
-patch pgrep pidof pkill pmap printenv printf ps pwd readlink realpath
-renice restorecon rm rmdir rmmod runcon sed sendevent seq setenforce
-setprop setsid sha1sum sha224sum sha256sum sha384sum sha512sum sleep
-sort split start stat stop strings **stty** swapoff swapon sync sysctl tac
-tail tar taskset tee time timeout top touch tr true truncate tty ulimit
-umount uname uniq unix2dos uptime usleep uudecode uuencode vmstat wc
-which whoami xargs xxd yes zcat
+toybox (0.8.4-ish): **[** acpi base64 basename **blkdiscard** blkid blockdev cal cat chattr chcon
+chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut date
+dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
+expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
+fsync getconf getenforce getfattr getopt grep groups gunzip gzip head
+help hostname hwclock i2cdetect i2cdump i2cget i2cset iconv id ifconfig
+inotifyd insmod install ionice iorenice iotop kill killall ln load\_policy
+log logname losetup ls lsattr lsmod lsof lspci lsusb makedevs md5sum
+microcom mkdir mkfifo mknod mkswap mktemp modinfo modprobe more mount
+mountpoint mv nbd-client nc netcat netstat nice nl nohup nproc nsenter
+od partprobe paste patch pgrep pidof ping ping6 pivot\_root pkill pmap
+printenv printf prlimit ps pwd pwdx readelf readlink realpath renice
+restorecon rev rfkill rm rmdir rmmod **rtcwake** runcon sed sendevent
+seq setenforce setfattr setsid sha1sum sha224sum sha256sum sha384sum
+sha512sum sleep sort split stat strings stty swapoff swapon sync sysctl
+tac tail tar taskset tee **test** time timeout top touch tr traceroute
+traceroute6 true truncate tty tunctl ulimit umount uname uniq unix2dos
+unlink unshare uptime usleep uudecode uuencode uuidgen vconfig vi
+vmstat watch wc which whoami xargs xxd yes zcat
 
 
-## Android 10 ("Q")
-
-BSD: grep fsck\_msdos newfs\_msdos
-
-bzip2: bzcat bzip2 bunzip2
-
-one-true-awk: awk
-
-toolbox: getevent getprop
-
-toybox (0.8.0-ish): acpi base64 basename **bc** **blkid** blockdev cal cat **chattr** chcon chgrp
-chmod chown chroot chrt cksum clear cmp comm cp cpio cut date dd df
-diff dirname dmesg dos2unix du echo **egrep** env expand expr fallocate
-false **fgrep** file find flock fmt free **freeramdisk** **fsfreeze** **getconf**
-getenforce **getfattr** grep groups gunzip gzip head **help** hostname hwclock
-**i2cdetect** **i2cdump** **i2cget** **i2cset** **iconv** id ifconfig inotifyd insmod
-**install** ionice iorenice **iotop** kill killall ln load\_policy log logname
-losetup ls **lsattr** lsmod lsof lspci lsusb **makedevs** md5sum microcom
-mkdir mkfifo mknod mkswap mktemp modinfo modprobe more mount mountpoint
-mv **nbd-client** **nc** **netcat** netstat nice nl nohup **nproc** **nsenter** od **partprobe**
-paste patch pgrep pidof **ping** **ping6** **pivot\_root** pkill pmap printenv
-printf **prlimit** ps pwd **pwdx** readlink realpath renice restorecon **rev**
-**rfkill** rm rmdir rmmod runcon sed sendevent seq setenforce **setfattr**
-setprop setsid sha1sum sha224sum sha256sum sha384sum sha512sum sleep
-sort split start stat stop strings stty swapoff swapon sync sysctl
-tac tail tar taskset tee time timeout top touch tr **traceroute** **traceroute6**
-true truncate tty **tunctl** ulimit umount uname uniq unix2dos **unlink**
-**unshare** uptime usleep uudecode uuencode **uuidgen** **vconfig** vmstat **watch**
-wc which whoami xargs xxd yes zcat
-
 ## Android 11 ("R")
 
 BSD: fsck\_msdos newfs\_msdos
@@ -245,34 +99,182 @@
 usleep uudecode uuencode uuidgen vconfig **vi** vmstat watch wc which
 whoami xargs xxd yes zcat
 
-## Android ("S")
 
-BSD: fsck\_msdos newfs\_msdos
+## Android 10 ("Q")
+
+BSD: grep fsck\_msdos newfs\_msdos
 
 bzip2: bzcat bzip2 bunzip2
 
-gavinhoward/bc: bc
+one-true-awk: awk
+
+toolbox: getevent getprop
+
+toybox (0.8.0-ish): acpi base64 basename **bc** **blkid** blockdev cal cat **chattr** chcon chgrp
+chmod chown chroot chrt cksum clear cmp comm cp cpio cut date dd df
+diff dirname dmesg dos2unix du echo **egrep** env expand expr fallocate
+false **fgrep** file find flock fmt free **freeramdisk** **fsfreeze** **getconf**
+getenforce **getfattr** grep groups gunzip gzip head **help** hostname hwclock
+**i2cdetect** **i2cdump** **i2cget** **i2cset** **iconv** id ifconfig inotifyd insmod
+**install** ionice iorenice **iotop** kill killall ln load\_policy log logname
+losetup ls **lsattr** lsmod lsof lspci lsusb **makedevs** md5sum microcom
+mkdir mkfifo mknod mkswap mktemp modinfo modprobe more mount mountpoint
+mv **nbd-client** **nc** **netcat** netstat nice nl nohup **nproc** **nsenter** od **partprobe**
+paste patch pgrep pidof **ping** **ping6** **pivot\_root** pkill pmap printenv
+printf **prlimit** ps pwd **pwdx** readlink realpath renice restorecon **rev**
+**rfkill** rm rmdir rmmod runcon sed sendevent seq setenforce **setfattr**
+setprop setsid sha1sum sha224sum sha256sum sha384sum sha512sum sleep
+sort split start stat stop strings stty swapoff swapon sync sysctl
+tac tail tar taskset tee time timeout top touch tr **traceroute** **traceroute6**
+true truncate tty **tunctl** ulimit umount uname uniq unix2dos **unlink**
+**unshare** uptime usleep uudecode uuencode **uuidgen** **vconfig** vmstat **watch**
+wc which whoami xargs xxd yes zcat
+
+
+## Android 9.0 (Pie)
+
+BSD: dd grep
+
+bzip2: bzcat bzip2 bunzip2
 
 one-true-awk: awk
 
-toolbox: getevent getprop setprop start stop
+toolbox: getevent getprop newfs\_msdos
 
-toybox (0.8.4-ish): **[** acpi base64 basename **blkdiscard** blkid blockdev cal cat chattr chcon
-chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut date
-dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
-expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
-fsync getconf getenforce getfattr getopt grep groups gunzip gzip head
-help hostname hwclock i2cdetect i2cdump i2cget i2cset iconv id ifconfig
-inotifyd insmod install ionice iorenice iotop kill killall ln load\_policy
-log logname losetup ls lsattr lsmod lsof lspci lsusb makedevs md5sum
-microcom mkdir mkfifo mknod mkswap mktemp modinfo modprobe more mount
-mountpoint mv nbd-client nc netcat netstat nice nl nohup nproc nsenter
-od partprobe paste patch pgrep pidof ping ping6 pivot\_root pkill pmap
-printenv printf prlimit ps pwd pwdx readelf readlink realpath renice
-restorecon rev rfkill rm rmdir rmmod **rtcwake** runcon sed sendevent
-seq setenforce setfattr setsid sha1sum sha224sum sha256sum sha384sum
-sha512sum sleep sort split stat strings stty swapoff swapon sync sysctl
-tac tail tar taskset tee **test** time timeout top touch tr traceroute
-traceroute6 true truncate tty tunctl ulimit umount uname uniq unix2dos
-unlink unshare uptime usleep uudecode uuencode uuidgen vconfig vi
-vmstat watch wc which whoami xargs xxd yes zcat
+toybox (0.7.6-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
+chroot chrt cksum clear cmp comm cp cpio cut date df diff dirname dmesg
+dos2unix du echo env expand expr fallocate false file find flock **fmt** free
+getenforce groups gunzip gzip head hostname hwclock id ifconfig inotifyd
+insmod ionice iorenice kill killall ln load\_policy log logname losetup ls
+lsmod lsof lspci lsusb md5sum microcom mkdir mkfifo mknod mkswap mktemp
+modinfo modprobe more mount mountpoint mv netstat nice nl nohup od paste
+patch pgrep pidof pkill pmap printenv printf ps pwd readlink realpath
+renice restorecon rm rmdir rmmod runcon sed sendevent seq setenforce
+setprop setsid sha1sum sha224sum sha256sum sha384sum sha512sum sleep
+sort split start stat stop strings **stty** swapoff swapon sync sysctl tac
+tail tar taskset tee time timeout top touch tr true truncate tty ulimit
+umount uname uniq unix2dos uptime usleep uudecode uuencode vmstat wc
+which whoami xargs xxd yes zcat
+
+
+## Android 8.0 (Oreo)
+
+BSD: dd grep
+
+bzip2: bzcat bzip2 bunzip2
+
+toolbox: getevent newfs\_msdos
+
+toybox (0.7.3-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
+chroot chrt cksum clear cmp comm cp cpio cut date df **diff** dirname dmesg
+dos2unix du echo env expand expr fallocate false **file** find flock free
+getenforce getprop groups **gunzip** **gzip** head hostname hwclock id ifconfig
+inotifyd insmod ionice iorenice kill killall ln load\_policy **log** logname
+losetup ls lsmod lsof **lspci** lsusb md5sum **microcom** mkdir **mkfifo** mknod
+mkswap mktemp modinfo **modprobe** more mount mountpoint mv netstat nice
+nl nohup od paste patch pgrep pidof pkill pmap printenv printf **ps** pwd
+readlink realpath renice restorecon rm rmdir rmmod runcon sed **sendevent**
+seq setenforce setprop setsid sha1sum **sha224sum** **sha256sum** **sha384sum**
+**sha512sum** sleep sort split start stat stop strings swapoff swapon sync
+sysctl tac tail tar taskset tee time timeout **top** touch tr true truncate
+tty ulimit umount uname uniq unix2dos uptime usleep **uudecode** **uuencode**
+vmstat wc which whoami xargs xxd yes **zcat**
+
+
+## Android 7.0 (Nougat)
+
+BSD: dd grep
+
+toolbox: getevent iftop ioctl log nandread newfs\_msdos ps prlimit
+sendevent start stop top
+
+toybox (0.7.0-ish): acpi **base64** basename blockdev bzcat cal cat chcon chgrp chmod
+chown chroot cksum clear comm cmp cp cpio cut date **df** dirname dmesg
+dos2unix **du** echo env expand expr fallocate false find **flock** free
+getenforce getprop groups head hostname hwclock id ifconfig inotifyd
+insmod **ionice** **iorenice** kill **killall** load\_policy ln logname losetup **ls**
+lsmod **lsof** lsusb md5sum mkdir mknod mkswap mktemp modinfo more *mount*
+mountpoint mv netstat nice nl nohup od paste patch pgrep pidof pkill
+pmap printenv printf pwd readlink realpath **renice** restorecon rm rmdir
+rmmod route runcon sed seq setenforce setprop setsid sha1sum sleep sort
+split stat strings swapoff swapon sync sysctl tac tail tar taskset tee
+time timeout touch tr true truncate **tty** **ulimit** umount uname uniq unix2dos
+**uptime** usleep vmstat wc which whoami xargs **xxd** yes
+
+
+## Android 6.0 (Marshmallow)
+
+BSD: dd du grep
+
+toolbox: df getevent iftop ioctl ionice log ls lsof mount nandread
+newfs\_msdos ps prlimit renice sendevent start stop top uptime watchprops
+
+toybox (0.5.2-ish): acpi basename blockdev bzcat cal cat chcon chgrp chmod chown
+chroot cksum clear comm cmp cp cpio cut date dirname dmesg dos2unix echo
+env expand expr fallocate false find free getenforce getprop groups
+head hostname hwclock id ifconfig inotifyd insmod kill load\_policy ln
+logname losetup lsmod lsusb md5sum mkdir mknod mkswap mktemp modinfo
+more mountpoint mv netstat nice nl nohup od paste patch pgrep pidof
+pkill pmap printenv printf pwd readlink realpath restorecon rm rmdir
+rmmod route runcon sed seq setenforce setprop setsid sha1sum sleep sort
+split stat strings swapoff swapon sync sysctl tac tail tar taskset tee
+time timeout touch tr true truncate umount uname uniq unix2dos usleep
+vmstat wc which whoami xargs yes
+
+
+## Android 5.0 (Lollipop)
+
+BSD: cat chown cp dd du grep kill ln mv printenv rm rmdir sleep sync
+
+toolbox: chcon chmod clear cmp date df dmesg getenforce getevent getprop
+getsebool hd id ifconfig iftop insmod ioctl ionice load\_policy log ls
+lsmod lsof md5 mkdir mknod mkswap mount nandread netstat newfs\_msdos
+nohup notify ps readlink renice restorecon rmmod route runcon schedtop
+sendevent setenforce setprop setsebool smd start stop swapoff swapon
+top touch umount uptime vmstat watchprops wipe
+
+
+## Android 4.4 (KitKat)
+
+BSD: cat cp dd du grep newfs\_msdos
+
+toolbox: chcon chmod chown clear cmp date df dmesg getenforce getevent
+getprop getsebool hd id ifconfig iftop insmod ioctl ionice kill ln
+load\_policy log ls lsmod lsof md5 mkdir mkswap mount mv nandread netstat
+notify printenv ps readlink renice restorecon rm rmdir rmmod route runcon
+schedtop sendevent setconsole setenforce setprop setsebool sleep smd start
+stop swapoff swapon sync top touch umount uptime vmstat watchprops wipe
+
+
+## Android 4.1-4.3 (JellyBean)
+
+BSD: cat cp dd du grep newfs\_msdos
+
+toolbox: chcon chmod chown clear cmp date df dmesg getenforce getevent
+getprop getsebool hd id ifconfig iftop insmod ioctl ionice kill ln
+load\_policy log ls lsmod lsof md5 mkdir mount mv nandread netstat notify
+printenv ps reboot renice restorecon rm rmdir rmmod route runcon schedtop
+sendevent setconsole setenforce setprop setsebool sleep smd start stop
+sync top touch umount uptime vmstat watchprops wipe
+
+
+## Android 4.0 (IceCreamSandwich)
+
+BSD: cat dd newfs\_msdos
+
+toolbox: chmod chown cmp date df dmesg getevent getprop hd id ifconfig
+iftop insmod ioctl ionice kill ln log ls lsmod lsof mkdir mount mv
+nandread netstat notify printenv ps reboot renice rm rmdir rmmod route
+schedtop sendevent setconsole setprop sleep smd start stop sync top
+touch umount uptime vmstat watchprops wipe
+
+
+## Android 2.3 (Gingerbread)
+
+BSD: cat dd newfs\_msdos
+
+toolbox: chmod chown cmp date df dmesg getevent getprop hd id ifconfig
+iftop insmod ioctl ionice kill ln log ls lsmod lsof mkdir mount mv
+nandread netstat notify printenv ps reboot renice rm rmdir rmmod route
+schedtop sendevent setconsole setprop sleep smd start stop sync top
+umount uptime vmstat watchprops wipe
\ No newline at end of file
diff --git a/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp b/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
index d787f7a..443a670 100644
--- a/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
+++ b/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
@@ -17,9 +17,10 @@
 
 #define LOG_TAG "android.hardware.keymaster@3.0-impl.trusty"
 
-#include <authorization_set.h>
 #include <cutils/log.h>
 #include <keymaster/android_keymaster_messages.h>
+#include <keymaster/authorization_set.h>
+#include <keymaster_tags.h>
 #include <trusty_keymaster/TrustyKeymaster3Device.h>
 #include <trusty_keymaster/ipc/trusty_keymaster_ipc.h>
 
diff --git a/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp b/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
index e68ba82..9c7e781 100644
--- a/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
+++ b/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
@@ -18,9 +18,10 @@
 #define LOG_TAG "android.hardware.keymaster@4.0-impl.trusty"
 
 #include <android/hardware/keymaster/3.0/IKeymasterDevice.h>
-#include <authorization_set.h>
 #include <cutils/log.h>
 #include <keymaster/android_keymaster_messages.h>
+#include <keymaster/authorization_set.h>
+#include <keymaster_tags.h>
 #include <trusty_keymaster/TrustyKeymaster4Device.h>
 #include <trusty_keymaster/ipc/trusty_keymaster_ipc.h>
 
diff --git a/trusty/keymaster/include/trusty_keymaster/TrustyKeyMintDevice.h b/trusty/keymaster/include/trusty_keymaster/TrustyKeyMintDevice.h
index 5fd628f..c8d8932 100644
--- a/trusty/keymaster/include/trusty_keymaster/TrustyKeyMintDevice.h
+++ b/trusty/keymaster/include/trusty_keymaster/TrustyKeyMintDevice.h
@@ -27,6 +27,7 @@
 using ::keymaster::TrustyKeymaster;
 using ::ndk::ScopedAStatus;
 using secureclock::TimeStampToken;
+using ::std::array;
 using ::std::optional;
 using ::std::shared_ptr;
 using ::std::vector;
@@ -77,8 +78,13 @@
                                const optional<TimeStampToken>& timestampToken) override;
     ScopedAStatus earlyBootEnded() override;
 
-    ScopedAStatus convertStorageKeyToEphemeral(const std::vector<uint8_t>& storageKeyBlob,
-                                               std::vector<uint8_t>* ephemeralKeyBlob) override;
+    ScopedAStatus convertStorageKeyToEphemeral(const vector<uint8_t>& storageKeyBlob,
+                                               vector<uint8_t>* ephemeralKeyBlob) override;
+
+    ScopedAStatus getRootOfTrustChallenge(array<uint8_t, 16>* challenge) override;
+    ScopedAStatus getRootOfTrust(const array<uint8_t, 16>& challenge,
+                                 vector<uint8_t>* rootOfTrust) override;
+    ScopedAStatus sendRootOfTrust(const vector<uint8_t>& rootOfTrust) override;
 
   protected:
     std::shared_ptr<TrustyKeymaster> impl_;
diff --git a/trusty/keymaster/keymint/TEST_MAPPING b/trusty/keymaster/keymint/TEST_MAPPING
new file mode 100644
index 0000000..2400ccd
--- /dev/null
+++ b/trusty/keymaster/keymint/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "vts_treble_vintf_framework_test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp b/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
index 68a7912..44780e8 100644
--- a/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
+++ b/trusty/keymaster/keymint/TrustyKeyMintDevice.cpp
@@ -306,7 +306,7 @@
 }
 
 ScopedAStatus TrustyKeyMintDevice::convertStorageKeyToEphemeral(
-        const std::vector<uint8_t>& storageKeyBlob, std::vector<uint8_t>* ephemeralKeyBlob) {
+        const vector<uint8_t>& storageKeyBlob, vector<uint8_t>* ephemeralKeyBlob) {
     keymaster::ExportKeyRequest request(impl_->message_version());
     request.SetKeyMaterial(storageKeyBlob.data(), storageKeyBlob.size());
     request.key_format = KM_KEY_FORMAT_RAW;
@@ -321,4 +321,17 @@
     return ScopedAStatus::ok();
 }
 
+ScopedAStatus TrustyKeyMintDevice::getRootOfTrustChallenge(array<uint8_t, 16>* /* challenge */) {
+    return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
+ScopedAStatus TrustyKeyMintDevice::getRootOfTrust(const array<uint8_t, 16>& /* challenge */,
+                                                  vector<uint8_t>* /* rootOfTrust */) {
+    return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
+ScopedAStatus TrustyKeyMintDevice::sendRootOfTrust(const vector<uint8_t>& /* rootOfTrust */) {
+    return kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED);
+}
+
 }  // namespace aidl::android::hardware::security::keymint::trusty
diff --git a/trusty/keymaster/keymint/TrustyRemotelyProvisionedComponentDevice.cpp b/trusty/keymaster/keymint/TrustyRemotelyProvisionedComponentDevice.cpp
index 5664829..099f189 100644
--- a/trusty/keymaster/keymint/TrustyRemotelyProvisionedComponentDevice.cpp
+++ b/trusty/keymaster/keymint/TrustyRemotelyProvisionedComponentDevice.cpp
@@ -71,9 +71,10 @@
 }  // namespace
 
 ScopedAStatus TrustyRemotelyProvisionedComponentDevice::getHardwareInfo(RpcHardwareInfo* info) {
-    info->versionNumber = 1;
+    info->versionNumber = 2;
     info->rpcAuthorName = "Google";
     info->supportedEekCurve = RpcHardwareInfo::CURVE_25519;
+    info->uniqueId = "Trusty: My password is ******";
     return ScopedAStatus::ok();
 }
 
diff --git a/trusty/keymaster/keymint/android.hardware.security.keymint-service.trusty.xml b/trusty/keymaster/keymint/android.hardware.security.keymint-service.trusty.xml
index 7ca5050..0b995a2 100644
--- a/trusty/keymaster/keymint/android.hardware.security.keymint-service.trusty.xml
+++ b/trusty/keymaster/keymint/android.hardware.security.keymint-service.trusty.xml
@@ -1,6 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.security.keymint</name>
+        <version>2</version>
         <fqname>IKeyMintDevice/default</fqname>
     </hal>
     <hal format="aidl">
@@ -13,6 +14,7 @@
     </hal>
     <hal format="aidl">
         <name>android.hardware.security.keymint</name>
+        <version>2</version>
         <fqname>IRemotelyProvisionedComponent/default</fqname>
     </hal>
 </manifest>
diff --git a/trusty/test/driver/Android.bp b/trusty/test/driver/Android.bp
new file mode 100644
index 0000000..b813a04
--- /dev/null
+++ b/trusty/test/driver/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_test {
+    name: "trusty_driver_test",
+    srcs: [
+        "**/*.py",
+    ],
+    test_suites: ["general-tests"],
+    version: {
+        py3: {
+            embedded_launcher: true,
+            enabled: true,
+        },
+    },
+}
diff --git a/trusty/test/driver/trusty_driver_test.py b/trusty/test/driver/trusty_driver_test.py
new file mode 100644
index 0000000..608fd47
--- /dev/null
+++ b/trusty/test/driver/trusty_driver_test.py
@@ -0,0 +1,81 @@
+#!/usr/bin/python
+#
+# Copyright 2022 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.
+
+"""Test cases for Trusty Linux Driver."""
+
+import os
+import unittest
+
+def ReadFile(file_path):
+    with open(file_path, 'r') as f:
+        # Strip all trailing spaces, newline and null characters.
+        return f.read().rstrip(' \n\x00')
+
+def WriteFile(file_path, s):
+    with open(file_path, 'w') as f:
+        f.write(s)
+
+def IsTrustySupported():
+    return os.path.exists("/dev/trusty-ipc-dev0")
+
+@unittest.skipIf(not IsTrustySupported(), "Device does not support Trusty")
+class TrustyDriverTest(unittest.TestCase):
+    def testIrqDriverBinding(self):
+        WriteFile("/sys/bus/platform/drivers/trusty-irq/unbind", "trusty:irq")
+        WriteFile("/sys/bus/platform/drivers/trusty-irq/bind", "trusty:irq")
+
+    def testLogDriverBinding(self):
+        WriteFile("/sys/bus/platform/drivers/trusty-log/unbind", "trusty:log")
+        WriteFile("/sys/bus/platform/drivers/trusty-log/bind", "trusty:log")
+
+    @unittest.skip("TODO(b/142275662): virtio remove currently hangs")
+    def testVirtioDriverBinding(self):
+        WriteFile("/sys/bus/platform/drivers/trusty-virtio/unbind",
+                  "trusty:virtio")
+        WriteFile("/sys/bus/platform/drivers/trusty-virtio/bind",
+                  "trusty:virtio")
+
+    @unittest.skip("TODO(b/142275662): virtio remove currently hangs")
+    def testTrustyDriverBinding(self):
+        WriteFile("/sys/bus/platform/drivers/trusty/unbind", "trusty")
+        WriteFile("/sys/bus/platform/drivers/trusty/bind", "trusty")
+
+    def testTrustyDriverVersion(self):
+        ver = ReadFile("/sys/bus/platform/devices/trusty/trusty_version")
+        self.assertTrue(ver.startswith("Project:"))
+
+    def testUntaintedLinux(self):
+        tainted = ReadFile("/proc/sys/kernel/tainted")
+        self.assertEqual(tainted, "0")
+
+    # stdcall test with shared memory buffers.
+    # Each test run takes up to 4 arguments:
+    # <obj_size>,<obj_count=1>,<repeat_share=1>,<repeat_access=3>
+    #
+    # Test single 4K shared memory object.
+    # Test 10 8MB objects, shared twice, each accessed twice. (8MB non-
+    # contiguous object is large enough to need several 4KB messages to
+    # describe)
+    # Test sharing 2 8MB objects 100 times without accessing it.
+    # Test 10 4K shared memory objects, shared 10 times, each accessed
+    # 10 times.
+    def testStdCall(self):
+        test = "/sys/devices/platform/trusty/trusty:test/trusty_test_run"
+        args = "0x1000 0x800000,10,2,2 0x800000,2,100,0 0x1000,10,10,10"
+        WriteFile(test, args)
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/trusty/trusty-test.mk b/trusty/trusty-test.mk
index 74106ec..3a43774 100644
--- a/trusty/trusty-test.mk
+++ b/trusty/trusty-test.mk
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 PRODUCT_PACKAGES += \
-	spiproxyd \
-	trusty_keymaster_set_attestation_key \
 	keymaster_soft_attestation_keys.xml \
-
+	spiproxyd \
+	trusty_driver_test \
+	trusty_keymaster_set_attestation_key \