Merge "Add cpu-target-features binary" into main
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 0c5543e..c365cac 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -359,6 +359,7 @@
         "libdebuggerd/test/dump_memory_test.cpp",
         "libdebuggerd/test/elf_fake.cpp",
         "libdebuggerd/test/log_fake.cpp",
+        "libdebuggerd/test/mte_stack_record_test.cpp",
         "libdebuggerd/test/open_files_list_test.cpp",
         "libdebuggerd/test/tombstone_proto_to_text_test.cpp",
     ],
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 8dd2b0d..c9235ee 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -662,6 +662,15 @@
         info.pac_enabled_keys = -1;
       }
 
+#if defined(__aarch64__)
+      struct iovec tls_iov = {
+          &info.tls,
+          sizeof(info.tls),
+      };
+      if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_TLS, reinterpret_cast<void*>(&tls_iov)) == -1) {
+        info.tls = 0;
+      }
+#endif
       if (thread == g_target_thread) {
         // Read the thread's registers along with the rest of the crash info out of the pipe.
         ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info, &recoverable_crash);
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 08619b9..962097e 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -86,7 +86,6 @@
 #define ARCH_SUFFIX ""
 #endif
 
-constexpr size_t kTagGranuleSize = 16;
 constexpr char kWaitForDebuggerKey[] = "debug.debuggerd.wait_for_debugger";
 
 #define TIMEOUT(seconds, expr)                                     \
@@ -742,6 +741,8 @@
 }
 
 #if defined(__aarch64__)
+constexpr size_t kTagGranuleSize = 16;
+
 static uintptr_t CreateTagMapping() {
   // Some of the MTE tag dump tests assert that there is an inaccessible page to the left and right
   // of the PROT_MTE page, so map three pages and set the two guard pages to PROT_NONE.
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index e26746b..42f0aa0 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -36,10 +36,12 @@
 #include <sys/uio.h>
 #include <sys/un.h>
 #include <sys/wait.h>
+#include <time.h>
 #include <unistd.h>
 
 #include <android-base/macros.h>
 #include <android-base/parsebool.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 #include <async_safe/log.h>
@@ -115,6 +117,59 @@
          (permissive_env && ParseBool(permissive_env) == ParseBoolResult::kTrue);
 }
 
+static bool parse_uint_with_error_reporting(const char* s, const char* name, int* v) {
+  if (android::base::ParseInt(s, v) && *v >= 0) {
+    return true;
+  }
+  async_safe_format_log(ANDROID_LOG_ERROR, "libc", "invalid %s: %s", name, s);
+  return false;
+}
+
+// We cannot use base::GetIntProperty, because that internally uses
+// std::string, which allocates.
+static bool property_parse_int(const char* name, int* out) {
+  const prop_info* pi = __system_property_find(name);
+  if (!pi) return false;
+  struct cookie_t {
+    int* out;
+    bool empty;
+  } cookie{out, true};
+  __system_property_read_callback(
+      pi,
+      [](void* raw_cookie, const char* name, const char* value, uint32_t) {
+        // Property is set to empty value, ignoring.
+        if (!*value) return;
+        cookie_t* cookie = reinterpret_cast<cookie_t*>(raw_cookie);
+        if (parse_uint_with_error_reporting(value, name, cookie->out)) cookie->empty = false;
+      },
+      &cookie);
+  return !cookie.empty;
+}
+
+static int permissive_mte_renable_timer() {
+  if (char* env = getenv("MTE_PERMISSIVE_REENABLE_TIME_CPUMS")) {
+    int v;
+    if (parse_uint_with_error_reporting(env, "MTE_PERMISSIVE_REENABLE_TIME_CPUMS", &v)) return v;
+  }
+
+  char process_sysprop_name[512];
+  async_safe_format_buffer(process_sysprop_name, sizeof(process_sysprop_name),
+                           "persist.sys.mte.permissive_reenable_timer.process.%s", getprogname());
+  int v;
+  if (property_parse_int(process_sysprop_name, &v)) return v;
+  if (property_parse_int("persist.sys.mte.permissive_reenable_timer.default", &v)) return v;
+  char process_deviceconf_sysprop_name[512];
+  async_safe_format_buffer(
+      process_deviceconf_sysprop_name, sizeof(process_deviceconf_sysprop_name),
+      "persist.device_config.memory_safety_native.permissive_reenable_timer.process.%s",
+      getprogname());
+  if (property_parse_int(process_deviceconf_sysprop_name, &v)) return v;
+  if (property_parse_int(
+          "persist.device_config.memory_safety_native.permissive_reenable_timer.default", &v))
+    return v;
+  return 0;
+}
+
 static inline void futex_wait(volatile void* ftx, int value) {
   syscall(__NR_futex, ftx, FUTEX_WAIT, value, nullptr, nullptr, 0);
 }
@@ -599,12 +654,40 @@
     if (tagged_addr_ctrl < 0) {
       fatal_errno("failed to PR_GET_TAGGED_ADDR_CTRL");
     }
+    int previous = tagged_addr_ctrl & PR_MTE_TCF_MASK;
     tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_NONE;
     if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) < 0) {
       fatal_errno("failed to PR_SET_TAGGED_ADDR_CTRL");
     }
-    async_safe_format_log(ANDROID_LOG_ERROR, "libc",
-                          "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING.");
+    if (int reenable_timer = permissive_mte_renable_timer()) {
+      async_safe_format_log(ANDROID_LOG_ERROR, "libc",
+                            "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING WITH "
+                            "MTE DISABLED FOR %d MS OF CPU TIME.",
+                            reenable_timer);
+      timer_t timerid{};
+      struct sigevent sev {};
+      sev.sigev_signo = BIONIC_ENABLE_MTE;
+      sev.sigev_notify = SIGEV_THREAD_ID;
+      sev.sigev_value.sival_int = previous;
+      sev.sigev_notify_thread_id = __gettid();
+      // This MUST be CLOCK_THREAD_CPUTIME_ID. If we used CLOCK_MONOTONIC we could get stuck
+      // in an endless loop of re-running the same instruction, calling this signal handler,
+      // and re-enabling MTE before we had a chance to re-run the instruction.
+      if (timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &timerid) == -1) {
+        fatal_errno("timer_create() failed");
+      }
+      struct itimerspec its {};
+      its.it_value.tv_sec = reenable_timer / 1000;
+      its.it_value.tv_nsec = (reenable_timer % 1000) * 1000000;
+
+      if (timer_settime(timerid, 0, &its, nullptr) == -1) {
+        fatal_errno("timer_settime() failed");
+      }
+    } else {
+      async_safe_format_log(
+          ANDROID_LOG_ERROR, "libc",
+          "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING WITH MTE DISABLED.");
+    }
     pthread_mutex_unlock(&crash_mutex);
   }
 
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
index dfdfabd..074b095 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
@@ -73,5 +73,8 @@
 
 void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame);
 void set_human_readable_cause(Cause* cause, uint64_t fault_addr);
-
+#if defined(__aarch64__)
+void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls,
+                        StackHistoryBuffer& shb_ob, bool nounwind = false);
+#endif
 #endif  // _DEBUGGERD_TOMBSTONE_H
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index c799f24..f7fc2a3 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -41,6 +41,9 @@
   siginfo_t* siginfo = nullptr;
 
   std::unique_ptr<unwindstack::Regs> guest_registers;
+#if defined(__aarch64__)
+  uintptr_t tls;  // This is currently used for MTE stack history buffer.
+#endif
 };
 
 // This struct is written into a pipe from inside the crashing process.
diff --git a/debuggerd/libdebuggerd/test/host_signal_fixup.h b/debuggerd/libdebuggerd/test/host_signal_fixup.h
deleted file mode 100644
index 762bae5..0000000
--- a/debuggerd/libdebuggerd/test/host_signal_fixup.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H
-#define _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H
-
-#include <signal.h>
-
-#if !defined(__BIONIC__)
-
-// In order to compile parts of debuggerd for the host, we need to
-// define these values.
-
-#if !defined(NSIGILL)
-#define NSIGILL ILL_BADSTK
-#endif
-
-#if !defined(BUS_MCEERR_AR)
-#define BUS_MCEERR_AR 4
-#endif
-#if !defined(BUS_MCEERR_AO)
-#define BUS_MCEERR_AO 5
-#endif
-#if !defined(NSIGBUS)
-#define NSIGBUS BUS_MCEERR_AO
-#endif
-
-#if !defined(NSIGFPE)
-#define NSIGFPE FPE_FLTSUB
-#endif
-
-#if !defined(NSIGSEGV)
-#define NSIGSEGV SEGV_ACCERR
-#endif
-
-#if !defined(TRAP_BRANCH)
-#define TRAP_BRANCH 3
-#endif
-#if !defined(TRAP_HWBKPT)
-#define TRAP_HWBKPT 4
-#endif
-#if !defined(NSIGTRAP)
-#define NSIGTRAP TRAP_HWBKPT
-#endif
-
-#if !defined(SI_DETHREAD)
-#define SI_DETHREAD (-7)
-#endif
-
-#endif
-
-#endif // _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H
diff --git a/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp b/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp
new file mode 100644
index 0000000..4b788f3
--- /dev/null
+++ b/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#if defined(__aarch64__)
+
+#include <stdint.h>
+#include <sys/mman.h>
+
+#include <optional>
+
+#include "bionic/mte.h"
+#include "bionic/page.h"
+#include "unwindstack/AndroidUnwinder.h"
+#include "unwindstack/Memory.h"
+
+#include <android-base/test_utils.h>
+#include "gtest/gtest.h"
+
+#include "libdebuggerd/tombstone.h"
+
+struct ScopedUnmap {
+  void* ptr;
+  size_t size;
+  ~ScopedUnmap() { munmap(ptr, size); }
+};
+
+class MteStackHistoryTest : public ::testing::TestWithParam<int> {
+  void SetUp() override {
+#if !defined(__aarch64__)
+    GTEST_SKIP();
+#endif
+    SKIP_WITH_HWASAN;
+    unwinder.emplace();
+    unwindstack::ErrorData E;
+    ASSERT_TRUE(unwinder->Initialize(E));
+  }
+
+ protected:
+  // std::optional so we don't construct it for the SKIP cases.
+  std::optional<unwindstack::AndroidLocalUnwinder> unwinder;
+};
+
+TEST(MteStackHistoryUnwindTest, TestOne) {
+#if !defined(__aarch64__)
+  GTEST_SKIP();
+#endif
+  SKIP_WITH_HWASAN;
+  size_t size = stack_mte_ringbuffer_size(0);
+  char* data = static_cast<char*>(stack_mte_ringbuffer_allocate(0, nullptr));
+  ScopedUnmap s{data, size};
+
+  uintptr_t taggedfp = (1ULL << 56) | 1;
+  uintptr_t pc = reinterpret_cast<uintptr_t>(&memcpy);
+  memcpy(data, &pc, sizeof(pc));
+  memcpy(data + 8, &taggedfp, sizeof(taggedfp));
+
+  // The MTE TLS is at TLS - 3, so we allocate 3 placeholders.
+  void* tls[4] = {data + 16};
+
+  unwindstack::AndroidLocalUnwinder unwinder;
+  unwindstack::ErrorData E;
+  unwinder.Initialize(E);
+  StackHistoryBuffer shb;
+  dump_stack_history(&unwinder, reinterpret_cast<uintptr_t>(&tls[3]), shb, /* nounwind= */ false);
+  ASSERT_EQ(shb.entries_size(), 1);
+  const StackHistoryBufferEntry& e = shb.entries(0);
+  EXPECT_EQ(e.addr().pc(), pc);
+  EXPECT_EQ(e.addr().file_name(), "/apex/com.android.runtime/lib64/bionic/libc.so");
+  EXPECT_EQ(e.fp(), 1ULL);
+  EXPECT_EQ(e.tag(), 1ULL);
+}
+
+TEST_P(MteStackHistoryTest, TestEmpty) {
+  int size_cls = GetParam();
+  size_t size = stack_mte_ringbuffer_size(size_cls);
+  void* data = stack_mte_ringbuffer_allocate(size_cls, nullptr);
+  ScopedUnmap s{data, size};
+  // The MTE TLS is at TLS - 3, so we allocate 3 placeholders.
+  void* tls[4] = {data};
+
+  StackHistoryBuffer shb;
+  dump_stack_history(&*unwinder, reinterpret_cast<uintptr_t>(&tls[3]), shb, /* nounwind= */ true);
+  EXPECT_EQ(shb.entries_size(), 0);
+}
+
+TEST_P(MteStackHistoryTest, TestFull) {
+  int size_cls = GetParam();
+  size_t size = stack_mte_ringbuffer_size(size_cls);
+  char* data = static_cast<char*>(stack_mte_ringbuffer_allocate(size_cls, nullptr));
+  ScopedUnmap s{data, size};
+  uintptr_t itr = 1;
+  for (char* d = data; d < &data[size]; d += 16) {
+    uintptr_t taggedfp = ((itr & 15) << 56) | itr;
+    uintptr_t pc = itr;
+    memcpy(d, &pc, sizeof(pc));
+    memcpy(d + 8, &taggedfp, sizeof(taggedfp));
+    ++itr;
+  }
+  // The MTE TLS is at TLS - 3, so we allocate 3 placeholders.
+  // Because the buffer is full, and we point at one past the last inserted element,
+  // due to wrap-around we point at the beginning of the buffer.
+  void* tls[4] = {data};
+
+  StackHistoryBuffer shb;
+  dump_stack_history(&*unwinder, reinterpret_cast<uintptr_t>(&tls[3]), shb, /* nounwind= */ true);
+  EXPECT_EQ(static_cast<size_t>(shb.entries_size()), size / 16);
+  for (const auto& entry : shb.entries()) {
+    EXPECT_EQ(entry.addr().pc(), --itr);
+    EXPECT_EQ(entry.addr().pc(), entry.fp());
+    EXPECT_EQ(entry.addr().pc() & 15, entry.tag());
+  }
+}
+
+TEST_P(MteStackHistoryTest, TestHalfFull) {
+  int size_cls = GetParam();
+  size_t size = stack_mte_ringbuffer_size(size_cls);
+  size_t half_size = size / 2;
+
+  char* data = static_cast<char*>(stack_mte_ringbuffer_allocate(size_cls, nullptr));
+  ScopedUnmap s{data, size};
+
+  uintptr_t itr = 1;
+  for (char* d = data; d < &data[half_size]; d += 16) {
+    uintptr_t taggedfp = ((itr & 15) << 56) | itr;
+    uintptr_t pc = itr;
+    memcpy(d, &pc, sizeof(pc));
+    memcpy(d + 8, &taggedfp, sizeof(taggedfp));
+    ++itr;
+  }
+  // The MTE TLS is at TLS - 3, so we allocate 3 placeholders.
+  void* tls[4] = {&data[half_size]};
+
+  StackHistoryBuffer shb;
+  dump_stack_history(&*unwinder, reinterpret_cast<uintptr_t>(&tls[3]), shb, /* nounwind= */ true);
+  EXPECT_EQ(static_cast<size_t>(shb.entries_size()), half_size / 16);
+  for (const auto& entry : shb.entries()) {
+    EXPECT_EQ(entry.addr().pc(), --itr);
+    EXPECT_EQ(entry.addr().pc(), entry.fp());
+    EXPECT_EQ(entry.addr().pc() & 15, entry.tag());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(MteStackHistoryTestInstance, MteStackHistoryTest, testing::Range(0, 8));
+
+#endif
diff --git a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
index a4c08a4..4fd2643 100644
--- a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
@@ -134,3 +134,31 @@
   ProtoToString();
   EXPECT_MATCH(text_, R"(CRASH_DETAIL_NAME: 'helloworld\\1\\255\\3')");
 }
+
+TEST_F(TombstoneProtoToTextTest, stack_record) {
+  auto* cause = tombstone_->add_causes();
+  cause->set_human_readable("stack tag-mismatch on thread 123");
+  auto* stack = tombstone_->mutable_stack_history_buffer();
+  stack->set_tid(123);
+  {
+    auto* shb_entry = stack->add_entries();
+    shb_entry->set_fp(0x1);
+    shb_entry->set_tag(0xb);
+    auto* addr = shb_entry->mutable_addr();
+    addr->set_rel_pc(0x567);
+    addr->set_file_name("foo.so");
+    addr->set_build_id("ABC123");
+  }
+  {
+    auto* shb_entry = stack->add_entries();
+    shb_entry->set_fp(0x2);
+    shb_entry->set_tag(0xc);
+    auto* addr = shb_entry->mutable_addr();
+    addr->set_rel_pc(0x678);
+    addr->set_file_name("bar.so");
+  }
+  ProtoToString();
+  EXPECT_MATCH(text_, "stack tag-mismatch on thread 123");
+  EXPECT_MATCH(text_, "stack_record fp:0x1 tag:0xb pc:foo\\.so\\+0x567 \\(BuildId: ABC123\\)");
+  EXPECT_MATCH(text_, "stack_record fp:0x2 tag:0xc pc:bar\\.so\\+0x678");
+}
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 3e8ab6e..b6fc4e2 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -27,6 +27,7 @@
 #include <inttypes.h>
 #include <signal.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
@@ -49,9 +50,11 @@
 
 #include <android/log.h>
 #include <android/set_abort_message.h>
-#include <bionic/macros.h>
-#include <bionic/reserved_signals.h>
 #include <bionic/crash_detail_internal.h>
+#include <bionic/macros.h>
+#include <bionic/mte.h>
+#include <bionic/reserved_signals.h>
+#include <bionic/tls_defines.h>
 #include <log/log.h>
 #include <log/log_read.h>
 #include <log/logprint.h>
@@ -202,8 +205,117 @@
       error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address()));
 }
 
+#if defined(__aarch64__)
+void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls,
+                        StackHistoryBuffer& shb_obj, bool nounwind) {
+  auto process_memory = unwinder->GetProcessMemory();
+  target_tls += sizeof(void*) * TLS_SLOT_STACK_MTE;
+  uintptr_t stack_mte;
+  if (!process_memory->ReadFully(target_tls, &stack_mte, sizeof(stack_mte))) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                          "dump_stack_history: failed to read TLS_SLOT_STACK_MTE: %m");
+    return;
+  }
+  if (stack_mte == 0) {
+    async_safe_format_log(ANDROID_LOG_DEBUG, LOG_TAG,
+                          "dump_stack_history: stack history buffer is null");
+    return;
+  }
+  uintptr_t untagged_stack_mte = untag_address(stack_mte);
+  uintptr_t buf_size = stack_mte_ringbuffer_size_from_pointer(stack_mte);
+  if (buf_size == 0) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_stack_history: empty size");
+    return;
+  }
+  uintptr_t buf_start = untagged_stack_mte & ~(buf_size - 1ULL);
+  std::vector<char> buf(buf_size);
+  if (!process_memory->ReadFully(buf_start, buf.data(), buf.size())) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                          "dump_stack_history: failed to read stack history: %m");
+    return;
+  }
+  uintptr_t original_off = untagged_stack_mte - buf_start;
+  if (original_off % 16 || original_off > buf_size) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                          "dump_stack_history: invalid offset: %" PRIuPTR, original_off);
+    return;
+  }
+
+  // The original_off is the next slot that would have been written, so the last
+  // slot that was written is the previous one.
+  for (uintptr_t idx = 16; idx <= buf_size; idx += 16) {
+    int64_t off = original_off - idx;
+    if (off < 0) off += buf_size;
+    uintptr_t pc, taggedfp;
+    memcpy(&pc, &(buf[off]), sizeof(pc));
+    memcpy(&taggedfp, &(buf[off + sizeof(pc)]), sizeof(taggedfp));
+
+    if (pc == 0) break;
+    uintptr_t fp = untag_address(taggedfp);
+    uintptr_t tag = taggedfp >> 56;
+
+    unwindstack::FrameData frame_data;
+
+    if (nounwind) {
+      frame_data.pc = pc;
+    } else {
+      // +4 is to counteract the "pc adjustment" in BuildFrameFromPcOnly.
+      // BuildFrameFromPcOnly assumes we are unwinding, so it needs to correct for that
+      // the PC is the return address. That is not the case here.
+      // It doesn't really matter, because either should be in the correct function, but
+      // this is more correct (and consistent with the nounwind case).
+      frame_data = unwinder->BuildFrameFromPcOnly(pc);
+      frame_data.pc += 4;
+      frame_data.rel_pc += 4;
+    }
+
+    StackHistoryBufferEntry* entry = shb_obj.add_entries();
+    fill_in_backtrace_frame(entry->mutable_addr(), frame_data);
+    entry->set_fp(fp);
+    entry->set_tag(tag);
+  }
+}
+
+static pid_t get_containing_thread(unwindstack::MapInfo* map_info, pid_t main_tid) {
+  if (map_info == nullptr) return 0;
+
+  std::string name = map_info->name();
+  if (name == "[stack]") {
+    return main_tid;
+  }
+  int tid;
+  if (sscanf(name.c_str(), "[anon:stack_and_tls:%d", &tid) != 1) {
+    return 0;
+  }
+  return tid;
+}
+
+static std::optional<std::string> maybe_stack_mte_cause(
+    Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& target_thread,
+    [[maybe_unused]] const std::map<pid_t, ThreadInfo>& threads, uint64_t fault_addr) {
+  unwindstack::Maps* maps = unwinder->GetMaps();
+  auto map_info = maps->Find(untag_address(fault_addr));
+  pid_t tid = get_containing_thread(map_info.get(), target_thread.tid);
+  if (!tid) {
+    return std::nullopt;
+  }
+  auto it = threads.find(tid);
+  if (it != threads.end()) {
+    StackHistoryBuffer* shb = tombstone->mutable_stack_history_buffer();
+    shb->set_tid(tid);
+    dump_stack_history(unwinder, it->second.tls, *shb);
+  } else {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                          "dump_probable_cause: unknown target thread %d", tid);
+  }
+  return StringPrintf("stack tag-mismatch on thread %u", tid);
+}
+
+#endif
+
 static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder,
-                                const ProcessInfo& process_info, const ThreadInfo& target_thread) {
+                                const ProcessInfo& process_info, const ThreadInfo& target_thread,
+                                [[maybe_unused]] const std::map<pid_t, ThreadInfo>& threads) {
 #if defined(USE_SCUDO)
   ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
   if (scudo_crash_data.CrashIsMine()) {
@@ -247,7 +359,15 @@
     } else {
       cause = get_stack_overflow_cause(fault_addr, target_thread.registers->sp(), maps);
     }
-  } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
+  }
+#if defined(__aarch64__) && defined(SEGV_MTESERR)
+  else if (si->si_signo == SIGSEGV && si->si_code == SEGV_MTESERR) {
+    // If this was a heap MTE crash, it would have been handled by scudo. Checking whether it
+    // is a stack one.
+    cause = maybe_stack_mte_cause(tombstone, unwinder, target_thread, threads, fault_addr);
+  }
+#endif
+  else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
     cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
                          si->si_syscall);
   }
@@ -787,7 +907,7 @@
     }
   }
 
-  dump_probable_cause(&result, unwinder, process_info, target_thread);
+  dump_probable_cause(&result, unwinder, process_info, target_thread, threads);
 
   dump_mappings(&result, unwinder->GetMaps(), unwinder->GetProcessMemory());
 
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 1900719..c3f9470 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -511,6 +511,19 @@
         "order of likelihood.");
   }
 
+  if (tombstone.has_stack_history_buffer()) {
+    for (const StackHistoryBufferEntry& shbe : tombstone.stack_history_buffer().entries()) {
+      std::string stack_record_str = StringPrintf(
+          "stack_record fp:0x%" PRIx64 " tag:0x%" PRIx64 " pc:%s+0x%" PRIx64, shbe.fp(), shbe.tag(),
+          shbe.addr().file_name().c_str(), shbe.addr().rel_pc());
+      if (!shbe.addr().build_id().empty()) {
+        StringAppendF(&stack_record_str, " (BuildId: %s)", shbe.addr().build_id().c_str());
+      }
+
+      CBL("%s", stack_record_str.c_str());
+    }
+  }
+
   for (const Cause& cause : tombstone.causes()) {
     if (tombstone.causes_size() > 1) {
       CBS("");
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index b662d36..444c973 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -22,6 +22,21 @@
   reserved 3 to 999;
 }
 
+message StackHistoryBufferEntry {
+  BacktraceFrame addr = 1;
+  uint64 fp = 2;
+  uint64 tag = 3;
+
+  reserved 4 to 999;
+}
+
+message StackHistoryBuffer {
+  uint64 tid = 1;
+  repeated StackHistoryBufferEntry entries = 2;
+
+  reserved 3 to 999;
+}
+
 message Tombstone {
   Architecture arch = 1;
   Architecture guest_arch = 24;
@@ -53,7 +68,9 @@
   uint32 page_size = 22;
   bool has_been_16kb_mode = 23;
 
-  reserved 26 to 999;
+  StackHistoryBuffer stack_history_buffer = 26;
+
+  reserved 27 to 999;
 }
 
 enum Architecture {
diff --git a/debuggerd/test_permissive_mte/mte_crash.cpp b/debuggerd/test_permissive_mte/mte_crash.cpp
index 97ad73f..75b70ed 100644
--- a/debuggerd/test_permissive_mte/mte_crash.cpp
+++ b/debuggerd/test_permissive_mte/mte_crash.cpp
@@ -20,5 +20,14 @@
 int main(int, char**) {
   volatile char* f = (char*)malloc(1);
   printf("%c\n", f[17]);
+#ifdef __aarch64__
+  if (getenv("MTE_PERMISSIVE_REENABLE_TIME_CPUMS")) {
+    // Burn some cycles because the MTE_PERMISSIVE_REENABLE_TIME_CPUMS is based on CPU clock.
+    for (int i = 0; i < 1000000000; ++i) {
+      asm("isb");
+    }
+    printf("%c\n", f[17]);
+  }
+#endif
   return 0;
 }
diff --git a/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
index 0203adc..544299d 100644
--- a/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
+++ b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
@@ -97,6 +97,34 @@
     }
     assertThat(numberTombstones).isEqualTo(1);
   }
+
+  @Test
+  public void testReenableCrash() throws Exception {
+    CommandResult result =
+        getDevice().executeShellV2Command("MTE_PERMISSIVE=1 MTE_PERMISSIVE_REENABLE_TIME_CPUMS=1 "
+                                          + "/data/local/tmp/mte_crash testReenableCrash "
+                                          + mUUID);
+    assertThat(result.getExitCode()).isEqualTo(0);
+    int numberTombstones = 0;
+    String[] tombstones = getDevice().getChildren("/data/tombstones");
+    for (String tombstone : tombstones) {
+      if (!tombstone.endsWith(".pb")) {
+        continue;
+      }
+      String tombstonePath = "/data/tombstones/" + tombstone;
+      Tombstone tombstoneProto = parseTombstone(tombstonePath);
+      if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) {
+        continue;
+      }
+      if (!tombstoneProto.getCommandLineList().stream().anyMatch(
+              x -> x.contains("testReenableCrash"))) {
+        continue;
+      }
+      numberTombstones++;
+    }
+    assertThat(numberTombstones).isEqualTo(2);
+  }
+
   @Test
   public void testCrashProperty() throws Exception {
     String prevValue = getDevice().getProperty("persist.sys.mte.permissive");
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index fa67d46..2c72379 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -158,7 +158,7 @@
       }
     }
 
-    return std::move(result);
+    return result;
   }
 
   std::optional<CrashOutput> get_output(DebuggerdDumpType dump_type) {
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 774af28..bfe0768 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -186,6 +186,7 @@
         "libprotobuf-cpp-lite",
         "libsparse",
         "libutils",
+        "libselinux",
     ],
 
     static_libs: [
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 3e70f79..76578dd 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1433,16 +1433,16 @@
 // When multiple fstab records share the same mount_point, it will try to mount each
 // one in turn, and ignore any duplicates after a first successful mount.
 // Returns -1 on error, and  FS_MGR_MNTALL_* otherwise.
-MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) {
+int fs_mgr_mount_all(Fstab* fstab, int mount_mode) {
     int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE;
     int error_count = 0;
     CheckpointManager checkpoint_manager;
     AvbUniquePtr avb_handle(nullptr);
     bool wiped = false;
-
     bool userdata_mounted = false;
+
     if (fstab->empty()) {
-        return {FS_MGR_MNTALL_FAIL, userdata_mounted};
+        return FS_MGR_MNTALL_FAIL;
     }
 
     bool scratch_can_be_mounted = true;
@@ -1521,7 +1521,7 @@
                 if (!avb_handle) {
                     LERROR << "Failed to open AvbHandle";
                     set_type_property(encryptable);
-                    return {FS_MGR_MNTALL_FAIL, userdata_mounted};
+                    return FS_MGR_MNTALL_FAIL;
                 }
             }
             if (avb_handle->SetUpAvbHashtree(&current_entry, true /* wait_for_verity_dev */) ==
@@ -1557,7 +1557,7 @@
 
             if (status == FS_MGR_MNTALL_FAIL) {
                 // Fatal error - no point continuing.
-                return {status, userdata_mounted};
+                return status;
             }
 
             if (status != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
@@ -1577,7 +1577,7 @@
                                   nullptr)) {
                         LERROR << "Encryption failed";
                         set_type_property(encryptable);
-                        return {FS_MGR_MNTALL_FAIL, userdata_mounted};
+                        return FS_MGR_MNTALL_FAIL;
                     }
                 }
             }
@@ -1696,9 +1696,9 @@
     set_type_property(encryptable);
 
     if (error_count) {
-        return {FS_MGR_MNTALL_FAIL, userdata_mounted};
+        return FS_MGR_MNTALL_FAIL;
     } else {
-        return {encryptable, userdata_mounted};
+        return encryptable;
     }
 }
 
@@ -1735,190 +1735,6 @@
     return ret;
 }
 
-static std::chrono::milliseconds GetMillisProperty(const std::string& name,
-                                                   std::chrono::milliseconds default_value) {
-    auto value = GetUintProperty(name, static_cast<uint64_t>(default_value.count()));
-    return std::chrono::milliseconds(std::move(value));
-}
-
-static bool fs_mgr_unmount_all_data_mounts(const std::string& data_block_device) {
-    LINFO << __FUNCTION__ << "(): about to umount everything on top of " << data_block_device;
-    Timer t;
-    auto timeout = GetMillisProperty("init.userspace_reboot.userdata_remount.timeoutmillis", 5s);
-    while (true) {
-        bool umount_done = true;
-        Fstab proc_mounts;
-        if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) {
-            LERROR << __FUNCTION__ << "(): Can't read /proc/mounts";
-            return false;
-        }
-        // Now proceed with other bind mounts on top of /data.
-        for (const auto& entry : proc_mounts) {
-            std::string block_device;
-            if (StartsWith(entry.blk_device, "/dev/block") &&
-                !Realpath(entry.blk_device, &block_device)) {
-                PWARNING << __FUNCTION__ << "(): failed to realpath " << entry.blk_device;
-                block_device = entry.blk_device;
-            }
-            if (data_block_device == block_device) {
-                if (umount2(entry.mount_point.c_str(), 0) != 0) {
-                    PERROR << __FUNCTION__ << "(): Failed to umount " << entry.mount_point;
-                    umount_done = false;
-                }
-            }
-        }
-        if (umount_done) {
-            LINFO << __FUNCTION__ << "(): Unmounting /data took " << t;
-            return true;
-        }
-        if (t.duration() > timeout) {
-            LERROR << __FUNCTION__ << "(): Timed out unmounting all mounts on "
-                   << data_block_device;
-            Fstab remaining_mounts;
-            if (!ReadFstabFromFile("/proc/mounts", &remaining_mounts)) {
-                LERROR << __FUNCTION__ << "(): Can't read /proc/mounts";
-            } else {
-                LERROR << __FUNCTION__ << "(): Following mounts remaining";
-                for (const auto& e : remaining_mounts) {
-                    LERROR << __FUNCTION__ << "(): mount point: " << e.mount_point
-                           << " block device: " << e.blk_device;
-                }
-            }
-            return false;
-        }
-        std::this_thread::sleep_for(50ms);
-    }
-}
-
-static bool UnwindDmDeviceStack(const std::string& block_device,
-                                std::vector<std::string>* dm_stack) {
-    if (!StartsWith(block_device, "/dev/block/")) {
-        LWARNING << block_device << " is not a block device";
-        return false;
-    }
-    std::string current = block_device;
-    DeviceMapper& dm = DeviceMapper::Instance();
-    while (true) {
-        dm_stack->push_back(current);
-        if (!dm.IsDmBlockDevice(current)) {
-            break;
-        }
-        auto parent = dm.GetParentBlockDeviceByPath(current);
-        if (!parent) {
-            return false;
-        }
-        current = *parent;
-    }
-    return true;
-}
-
-FstabEntry* fs_mgr_get_mounted_entry_for_userdata(Fstab* fstab,
-                                                  const std::string& data_block_device) {
-    std::vector<std::string> dm_stack;
-    if (!UnwindDmDeviceStack(data_block_device, &dm_stack)) {
-        LERROR << "Failed to unwind dm-device stack for " << data_block_device;
-        return nullptr;
-    }
-    for (auto& entry : *fstab) {
-        if (entry.mount_point != "/data") {
-            continue;
-        }
-        std::string block_device;
-        if (entry.fs_mgr_flags.logical) {
-            if (!fs_mgr_update_logical_partition(&entry)) {
-                LERROR << "Failed to update logic partition " << entry.blk_device;
-                continue;
-            }
-            block_device = entry.blk_device;
-        } else if (!Realpath(entry.blk_device, &block_device)) {
-            PWARNING << "Failed to realpath " << entry.blk_device;
-            block_device = entry.blk_device;
-        }
-        if (std::find(dm_stack.begin(), dm_stack.end(), block_device) != dm_stack.end()) {
-            return &entry;
-        }
-    }
-    LERROR << "Didn't find entry that was used to mount /data onto " << data_block_device;
-    return nullptr;
-}
-
-// TODO(b/143970043): return different error codes based on which step failed.
-int fs_mgr_remount_userdata_into_checkpointing(Fstab* fstab) {
-    Fstab proc_mounts;
-    if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) {
-        LERROR << "Can't read /proc/mounts";
-        return -1;
-    }
-    auto mounted_entry = GetEntryForMountPoint(&proc_mounts, "/data");
-    if (mounted_entry == nullptr) {
-        LERROR << "/data is not mounted";
-        return -1;
-    }
-    std::string block_device;
-    if (!Realpath(mounted_entry->blk_device, &block_device)) {
-        PERROR << "Failed to realpath " << mounted_entry->blk_device;
-        return -1;
-    }
-    auto fstab_entry = fs_mgr_get_mounted_entry_for_userdata(fstab, block_device);
-    if (fstab_entry == nullptr) {
-        LERROR << "Can't find /data in fstab";
-        return -1;
-    }
-    bool force_umount = GetBoolProperty("sys.init.userdata_remount.force_umount", false);
-    if (force_umount) {
-        LINFO << "Will force an umount of userdata even if it's not required";
-    }
-    if (!force_umount && !SupportsCheckpoint(fstab_entry)) {
-        LINFO << "Userdata doesn't support checkpointing. Nothing to do";
-        return 0;
-    }
-    CheckpointManager checkpoint_manager;
-    if (!force_umount && !checkpoint_manager.NeedsCheckpoint()) {
-        LINFO << "Checkpointing not needed. Don't remount";
-        return 0;
-    }
-    if (!force_umount && fstab_entry->fs_mgr_flags.checkpoint_fs) {
-        // Userdata is f2fs, simply remount it.
-        if (!checkpoint_manager.Update(fstab_entry)) {
-            LERROR << "Failed to remount userdata in checkpointing mode";
-            return -1;
-        }
-        if (mount(block_device.c_str(), fstab_entry->mount_point.c_str(), "none",
-                  MS_REMOUNT | fstab_entry->flags, fstab_entry->fs_options.c_str()) != 0) {
-            PERROR << "Failed to remount userdata in checkpointing mode";
-            return -1;
-        }
-    } else {
-        LINFO << "Unmounting /data before remounting into checkpointing mode";
-        if (!fs_mgr_unmount_all_data_mounts(block_device)) {
-            LERROR << "Failed to umount /data";
-            return -1;
-        }
-        DeviceMapper& dm = DeviceMapper::Instance();
-        while (dm.IsDmBlockDevice(block_device)) {
-            auto next_device = dm.GetParentBlockDeviceByPath(block_device);
-            auto name = dm.GetDmDeviceNameByPath(block_device);
-            if (!name) {
-                LERROR << "Failed to get dm-name for " << block_device;
-                return -1;
-            }
-            LINFO << "Deleting " << block_device << " named " << *name;
-            if (!dm.DeleteDevice(*name, 3s)) {
-                return -1;
-            }
-            if (!next_device) {
-                LERROR << "Failed to find parent device for " << block_device;
-            }
-            block_device = *next_device;
-        }
-        LINFO << "Remounting /data";
-        // TODO(b/143970043): remove this hack after fs_mgr_mount_all is refactored.
-        auto result = fs_mgr_mount_all(fstab, MOUNT_MODE_ONLY_USERDATA);
-        return result.code == FS_MGR_MNTALL_FAIL ? -1 : 0;
-    }
-    return 0;
-}
-
 // wrapper to __mount() and expects a fully prepared fstab_rec,
 // unlike fs_mgr_do_mount which does more things with avb / verity etc.
 int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& alt_mount_point) {
diff --git a/fs_mgr/fs_mgr_priv.h b/fs_mgr/fs_mgr_priv.h
index 7e4d5e5..84e4924 100644
--- a/fs_mgr/fs_mgr_priv.h
+++ b/fs_mgr/fs_mgr_priv.h
@@ -80,10 +80,8 @@
 
 using namespace std::chrono_literals;
 
-bool fs_mgr_set_blk_ro(const std::string& blockdev, bool readonly = true);
 bool fs_mgr_is_device_unlocked();
 
-bool fs_mgr_is_ext4(const std::string& blk_device);
 bool fs_mgr_is_f2fs(const std::string& blk_device);
 
 bool fs_mgr_filesystem_available(const std::string& filesystem);
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 2e1cf76..9cfa93f 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -58,13 +58,8 @@
 #define FS_MGR_MNTALL_DEV_NEEDS_RECOVERY 4
 #define FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE 0
 #define FS_MGR_MNTALL_FAIL (-1)
-
-struct MountAllResult {
-    // One of the FS_MGR_MNTALL_* returned code defined above.
-    int code;
-    // Whether userdata was mounted as a result of |fs_mgr_mount_all| call.
-    bool userdata_mounted;
-};
+// fs_mgr_mount_all() updates fstab entries that reference device-mapper.
+int fs_mgr_mount_all(android::fs_mgr::Fstab* fstab, int mount_mode);
 
 struct HashtreeInfo {
     // The hash algorithm used to build the merkle tree.
@@ -75,13 +70,6 @@
     bool check_at_most_once;
 };
 
-// fs_mgr_mount_all() updates fstab entries that reference device-mapper.
-// Returns a |MountAllResult|. The first element is one of the FS_MNG_MNTALL_* return codes
-// defined above, and the second element tells whether this call to fs_mgr_mount_all was responsible
-// for mounting userdata. Later is required for init to correctly enqueue fs-related events as part
-// of userdata remount during userspace reboot.
-MountAllResult fs_mgr_mount_all(android::fs_mgr::Fstab* fstab, int mount_mode);
-
 #define FS_MGR_DOMNT_FAILED (-1)
 #define FS_MGR_DOMNT_BUSY (-2)
 #define FS_MGR_DOMNT_SUCCESS 0
@@ -116,6 +104,12 @@
 // returned. Otherwise, it will use the current slot.
 std::string fs_mgr_get_super_partition_name(int slot = -1);
 
+// Set readonly for the block device
+bool fs_mgr_set_blk_ro(const std::string& blockdev, bool readonly = true);
+
+// Check if the block device has ext4 filesystem
+bool fs_mgr_is_ext4(const std::string& blk_device);
+
 enum FsMgrUmountStatus : int {
     SUCCESS = 0,
     ERROR_UNKNOWN = 1 << 0,
@@ -127,11 +121,6 @@
 // it destroys verity devices from device mapper after the device is unmounted.
 int fs_mgr_umount_all(android::fs_mgr::Fstab* fstab);
 
-// Finds a entry in |fstab| that was used to mount a /data on |data_block_device|.
-android::fs_mgr::FstabEntry* fs_mgr_get_mounted_entry_for_userdata(
-        android::fs_mgr::Fstab* fstab, const std::string& data_block_device);
-int fs_mgr_remount_userdata_into_checkpointing(android::fs_mgr::Fstab* fstab);
-
 // Finds the dm_bow device on which this block device is stacked, or returns
 // empty string
 std::string fs_mgr_find_bow_device(const std::string& block_device);
diff --git a/fs_mgr/include/fs_mgr/roots.h b/fs_mgr/include/fs_mgr/roots.h
index 65c59cf..e19f9ad 100644
--- a/fs_mgr/include/fs_mgr/roots.h
+++ b/fs_mgr/include/fs_mgr/roots.h
@@ -29,6 +29,8 @@
 // first match or nullptr.
 FstabEntry* GetEntryForPath(Fstab* fstab, const std::string& path);
 
+std::vector<FstabEntry*> GetEntriesForPath(Fstab* fstab, const std::string& path);
+
 // Make sure that the volume 'path' is on is mounted.
 // * If 'mount_point' is nullptr, use mount point in fstab. Caller can call
 //   fs_mgr_ensure_path_unmounted() with the same 'path' argument to unmount.
diff --git a/fs_mgr/libfstab/fstab_priv.h b/fs_mgr/libfstab/fstab_priv.h
index 5105da0..73cb175 100644
--- a/fs_mgr/libfstab/fstab_priv.h
+++ b/fs_mgr/libfstab/fstab_priv.h
@@ -39,9 +39,6 @@
 void ImportBootconfigFromString(const std::string& bootconfig,
                                 const std::function<void(std::string, std::string)>& fn);
 
-bool GetBootconfigFromString(const std::string& bootconfig, const std::string& key,
-                             std::string* out);
-
 void ImportKernelCmdlineFromString(const std::string& cmdline,
                                    const std::function<void(std::string, std::string)>& fn);
 
diff --git a/fs_mgr/libfstab/include/fstab/fstab.h b/fs_mgr/libfstab/include/fstab/fstab.h
index 1696daf..21fe017 100644
--- a/fs_mgr/libfstab/include/fstab/fstab.h
+++ b/fs_mgr/libfstab/include/fstab/fstab.h
@@ -153,5 +153,8 @@
 // Return the "other" slot for the given slot suffix.
 std::string OtherSlotSuffix(const std::string& suffix);
 
+bool GetBootconfigFromString(const std::string& bootconfig, const std::string& key,
+                             std::string* out);
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index cc6db35..50efb03 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -91,6 +91,7 @@
         "partition_cow_creator.cpp",
         "return.cpp",
         "utility.cpp",
+        "scratch_super.cpp",
     ],
 }
 
@@ -110,6 +111,9 @@
     static_libs: [
         "libfs_mgr_binder",
     ],
+    whole_static_libs: [
+        "libselinux",
+    ],
 }
 
 cc_library {
@@ -128,6 +132,9 @@
     static_libs: [
         "libsnapshot_cow",
     ],
+    whole_static_libs: [
+        "libselinux",
+    ],
 }
 
 cc_library_static {
@@ -142,6 +149,7 @@
     ],
     static_libs: [
         "libfs_mgr",
+        "libselinux",
     ],
 }
 
@@ -159,6 +167,9 @@
     static_libs: [
         "libfs_mgr",
     ],
+    whole_static_libs: [
+        "libselinux",
+    ],
 }
 
 cc_defaults {
@@ -241,6 +252,7 @@
         "libfs_mgr",
         "libgmock",
         "libgtest",
+        "libselinux",
     ],
 }
 
@@ -283,7 +295,6 @@
     ],
     auto_gen_config: true,
     require_root: true,
-    compile_multilib: "first",
 }
 
 cc_test {
@@ -294,8 +305,17 @@
     ],
     test_suites: [
         "vts",
-        "device-tests",
+        "general-tests",
     ],
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
     test_options: {
         min_shipping_api_level: 30,
     },
@@ -311,8 +331,9 @@
         "-DLIBSNAPSHOT_TEST_VAB_LEGACY",
     ],
     test_suites: [
-        "device-tests",
+        "general-tests",
     ],
+    compile_multilib: "64",
     test_options: {
         // Legacy VAB launched in Android R.
         min_shipping_api_level: 30,
@@ -351,6 +372,7 @@
     ],
     srcs: [
         "snapshotctl.cpp",
+        "scratch_super.cpp",
     ],
     static_libs: [
         "libbrotli",
@@ -420,7 +442,7 @@
         "libstorage_literals_headers",
     ],
     test_suites: [
-        "device-tests",
+        "general-tests",
     ],
     test_options: {
         min_shipping_api_level: 30,
diff --git a/fs_mgr/libsnapshot/device_info.cpp b/fs_mgr/libsnapshot/device_info.cpp
index 0ab6103..19f3e02 100644
--- a/fs_mgr/libsnapshot/device_info.cpp
+++ b/fs_mgr/libsnapshot/device_info.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "device_info.h"
+#include "scratch_super.h"
 
 #include <android-base/logging.h>
 #include <fs_mgr.h>
@@ -37,8 +38,24 @@
 constexpr bool kIsRecovery = false;
 #endif
 
+DeviceInfo::DeviceInfo() {
+    std::string scratch_device = android::snapshot::GetScratchOtaMetadataPartition();
+    if (!scratch_device.empty()) {
+        std::string scratch_metadata =
+                android::snapshot::MapScratchOtaMetadataPartition(scratch_device);
+        if (!scratch_metadata.empty()) {
+            SetMetadataDir(scratch_metadata);
+            SetTempMetadata();
+        }
+    }
+}
+
 std::string DeviceInfo::GetMetadataDir() const {
-    return "/metadata/ota"s;
+    return metadata_dir_;
+}
+
+void DeviceInfo::SetMetadataDir(const std::string& value) {
+    metadata_dir_ = value;
 }
 
 std::string DeviceInfo::GetSlotSuffix() const {
@@ -104,6 +121,24 @@
     return first_stage_init_;
 }
 
+bool DeviceInfo::SetActiveBootSlot([[maybe_unused]] unsigned int slot) {
+#ifdef LIBSNAPSHOT_USE_HAL
+    if (!EnsureBootHal()) {
+        return false;
+    }
+
+    CommandResult result = boot_control_->SetActiveBootSlot(slot);
+    if (!result.success) {
+        LOG(ERROR) << "Error setting slot " << slot << " active: " << result.errMsg;
+        return false;
+    }
+    return true;
+#else
+    LOG(ERROR) << "HAL support not enabled.";
+    return false;
+#endif
+}
+
 bool DeviceInfo::SetSlotAsUnbootable([[maybe_unused]] unsigned int slot) {
 #ifdef LIBSNAPSHOT_USE_HAL
     if (!EnsureBootHal()) {
diff --git a/fs_mgr/libsnapshot/device_info.h b/fs_mgr/libsnapshot/device_info.h
index d06f1be..e93ec49 100644
--- a/fs_mgr/libsnapshot/device_info.h
+++ b/fs_mgr/libsnapshot/device_info.h
@@ -29,6 +29,7 @@
     using MergeStatus = ::aidl::android::hardware::boot::MergeStatus;
 
   public:
+    DeviceInfo();
     std::string GetMetadataDir() const override;
     std::string GetSlotSuffix() const override;
     std::string GetOtherSlotSuffix() const override;
@@ -36,19 +37,25 @@
     std::string GetSuperDevice(uint32_t slot) const override;
     bool IsOverlayfsSetup() const override;
     bool SetBootControlMergeStatus(MergeStatus status) override;
+    bool SetActiveBootSlot(unsigned int slot) override;
     bool SetSlotAsUnbootable(unsigned int slot) override;
     bool IsRecovery() const override;
     std::unique_ptr<IImageManager> OpenImageManager() const override;
     bool IsFirstStageInit() const override;
     android::dm::IDeviceMapper& GetDeviceMapper() override;
-
+    void SetMetadataDir(const std::string& value);
     void set_first_stage_init(bool value) { first_stage_init_ = value; }
+    bool IsTempMetadata() const override { return temp_metadata_; }
+    void SetTempMetadata() { temp_metadata_ = true; }
 
   private:
     bool EnsureBootHal();
 
     android::fs_mgr::PartitionOpener opener_;
     bool first_stage_init_ = false;
+    // Default value
+    std::string metadata_dir_ = "/metadata/ota";
+    bool temp_metadata_ = false;
 #ifdef LIBSNAPSHOT_USE_HAL
     std::unique_ptr<::android::hal::BootControlClient> boot_control_;
 #endif
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
index 573a85b..ca1ac1e 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
@@ -29,6 +29,7 @@
     MOCK_METHOD(const android::fs_mgr::IPartitionOpener&, GetPartitionOpener, (), (const));
     MOCK_METHOD(bool, IsOverlayfsSetup, (), (const, override));
     MOCK_METHOD(bool, SetBootControlMergeStatus, (MergeStatus status), (override));
+    MOCK_METHOD(bool, SetActiveBootSlot, (unsigned int slot), (override));
     MOCK_METHOD(bool, SetSlotAsUnbootable, (unsigned int slot), (override));
     MOCK_METHOD(bool, IsRecovery, (), (const, override));
     MOCK_METHOD(bool, IsFirstStageInit, (), (const, override));
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 4a3ec1d..7ae55db 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -104,12 +104,14 @@
         virtual const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const = 0;
         virtual bool IsOverlayfsSetup() const = 0;
         virtual bool SetBootControlMergeStatus(MergeStatus status) = 0;
+        virtual bool SetActiveBootSlot(unsigned int slot) = 0;
         virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
         virtual bool IsRecovery() const = 0;
         virtual bool IsTestDevice() const { return false; }
         virtual bool IsFirstStageInit() const = 0;
         virtual std::unique_ptr<IImageManager> OpenImageManager() const = 0;
         virtual android::dm::IDeviceMapper& GetDeviceMapper() = 0;
+        virtual bool IsTempMetadata() const = 0;
 
         // Helper method for implementing OpenImageManager.
         std::unique_ptr<IImageManager> OpenImageManager(const std::string& gsid_dir) const;
@@ -328,6 +330,10 @@
     // might be needed to perform first-stage mounts.
     static bool IsSnapshotManagerNeeded();
 
+    // Map the temp OTA metadata partition from super
+    static bool MapTempOtaMetadataPartitionIfNeeded(
+            const std::function<bool(const std::string&)>& init);
+
     // Helper function for second stage init to restorecon on the rollback indicator.
     static std::string GetGlobalRollbackIndicatorPath();
 
@@ -675,6 +681,8 @@
     std::string GetBootSnapshotsWithoutSlotSwitchPath();
     std::string GetSnapuserdFromSystemPath();
 
+    bool HasForwardMergeIndicator();
+
     const LpMetadata* ReadOldPartitionMetadata(LockedFile* lock);
 
     bool MapAllPartitions(LockedFile* lock, const std::string& super_device, uint32_t slot,
@@ -785,11 +793,8 @@
     bool UpdateForwardMergeIndicator(bool wipe);
 
     // Helper for HandleImminentDataWipe.
-    // Call ProcessUpdateState and handle states with special rules before data wipe. Specifically,
-    // if |allow_forward_merge| and allow-forward-merge indicator exists, initiate merge if
-    // necessary.
-    UpdateState ProcessUpdateStateOnDataWipe(bool allow_forward_merge,
-                                             const std::function<bool()>& callback);
+    // Call ProcessUpdateState and handle states with special rules before data wipe.
+    UpdateState ProcessUpdateStateOnDataWipe(const std::function<bool()>& callback);
 
     // Return device string of a mapped image, or if it is not available, the mapped image path.
     bool GetMappedImageDeviceStringOrPath(const std::string& device_name,
@@ -848,7 +853,6 @@
     std::string metadata_dir_;
     std::unique_ptr<IImageManager> images_;
     bool use_first_stage_snapuserd_ = false;
-    bool in_factory_data_reset_ = false;
     std::function<bool(const std::string&)> uevent_regen_callback_;
     std::unique_ptr<SnapuserdClient> snapuserd_client_;
     std::unique_ptr<LpMetadata> old_partition_metadata_;
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 0afd8bd..1cd6651 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -79,7 +79,7 @@
         : TestDeviceInfo(fake_super) {
         set_slot_suffix(slot_suffix);
     }
-    std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
+    std::string GetMetadataDir() const override { return metadata_dir_; }
     std::string GetSlotSuffix() const override { return slot_suffix_; }
     std::string GetOtherSlotSuffix() const override { return slot_suffix_ == "_a" ? "_b" : "_a"; }
     std::string GetSuperDevice([[maybe_unused]] uint32_t slot) const override { return "super"; }
@@ -92,6 +92,7 @@
     }
     bool IsOverlayfsSetup() const override { return false; }
     bool IsRecovery() const override { return recovery_; }
+    bool SetActiveBootSlot([[maybe_unused]] unsigned int slot) override { return true; }
     bool SetSlotAsUnbootable(unsigned int slot) override {
         unbootable_slots_.insert(slot);
         return true;
@@ -119,6 +120,7 @@
     void set_dm(android::dm::IDeviceMapper* dm) { dm_ = dm; }
 
     MergeStatus merge_status() const { return merge_status_; }
+    bool IsTempMetadata() const override { return temp_metadata_; }
 
   private:
     std::string slot_suffix_ = "_a";
@@ -128,6 +130,8 @@
     bool first_stage_init_ = false;
     std::unordered_set<uint32_t> unbootable_slots_;
     android::dm::IDeviceMapper* dm_ = nullptr;
+    std::string metadata_dir_ = "/metadata/ota/test";
+    bool temp_metadata_ = false;
 };
 
 class DeviceMapperWrapper : public android::dm::IDeviceMapper {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 871ed27..9e7cf7a2 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -82,7 +82,7 @@
                                                  CowOperationType type);
     size_t GetCompressionFactor(const size_t blocks_to_compress, CowOperationType type) const;
 
-    constexpr bool IsBlockAligned(const size_t size) {
+    constexpr bool IsBlockAligned(const uint64_t size) {
         // These are the only block size supported. Block size beyond 256k
         // may impact random read performance post OTA boot.
         const size_t values[] = {4_KiB, 8_KiB, 16_KiB, 32_KiB, 64_KiB, 128_KiB, 256_KiB};
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
index a4a2c1a..8356c0c 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -250,8 +250,8 @@
                                 .target_partition = system_b,
                                 .current_metadata = builder_a.get(),
                                 .current_suffix = "_a",
-                                .using_snapuserd = true,
-                                .update = &update};
+                                .update = &update,
+                                .using_snapuserd = true};
 
     auto ret = creator.Run();
     ASSERT_TRUE(ret.has_value());
@@ -276,8 +276,8 @@
                                 .target_partition = system_b,
                                 .current_metadata = builder_a.get(),
                                 .current_suffix = "_a",
-                                .using_snapuserd = true,
-                                .update = nullptr};
+                                .update = nullptr,
+                                .using_snapuserd = true};
 
     auto ret = creator.Run();
     ASSERT_FALSE(ret.has_value());
diff --git a/fs_mgr/libsnapshot/scratch_super.cpp b/fs_mgr/libsnapshot/scratch_super.cpp
new file mode 100644
index 0000000..805abf3
--- /dev/null
+++ b/fs_mgr/libsnapshot/scratch_super.cpp
@@ -0,0 +1,417 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <selinux/selinux.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/properties.h>
+#include <android-base/scopeguard.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+
+#include <libsnapshot/snapshot.h>
+
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <fstab/fstab.h>
+#include <liblp/builder.h>
+#include <storage_literals/storage_literals.h>
+#include <algorithm>
+#include <filesystem>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "device_info.h"
+#include "scratch_super.h"
+
+using namespace std::literals;
+using namespace android::dm;
+using namespace android::fs_mgr;
+using namespace android::storage_literals;
+
+namespace android {
+namespace snapshot {
+
+static bool UmountScratch() {
+    auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota";
+    std::error_code ec;
+
+    if (std::filesystem::remove_all(ota_dir, ec) == static_cast<std::uintmax_t>(-1)) {
+        LOG(ERROR) << "Failed to remove OTA directory: " << ec.message();
+        return false;
+    }
+
+    if (umount(kOtaMetadataMount) != 0) {
+        PLOG(ERROR) << "UmountScratch failed";
+        return false;
+    }
+
+    LOG(INFO) << "umount scratch_super success";
+    return true;
+}
+
+bool CleanupScratchOtaMetadataIfPresent(const ISnapshotManager::IDeviceInfo* info) {
+    if (!UmountScratch()) {
+        return false;
+    }
+
+    std::unique_ptr<MetadataBuilder> builder;
+    const auto partition_name = android::base::Basename(kOtaMetadataMount);
+    const std::vector<int> slots = {0, 1};
+
+    if (info == nullptr) {
+        info = new android::snapshot::DeviceInfo();
+    }
+
+    std::string super_device;
+    if (info->IsTestDevice()) {
+        super_device = "super";
+    } else {
+        super_device = kPhysicalDevice + fs_mgr_get_super_partition_name();
+    }
+    const auto& opener = info->GetPartitionOpener();
+    std::string slot_suffix = info->GetSlotSuffix();
+    int slot = SlotNumberForSlotSuffix(slot_suffix);
+    // Walk both the slots and clean up metadata related to scratch space from
+    // both the slots.
+    for (auto slot : slots) {
+        std::unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(opener, super_device, slot);
+        if (!builder) {
+            return false;
+        }
+
+        if (builder->FindPartition(partition_name) != nullptr) {
+            builder->RemovePartition(partition_name);
+            auto metadata = builder->Export();
+            if (!metadata) {
+                return false;
+            }
+            if (!UpdatePartitionTable(info->GetPartitionOpener(), super_device, *metadata.get(),
+                                      slot)) {
+                LOG(ERROR) << "UpdatePartitionTable failed for slot: " << slot;
+                return false;
+            }
+            if (DestroyLogicalPartition(partition_name)) {
+                LOG(INFO) << "CleanupScratchOtaMetadata success for slot: " << slot;
+            }
+        }
+    }
+
+    return true;
+}
+
+static bool SetupOTADirs() {
+    if (setfscreatecon(android::snapshot::kOtaMetadataFileContext)) {
+        PLOG(ERROR) << "setfscreatecon failed: " << android::snapshot::kOtaMetadataFileContext;
+        return false;
+    }
+    const auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota";
+    if (mkdir(ota_dir.c_str(), 0755) != 0 && errno != EEXIST) {
+        PLOG(ERROR) << "mkdir " << ota_dir;
+        return false;
+    }
+
+    const auto snapshot_dir = ota_dir + "/" + "snapshots";
+    if (mkdir(snapshot_dir.c_str(), 0755) != 0 && errno != EEXIST) {
+        PLOG(ERROR) << "mkdir " << snapshot_dir;
+        return false;
+    }
+    if (setfscreatecon(nullptr)) {
+        PLOG(ERROR) << "setfscreatecon null";
+        return false;
+    }
+    return true;
+}
+
+static bool MountScratch(const std::string& device_path) {
+    if (access(device_path.c_str(), R_OK | W_OK)) {
+        LOG(ERROR) << "Path does not exist or is not readwrite: " << device_path;
+        return false;
+    }
+
+    std::string filesystem_candidate;
+    if (fs_mgr_is_ext4(device_path)) {
+        filesystem_candidate = "ext4";
+    } else {
+        LOG(ERROR) << "Scratch partition is not ext4";
+        return false;
+    }
+    if (setfscreatecon(android::snapshot::kOtaMetadataFileContext)) {
+        PLOG(ERROR) << "setfscreatecon failed: " << android::snapshot::kOtaMetadataFileContext;
+        return false;
+    }
+    if (mkdir(kOtaMetadataMount, 0755) && (errno != EEXIST)) {
+        PLOG(ERROR) << "create " << kOtaMetadataMount;
+        return false;
+    }
+
+    android::fs_mgr::FstabEntry entry;
+    entry.blk_device = device_path;
+    entry.mount_point = kOtaMetadataMount;
+    entry.flags = MS_NOATIME;
+    entry.flags |= MS_SYNCHRONOUS;
+    entry.fs_options = "nodiscard";
+    fs_mgr_set_blk_ro(device_path, false);
+    entry.fs_mgr_flags.check = true;
+
+    bool mounted = false;
+    entry.fs_type = filesystem_candidate.c_str();
+    if (fs_mgr_do_mount_one(entry) == 0) {
+        mounted = true;
+    }
+
+    if (setfscreatecon(nullptr)) {
+        PLOG(ERROR) << "setfscreatecon null";
+        return false;
+    }
+    if (!mounted) {
+        rmdir(kOtaMetadataMount);
+        return false;
+    }
+
+    return true;
+}
+
+static bool MakeScratchFilesystem(const std::string& scratch_device) {
+    std::string fs_type;
+    std::string command;
+    if (!access(kMkExt4, X_OK)) {
+        fs_type = "ext4";
+        command = kMkExt4 + " -F -b 4096 -t ext4 -m 0 -O has_journal -M "s + kOtaMetadataMount;
+    } else {
+        LOG(ERROR) << "No supported mkfs command or filesystem driver available, supported "
+                      "filesystems "
+                      "are: f2fs, ext4";
+        return false;
+    }
+    command += " " + scratch_device + " >/dev/null 2>/dev/null </dev/null";
+    fs_mgr_set_blk_ro(scratch_device, false);
+    auto ret = system(command.c_str());
+    if (ret) {
+        LOG(ERROR) << "make " << fs_type << " filesystem on " << scratch_device
+                   << " return=" << ret;
+        return false;
+    }
+    return true;
+}
+
+static bool CreateDynamicScratch(const ISnapshotManager::IDeviceInfo* info,
+                                 std::string* scratch_device) {
+    const auto partition_name = android::base::Basename(kOtaMetadataMount);
+    auto& dm = DeviceMapper::Instance();
+    if (info == nullptr) {
+        info = new android::snapshot::DeviceInfo();
+    }
+
+    std::string super_device;
+    if (info->IsTestDevice()) {
+        super_device = "super";
+    } else {
+        super_device = kPhysicalDevice + fs_mgr_get_super_partition_name();
+    }
+
+    bool partition_exists = dm.GetState(partition_name) != DmDeviceState::INVALID;
+    if (partition_exists) {
+        LOG(ERROR) << "Partition already exists: " << partition_name;
+        return false;
+    }
+
+    const auto& opener = info->GetPartitionOpener();
+    std::string slot_suffix = info->GetSlotSuffix();
+    int slot = SlotNumberForSlotSuffix(slot_suffix);
+    std::unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(opener, super_device, slot);
+
+    if (!builder) {
+        LOG(ERROR) << "open " << super_device << " failed";
+        return false;
+    }
+
+    auto partition = builder->FindPartition(partition_name);
+    partition_exists = partition != nullptr;
+    if (partition_exists) {
+        LOG(ERROR) << "Partition exists in super metadata";
+        return false;
+    }
+
+    partition = builder->AddPartition(partition_name, LP_PARTITION_ATTR_NONE);
+    if (!partition) {
+        LOG(ERROR) << "AddPartition failed " << partition_name;
+        return false;
+    }
+
+    auto free_space = builder->AllocatableSpace() - builder->UsedSpace();
+    if (free_space < kOtaMetadataPartitionSize) {
+        LOG(ERROR) << "No space in super partition. Free space: " << free_space
+                   << " Requested space: " << kOtaMetadataPartitionSize;
+        return false;
+    }
+
+    LOG(INFO) << "CreateDynamicScratch: free_space: " << free_space
+              << " scratch_size: " << kOtaMetadataPartitionSize << " slot_number: " << slot;
+
+    if (!builder->ResizePartition(partition, kOtaMetadataPartitionSize)) {
+        LOG(ERROR) << "ResizePartition failed: " << partition_name << " free_space: " << free_space
+                   << " scratch_size: " << kOtaMetadataPartitionSize;
+        return false;
+    }
+
+    auto metadata = builder->Export();
+    CreateLogicalPartitionParams params;
+
+    if (!metadata ||
+        !UpdatePartitionTable(info->GetPartitionOpener(), super_device, *metadata.get(), slot)) {
+        LOG(ERROR) << "UpdatePartitionTable failed: " << partition_name;
+        return false;
+    }
+    params = {
+            .block_device = super_device,
+            .metadata_slot = slot,
+            .partition_name = partition_name,
+            .force_writable = true,
+            .timeout_ms = 10s,
+            .partition_opener = &info->GetPartitionOpener(),
+    };
+
+    if (!CreateLogicalPartition(params, scratch_device)) {
+        LOG(ERROR) << "CreateLogicalPartition failed";
+        return false;
+    }
+
+    LOG(INFO) << "Scratch device created successfully: " << *scratch_device << " slot: " << slot;
+    return true;
+}
+
+bool IsScratchOtaMetadataOnSuper() {
+    auto partition_name = android::base::Basename(kOtaMetadataMount);
+    auto source_slot = fs_mgr_get_slot_suffix();
+    auto source_slot_number = SlotNumberForSlotSuffix(source_slot);
+
+    const auto super_device =
+            kPhysicalDevice + fs_mgr_get_super_partition_name(!source_slot_number);
+
+    auto metadata = android::fs_mgr::ReadMetadata(super_device, !source_slot_number);
+    if (!metadata) {
+        return false;
+    }
+    auto partition = android::fs_mgr::FindPartition(*metadata.get(), partition_name);
+    if (!partition) {
+        return false;
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    if (dm.GetState(partition_name) == DmDeviceState::ACTIVE) {
+        LOG(INFO) << "Partition: " << partition_name << " is active";
+        return true;
+    }
+
+    CreateLogicalPartitionParams params = {
+            .block_device = super_device,
+            .metadata = metadata.get(),
+            .partition = partition,
+    };
+
+    std::string scratch_path;
+    if (!CreateLogicalPartition(params, &scratch_path)) {
+        LOG(ERROR) << "Could not create logical partition: " << partition_name;
+        return false;
+    }
+    LOG(INFO) << "Scratch device: " << scratch_path << " created successfully";
+
+    return true;
+}
+
+std::string GetScratchOtaMetadataPartition() {
+    std::string device;
+    auto& dm = DeviceMapper::Instance();
+    auto partition_name = android::base::Basename(kOtaMetadataMount);
+
+    bool invalid_partition = (dm.GetState(partition_name) == DmDeviceState::INVALID);
+    if (!invalid_partition && dm.GetDmDevicePathByName(partition_name, &device)) {
+        return device;
+    }
+    return "";
+}
+
+static bool ScratchAlreadyMounted(const std::string& mount_point) {
+    android::fs_mgr::Fstab fstab;
+    if (!ReadFstabFromProcMounts(&fstab)) {
+        return false;
+    }
+    for (const auto& entry : GetEntriesForMountPoint(&fstab, mount_point)) {
+        if (entry->fs_type == "ext4") {
+            return true;
+        }
+    }
+    return false;
+}
+
+std::string MapScratchOtaMetadataPartition(const std::string& scratch_device) {
+    if (!ScratchAlreadyMounted(kOtaMetadataMount)) {
+        if (!MountScratch(scratch_device)) {
+            return "";
+        }
+    }
+
+    auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota";
+    if (access(ota_dir.c_str(), F_OK) != 0) {
+        return "";
+    }
+    return ota_dir;
+}
+
+// Entry point to create a scratch device on super partition
+// This will create a 1MB space in super. The space will be
+// from the current active slot. Ext4 filesystem will be created
+// on this scratch device and all the OTA related directories
+// will be created.
+bool CreateScratchOtaMetadataOnSuper(const ISnapshotManager::IDeviceInfo* info) {
+    std::string scratch_device;
+
+    if (!CreateDynamicScratch(info, &scratch_device)) {
+        LOG(ERROR) << "CreateDynamicScratch failed";
+        return false;
+    }
+    if (!MakeScratchFilesystem(scratch_device)) {
+        LOG(ERROR) << "MakeScratchFilesystem failed";
+        return false;
+    }
+    if (!MountScratch(scratch_device)) {
+        LOG(ERROR) << "MountScratch failed";
+        return false;
+    }
+    if (!SetupOTADirs()) {
+        LOG(ERROR) << "SetupOTADirs failed";
+        return false;
+    }
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/scratch_super.h b/fs_mgr/libsnapshot/scratch_super.h
new file mode 100644
index 0000000..3e6fe70
--- /dev/null
+++ b/fs_mgr/libsnapshot/scratch_super.h
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+namespace android {
+namespace snapshot {
+
+constexpr char kMkExt4[] = "/system/bin/mke2fs";
+constexpr char kOtaMetadataFileContext[] = "u:object_r:ota_metadata_file:s0";
+constexpr char kOtaMetadataMount[] = "/mnt/scratch_ota_metadata_super";
+const size_t kOtaMetadataPartitionSize = uint64_t(1 * 1024 * 1024);
+constexpr char kPhysicalDevice[] = "/dev/block/by-name/";
+
+bool IsScratchOtaMetadataOnSuper();
+std::string GetScratchOtaMetadataPartition();
+std::string MapScratchOtaMetadataPartition(const std::string& device);
+bool CreateScratchOtaMetadataOnSuper(const ISnapshotManager::IDeviceInfo* info = nullptr);
+bool CleanupScratchOtaMetadataIfPresent(const ISnapshotManager::IDeviceInfo* info = nullptr);
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 265445b..6c3bedd 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -48,6 +48,7 @@
 #include <libsnapshot/snapshot_stats.h>
 #include "device_info.h"
 #include "partition_cow_creator.h"
+#include "scratch_super.h"
 #include "snapshot_metadata_updater.h"
 #include "utility.h"
 
@@ -117,7 +118,11 @@
         info = new DeviceInfo();
     }
 
-    return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
+    auto sm = std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
+    if (info->IsTempMetadata()) {
+        LOG(INFO) << "Using temp metadata from super";
+    }
+    return sm;
 }
 
 std::unique_ptr<SnapshotManager> SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) {
@@ -1110,6 +1115,13 @@
         if (result.state == UpdateState::MergeFailed) {
             AcknowledgeMergeFailure(result.failure_code);
         }
+
+        if (result.state == UpdateState::MergeCompleted) {
+            if (device_->IsTempMetadata()) {
+                CleanupScratchOtaMetadataIfPresent();
+            }
+        }
+
         if (result.state != UpdateState::Merging) {
             // Either there is no merge, or the merge was finished, so no need
             // to keep waiting.
@@ -2310,7 +2322,27 @@
 }
 
 bool SnapshotManager::IsSnapshotManagerNeeded() {
-    return access(kBootIndicatorPath, F_OK) == 0;
+    if (access(kBootIndicatorPath, F_OK) == 0) {
+        return true;
+    }
+
+    if (IsScratchOtaMetadataOnSuper()) {
+        return true;
+    }
+
+    return false;
+}
+
+bool SnapshotManager::MapTempOtaMetadataPartitionIfNeeded(
+        const std::function<bool(const std::string&)>& init) {
+    auto device = android::snapshot::GetScratchOtaMetadataPartition();
+    if (!device.empty()) {
+        init(device);
+        if (android::snapshot::MapScratchOtaMetadataPartition(device).empty()) {
+            return false;
+        }
+    }
+    return true;
 }
 
 std::string SnapshotManager::GetGlobalRollbackIndicatorPath() {
@@ -2397,6 +2429,12 @@
             continue;
         }
 
+        if (GetPartitionName(partition) ==
+            android::base::Basename(android::snapshot::kOtaMetadataMount)) {
+            LOG(INFO) << "Partition: " << GetPartitionName(partition) << " skipping";
+            continue;
+        }
+
         CreateLogicalPartitionParams params = {
                 .block_device = super_device,
                 .metadata = metadata.get(),
@@ -4005,44 +4043,90 @@
         // We allow the wipe to continue, because if we can't mount /metadata,
         // it is unlikely the device would have booted anyway. If there is no
         // metadata partition, then the device predates Virtual A/B.
+        LOG(INFO) << "/metadata not found; allowing wipe.";
         return true;
     }
 
-    // Check this early, so we don't accidentally start trying to populate
-    // the state file in recovery. Note we don't call GetUpdateState since
-    // we want errors in acquiring the lock to be propagated, instead of
-    // returning UpdateState::None.
-    auto state_file = GetStateFilePath();
-    if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) {
-        return true;
-    }
-
-    auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
-    auto super_path = device_->GetSuperDevice(slot_number);
-    if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) {
-        LOG(ERROR) << "Unable to map partitions to complete merge.";
-        return false;
-    }
-
-    auto process_callback = [&]() -> bool {
-        if (callback) {
-            callback();
+    // This could happen if /metadata mounted but there is no filesystem
+    // structure. Weird, but we have to assume there's no OTA pending, and
+    // thus we let the wipe proceed.
+    UpdateState state;
+    {
+        auto lock = LockExclusive();
+        if (!lock) {
+            LOG(ERROR) << "Unable to determine update state; allowing wipe.";
+            return true;
         }
-        return true;
-    };
 
-    in_factory_data_reset_ = true;
-    UpdateState state =
-            ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback);
-    in_factory_data_reset_ = false;
-
-    if (state == UpdateState::MergeFailed) {
-        return false;
+        state = ReadUpdateState(lock.get());
+        LOG(INFO) << "Update state before wipe: " << state << "; slot: " << GetCurrentSlot()
+                  << "; suffix: " << device_->GetSlotSuffix();
     }
 
-    // Nothing should be depending on partitions now, so unmap them all.
-    if (!UnmapAllPartitionsInRecovery()) {
-        LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
+    bool try_merge = false;
+    switch (state) {
+        case UpdateState::None:
+        case UpdateState::Initiated:
+            LOG(INFO) << "Wipe is not impacted by update state; allowing wipe.";
+            break;
+        case UpdateState::Unverified:
+            if (GetCurrentSlot() != Slot::Target) {
+                LOG(INFO) << "Wipe is not impacted by rolled back update; allowing wipe";
+                break;
+            }
+            if (!HasForwardMergeIndicator()) {
+                auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+                auto other_slot_number = SlotNumberForSlotSuffix(device_->GetOtherSlotSuffix());
+
+                // We're not allowed to forward merge, so forcefully rollback the
+                // slot switch.
+                LOG(INFO) << "Allowing wipe due to lack of forward merge indicator; reverting to "
+                             "old slot since update will be deleted.";
+                device_->SetSlotAsUnbootable(slot_number);
+                device_->SetActiveBootSlot(other_slot_number);
+                break;
+            }
+
+            // Forward merge indicator means we have to mount snapshots and try to merge.
+            LOG(INFO) << "Forward merge indicator is present.";
+            try_merge = true;
+            break;
+        case UpdateState::Merging:
+        case UpdateState::MergeFailed:
+            try_merge = true;
+            break;
+        case UpdateState::MergeNeedsReboot:
+        case UpdateState::Cancelled:
+            LOG(INFO) << "Unexpected update state in recovery; allowing wipe.";
+            break;
+        default:
+            break;
+    }
+
+    if (try_merge) {
+        auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+        auto super_path = device_->GetSuperDevice(slot_number);
+        if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) {
+            LOG(ERROR) << "Unable to map partitions to complete merge.";
+            return false;
+        }
+
+        auto process_callback = [&]() -> bool {
+            if (callback) {
+                callback();
+            }
+            return true;
+        };
+
+        state = ProcessUpdateStateOnDataWipe(process_callback);
+        if (state == UpdateState::MergeFailed) {
+            return false;
+        }
+
+        // Nothing should be depending on partitions now, so unmap them all.
+        if (!UnmapAllPartitionsInRecovery()) {
+            LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
+        }
     }
 
     if (state != UpdateState::None) {
@@ -4088,58 +4172,40 @@
     return true;
 }
 
-UpdateState SnapshotManager::ProcessUpdateStateOnDataWipe(bool allow_forward_merge,
-                                                          const std::function<bool()>& callback) {
-    auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
-    UpdateState state = ProcessUpdateState(callback);
-    LOG(INFO) << "Update state in recovery: " << state;
-    switch (state) {
-        case UpdateState::MergeFailed:
-            LOG(ERROR) << "Unrecoverable merge failure detected.";
-            return state;
-        case UpdateState::Unverified: {
-            // If an OTA was just applied but has not yet started merging:
-            //
-            // - if forward merge is allowed, initiate merge and call
-            // ProcessUpdateState again.
-            //
-            // - if forward merge is not allowed, we
-            // have no choice but to revert slots, because the current slot will
-            // immediately become unbootable. Rather than wait for the device
-            // to reboot N times until a rollback, we proactively disable the
-            // new slot instead.
-            //
-            // Since the rollback is inevitable, we don't treat a HAL failure
-            // as an error here.
-            auto slot = GetCurrentSlot();
-            if (slot == Slot::Target) {
-                if (allow_forward_merge &&
-                    access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0) {
-                    LOG(INFO) << "Forward merge allowed, initiating merge now.";
-
-                    if (!InitiateMerge()) {
-                        LOG(ERROR) << "Failed to initiate merge on data wipe.";
-                        return UpdateState::MergeFailed;
-                    }
-                    return ProcessUpdateStateOnDataWipe(false /* allow_forward_merge */, callback);
+UpdateState SnapshotManager::ProcessUpdateStateOnDataWipe(const std::function<bool()>& callback) {
+    while (true) {
+        UpdateState state = ProcessUpdateState(callback);
+        LOG(INFO) << "Processed updated state in recovery: " << state;
+        switch (state) {
+            case UpdateState::MergeFailed:
+                LOG(ERROR) << "Unrecoverable merge failure detected.";
+                return state;
+            case UpdateState::Unverified: {
+                // Unverified was already handled earlier, in HandleImminentDataWipe,
+                // but it will fall through here if a forward merge is required.
+                //
+                // If InitiateMerge fails, we early return. If it succeeds, then we
+                // are guaranteed that the next call to ProcessUpdateState will not
+                // return Unverified.
+                if (!InitiateMerge()) {
+                    LOG(ERROR) << "Failed to initiate merge on data wipe.";
+                    return UpdateState::MergeFailed;
                 }
-
-                LOG(ERROR) << "Reverting to old slot since update will be deleted.";
-                device_->SetSlotAsUnbootable(slot_number);
-            } else {
-                LOG(INFO) << "Booting from " << slot << " slot, no action is taken.";
+                continue;
             }
-            break;
+            case UpdateState::MergeNeedsReboot:
+                // We shouldn't get here, because nothing is depending on
+                // logical partitions.
+                LOG(ERROR) << "Unexpected merge-needs-reboot state in recovery.";
+                return state;
+            default:
+                return state;
         }
-        case UpdateState::MergeNeedsReboot:
-            // We shouldn't get here, because nothing is depending on
-            // logical partitions.
-            LOG(ERROR) << "Unexpected merge-needs-reboot state in recovery.";
-            break;
-        default:
-            break;
     }
-    return state;
+}
+
+bool SnapshotManager::HasForwardMergeIndicator() {
+    return access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0;
 }
 
 bool SnapshotManager::EnsureNoOverflowSnapshot(LockedFile* lock) {
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index d66490c..46c3a35 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -47,6 +47,7 @@
 #include <android/snapshot/snapshot.pb.h>
 #include <libsnapshot/test_helpers.h>
 #include "partition_cow_creator.h"
+#include "scratch_super.h"
 #include "utility.h"
 
 // Mock classes are not used. Header included to ensure mocked class definition aligns with the
@@ -1342,6 +1343,15 @@
     DynamicPartitionGroup* group_ = nullptr;
 };
 
+TEST_F(SnapshotUpdateTest, SuperOtaMetadataTest) {
+    auto info = new TestDeviceInfo(fake_super);
+    ASSERT_TRUE(CreateScratchOtaMetadataOnSuper(info));
+    std::string scratch_device = GetScratchOtaMetadataPartition();
+    ASSERT_NE(scratch_device, "");
+    ASSERT_NE(MapScratchOtaMetadataPartition(scratch_device), "");
+    ASSERT_TRUE(CleanupScratchOtaMetadataIfPresent(info));
+}
+
 // Test full update flow executed by update_engine. Some partitions uses super empty space,
 // some uses images, and some uses both.
 // Also test UnmapUpdateSnapshot unmaps everything.
@@ -2102,10 +2112,10 @@
     test_device->set_recovery(true);
     auto new_sm = NewManagerForFirstStageMount(test_device);
 
+    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified);
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     // Manually mount metadata so that we can call GetUpdateState() below.
     MountMetadata();
-    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
     EXPECT_TRUE(test_device->IsSlotUnbootable(1));
     EXPECT_FALSE(test_device->IsSlotUnbootable(0));
 }
@@ -2127,6 +2137,7 @@
     test_device->set_recovery(true);
     auto new_sm = NewManagerForFirstStageMount(test_device);
 
+    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified);
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
     EXPECT_FALSE(test_device->IsSlotUnbootable(0));
@@ -2135,10 +2146,6 @@
 
 // Test update package that requests data wipe.
 TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) {
-    if (ShouldSkipLegacyMerging()) {
-        GTEST_SKIP() << "Skipping legacy merge in test";
-    }
-
     AddOperationForPartitions();
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
@@ -2157,6 +2164,7 @@
     test_device->set_recovery(true);
     auto new_sm = NewManagerForFirstStageMount(test_device);
 
+    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified);
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     // Manually mount metadata so that we can call GetUpdateState() below.
     MountMetadata();
@@ -2178,10 +2186,6 @@
 
 // Test update package that requests data wipe.
 TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) {
-    if (ShouldSkipLegacyMerging()) {
-        GTEST_SKIP() << "Skipping legacy merge in test";
-    }
-
     AddOperationForPartitions();
 
     // Execute the update.
@@ -2222,6 +2226,7 @@
     test_device->set_recovery(true);
     auto new_sm = NewManagerForFirstStageMount(test_device);
 
+    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified);
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     // Manually mount metadata so that we can call GetUpdateState() below.
     MountMetadata();
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index 0158d4d..23c3ccf 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -16,7 +16,6 @@
 
 #include <sysexits.h>
 #include <unistd.h>
-
 #include <chrono>
 #include <filesystem>
 #include <fstream>
@@ -46,6 +45,7 @@
 #include <storage_literals/storage_literals.h>
 
 #include "partition_cow_creator.h"
+#include "scratch_super.h"
 
 #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
 #include <BootControlClient.h>
@@ -57,6 +57,8 @@
 using android::base::LogdLogger;
 using android::base::StderrLogger;
 using android::base::TeeLogger;
+using namespace android::dm;
+using namespace android::fs_mgr;
 using android::fs_mgr::CreateLogicalPartitionParams;
 using android::fs_mgr::FindPartition;
 using android::fs_mgr::GetPartitionSize;
@@ -97,7 +99,7 @@
 #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
 class MapSnapshots {
   public:
-    MapSnapshots(std::string path = "");
+    MapSnapshots(std::string path = "", bool metadata_super = false);
     bool CreateSnapshotDevice(std::string& partition_name, std::string& patch);
     bool InitiateThreadedSnapshotWrite(std::string& pname, std::string& snapshot_patch);
     bool FinishSnapshotWrites();
@@ -122,15 +124,12 @@
 
     std::vector<std::string> patchfiles_;
     chromeos_update_engine::DeltaArchiveManifest manifest_;
+    bool metadata_super_ = false;
 };
 
-MapSnapshots::MapSnapshots(std::string path) {
-    sm_ = SnapshotManager::New();
-    if (!sm_) {
-        std::cout << "Failed to create snapshotmanager";
-        exit(1);
-    }
+MapSnapshots::MapSnapshots(std::string path, bool metadata_super) {
     snapshot_dir_path_ = path + "/";
+    metadata_super_ = metadata_super;
 }
 
 std::string MapSnapshots::GetGroupName(const android::fs_mgr::LpMetadata& pt,
@@ -150,6 +149,12 @@
 }
 
 bool MapSnapshots::PrepareUpdate() {
+    if (metadata_super_ && !CreateScratchOtaMetadataOnSuper()) {
+        LOG(ERROR) << "Failed to create OTA metadata on super";
+        return false;
+    }
+    sm_ = SnapshotManager::New();
+
     auto source_slot = fs_mgr_get_slot_suffix();
     auto source_slot_number = SlotNumberForSlotSuffix(source_slot);
     auto super_source = fs_mgr_get_super_partition_name(source_slot_number);
@@ -234,14 +239,22 @@
 
 bool MapSnapshots::GetCowDevicePath(std::string partition_name, std::string* cow_path) {
     auto& dm = android::dm::DeviceMapper::Instance();
-    std::string cow_device = partition_name + "-cow";
+
+    std::string cow_device = partition_name + "-cow-img";
+    if (metadata_super_) {
+        // If COW device exists on /data, then data wipe cannot be done.
+        if (dm.GetDmDevicePathByName(cow_device, cow_path)) {
+            LOG(ERROR) << "COW device exists on /data: " << *cow_path;
+            return false;
+        }
+    }
+
+    cow_device = partition_name + "-cow";
     if (dm.GetDmDevicePathByName(cow_device, cow_path)) {
         return true;
     }
 
     LOG(INFO) << "Failed to find cow path: " << cow_device << " Checking the device for -img path";
-    // If the COW device exists only on /data
-    cow_device = partition_name + "-cow-img";
     if (!dm.GetDmDevicePathByName(cow_device, cow_path)) {
         LOG(ERROR) << "Failed to cow path: " << cow_device;
         return false;
@@ -321,6 +334,12 @@
 }
 
 bool MapSnapshots::BeginUpdate() {
+    if (metadata_super_ && !CreateScratchOtaMetadataOnSuper()) {
+        LOG(ERROR) << "Failed to create OTA metadata on super";
+        return false;
+    }
+    sm_ = SnapshotManager::New();
+
     lock_ = sm_->LockExclusive();
     std::vector<std::string> snapshots;
     sm_->ListSnapshots(lock_.get(), &snapshots);
@@ -470,10 +489,12 @@
 }
 
 bool MapSnapshots::UnmapCowImagePath(std::string& name) {
+    sm_ = SnapshotManager::New();
     return sm_->UnmapCowImage(name);
 }
 
 bool MapSnapshots::DeleteSnapshots() {
+    sm_ = SnapshotManager::New();
     lock_ = sm_->LockExclusive();
     if (!sm_->RemoveAllUpdateState(lock_.get())) {
         LOG(ERROR) << "Remove All Update State failed";
@@ -583,13 +604,19 @@
     }
 
     if (argc < 3) {
-        std::cerr << " apply-update <directory location where snapshot patches are present>"
+        std::cerr << " apply-update <directory location where snapshot patches are present> {-w}"
                      "    Apply the snapshots to the COW block device\n";
         return false;
     }
 
     std::string path = std::string(argv[2]);
-    MapSnapshots cow(path);
+    bool metadata_on_super = false;
+    if (argc == 4) {
+        if (std::string(argv[3]) == "-w") {
+            metadata_on_super = true;
+        }
+    }
+    MapSnapshots cow(path, metadata_on_super);
     if (!cow.ApplyUpdate()) {
         return false;
     }
@@ -607,7 +634,7 @@
     }
 
     if (argc < 3) {
-        std::cerr << " map-snapshots <directory location where snapshot patches are present>"
+        std::cerr << " map-snapshots <directory location where snapshot patches are present> {-w}"
                      "    Map all snapshots based on patches present in the directory\n";
         return false;
     }
@@ -638,7 +665,14 @@
         }
     }
 
-    MapSnapshots cow(path);
+    bool metadata_on_super = false;
+    if (argc == 4) {
+        if (std::string(argv[3]) == "-w") {
+            metadata_on_super = true;
+        }
+    }
+
+    MapSnapshots cow(path, metadata_on_super);
     if (!cow.BeginUpdate()) {
         LOG(ERROR) << "BeginUpdate failed";
         return false;
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index efbcb5a..8d0bf7d 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -42,6 +42,7 @@
     static_libs: [
         "libcutils_sockets",
         "libfs_mgr_file_wait",
+        "libdm",
     ],
     shared_libs: [
         "libbase",
@@ -238,9 +239,19 @@
     test_options: {
         min_shipping_api_level: 30,
     },
+
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
     auto_gen_config: true,
     require_root: true,
-    compile_multilib: "first",
 }
 
 cc_test {
@@ -248,7 +259,7 @@
     defaults: ["snapuserd_test_defaults"],
     host_supported: true,
     test_suites: [
-        "device-tests",
+        "general-tests",
     ],
     test_options: {
         test_runner_options: [
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 789c980..ddefb9f 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -35,6 +35,7 @@
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <fs_mgr/file_wait.h>
+#include <libdm/dm.h>
 #include <snapuserd/snapuserd_client.h>
 
 namespace android {
@@ -333,7 +334,21 @@
 }
 
 std::string SnapuserdClient::GetDaemonAliveIndicatorPath() {
-    return "/metadata/ota/" + std::string(kDaemonAliveIndicator);
+    std::string metadata_dir;
+    std::string temp_metadata_mnt = "/mnt/scratch_ota_metadata_super";
+
+    auto& dm = ::android::dm::DeviceMapper::Instance();
+    auto partition_name = android::base::Basename(temp_metadata_mnt);
+
+    bool invalid_partition = (dm.GetState(partition_name) == dm::DmDeviceState::INVALID);
+    std::string temp_device;
+    if (!invalid_partition && dm.GetDmDevicePathByName(partition_name, &temp_device)) {
+        metadata_dir = temp_metadata_mnt + "/" + "ota/";
+    } else {
+        metadata_dir = "/metadata/ota/";
+    }
+
+    return metadata_dir + std::string(kDaemonAliveIndicator);
 }
 
 bool SnapuserdClient::IsTransitionedDaemonReady() {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
index 04b2736..378d809 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
@@ -57,7 +57,7 @@
     bool ReadFromSourceDevice(const CowOperation* cow_op, void* buffer);
     bool ReadDataFromBaseDevice(sector_t sector, void* buffer, size_t read_size);
 
-    constexpr bool IsBlockAligned(size_t size) { return ((size & (BLOCK_SZ - 1)) == 0); }
+    constexpr bool IsBlockAligned(uint64_t size) { return ((size & (BLOCK_SZ - 1)) == 0); }
     constexpr sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
     constexpr chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
 
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 c0af5c5..013df35 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -22,6 +22,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/socket.h>
+#include <sys/system_properties.h>
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -35,9 +36,6 @@
 #include <snapuserd/snapuserd_client.h>
 #include "snapuserd_server.h"
 
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
-
 namespace android {
 namespace snapshot {
 
diff --git a/fs_mgr/tests/AndroidTest.xml b/fs_mgr/tests/AndroidTest.xml
index de835b3..1c06ebd 100644
--- a/fs_mgr/tests/AndroidTest.xml
+++ b/fs_mgr/tests/AndroidTest.xml
@@ -16,6 +16,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user_on_secondary_display" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="CtsFsMgrTestCases->/data/local/tmp/CtsFsMgrTestCases" />
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 322bf1b..bd3d6b5 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -1062,23 +1062,6 @@
             << "Default fstab doesn't contain /data entry";
 }
 
-TEST(fs_mgr, UserdataMountedFromDefaultFstab) {
-    if (getuid() != 0) {
-        GTEST_SKIP() << "Must be run as root.";
-        return;
-    }
-    Fstab fstab;
-    ASSERT_TRUE(ReadDefaultFstab(&fstab)) << "Failed to read default fstab";
-    Fstab proc_mounts;
-    ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &proc_mounts)) << "Failed to read /proc/mounts";
-    auto mounted_entry = GetEntryForMountPoint(&proc_mounts, "/data");
-    ASSERT_NE(mounted_entry, nullptr) << "/data is not mounted";
-    std::string block_device;
-    ASSERT_TRUE(android::base::Realpath(mounted_entry->blk_device, &block_device));
-    ASSERT_NE(nullptr, fs_mgr_get_mounted_entry_for_userdata(&fstab, block_device))
-            << "/data wasn't mounted from default fstab";
-}
-
 TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Readahead_Size_KB) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
diff --git a/init/Android.bp b/init/Android.bp
index ffb6380..18a79d6 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -38,7 +38,6 @@
     "capabilities.cpp",
     "epoll.cpp",
     "import_parser.cpp",
-    "interface_utils.cpp",
     "interprocess_fifo.cpp",
     "keychords.cpp",
     "parser.cpp",
@@ -85,10 +84,6 @@
     "ueventd.cpp",
     "ueventd_parser.cpp",
 ]
-init_host_sources = [
-    "check_builtins.cpp",
-    "host_import_parser.cpp",
-]
 
 soong_config_module_type {
     name: "libinit_cc_defaults",
@@ -190,7 +185,6 @@
         "libext4_utils",
         "libfs_mgr",
         "libgsi",
-        "libhidl-gen-utils",
         "liblog",
         "liblogwrap",
         "liblp",
@@ -226,7 +220,6 @@
     ],
     whole_static_libs: [
         "libcap",
-        "libcom.android.sysprop.init",
     ],
     header_libs: ["bootimg_headers"],
     proto: {
@@ -608,8 +601,6 @@
     whole_static_libs: ["libcap"],
     shared_libs: [
         "libcutils",
-        "libhidl-gen-utils",
-        "libhidlmetadata",
         "liblog",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
@@ -617,9 +608,6 @@
     proto: {
         type: "lite",
     },
-    generated_headers: [
-        "generated_stub_builtin_function_map",
-    ],
     target: {
         android: {
             enabled: false,
@@ -638,17 +626,43 @@
 cc_binary {
     name: "host_init_verifier",
     defaults: ["init_host_defaults"],
-    srcs: ["host_init_verifier.cpp"] + init_common_sources + init_host_sources,
+    srcs: [
+        "check_builtins.cpp",
+        "host_import_parser.cpp",
+        "host_init_verifier.cpp",
+        "interface_utils.cpp",
+    ] + init_common_sources,
     generated_headers: [
         "generated_android_ids",
+        "generated_stub_builtin_function_map",
     ],
+    shared_libs: [
+        "libhidl-gen-utils",
+        "libhidlmetadata",
+    ],
+}
+
+genrule {
+    name: "noop_builtin_function_map",
+    tool_files: ["host_builtin_map.py"],
+    out: ["noop_builtin_function_map.h"],
+    srcs: [
+        "builtins.cpp",
+        "noop_builtins.cpp",
+    ],
+    cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location noop_builtins.cpp) > $(out)",
 }
 
 cc_library_host_static {
     name: "libinit_host",
     defaults: ["init_host_defaults"],
-    srcs: init_common_sources + init_host_sources,
+    srcs: [
+        "noop_builtins.cpp",
+    ] + init_common_sources,
     export_include_dirs: ["."],
+    generated_headers: [
+        "noop_builtin_function_map",
+    ],
     proto: {
         export_proto_headers: true,
     },
diff --git a/init/README.md b/init/README.md
index 0bb26e8..1e15765 100644
--- a/init/README.md
+++ b/init/README.md
@@ -636,7 +636,7 @@
   Properties are expanded within _level_.
 
 `mark_post_data`
-> Used to mark the point right after /data is mounted.
+> (This action is deprecated and no-op.)
 
 `mkdir <path> [<mode>] [<owner>] [<group>] [encryption=<action>] [key=<key>]`
 > Create a directory at _path_, optionally with the given mode, owner, and
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 606ea8c..3f3bec6 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -46,7 +46,6 @@
 #include <map>
 #include <memory>
 
-#include <InitProperties.sysprop.h>
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -606,8 +605,6 @@
     return Error() << "Invalid code: " << code;
 }
 
-static int initial_mount_fstab_return_code = -1;
-
 /* <= Q: mount_all <fstab> [ <path> ]* [--<options>]*
  * >= R: mount_all [ <fstab> ] [--<options>]*
  *
@@ -648,19 +645,10 @@
         import_late(mount_all->rc_paths);
     }
 
-    if (mount_fstab_result.userdata_mounted) {
-        // This call to fs_mgr_mount_all mounted userdata. Keep the result in
-        // order for userspace reboot to correctly remount userdata.
-        LOG(INFO) << "Userdata mounted using "
-                  << (mount_all->fstab_path.empty() ? "(default fstab)" : mount_all->fstab_path)
-                  << " result : " << mount_fstab_result.code;
-        initial_mount_fstab_return_code = mount_fstab_result.code;
-    }
-
     if (queue_event) {
         /* queue_fs_event will queue event based on mount_fstab return code
          * and return processed return code*/
-        auto queue_fs_result = queue_fs_event(mount_fstab_result.code);
+        auto queue_fs_result = queue_fs_event(mount_fstab_result);
         if (!queue_fs_result.ok()) {
             return Error() << "queue_fs_event() failed: " << queue_fs_result.error();
         }
@@ -1148,29 +1136,19 @@
 }
 
 static Result<void> ExecVdcRebootOnFailure(const std::string& vdc_arg) {
-    bool should_reboot_into_recovery = true;
     auto reboot_reason = vdc_arg + "_failed";
-    if (android::sysprop::InitProperties::userspace_reboot_in_progress().value_or(false)) {
-        should_reboot_into_recovery = false;
-        reboot_reason = "userspace_failed," + vdc_arg;
-    }
 
-    auto reboot = [reboot_reason, should_reboot_into_recovery](const std::string& message) {
+    auto reboot = [reboot_reason](const std::string& message) {
         // TODO (b/122850122): support this in gsi
-        if (should_reboot_into_recovery) {
-            if (IsFbeEnabled() && !android::gsi::IsGsiRunning()) {
-                LOG(ERROR) << message << ": Rebooting into recovery, reason: " << reboot_reason;
-                if (auto result = reboot_into_recovery(
-                            {"--prompt_and_wipe_data", "--reason="s + reboot_reason});
-                    !result.ok()) {
-                    LOG(FATAL) << "Could not reboot into recovery: " << result.error();
-                }
-            } else {
-                LOG(ERROR) << "Failure (reboot suppressed): " << reboot_reason;
+        if (IsFbeEnabled() && !android::gsi::IsGsiRunning()) {
+            LOG(ERROR) << message << ": Rebooting into recovery, reason: " << reboot_reason;
+            if (auto result = reboot_into_recovery(
+                        {"--prompt_and_wipe_data", "--reason="s + reboot_reason});
+                !result.ok()) {
+                LOG(FATAL) << "Could not reboot into recovery: " << result.error();
             }
         } else {
-            LOG(ERROR) << message << ": rebooting, reason: " << reboot_reason;
-            trigger_shutdown("reboot," + reboot_reason);
+            LOG(ERROR) << "Failure (reboot suppressed): " << reboot_reason;
         }
     };
 
@@ -1178,29 +1156,6 @@
     return ExecWithFunctionOnFailure(args, reboot);
 }
 
-static Result<void> do_remount_userdata(const BuiltinArguments& args) {
-    if (initial_mount_fstab_return_code == -1) {
-        return Error() << "Calling remount_userdata too early";
-    }
-    Fstab fstab;
-    if (!ReadDefaultFstab(&fstab)) {
-        // TODO(b/135984674): should we reboot here?
-        return Error() << "Failed to read fstab";
-    }
-    // TODO(b/135984674): check that fstab contains /data.
-    if (auto rc = fs_mgr_remount_userdata_into_checkpointing(&fstab); rc < 0) {
-        std::string proc_mounts_output;
-        android::base::ReadFileToString("/proc/mounts", &proc_mounts_output, true);
-        android::base::WriteStringToFile(proc_mounts_output,
-                                         "/metadata/userspacereboot/mount_info.txt");
-        trigger_shutdown("reboot,mount_userdata_failed");
-    }
-    if (auto result = queue_fs_event(initial_mount_fstab_return_code); !result.ok()) {
-        return Error() << "queue_fs_event() failed: " << result.error();
-    }
-    return {};
-}
-
 static Result<void> do_installkey(const BuiltinArguments& args) {
     if (!is_file_crypto()) return {};
 
@@ -1216,8 +1171,7 @@
 }
 
 static Result<void> do_mark_post_data(const BuiltinArguments& args) {
-    ServiceList::GetInstance().MarkPostData();
-
+    LOG(INFO) << "deprecated action `mark_post_data` called.";
     return {};
 }
 
@@ -1361,7 +1315,6 @@
         {"umount_all",              {0,     1,    {false,  do_umount_all}}},
         {"update_linker_config",    {0,     0,    {false,  do_update_linker_config}}},
         {"readahead",               {1,     2,    {true,   do_readahead}}},
-        {"remount_userdata",        {0,     0,    {false,  do_remount_userdata}}},
         {"restart",                 {1,     2,    {false,  do_restart}}},
         {"restorecon",              {1,     kMax, {true,   do_restorecon}}},
         {"restorecon_recursive",    {1,     kMax, {true,   do_restorecon_recursive}}},
diff --git a/init/devices.cpp b/init/devices.cpp
index 5560c20..f2bb9d2 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -283,7 +283,7 @@
 
 void DeviceHandler::MakeDevice(const std::string& path, bool block, int major, int minor,
                                const std::vector<std::string>& links) const {
-    auto[mode, uid, gid] = GetDevicePermissions(path, links);
+    auto [mode, uid, gid] = GetDevicePermissions(path, links);
     mode |= (block ? S_IFBLK : S_IFCHR);
 
     std::string secontext;
@@ -330,11 +330,11 @@
             if (gid != s.st_gid) {
                 new_group = gid;
             }
-        if (mode != s.st_mode) {
-            if (chmod(path.c_str(), mode) != 0) {
-                PLOG(ERROR) << "Cannot chmod " << path << " to " << mode;
+            if (mode != s.st_mode) {
+                if (chmod(path.c_str(), mode) != 0) {
+                    PLOG(ERROR) << "Cannot chmod " << path << " to " << mode;
+                }
             }
-        }
         } else {
             PLOG(ERROR) << "Cannot stat " << path;
         }
@@ -531,7 +531,7 @@
         if (!ReadFileToString(boot_id_path, &boot_id)) {
             PLOG(ERROR) << "Cannot duplicate ashmem device node. Failed to read " << boot_id_path;
             return;
-        };
+        }
         boot_id = Trim(boot_id);
 
         Uevent dup_ashmem_uevent = uevent;
@@ -542,10 +542,10 @@
 }
 
 void DeviceHandler::HandleUevent(const Uevent& uevent) {
-  if (uevent.action == "add" || uevent.action == "change" ||
-      uevent.action == "bind" || uevent.action == "online") {
-    FixupSysPermissions(uevent.path, uevent.subsystem);
-  }
+    if (uevent.action == "add" || uevent.action == "change" || uevent.action == "bind" ||
+        uevent.action == "online") {
+        FixupSysPermissions(uevent.path, uevent.subsystem);
+    }
 
     // if it's not a /dev device, nothing to do
     if (uevent.major < 0 || uevent.minor < 0) return;
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index bfe636b..e06a645 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -38,6 +38,7 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android/avf_cc_flags.h>
+#include <fs_mgr.h>
 #include <modprobe/modprobe.h>
 #include <private/android_filesystem_config.h>
 
@@ -303,6 +304,22 @@
     return BootMode::NORMAL_MODE;
 }
 
+static void MaybeResumeFromHibernation(const std::string& bootconfig) {
+    std::string hibernationResumeDevice;
+    android::fs_mgr::GetBootconfigFromString(bootconfig, "androidboot.hibernation_resume_device",
+                                             &hibernationResumeDevice);
+    if (!hibernationResumeDevice.empty()) {
+        android::base::unique_fd fd(open("/sys/power/resume", O_RDWR | O_CLOEXEC));
+        if (fd >= 0) {
+            if (!android::base::WriteStringToFd(hibernationResumeDevice, fd)) {
+                PLOG(ERROR) << "Failed to write to /sys/power/resume";
+            }
+        } else {
+            PLOG(ERROR) << "Failed to open /sys/power/resume";
+        }
+    }
+}
+
 static std::unique_ptr<FirstStageMount> CreateFirstStageMount(const std::string& cmdline) {
     auto ret = FirstStageMount::Create(cmdline);
     if (ret.ok()) {
@@ -442,6 +459,8 @@
                   << module_elapse_time.count() << " ms";
     }
 
+    MaybeResumeFromHibernation(bootconfig);
+
     std::unique_ptr<FirstStageMount> fsm;
 
     bool created_devices = false;
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 927b45f..ece430b 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -371,6 +371,14 @@
     }
 
     if (SnapshotManager::IsSnapshotManagerNeeded()) {
+        auto init_devices = [this](const std::string& device) -> bool {
+            if (android::base::StartsWith(device, "/dev/block/dm-")) {
+                return block_dev_init_.InitDmDevice(device);
+            }
+            return block_dev_init_.InitDevices({device});
+        };
+
+        SnapshotManager::MapTempOtaMetadataPartitionIfNeeded(init_devices);
         auto sm = SnapshotManager::NewForFirstStageMount();
         if (!sm) {
             return false;
diff --git a/init/fuzzer/Android.bp b/init/fuzzer/Android.bp
index 5823932..8cfd597 100644
--- a/init/fuzzer/Android.bp
+++ b/init/fuzzer/Android.bp
@@ -30,7 +30,6 @@
     shared_libs: [
         "libbase",
         "libfs_mgr",
-        "libhidl-gen-utils",
         "liblog",
         "libprocessgroup",
         "libselinux",
@@ -49,7 +48,6 @@
     srcs: [
         "init_parser_fuzzer.cpp",
     ],
-    shared_libs: ["libhidlmetadata",],
     defaults: [
         "libinit_fuzzer_defaults",
     ],
diff --git a/init/fuzzer/init_parser_fuzzer.cpp b/init/fuzzer/init_parser_fuzzer.cpp
index dc76465..21b04f4 100644
--- a/init/fuzzer/init_parser_fuzzer.cpp
+++ b/init/fuzzer/init_parser_fuzzer.cpp
@@ -15,9 +15,7 @@
  */
 
 #include <fuzzer/FuzzedDataProvider.h>
-#include <hidl/metadata.h>
 #include <import_parser.h>
-#include <interface_utils.h>
 #include <rlimit_parser.h>
 
 using namespace android;
@@ -34,7 +32,6 @@
 };
 
 const int32_t kMaxBytes = 256;
-const std::string kValidInterfaces = "android.frameworks.vr.composer@2.0::IVrComposerClient";
 
 class InitParserFuzzer {
   public:
@@ -44,9 +41,6 @@
   private:
     void InvokeParser();
     void InvokeLimitParser();
-    void InvokeInterfaceUtils();
-    InterfaceInheritanceHierarchyMap GenerateHierarchyMap();
-    std::vector<HidlInterfaceMetadata> GenerateInterfaceMetadata();
 
     FuzzedDataProvider fdp_;
 };
@@ -64,60 +58,6 @@
     }
 }
 
-std::vector<HidlInterfaceMetadata> InitParserFuzzer::GenerateInterfaceMetadata() {
-    std::vector<HidlInterfaceMetadata> random_interface;
-    for (size_t idx = 0; idx < fdp_.ConsumeIntegral<size_t>(); ++idx) {
-        HidlInterfaceMetadata metadata;
-        metadata.name = fdp_.ConsumeRandomLengthString(kMaxBytes);
-        for (size_t idx1 = 0; idx1 < fdp_.ConsumeIntegral<size_t>(); ++idx1) {
-            metadata.inherited.push_back(fdp_.ConsumeRandomLengthString(kMaxBytes));
-        }
-        random_interface.push_back(metadata);
-    }
-    return random_interface;
-}
-
-InterfaceInheritanceHierarchyMap InitParserFuzzer::GenerateHierarchyMap() {
-    InterfaceInheritanceHierarchyMap result;
-    std::vector<HidlInterfaceMetadata> random_interface;
-    if (fdp_.ConsumeBool()) {
-        random_interface = GenerateInterfaceMetadata();
-    } else {
-        random_interface = HidlInterfaceMetadata::all();
-    }
-
-    for (const HidlInterfaceMetadata& iface : random_interface) {
-        std::set<FQName> inherited_interfaces;
-        for (const std::string& intf : iface.inherited) {
-            FQName fqname;
-            (void)fqname.setTo(intf);
-            inherited_interfaces.insert(fqname);
-        }
-        FQName fqname;
-        (void)fqname.setTo(iface.name);
-        result[fqname] = inherited_interfaces;
-    }
-    return result;
-}
-
-void InitParserFuzzer::InvokeInterfaceUtils() {
-    InterfaceInheritanceHierarchyMap hierarchy_map = GenerateHierarchyMap();
-    SetKnownInterfaces(hierarchy_map);
-    IsKnownInterface(fdp_.ConsumeRandomLengthString(kMaxBytes));
-    std::set<std::string> interface_set;
-    for (size_t idx = 0; idx < fdp_.ConsumeIntegral<size_t>(); ++idx) {
-        auto set_interface_values = fdp_.PickValueInArray<const std::function<void()>>({
-                [&]() {
-                    interface_set.insert(("aidl/" + fdp_.ConsumeRandomLengthString(kMaxBytes)));
-                },
-                [&]() { interface_set.insert(fdp_.ConsumeRandomLengthString(kMaxBytes)); },
-                [&]() { interface_set.insert(kValidInterfaces); },
-        });
-        set_interface_values();
-    }
-    CheckInterfaceInheritanceHierarchy(interface_set, hierarchy_map);
-}
-
 void InitParserFuzzer::InvokeParser() {
     Parser parser;
     std::string name = fdp_.ConsumeBool() ? fdp_.ConsumeRandomLengthString(kMaxBytes) : "import";
@@ -132,7 +72,6 @@
     while (fdp_.remaining_bytes()) {
         auto invoke_parser_fuzzer = fdp_.PickValueInArray<const std::function<void()>>({
                 [&]() { InvokeParser(); },
-                [&]() { InvokeInterfaceUtils(); },
                 [&]() { InvokeLimitParser(); },
         });
         invoke_parser_fuzzer();
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index f746ab9..287857a 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -297,9 +297,7 @@
     ActionManager& am = ActionManager::GetInstance();
     ServiceList& sl = ServiceList::GetInstance();
     Parser parser;
-    parser.AddSectionParser("service",
-                            std::make_unique<ServiceParser>(&sl, GetSubcontext(),
-                                                            *interface_inheritance_hierarchy_map));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sl, GetSubcontext()));
     parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, GetSubcontext()));
     parser.AddSectionParser("import", std::make_unique<HostImportParser>());
 
@@ -317,11 +315,23 @@
             return EXIT_FAILURE;
         }
     }
+
     size_t failures = parser.parse_error_count() + am.CheckAllCommands() + sl.CheckAllCommands();
     if (failures > 0) {
         LOG(ERROR) << "Failed to parse init scripts with " << failures << " error(s).";
         return EXIT_FAILURE;
     }
+
+    for (const auto& service : sl) {
+        if (const auto& result = CheckInterfaceInheritanceHierarchy(
+                    service->interfaces(), *interface_inheritance_hierarchy_map);
+            !result.ok()) {
+            LOG(ERROR) << service->filename() << ": invalid interface in service '"
+                       << service->name() << "': " << result.error();
+            return EXIT_FAILURE;
+        }
+    }
+
     return EXIT_SUCCESS;
 }
 
diff --git a/init/init.cpp b/init/init.cpp
index 19e909f..6c80899 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -26,13 +26,11 @@
 #include <sys/eventfd.h>
 #include <sys/mount.h>
 #include <sys/signalfd.h>
+#include <sys/system_properties.h>
 #include <sys/types.h>
 #include <sys/utsname.h>
 #include <unistd.h>
 
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
-
 #include <filesystem>
 #include <fstream>
 #include <functional>
@@ -270,8 +268,8 @@
 Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
     Parser parser;
 
-    parser.AddSectionParser("service", std::make_unique<ServiceParser>(
-                                               &service_list, GetSubcontext(), std::nullopt));
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, GetSubcontext()));
     parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
     parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
 
@@ -326,9 +324,7 @@
         }
     }
 #endif  // RECOVERY
-    parser.AddSectionParser("service",
-                            std::make_unique<ServiceParser>(&service_list, subcontext,
-                            std::nullopt));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontext));
     parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontext));
 
     return parser;
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 5088273..f280de9 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -62,8 +62,7 @@
     Action::set_function_map(&test_function_map);
 
     Parser parser;
-    parser.AddSectionParser("service",
-                            std::make_unique<ServiceParser>(service_list, nullptr, std::nullopt));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(service_list, nullptr));
     parser.AddSectionParser("on", std::make_unique<ActionParser>(action_manager, nullptr));
     parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
 
@@ -625,8 +624,7 @@
 
     ServiceList service_list;
     Parser parser;
-    parser.AddSectionParser("service",
-                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, nullptr));
 
     ASSERT_TRUE(parser.ParseConfig(tf.path));
 
@@ -657,8 +655,7 @@
 
     ServiceList service_list;
     Parser parser;
-    parser.AddSectionParser("service",
-                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, nullptr));
 
     ASSERT_TRUE(parser.ParseConfig(tf.path));
     ASSERT_EQ(1u, parser.parse_error_count());
diff --git a/init/interface_utils.cpp b/init/interface_utils.cpp
index 1b76bba..84407aa 100644
--- a/init/interface_utils.cpp
+++ b/init/interface_utils.cpp
@@ -39,27 +39,6 @@
     return android::base::Join(fqname_strings, " ");
 }
 
-}  // namespace
-
-Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
-                                                const InterfaceInheritanceHierarchyMap& hierarchy) {
-    std::set<FQName> interface_fqnames;
-    for (const std::string& instance : instances) {
-        // There is insufficient build-time information on AIDL interfaces to check them here
-        // TODO(b/139307527): Rework how services store interfaces to avoid excess string parsing
-        if (base::Split(instance, "/")[0] == "aidl") {
-            continue;
-        }
-
-        FqInstance fqinstance;
-        if (!fqinstance.setTo(instance)) {
-            return Error() << "Unable to parse interface instance '" << instance << "'";
-        }
-        interface_fqnames.insert(fqinstance.getFqName());
-    }
-    return CheckInterfaceInheritanceHierarchy(interface_fqnames, hierarchy);
-}
-
 Result<void> CheckInterfaceInheritanceHierarchy(const std::set<FQName>& interfaces,
                                                 const InterfaceInheritanceHierarchyMap& hierarchy) {
     std::ostringstream error_stream;
@@ -90,6 +69,27 @@
     return {};
 }
 
+}  // namespace
+
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
+                                                const InterfaceInheritanceHierarchyMap& hierarchy) {
+    std::set<FQName> interface_fqnames;
+    for (const std::string& instance : instances) {
+        // There is insufficient build-time information on AIDL interfaces to check them here
+        // TODO(b/139307527): Rework how services store interfaces to avoid excess string parsing
+        if (base::Split(instance, "/")[0] == "aidl") {
+            continue;
+        }
+
+        FqInstance fqinstance;
+        if (!fqinstance.setTo(instance)) {
+            return Error() << "Unable to parse interface instance '" << instance << "'";
+        }
+        interface_fqnames.insert(fqinstance.getFqName());
+    }
+    return CheckInterfaceInheritanceHierarchy(interface_fqnames, hierarchy);
+}
+
 std::optional<std::set<FQName>> known_interfaces;
 
 void SetKnownInterfaces(const InterfaceInheritanceHierarchyMap& hierarchy) {
diff --git a/init/interface_utils.h b/init/interface_utils.h
index 4ca377f..214feda 100644
--- a/init/interface_utils.h
+++ b/init/interface_utils.h
@@ -34,8 +34,6 @@
 // interface set. Uses the provided hierarchy data.
 Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
                                                 const InterfaceInheritanceHierarchyMap& hierarchy);
-Result<void> CheckInterfaceInheritanceHierarchy(const std::set<android::FQName>& interfaces,
-                                                const InterfaceInheritanceHierarchyMap& hierarchy);
 
 // Saves the set of known interfaces using the provided HIDL interface
 // inheritance hierarchy.
diff --git a/init/noop_builtins.cpp b/init/noop_builtins.cpp
new file mode 100644
index 0000000..c4e140b
--- /dev/null
+++ b/init/noop_builtins.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Note that parser will perform arity checks only.
+
+#include <android-base/result.h>
+
+#include "builtin_arguments.h"
+#include "builtins.h"
+
+namespace android::init {
+
+static base::Result<void> check_stub(const BuiltinArguments&) {
+    return {};
+}
+
+#include "noop_builtin_function_map.h"
+
+}  // namespace android::init
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 0d6eb15..f2606e3 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -31,14 +31,12 @@
 #include <sys/mman.h>
 #include <sys/poll.h>
 #include <sys/select.h>
+#include <sys/system_properties.h>
 #include <sys/types.h>
 #include <sys/un.h>
 #include <unistd.h>
 #include <wchar.h>
 
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
-
 #include <map>
 #include <memory>
 #include <mutex>
@@ -48,7 +46,6 @@
 #include <thread>
 #include <vector>
 
-#include <InitProperties.sysprop.h>
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -102,7 +99,6 @@
 using android::properties::ParsePropertyInfoFile;
 using android::properties::PropertyInfoAreaFile;
 using android::properties::PropertyInfoEntry;
-using android::sysprop::InitProperties::is_userspace_reboot_supported;
 
 namespace android {
 namespace init {
@@ -569,8 +565,8 @@
         }
         LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                   << process_log_string;
-        if (value == "reboot,userspace" && !is_userspace_reboot_supported().value_or(false)) {
-            *error = "Userspace reboot is not supported by this device";
+        if (value == "reboot,userspace") {
+            *error = "Userspace reboot is deprecated.";
             return {PROP_ERROR_INVALID_VALUE};
         }
     }
@@ -1255,6 +1251,16 @@
     update_sys_usb_config();
 }
 
+void PropertyLoadDerivedDefaults() {
+    const char* PAGE_PROP = "ro.boot.hardware.cpu.pagesize";
+    if (GetProperty(PAGE_PROP, "").empty()) {
+        std::string error;
+        if (PropertySetNoSocket(PAGE_PROP, std::to_string(getpagesize()), &error) != PROP_SUCCESS) {
+            LOG(ERROR) << "Could not set '" << PAGE_PROP << "' because: " << error;
+        }
+    }
+}
+
 bool LoadPropertyInfoFromFile(const std::string& filename,
                               std::vector<PropertyInfoEntry>* property_infos) {
     auto file_contents = std::string();
@@ -1425,6 +1431,7 @@
     ExportKernelBootProps();
 
     PropertyLoadBootDefaults();
+    PropertyLoadDerivedDefaults();
 }
 
 static void HandleInitSocket() {
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index 5f34cc4..c12ff72 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -16,11 +16,9 @@
 
 #include <errno.h>
 #include <sys/socket.h>
+#include <sys/system_properties.h>
 #include <sys/un.h>
 
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
-
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
 #include <android-base/strings.h>
@@ -82,12 +80,6 @@
         GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
-    const std::string original_value = GetProperty("init.userspace_reboot.is_supported", "");
-    auto guard = android::base::make_scope_guard([&original_value]() {
-        SetProperty("init.userspace_reboot.is_supported", original_value);
-    });
-
-    ASSERT_TRUE(SetProperty("init.userspace_reboot.is_supported", "false"));
     EXPECT_FALSE(SetProperty("sys.powerctl", "reboot,userspace"));
 }
 
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 150f8f4..ef9db9f 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -39,7 +39,6 @@
 #include <thread>
 #include <vector>
 
-#include <InitProperties.sysprop.h>
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -87,16 +86,6 @@
 
 static const std::set<std::string> kDebuggingServices{"tombstoned", "logd", "adbd", "console"};
 
-static std::set<std::string> GetPostDataDebuggingServices() {
-    std::set<std::string> ret;
-    for (const auto& s : ServiceList::GetInstance()) {
-        if (kDebuggingServices.count(s->name()) && s->is_post_data()) {
-            ret.insert(s->name());
-        }
-    }
-    return ret;
-}
-
 static void PersistRebootReason(const char* reason, bool write_to_property) {
     if (write_to_property) {
         SetProperty(LAST_REBOOT_REASON_PROPERTY, reason);
@@ -810,196 +799,6 @@
     }
 }
 
-static void LeaveShutdown() {
-    LOG(INFO) << "Leaving shutdown mode";
-    shutting_down = false;
-    StartSendingMessages();
-}
-
-static std::chrono::milliseconds GetMillisProperty(const std::string& name,
-                                                   std::chrono::milliseconds default_value) {
-    auto value = GetUintProperty(name, static_cast<uint64_t>(default_value.count()));
-    return std::chrono::milliseconds(std::move(value));
-}
-
-static Result<void> DoUserspaceReboot() {
-    LOG(INFO) << "Userspace reboot initiated";
-    // An ugly way to pass a more precise reason on why fallback to hard reboot was triggered.
-    std::string sub_reason = "";
-    auto guard = android::base::make_scope_guard([&sub_reason] {
-        // Leave shutdown so that we can handle a full reboot.
-        LeaveShutdown();
-        trigger_shutdown("reboot,userspace_failed,shutdown_aborted," + sub_reason);
-    });
-    // Triggering userspace-reboot-requested will result in a bunch of setprop
-    // actions. We should make sure, that all of them are propagated before
-    // proceeding with userspace reboot. Synchronously setting sys.init.userspace_reboot.in_progress
-    // property is not perfect, but it should do the trick.
-    if (!android::sysprop::InitProperties::userspace_reboot_in_progress(true)) {
-        sub_reason = "setprop";
-        return Error() << "Failed to set sys.init.userspace_reboot.in_progress property";
-    }
-    EnterShutdown();
-    if (!SetProperty("sys.powerctl", "")) {
-        sub_reason = "resetprop";
-        return Error() << "Failed to reset sys.powerctl property";
-    }
-    std::set<std::string> stop_first;
-    // Remember the services that were enabled. We will need to manually enable them again otherwise
-    // triggers like class_start won't restart them.
-    std::set<std::string> were_enabled;
-    for (const auto& s : ServiceList::GetInstance().services_in_shutdown_order()) {
-        if (s->is_post_data() && !kDebuggingServices.count(s->name())) {
-            stop_first.insert(s->name());
-        }
-        // TODO(ioffe): we should also filter out temporary services here.
-        if (s->is_post_data() && s->IsEnabled()) {
-            were_enabled.insert(s->name());
-        }
-    }
-    {
-        Timer sync_timer;
-        LOG(INFO) << "sync() before terminating services...";
-        sync();
-        LOG(INFO) << "sync() took " << sync_timer;
-    }
-    auto sigterm_timeout = GetMillisProperty("init.userspace_reboot.sigterm.timeoutmillis", 5s);
-    auto sigkill_timeout = GetMillisProperty("init.userspace_reboot.sigkill.timeoutmillis", 10s);
-    LOG(INFO) << "Timeout to terminate services: " << sigterm_timeout.count() << "ms "
-              << "Timeout to kill services: " << sigkill_timeout.count() << "ms";
-    std::string services_file_name = "/metadata/userspacereboot/services.txt";
-    const int flags = O_RDWR | O_CREAT | O_SYNC | O_APPEND | O_CLOEXEC;
-    StopServicesAndLogViolations(stop_first, sigterm_timeout, true /* SIGTERM */);
-    if (int r = StopServicesAndLogViolations(stop_first, sigkill_timeout, false /* SIGKILL */);
-        r > 0) {
-        auto fd = unique_fd(TEMP_FAILURE_RETRY(open(services_file_name.c_str(), flags, 0666)));
-        android::base::WriteStringToFd("Post-data services still running: \n", fd);
-        for (const auto& s : ServiceList::GetInstance()) {
-            if (s->IsRunning() && stop_first.count(s->name())) {
-                android::base::WriteStringToFd(s->name() + "\n", fd);
-            }
-        }
-        sub_reason = "sigkill";
-        return Error() << r << " post-data services are still running";
-    }
-    if (auto result = KillZramBackingDevice(); !result.ok()) {
-        sub_reason = "zram";
-        return result;
-    }
-    if (auto result = CallVdc("volume", "reset"); !result.ok()) {
-        sub_reason = "vold_reset";
-        return result;
-    }
-    const auto& debugging_services = GetPostDataDebuggingServices();
-    if (int r = StopServicesAndLogViolations(debugging_services, sigkill_timeout,
-                                             false /* SIGKILL */);
-        r > 0) {
-        auto fd = unique_fd(TEMP_FAILURE_RETRY(open(services_file_name.c_str(), flags, 0666)));
-        android::base::WriteStringToFd("Debugging services still running: \n", fd);
-        for (const auto& s : ServiceList::GetInstance()) {
-            if (s->IsRunning() && debugging_services.count(s->name())) {
-                android::base::WriteStringToFd(s->name() + "\n", fd);
-            }
-        }
-        sub_reason = "sigkill_debug";
-        return Error() << r << " debugging services are still running";
-    }
-    {
-        Timer sync_timer;
-        LOG(INFO) << "sync() after stopping services...";
-        sync();
-        LOG(INFO) << "sync() took " << sync_timer;
-    }
-    if (auto result = UnmountAllApexes(); !result.ok()) {
-        sub_reason = "apex";
-        return result;
-    }
-    if (!SwitchToMountNamespaceIfNeeded(NS_BOOTSTRAP).ok()) {
-        sub_reason = "ns_switch";
-        return Error() << "Failed to switch to bootstrap namespace";
-    }
-    ActionManager::GetInstance().RemoveActionIf([](const auto& action) -> bool {
-        if (action->IsFromApex()) {
-            std::string trigger_name = action->BuildTriggersString();
-            LOG(INFO) << "Removing action (" << trigger_name << ") from (" << action->filename()
-                      << ":" << action->line() << ")";
-            return true;
-        }
-        return false;
-    });
-    // Remove services that were defined in an APEX
-    ServiceList::GetInstance().RemoveServiceIf([](const std::unique_ptr<Service>& s) -> bool {
-        if (s->is_from_apex()) {
-            LOG(INFO) << "Removing service '" << s->name() << "' because it's defined in an APEX";
-            return true;
-        }
-        return false;
-    });
-    // Re-enable services
-    for (const auto& s : ServiceList::GetInstance()) {
-        if (were_enabled.count(s->name())) {
-            LOG(INFO) << "Re-enabling service '" << s->name() << "'";
-            s->Enable();
-        }
-    }
-    ServiceList::GetInstance().ResetState();
-    LeaveShutdown();
-    ActionManager::GetInstance().QueueEventTrigger("userspace-reboot-resume");
-    guard.Disable();  // Go on with userspace reboot.
-    return {};
-}
-
-static void UserspaceRebootWatchdogThread() {
-    auto started_timeout = GetMillisProperty("init.userspace_reboot.started.timeoutmillis", 10s);
-    if (!WaitForProperty("sys.init.userspace_reboot.in_progress", "1", started_timeout)) {
-        LOG(ERROR) << "Userspace reboot didn't start in " << started_timeout.count()
-                   << "ms. Switching to full reboot";
-        // Init might be wedged, don't try to write reboot reason into a persistent property and do
-        // a dirty reboot.
-        PersistRebootReason("userspace_failed,watchdog_triggered,failed_to_start", false);
-        RebootSystem(ANDROID_RB_RESTART2, "userspace_failed,watchdog_triggered,failed_to_start");
-    }
-    LOG(INFO) << "Starting userspace reboot watchdog";
-    auto watchdog_timeout = GetMillisProperty("init.userspace_reboot.watchdog.timeoutmillis", 5min);
-    LOG(INFO) << "UserspaceRebootWatchdog timeout: " << watchdog_timeout.count() << "ms";
-    if (!WaitForProperty("sys.boot_completed", "1", watchdog_timeout)) {
-        LOG(ERROR) << "Failed to boot in " << watchdog_timeout.count()
-                   << "ms. Switching to full reboot";
-        // In this case device is in a boot loop. Only way to recover is to do dirty reboot.
-        // Since init might be wedged, don't try to write reboot reason into a persistent property.
-        PersistRebootReason("userspace_failed,watchdog_triggered,failed_to_boot", false);
-        RebootSystem(ANDROID_RB_RESTART2, "userspace_failed,watchdog_triggered,failed_to_boot");
-    }
-    LOG(INFO) << "Device booted, stopping userspace reboot watchdog";
-}
-
-static void HandleUserspaceReboot() {
-    if (!android::sysprop::InitProperties::is_userspace_reboot_supported().value_or(false)) {
-        LOG(ERROR) << "Attempted a userspace reboot on a device that doesn't support it";
-        return;
-    }
-    // Spinnig up a separate thread will fail the setns call later in the boot sequence.
-    // Fork a new process to monitor userspace reboot while we are investigating a better solution.
-    pid_t pid = fork();
-    if (pid < 0) {
-        PLOG(ERROR) << "Failed to fork process for userspace reboot watchdog. Switching to full "
-                    << "reboot";
-        trigger_shutdown("reboot,userspace_failed,watchdog_fork");
-        return;
-    }
-    if (pid == 0) {
-        // Child
-        UserspaceRebootWatchdogThread();
-        _exit(EXIT_SUCCESS);
-    }
-    LOG(INFO) << "Clearing queue and starting userspace-reboot-requested trigger";
-    auto& am = ActionManager::GetInstance();
-    am.ClearQueue();
-    am.QueueEventTrigger("userspace-reboot-requested");
-    auto handler = [](const BuiltinArguments&) { return DoUserspaceReboot(); };
-    am.QueueBuiltinAction(handler, "userspace-reboot");
-}
-
 /**
  * Check if "command" field is set in bootloader message.
  *
@@ -1030,7 +829,6 @@
     std::string reboot_target = "";
     bool run_fsck = false;
     bool command_invalid = false;
-    bool userspace_reboot = false;
 
     if (cmd_params[0] == "shutdown") {
         cmd = ANDROID_RB_POWEROFF;
@@ -1051,8 +849,8 @@
         if (cmd_params.size() >= 2) {
             reboot_target = cmd_params[1];
             if (reboot_target == "userspace") {
-                LOG(INFO) << "Userspace reboot requested";
-                userspace_reboot = true;
+                LOG(ERROR) << "Userspace reboot is deprecated.";
+                return;
             }
             // adb reboot fastboot should boot into bootloader for devices not
             // supporting logical partitions.
@@ -1130,11 +928,6 @@
     // messages, etc) from properties during reboot.
     StopSendingMessages();
 
-    if (userspace_reboot) {
-        HandleUserspaceReboot();
-        return;
-    }
-
     LOG(INFO) << "Clear action queue and start shutdown trigger";
     ActionManager::GetInstance().ClearQueue();
     // Queue shutdown trigger first
diff --git a/init/reboot_test.cpp b/init/reboot_test.cpp
index b3d038d..b7a1cfd 100644
--- a/init/reboot_test.cpp
+++ b/init/reboot_test.cpp
@@ -103,8 +103,7 @@
                                        "$selabel", GetSecurityContext(), false);
     ServiceList& service_list = ServiceList::GetInstance();
     Parser parser;
-    parser.AddSectionParser("service",
-                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, nullptr));
 
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
diff --git a/init/service.cpp b/init/service.cpp
index 31308a0..d76a5d5 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -653,8 +653,6 @@
         SetMountNamespace();
     }
 
-    post_data_ = ServiceList::GetInstance().IsPostData();
-
     LOG(INFO) << "starting service '" << name_ << "'...";
 
     std::vector<Descriptor> descriptors;
diff --git a/init/service.h b/init/service.h
index 5e9af25..ae75553 100644
--- a/init/service.h
+++ b/init/service.h
@@ -144,8 +144,6 @@
     std::optional<std::chrono::seconds> timeout_period() const { return timeout_period_; }
     const std::vector<std::string>& args() const { return args_; }
     bool is_updatable() const { return updatable_; }
-    bool is_post_data() const { return post_data_; }
-    bool is_from_apex() const { return base::StartsWith(filename_, "/apex/"); }
     void set_oneshot(bool value) {
         if (value) {
             flags_ |= SVC_ONESHOT;
@@ -244,8 +242,6 @@
 
     std::optional<MountNamespace> mount_namespace_;
 
-    bool post_data_ = false;
-
     std::optional<std::string> on_failure_reboot_target_;
 
     std::string filename_;
diff --git a/init/service_list.cpp b/init/service_list.cpp
index 1c56e8a..e6cc2c9 100644
--- a/init/service_list.cpp
+++ b/init/service_list.cpp
@@ -68,14 +68,6 @@
     }
 }
 
-void ServiceList::MarkPostData() {
-    post_data_ = true;
-}
-
-bool ServiceList::IsPostData() {
-    return post_data_;
-}
-
 void ServiceList::StartDelayedServices() {
     for (const auto& name : delayed_service_names_) {
         Service* service = FindService(name);
diff --git a/init/service_list.h b/init/service_list.h
index 44e8453..fd7fc05 100644
--- a/init/service_list.h
+++ b/init/service_list.h
@@ -83,19 +83,14 @@
     auto end() const { return services_.end(); }
     const std::vector<Service*> services_in_shutdown_order() const;
 
-    void MarkPostData();
-    bool IsPostData();
     void DelayService(const Service& service);
     void StartDelayedServices();
 
-    void ResetState() { post_data_ = false; }
-
     auto size() const { return services_.size(); }
 
   private:
     std::vector<std::unique_ptr<Service>> services_;
 
-    bool post_data_ = false;
     std::vector<std::string> delayed_service_names_;
 };
 
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 6781c70..e6f3af6 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -27,7 +27,6 @@
 #include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
-#include <hidl-util/FQName.h>
 #include <processgroup/processgroup.h>
 #include <system/thread_defs.h>
 
@@ -201,24 +200,6 @@
 Result<void> ServiceParser::ParseInterface(std::vector<std::string>&& args) {
     const std::string& interface_name = args[1];
     const std::string& instance_name = args[2];
-
-    // AIDL services don't use fully qualified names and instead just use "interface aidl <name>"
-    if (interface_name != "aidl") {
-        FQName fq_name;
-        if (!FQName::parse(interface_name, &fq_name)) {
-            return Error() << "Invalid fully-qualified name for interface '" << interface_name
-                           << "'";
-        }
-
-        if (!fq_name.isFullyQualified()) {
-            return Error() << "Interface name not fully-qualified '" << interface_name << "'";
-        }
-
-        if (fq_name.isValidValueName()) {
-            return Error() << "Interface name must not be a value name '" << interface_name << "'";
-        }
-    }
-
     const std::string fullname = interface_name + "/" + instance_name;
 
     for (const auto& svc : *service_list_) {
@@ -702,14 +683,6 @@
         }
     }
 
-    if (interface_inheritance_hierarchy_) {
-        if (const auto& check_hierarchy_result = CheckInterfaceInheritanceHierarchy(
-                    service_->interfaces(), *interface_inheritance_hierarchy_);
-            !check_hierarchy_result.ok()) {
-            return Error() << check_hierarchy_result.error();
-        }
-    }
-
     if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__) {
         if ((service_->flags() & SVC_CRITICAL) != 0 && (service_->flags() & SVC_ONESHOT) != 0) {
             return Error() << "service '" << service_->name()
diff --git a/init/service_parser.h b/init/service_parser.h
index 670a5c6..f06cfc4 100644
--- a/init/service_parser.h
+++ b/init/service_parser.h
@@ -18,7 +18,6 @@
 
 #include <vector>
 
-#include "interface_utils.h"
 #include "parser.h"
 #include "service.h"
 #include "service_list.h"
@@ -29,13 +28,8 @@
 
 class ServiceParser : public SectionParser {
   public:
-    ServiceParser(
-            ServiceList* service_list, Subcontext* subcontext,
-            const std::optional<InterfaceInheritanceHierarchyMap>& interface_inheritance_hierarchy)
-        : service_list_(service_list),
-          subcontext_(subcontext),
-          interface_inheritance_hierarchy_(interface_inheritance_hierarchy),
-          service_(nullptr) {}
+    ServiceParser(ServiceList* service_list, Subcontext* subcontext)
+        : service_list_(service_list), subcontext_(subcontext), service_(nullptr) {}
     Result<void> ParseSection(std::vector<std::string>&& args, const std::string& filename,
                               int line) override;
     Result<void> ParseLineSection(std::vector<std::string>&& args, int line) override;
@@ -88,7 +82,6 @@
 
     ServiceList* service_list_;
     Subcontext* subcontext_;
-    std::optional<InterfaceInheritanceHierarchyMap> interface_inheritance_hierarchy_;
     std::unique_ptr<Service> service_;
     std::string filename_;
 };
diff --git a/init/service_test.cpp b/init/service_test.cpp
index a3590b5..53b53ed 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -253,8 +253,7 @@
                                        "$selabel", GetSecurityContext(), false);
     ServiceList& service_list = ServiceList::GetInstance();
     Parser parser;
-    parser.AddSectionParser("service",
-                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, nullptr));
 
     TemporaryFile tf;
     ASSERT_GE(tf.fd, 0);
diff --git a/init/sysprop/Android.bp b/init/sysprop/Android.bp
deleted file mode 100644
index 296cdc1..0000000
--- a/init/sysprop/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_core_init_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_core_init_license"],
-}
-
-sysprop_library {
-  name: "com.android.sysprop.init",
-  srcs: ["InitProperties.sysprop"],
-  property_owner: "Platform",
-  api_packages: ["android.sysprop"],
-  recovery_available: true,
-}
diff --git a/init/sysprop/InitProperties.sysprop b/init/sysprop/InitProperties.sysprop
deleted file mode 100644
index 24c2434..0000000
--- a/init/sysprop/InitProperties.sysprop
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-owner: Platform
-module: "android.sysprop.InitProperties"
-
-# Serves as a signal to all processes that userspace reboot is happening.
-prop {
-    api_name: "userspace_reboot_in_progress"
-    type: Boolean
-    scope: Public
-    access: ReadWrite
-    prop_name: "sys.init.userspace_reboot.in_progress"
-    integer_as_bool: true
-}
-
-# Shows whenever the device supports userspace reboot or not.
-prop {
-    api_name: "is_userspace_reboot_supported"
-    type: Boolean
-    scope: Public
-    access: Readonly
-    prop_name: "init.userspace_reboot.is_supported"
-    integer_as_bool: true
-}
diff --git a/init/sysprop/api/com.android.sysprop.init-current.txt b/init/sysprop/api/com.android.sysprop.init-current.txt
deleted file mode 100644
index 01f4e9a..0000000
--- a/init/sysprop/api/com.android.sysprop.init-current.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-props {
-  module: "android.sysprop.InitProperties"
-  prop {
-    api_name: "is_userspace_reboot_supported"
-    prop_name: "init.userspace_reboot.is_supported"
-    integer_as_bool: true
-  }
-  prop {
-    api_name: "userspace_reboot_in_progress"
-    access: ReadWrite
-    prop_name: "sys.init.userspace_reboot.in_progress"
-    integer_as_bool: true
-  }
-}
diff --git a/init/sysprop/api/com.android.sysprop.init-latest.txt b/init/sysprop/api/com.android.sysprop.init-latest.txt
deleted file mode 100644
index 01f4e9a..0000000
--- a/init/sysprop/api/com.android.sysprop.init-latest.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-props {
-  module: "android.sysprop.InitProperties"
-  prop {
-    api_name: "is_userspace_reboot_supported"
-    prop_name: "init.userspace_reboot.is_supported"
-    integer_as_bool: true
-  }
-  prop {
-    api_name: "userspace_reboot_in_progress"
-    access: ReadWrite
-    prop_name: "sys.init.userspace_reboot.in_progress"
-    integer_as_bool: true
-  }
-}
diff --git a/init/test_kill_services/OWNERS b/init/test_kill_services/OWNERS
new file mode 100644
index 0000000..40164aa
--- /dev/null
+++ b/init/test_kill_services/OWNERS
@@ -0,0 +1 @@
+smoreland@google.com
diff --git a/init/test_kill_services/init_kill_services_test.cpp b/init/test_kill_services/init_kill_services_test.cpp
index 3af92bb..efba9f6 100644
--- a/init/test_kill_services/init_kill_services_test.cpp
+++ b/init/test_kill_services/init_kill_services_test.cpp
@@ -87,6 +87,25 @@
     return info.param;
 }
 
-INSTANTIATE_TEST_CASE_P(DeathTest, InitKillServicesTest,
-                        ::testing::Values("lmkd", "ueventd", "hwservicemanager", "servicemanager"),
-                        PrintName);
+INSTANTIATE_TEST_CASE_P(
+        DeathTest, InitKillServicesTest,
+        ::testing::Values(
+                // clang-format off
+
+// TODO: we may want a more automatic way of testing this for services based on some
+// criteria (e.g. not disabled), but for now adding core services one at a time
+
+// BEGIN INTERNAL ONLY MERGE GUARD (add things here if internal only, move down later)
+// END INTERNAL ONLY MERGE GUARD
+
+// BEGIN AOSP ONLY (add things here if adding to AOSP)
+    "lmkd",
+    "ueventd",
+    "hwservicemanager",
+    "servicemanager",
+    "system_suspend"
+// END AOSP ONLY
+
+                // clang-format on
+                ),
+        PrintName);
diff --git a/init/test_utils/service_utils.cpp b/init/test_utils/service_utils.cpp
index 6426ed9..7002a67 100644
--- a/init/test_utils/service_utils.cpp
+++ b/init/test_utils/service_utils.cpp
@@ -30,8 +30,7 @@
 android::base::Result<ServiceInterfacesMap> GetOnDeviceServiceInterfacesMap() {
     ServiceList& service_list = ServiceList::GetInstance();
     Parser parser;
-    parser.AddSectionParser("service",
-                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, nullptr));
     for (const auto& location : {
                  "/init.rc",
                  "/system/etc/init",
diff --git a/libcutils/properties.cpp b/libcutils/properties.cpp
index 03f0496..d3a2b50 100644
--- a/libcutils/properties.cpp
+++ b/libcutils/properties.cpp
@@ -93,8 +93,7 @@
 
 #if __has_include(<sys/system_properties.h>)
 
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
+#include <sys/system_properties.h>
 
 struct callback_data {
     void (*callback)(const char* name, const char* value, void* cookie);
diff --git a/libcutils/trace-dev.inc b/libcutils/trace-dev.inc
index 3bc6dc3..c911b4f 100644
--- a/libcutils/trace-dev.inc
+++ b/libcutils/trace-dev.inc
@@ -36,8 +36,7 @@
 #include <log/log_properties.h>
 
 #if defined(__BIONIC__)
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
+#include <sys/system_properties.h>
 #endif
 
 /**
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 33e00bc..a60bfe9 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -79,12 +79,12 @@
     ],
     static_libs: [
         "libjsoncpp",
+        "libprocessgroup_util",
     ],
     // for cutils/android_filesystem_config.h
     header_libs: [
         "libcutils_headers",
         "libprocessgroup_headers",
-        "libprocessgroup_util",
     ],
     export_include_dirs: ["include"],
     export_header_lib_headers: [
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 52b5afe..fb01cfd 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -38,26 +38,26 @@
 static constexpr const char* CGROUP_TASKS_FILE = "/tasks";
 static constexpr const char* CGROUP_TASKS_FILE_V2 = "/cgroup.threads";
 
-uint32_t CgroupController::version() const {
+uint32_t CgroupControllerWrapper::version() const {
     CHECK(HasValue());
     return ACgroupController_getVersion(controller_);
 }
 
-const char* CgroupController::name() const {
+const char* CgroupControllerWrapper::name() const {
     CHECK(HasValue());
     return ACgroupController_getName(controller_);
 }
 
-const char* CgroupController::path() const {
+const char* CgroupControllerWrapper::path() const {
     CHECK(HasValue());
     return ACgroupController_getPath(controller_);
 }
 
-bool CgroupController::HasValue() const {
+bool CgroupControllerWrapper::HasValue() const {
     return controller_ != nullptr;
 }
 
-bool CgroupController::IsUsable() {
+bool CgroupControllerWrapper::IsUsable() {
     if (!HasValue()) return false;
 
     if (state_ == UNKNOWN) {
@@ -72,7 +72,7 @@
     return state_ == USABLE;
 }
 
-std::string CgroupController::GetTasksFilePath(const std::string& rel_path) const {
+std::string CgroupControllerWrapper::GetTasksFilePath(const std::string& rel_path) const {
     std::string tasks_path = path();
 
     if (!rel_path.empty()) {
@@ -81,8 +81,8 @@
     return (version() == 1) ? tasks_path + CGROUP_TASKS_FILE : tasks_path + CGROUP_TASKS_FILE_V2;
 }
 
-std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
-                                               pid_t pid) const {
+std::string CgroupControllerWrapper::GetProcsFilePath(const std::string& rel_path, uid_t uid,
+                                                      pid_t pid) const {
     std::string proc_path(path());
     proc_path.append("/").append(rel_path);
     proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
@@ -91,7 +91,7 @@
     return proc_path.append(CGROUP_PROCS_FILE);
 }
 
-bool CgroupController::GetTaskGroup(pid_t tid, std::string* group) const {
+bool CgroupControllerWrapper::GetTaskGroup(pid_t tid, std::string* group) const {
     std::string file_name = StringPrintf("/proc/%d/cgroup", tid);
     std::string content;
     if (!android::base::ReadFileToString(file_name, &content)) {
@@ -175,40 +175,40 @@
     }
 }
 
-CgroupController CgroupMap::FindController(const std::string& name) const {
+CgroupControllerWrapper CgroupMap::FindController(const std::string& name) const {
     if (!loaded_) {
         LOG(ERROR) << "CgroupMap::FindController called for [" << getpid()
                    << "] failed, RC file was not initialized properly";
-        return CgroupController(nullptr);
+        return CgroupControllerWrapper(nullptr);
     }
 
     auto controller_count = ACgroupFile_getControllerCount();
     for (uint32_t i = 0; i < controller_count; ++i) {
         const ACgroupController* controller = ACgroupFile_getController(i);
         if (name == ACgroupController_getName(controller)) {
-            return CgroupController(controller);
+            return CgroupControllerWrapper(controller);
         }
     }
 
-    return CgroupController(nullptr);
+    return CgroupControllerWrapper(nullptr);
 }
 
-CgroupController CgroupMap::FindControllerByPath(const std::string& path) const {
+CgroupControllerWrapper CgroupMap::FindControllerByPath(const std::string& path) const {
     if (!loaded_) {
         LOG(ERROR) << "CgroupMap::FindControllerByPath called for [" << getpid()
                    << "] failed, RC file was not initialized properly";
-        return CgroupController(nullptr);
+        return CgroupControllerWrapper(nullptr);
     }
 
     auto controller_count = ACgroupFile_getControllerCount();
     for (uint32_t i = 0; i < controller_count; ++i) {
         const ACgroupController* controller = ACgroupFile_getController(i);
         if (StartsWith(path, ACgroupController_getPath(controller))) {
-            return CgroupController(controller);
+            return CgroupControllerWrapper(controller);
         }
     }
 
-    return CgroupController(nullptr);
+    return CgroupControllerWrapper(nullptr);
 }
 
 int CgroupMap::ActivateControllers(const std::string& path) const {
diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h
index 31925d5..3642794 100644
--- a/libprocessgroup/cgroup_map.h
+++ b/libprocessgroup/cgroup_map.h
@@ -23,10 +23,10 @@
 #include <android/cgrouprc.h>
 
 // Convenient wrapper of an ACgroupController pointer.
-class CgroupController {
+class CgroupControllerWrapper {
   public:
     // Does not own controller
-    explicit CgroupController(const ACgroupController* controller)
+    explicit CgroupControllerWrapper(const ACgroupController* controller)
         : controller_(controller) {}
 
     uint32_t version() const;
@@ -53,12 +53,9 @@
 
 class CgroupMap {
   public:
-    // Selinux policy ensures only init process can successfully use this function
-    static bool SetupCgroups();
-
     static CgroupMap& GetInstance();
-    CgroupController FindController(const std::string& name) const;
-    CgroupController FindControllerByPath(const std::string& path) const;
+    CgroupControllerWrapper FindController(const std::string& name) const;
+    CgroupControllerWrapper FindControllerByPath(const std::string& path) const;
     int ActivateControllers(const std::string& path) const;
 
   private:
diff --git a/libprocessgroup/cgrouprc/Android.bp b/libprocessgroup/cgrouprc/Android.bp
index 7522cfe..cb91247 100644
--- a/libprocessgroup/cgrouprc/Android.bp
+++ b/libprocessgroup/cgrouprc/Android.bp
@@ -32,8 +32,8 @@
         symbol_file: "libcgrouprc.map.txt",
     },
     srcs: [
-        "cgroup_controller.cpp",
-        "cgroup_file.cpp",
+        "a_cgroup_controller.cpp",
+        "a_cgroup_file.cpp",
     ],
     cflags: [
         "-Wall",
diff --git a/libprocessgroup/cgrouprc/cgroup_controller.cpp b/libprocessgroup/cgrouprc/a_cgroup_controller.cpp
similarity index 100%
rename from libprocessgroup/cgrouprc/cgroup_controller.cpp
rename to libprocessgroup/cgrouprc/a_cgroup_controller.cpp
diff --git a/libprocessgroup/cgrouprc/cgroup_file.cpp b/libprocessgroup/cgrouprc/a_cgroup_file.cpp
similarity index 100%
rename from libprocessgroup/cgrouprc/cgroup_file.cpp
rename to libprocessgroup/cgrouprc/a_cgroup_file.cpp
diff --git a/libprocessgroup/include/processgroup/sched_policy.h b/libprocessgroup/include/processgroup/sched_policy.h
index a18847e..1b6ea66 100644
--- a/libprocessgroup/include/processgroup/sched_policy.h
+++ b/libprocessgroup/include/processgroup/sched_policy.h
@@ -48,6 +48,7 @@
     SP_TOP_APP = 5,
     SP_RT_APP = 6,
     SP_RESTRICTED = 7,
+    SP_FOREGROUND_WINDOW = 8,
     SP_CNT,
     SP_MAX = SP_CNT - 1,
     SP_SYSTEM_DEFAULT = SP_FOREGROUND,
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 387c104..83a2258 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -94,7 +94,7 @@
 }
 
 static bool CgroupGetMemcgAppsPath(std::string* path) {
-    CgroupController controller = CgroupMap::GetInstance().FindController("memory");
+    CgroupControllerWrapper controller = CgroupMap::GetInstance().FindController("memory");
 
     if (!controller.HasValue()) {
         return false;
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 1fc66ba..ec125aa 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -11,6 +11,11 @@
       "File": "foreground/cpus"
     },
     {
+      "Name": "HighCapacityWICPUs",
+      "Controller": "cpuset",
+      "File": "foreground_window/cpus"
+    },
+    {
       "Name": "MaxCapacityCPUs",
       "Controller": "cpuset",
       "File": "top-app/cpus"
@@ -159,6 +164,19 @@
       ]
     },
     {
+      "Name": "HighPerformanceWI",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpu",
+            "Path": "foreground_window"
+          }
+        }
+      ]
+    },
+    {
       "Name": "MaxPerformance",
       "Actions": [
         {
@@ -382,6 +400,19 @@
       ]
     },
     {
+      "Name": "ProcessCapacityHighWI",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "foreground_window"
+          }
+        }
+      ]
+    },
+    {
       "Name": "ProcessCapacityMax",
       "Actions": [
         {
@@ -639,6 +670,10 @@
       "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
     },
     {
+      "Name": "SCHED_SP_FOREGROUND_WINDOW",
+      "Profiles": [ "HighPerformanceWI", "HighIoPriority", "TimerSlackNormal" ]
+    },
+    {
       "Name": "SCHED_SP_TOP_APP",
       "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
     },
@@ -667,6 +702,10 @@
       "Profiles": [ "HighPerformance", "ProcessCapacityHigh", "HighIoPriority", "TimerSlackNormal" ]
     },
     {
+      "Name": "CPUSET_SP_FOREGROUND_WINDOW",
+      "Profiles": [ "HighPerformanceWI", "ProcessCapacityHighWI", "HighIoPriority", "TimerSlackNormal" ]
+    },
+    {
       "Name": "CPUSET_SP_TOP_APP",
       "Profiles": [ "MaxPerformance", "ProcessCapacityMax", "MaxIoPriority", "TimerSlackNormal" ]
     },
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index 0f2640a..042bcd2 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -58,6 +58,8 @@
             return SetTaskProfiles(tid, {"CPUSET_SP_SYSTEM"}, true) ? 0 : -1;
         case SP_RESTRICTED:
             return SetTaskProfiles(tid, {"CPUSET_SP_RESTRICTED"}, true) ? 0 : -1;
+        case SP_FOREGROUND_WINDOW:
+            return SetTaskProfiles(tid, {"CPUSET_SP_FOREGROUND_WINDOW"}, true) ? 0 : -1;
         default:
             break;
     }
@@ -110,6 +112,9 @@
         case SP_RT_APP:
             SLOGD("RT  tid %d (%s)", tid, thread_name);
             break;
+        case SP_FOREGROUND_WINDOW:
+            SLOGD("WI  tid %d (%s)", tid, thread_name);
+            break;
         default:
             SLOGD("??? tid %d (%s)", tid, thread_name);
             break;
@@ -129,6 +134,8 @@
             return SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
         case SP_RT_APP:
             return SetTaskProfiles(tid, {"SCHED_SP_RT_APP"}, true) ? 0 : -1;
+        case SP_FOREGROUND_WINDOW:
+            return SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND_WINDOW"}, true) ? 0 : -1;
         default:
             return SetTaskProfiles(tid, {"SCHED_SP_DEFAULT"}, true) ? 0 : -1;
     }
@@ -179,6 +186,8 @@
         *policy = SP_TOP_APP;
     } else if (group == "restricted") {
         *policy = SP_RESTRICTED;
+    } else if (group == "foreground_window") {
+        *policy = SP_FOREGROUND_WINDOW;
     } else {
         errno = ERANGE;
         return -1;
@@ -235,7 +244,7 @@
     static const char* const kSchedPolicyNames[] = {
             [SP_BACKGROUND] = "bg", [SP_FOREGROUND] = "fg", [SP_SYSTEM] = "  ",
             [SP_AUDIO_APP] = "aa",  [SP_AUDIO_SYS] = "as",  [SP_TOP_APP] = "ta",
-            [SP_RT_APP] = "rt",     [SP_RESTRICTED] = "rs",
+            [SP_RT_APP] = "rt",     [SP_RESTRICTED] = "rs", [SP_FOREGROUND_WINDOW] = "wi",
     };
     static_assert(arraysize(kSchedPolicyNames) == SP_CNT, "missing name");
     if (policy < SP_BACKGROUND || policy >= SP_CNT) {
@@ -249,14 +258,16 @@
      *  cpuset profile array for:
      *  SP_DEFAULT(-1), SP_BACKGROUND(0), SP_FOREGROUND(1),
      *  SP_SYSTEM(2), SP_AUDIO_APP(3), SP_AUDIO_SYS(4),
-     *  SP_TOP_APP(5), SP_RT_APP(6), SP_RESTRICTED(7)
+     *  SP_TOP_APP(5), SP_RT_APP(6), SP_RESTRICTED(7),
+     *  SP_FOREGROUND_WINDOW(8)
      *  index is policy + 1
      *  this need keep in sync with SchedPolicy enum
      */
     static constexpr const char* kCpusetProfiles[SP_CNT + 1] = {
-            "CPUSET_SP_DEFAULT", "CPUSET_SP_BACKGROUND", "CPUSET_SP_FOREGROUND",
-            "CPUSET_SP_SYSTEM",  "CPUSET_SP_FOREGROUND", "CPUSET_SP_FOREGROUND",
-            "CPUSET_SP_TOP_APP", "CPUSET_SP_DEFAULT",    "CPUSET_SP_RESTRICTED"};
+            "CPUSET_SP_DEFAULT",      "CPUSET_SP_BACKGROUND", "CPUSET_SP_FOREGROUND",
+            "CPUSET_SP_SYSTEM",       "CPUSET_SP_FOREGROUND", "CPUSET_SP_FOREGROUND",
+            "CPUSET_SP_TOP_APP",      "CPUSET_SP_DEFAULT",    "CPUSET_SP_RESTRICTED",
+            "CPUSET_SP_FOREGROUND_WINDOW"};
     if (policy < SP_DEFAULT || policy >= SP_CNT) {
         return nullptr;
     }
@@ -268,14 +279,16 @@
      *  sched profile array for:
      *  SP_DEFAULT(-1), SP_BACKGROUND(0), SP_FOREGROUND(1),
      *  SP_SYSTEM(2), SP_AUDIO_APP(3), SP_AUDIO_SYS(4),
-     *  SP_TOP_APP(5), SP_RT_APP(6), SP_RESTRICTED(7)
+     *  SP_TOP_APP(5), SP_RT_APP(6), SP_RESTRICTED(7),
+     *  SP_FOREGROUND_WINDOW(8)
      *  index is policy + 1
      *  this need keep in sync with SchedPolicy enum
      */
     static constexpr const char* kSchedProfiles[SP_CNT + 1] = {
-            "SCHED_SP_DEFAULT", "SCHED_SP_BACKGROUND", "SCHED_SP_FOREGROUND",
-            "SCHED_SP_SYSTEM",  "SCHED_SP_FOREGROUND", "SCHED_SP_FOREGROUND",
-            "SCHED_SP_TOP_APP", "SCHED_SP_RT_APP",     "SCHED_SP_DEFAULT"};
+            "SCHED_SP_DEFAULT",      "SCHED_SP_BACKGROUND", "SCHED_SP_FOREGROUND",
+            "SCHED_SP_SYSTEM",       "SCHED_SP_FOREGROUND", "SCHED_SP_FOREGROUND",
+            "SCHED_SP_TOP_APP",      "SCHED_SP_RT_APP",     "SCHED_SP_DEFAULT",
+            "SCHED_SP_FOREGROUND_WINDOW"};
     if (policy < SP_DEFAULT || policy >= SP_CNT) {
         return nullptr;
     }
diff --git a/libprocessgroup/setup/Android.bp b/libprocessgroup/setup/Android.bp
index 76f0a11..1a4ad01 100644
--- a/libprocessgroup/setup/Android.bp
+++ b/libprocessgroup/setup/Android.bp
@@ -34,10 +34,10 @@
     ],
     static_libs: [
         "libcgrouprc_format",
+        "libprocessgroup_util",
     ],
     header_libs: [
         "libprocessgroup_headers",
-        "libprocessgroup_util",
     ],
     export_header_lib_headers: [
         "libprocessgroup_headers",
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 4870548..67ecc1d 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -123,8 +123,8 @@
     return file_name_;
 }
 
-void ProfileAttribute::Reset(const CgroupController& controller, const std::string& file_name,
-                             const std::string& file_v2_name) {
+void ProfileAttribute::Reset(const CgroupControllerWrapper& controller,
+                             const std::string& file_name, const std::string& file_v2_name) {
     controller_ = controller;
     file_name_ = file_name;
     file_v2_name_ = file_v2_name;
@@ -333,7 +333,7 @@
     return optional_;
 }
 
-SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
+SetCgroupAction::SetCgroupAction(const CgroupControllerWrapper& c, const std::string& p)
     : controller_(c), path_(p) {
     FdCacheHelper::Init(controller_.GetTasksFilePath(path_), fd_[ProfileAction::RCT_TASK]);
     // uid and pid don't matter because IsAppDependentPath ensures the path doesn't use them
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 184e9e3..abb3ca5 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -32,9 +32,9 @@
 class IProfileAttribute {
   public:
     virtual ~IProfileAttribute() = 0;
-    virtual void Reset(const CgroupController& controller, const std::string& file_name,
+    virtual void Reset(const CgroupControllerWrapper& controller, const std::string& file_name,
                        const std::string& file_v2_name) = 0;
-    virtual const CgroupController* controller() const = 0;
+    virtual const CgroupControllerWrapper* controller() const = 0;
     virtual const std::string& file_name() const = 0;
     virtual bool GetPathForProcess(uid_t uid, pid_t pid, std::string* path) const = 0;
     virtual bool GetPathForTask(pid_t tid, std::string* path) const = 0;
@@ -46,14 +46,14 @@
     // Cgroup attributes may have different names in the v1 and v2 hierarchies. If `file_v2_name` is
     // not empty, `file_name` is the name for the v1 hierarchy and `file_v2_name` is the name for
     // the v2 hierarchy. If `file_v2_name` is empty, `file_name` is used for both hierarchies.
-    ProfileAttribute(const CgroupController& controller, const std::string& file_name,
+    ProfileAttribute(const CgroupControllerWrapper& controller, const std::string& file_name,
                      const std::string& file_v2_name)
         : controller_(controller), file_name_(file_name), file_v2_name_(file_v2_name) {}
     ~ProfileAttribute() = default;
 
-    const CgroupController* controller() const override { return &controller_; }
+    const CgroupControllerWrapper* controller() const override { return &controller_; }
     const std::string& file_name() const override;
-    void Reset(const CgroupController& controller, const std::string& file_name,
+    void Reset(const CgroupControllerWrapper& controller, const std::string& file_name,
                const std::string& file_v2_name) override;
 
     bool GetPathForProcess(uid_t uid, pid_t pid, std::string* path) const override;
@@ -61,7 +61,7 @@
     bool GetPathForUID(uid_t uid, std::string* path) const override;
 
   private:
-    CgroupController controller_;
+    CgroupControllerWrapper controller_;
     std::string file_name_;
     std::string file_v2_name_;
 };
@@ -142,7 +142,7 @@
 // Set cgroup profile element
 class SetCgroupAction : public ProfileAction {
   public:
-    SetCgroupAction(const CgroupController& c, const std::string& p);
+    SetCgroupAction(const CgroupControllerWrapper& c, const std::string& p);
 
     const char* Name() const override { return "SetCgroup"; }
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
@@ -152,10 +152,10 @@
     bool IsValidForProcess(uid_t uid, pid_t pid) const override;
     bool IsValidForTask(pid_t tid) const override;
 
-    const CgroupController* controller() const { return &controller_; }
+    const CgroupControllerWrapper* controller() const { return &controller_; }
 
   private:
-    CgroupController controller_;
+    CgroupControllerWrapper controller_;
     std::string path_;
     android::base::unique_fd fd_[ProfileAction::RCT_COUNT];
     mutable std::mutex fd_mutex_;
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index d19da2b..dff6d67 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -102,10 +102,10 @@
   public:
     ProfileAttributeMock(const std::string& file_name) : file_name_(file_name) {}
     ~ProfileAttributeMock() override = default;
-    void Reset(const CgroupController&, const std::string&, const std::string&) override {
+    void Reset(const CgroupControllerWrapper&, const std::string&, const std::string&) override {
         CHECK(false);
     }
-    const CgroupController* controller() const override {
+    const CgroupControllerWrapper* controller() const override {
         CHECK(false);
         return {};
     }
diff --git a/libprocessgroup/util/Android.bp b/libprocessgroup/util/Android.bp
index 4a940b7..54ba69b 100644
--- a/libprocessgroup/util/Android.bp
+++ b/libprocessgroup/util/Android.bp
@@ -19,7 +19,7 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_library_headers {
+cc_library_static {
     name: "libprocessgroup_util",
     vendor_available: true,
     product_available: true,
@@ -36,12 +36,15 @@
     export_include_dirs: [
         "include",
     ],
+    srcs: [
+        "util.cpp",
+    ],
     defaults: ["libprocessgroup_build_flags_cc"],
 }
 
 cc_test {
     name: "libprocessgroup_util_test",
-    header_libs: ["libprocessgroup_util"],
+    static_libs: ["libprocessgroup_util"],
     srcs: ["tests/util.cpp"],
     test_suites: ["general-tests"],
 }
diff --git a/libprocessgroup/util/include/processgroup/util.h b/libprocessgroup/util/include/processgroup/util.h
index 5240744..8d013af 100644
--- a/libprocessgroup/util/include/processgroup/util.h
+++ b/libprocessgroup/util/include/processgroup/util.h
@@ -16,46 +16,10 @@
 
 #pragma once
 
-#include <algorithm>
-#include <iterator>
 #include <string>
 
 namespace util {
 
-namespace internal {
-
-const char SEP = '/';
-
-std::string DeduplicateAndTrimSeparators(const std::string& path) {
-    bool lastWasSep = false;
-    std::string ret;
-
-    std::copy_if(path.begin(), path.end(), std::back_inserter(ret), [&lastWasSep](char c) {
-        if (lastWasSep) {
-            if (c == SEP) return false;
-            lastWasSep = false;
-        } else if (c == SEP) {
-            lastWasSep = true;
-        }
-        return true;
-    });
-
-    if (ret.length() > 1 && ret.back() == SEP) ret.pop_back();
-
-    return ret;
-}
-
-}  // namespace internal
-
-unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path) {
-    const std::string deduped_root = internal::DeduplicateAndTrimSeparators(controller_root);
-    const std::string deduped_path = internal::DeduplicateAndTrimSeparators(cgroup_path);
-
-    if (deduped_root.empty() || deduped_path.empty() || !deduped_path.starts_with(deduped_root))
-        return 0;
-
-    return std::count(deduped_path.begin() + deduped_root.size(), deduped_path.end(),
-                      internal::SEP);
-}
+unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path);
 
 }  // namespace util
diff --git a/libprocessgroup/util/util.cpp b/libprocessgroup/util/util.cpp
new file mode 100644
index 0000000..9b88a22
--- /dev/null
+++ b/libprocessgroup/util/util.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <processgroup/util.h>
+
+#include <algorithm>
+#include <iterator>
+
+namespace {
+
+const char SEP = '/';
+
+std::string DeduplicateAndTrimSeparators(const std::string& path) {
+    bool lastWasSep = false;
+    std::string ret;
+
+    std::copy_if(path.begin(), path.end(), std::back_inserter(ret), [&lastWasSep](char c) {
+        if (lastWasSep) {
+            if (c == SEP) return false;
+            lastWasSep = false;
+        } else if (c == SEP) {
+            lastWasSep = true;
+        }
+        return true;
+    });
+
+    if (ret.length() > 1 && ret.back() == SEP) ret.pop_back();
+
+    return ret;
+}
+
+}  // anonymous namespace
+
+namespace util {
+
+unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path) {
+    const std::string deduped_root = DeduplicateAndTrimSeparators(controller_root);
+    const std::string deduped_path = DeduplicateAndTrimSeparators(cgroup_path);
+
+    if (deduped_root.empty() || deduped_path.empty() || !deduped_path.starts_with(deduped_root))
+        return 0;
+
+    return std::count(deduped_path.begin() + deduped_root.size(), deduped_path.end(), SEP);
+}
+
+}  // namespace util
diff --git a/rootdir/Android.bp b/rootdir/Android.bp
index 1e7b48b..5f0a2c3 100644
--- a/rootdir/Android.bp
+++ b/rootdir/Android.bp
@@ -189,3 +189,9 @@
         (default, default): [],
     }),
 }
+
+filegroup {
+    name: "ramdisk_node_list",
+    srcs: ["ramdisk_node_list"],
+    export_to_make_var: "RAMDISK_NODE_LIST",
+}
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index e743743..ac9ca85 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -35,7 +35,8 @@
     ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
     ln -sf /data/user_de/0/com.android.shell/files/bugreports $(TARGET_ROOT_OUT)/bugreports; \
     ln -sfn /sys/kernel/debug $(TARGET_ROOT_OUT)/d; \
-    ln -sf /storage/self/primary $(TARGET_ROOT_OUT)/sdcard
+    ln -sf /storage/self/primary $(TARGET_ROOT_OUT)/sdcard; \
+    ln -sf /product/etc/security/adb_keys $(TARGET_ROOT_OUT)/adb_keys
 
 ALL_ROOTDIR_SYMLINKS := \
   $(TARGET_ROOT_OUT)/bin \
@@ -150,18 +151,3 @@
 init.environ.rc-soong := $(call intermediates-dir-for,ETC,init.environ.rc-soong)/init.environ.rc-soong
 $(eval $(call copy-one-file,$(init.environ.rc-soong),$(LOCAL_BUILT_MODULE)))
 init.environ.rc-soong :=
-
-#######################################
-# ramdisk_node_list
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := ramdisk_node_list
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(PRODUCT_OUT)
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(BUILD_PREBUILT)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 7c089c2..63e3d06 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -103,7 +103,7 @@
 
     # Mix device-specific information into the entropy pool
     copy /proc/cmdline /dev/urandom
-    copy /system/etc/prop.default /dev/urandom
+    copy /proc/bootconfig /dev/urandom
 
     symlink /proc/self/fd/0 /dev/stdin
     symlink /proc/self/fd/1 /dev/stdout
@@ -145,6 +145,7 @@
 
     # cpuctl hierarchy for devices using utilclamp
     mkdir /dev/cpuctl/foreground
+    mkdir /dev/cpuctl/foreground_window
     mkdir /dev/cpuctl/background
     mkdir /dev/cpuctl/top-app
     mkdir /dev/cpuctl/rt
@@ -153,6 +154,7 @@
     mkdir /dev/cpuctl/dex2oat
     chown system system /dev/cpuctl
     chown system system /dev/cpuctl/foreground
+    chown system system /dev/cpuctl/foreground_window
     chown system system /dev/cpuctl/background
     chown system system /dev/cpuctl/top-app
     chown system system /dev/cpuctl/rt
@@ -161,6 +163,7 @@
     chown system system /dev/cpuctl/dex2oat
     chown system system /dev/cpuctl/tasks
     chown system system /dev/cpuctl/foreground/tasks
+    chown system system /dev/cpuctl/foreground_window/tasks
     chown system system /dev/cpuctl/background/tasks
     chown system system /dev/cpuctl/top-app/tasks
     chown system system /dev/cpuctl/rt/tasks
@@ -169,6 +172,7 @@
     chown system system /dev/cpuctl/dex2oat/tasks
     chown system system /dev/cpuctl/cgroup.procs
     chown system system /dev/cpuctl/foreground/cgroup.procs
+    chown system system /dev/cpuctl/foreground_window/cgroup.procs
     chown system system /dev/cpuctl/background/cgroup.procs
     chown system system /dev/cpuctl/top-app/cgroup.procs
     chown system system /dev/cpuctl/rt/cgroup.procs
@@ -177,6 +181,7 @@
     chown system system /dev/cpuctl/dex2oat/cgroup.procs
     chmod 0664 /dev/cpuctl/tasks
     chmod 0664 /dev/cpuctl/foreground/tasks
+    chmod 0664 /dev/cpuctl/foreground_window/tasks
     chmod 0664 /dev/cpuctl/background/tasks
     chmod 0664 /dev/cpuctl/top-app/tasks
     chmod 0664 /dev/cpuctl/rt/tasks
@@ -185,6 +190,7 @@
     chmod 0664 /dev/cpuctl/dex2oat/tasks
     chmod 0664 /dev/cpuctl/cgroup.procs
     chmod 0664 /dev/cpuctl/foreground/cgroup.procs
+    chmod 0664 /dev/cpuctl/foreground_window/cgroup.procs
     chmod 0664 /dev/cpuctl/background/cgroup.procs
     chmod 0664 /dev/cpuctl/top-app/cgroup.procs
     chmod 0664 /dev/cpuctl/rt/cgroup.procs
@@ -354,6 +360,9 @@
     mkdir /dev/cpuset/foreground
     copy /dev/cpuset/cpus /dev/cpuset/foreground/cpus
     copy /dev/cpuset/mems /dev/cpuset/foreground/mems
+    mkdir /dev/cpuset/foreground_window
+    copy /dev/cpuset/cpus /dev/cpuset/foreground_window/cpus
+    copy /dev/cpuset/mems /dev/cpuset/foreground_window/mems
     mkdir /dev/cpuset/background
     copy /dev/cpuset/cpus /dev/cpuset/background/cpus
     copy /dev/cpuset/mems /dev/cpuset/background/mems
@@ -382,6 +391,7 @@
     # change permissions for all cpusets we'll touch at runtime
     chown system system /dev/cpuset
     chown system system /dev/cpuset/foreground
+    chown system system /dev/cpuset/foreground_window
     chown system system /dev/cpuset/background
     chown system system /dev/cpuset/system-background
     chown system system /dev/cpuset/top-app
@@ -389,6 +399,7 @@
     chown system system /dev/cpuset/camera-daemon
     chown system system /dev/cpuset/tasks
     chown system system /dev/cpuset/foreground/tasks
+    chown system system /dev/cpuset/foreground_window/tasks
     chown system system /dev/cpuset/background/tasks
     chown system system /dev/cpuset/system-background/tasks
     chown system system /dev/cpuset/top-app/tasks
@@ -396,6 +407,7 @@
     chown system system /dev/cpuset/camera-daemon/tasks
     chown system system /dev/cpuset/cgroup.procs
     chown system system /dev/cpuset/foreground/cgroup.procs
+    chown system system /dev/cpuset/foreground_window/cgroup.procs
     chown system system /dev/cpuset/background/cgroup.procs
     chown system system /dev/cpuset/system-background/cgroup.procs
     chown system system /dev/cpuset/top-app/cgroup.procs
@@ -406,6 +418,7 @@
     chmod 0775 /dev/cpuset/system-background
 
     chmod 0664 /dev/cpuset/foreground/tasks
+    chmod 0664 /dev/cpuset/foreground_window/tasks
     chmod 0664 /dev/cpuset/background/tasks
     chmod 0664 /dev/cpuset/system-background/tasks
     chmod 0664 /dev/cpuset/top-app/tasks
@@ -413,6 +426,7 @@
     chmod 0664 /dev/cpuset/tasks
     chmod 0664 /dev/cpuset/camera-daemon/tasks
     chmod 0664 /dev/cpuset/foreground/cgroup.procs
+    chmod 0664 /dev/cpuset/foreground_window/cgroup.procs
     chmod 0664 /dev/cpuset/background/cgroup.procs
     chmod 0664 /dev/cpuset/system-background/cgroup.procs
     chmod 0664 /dev/cpuset/top-app/cgroup.procs
@@ -620,7 +634,6 @@
     mkdir /metadata/bootstat 0750 system log
     mkdir /metadata/ota 0750 root system
     mkdir /metadata/ota/snapshots 0750 root system
-    mkdir /metadata/userspacereboot 0770 root system
     mkdir /metadata/watchdog 0770 root system
 
     mkdir /metadata/apex 0700 root system
@@ -683,8 +696,6 @@
 
 on post-fs-data
 
-    mark_post_data
-
     # Start checkpoint before we touch data
     exec - system system -- /system/bin/vdc checkpoint prepareCheckpoint
 
@@ -1331,46 +1342,6 @@
 on init && property:ro.debuggable=1
     start console
 
-on userspace-reboot-requested
-  # TODO(b/135984674): reset all necessary properties here.
-  setprop sys.boot_completed ""
-  setprop dev.bootcomplete ""
-  setprop sys.init.updatable_crashing ""
-  setprop sys.init.updatable_crashing_process_name ""
-  setprop sys.user.0.ce_available ""
-  setprop sys.shutdown.requested ""
-  setprop service.bootanim.exit ""
-  setprop service.bootanim.progress ""
-
-on userspace-reboot-fs-remount
-  # Make sure that vold is running.
-  # This is mostly a precaution measure in case vold for some reason wasn't running when
-  # userspace reboot was initiated.
-  start vold
-  exec - system system -- /system/bin/vdc checkpoint resetCheckpoint
-  exec - system system -- /system/bin/vdc checkpoint markBootAttempt
-  # Unmount /data_mirror mounts in the reverse order of corresponding mounts.
-  umount /data_mirror/data_ce/null/0
-  umount /data_mirror/data_ce/null
-  umount /data_mirror/data_de/null
-  umount /data_mirror/storage_area/0
-  umount /data_mirror/storage_area
-  umount /data_mirror/cur_profiles
-  umount /data_mirror/ref_profiles
-  umount /data_mirror
-  remount_userdata
-  start bootanim
-
-on userspace-reboot-resume
-  trigger userspace-reboot-fs-remount
-  trigger post-fs-data
-  trigger zygote-start
-  trigger early-boot
-  trigger boot
-
-on property:sys.boot_completed=1 && property:sys.init.userspace_reboot.in_progress=1
-  setprop sys.init.userspace_reboot.in_progress ""
-
 # Multi-Gen LRU Experiment
 on property:persist.device_config.mglru_native.lru_gen_config=none
   write /sys/kernel/mm/lru_gen/enabled 0
diff --git a/rootdir/init.usb.rc b/rootdir/init.usb.rc
index dde784e..b30d6d0 100644
--- a/rootdir/init.usb.rc
+++ b/rootdir/init.usb.rc
@@ -139,7 +139,3 @@
 on property:sys.usb.typec.power_role=sink
     write /sys/class/dual_role_usb/otg_default/power_role ${sys.usb.typec.power_role}
     setprop sys.usb.typec.state ${sys.usb.typec.power_role}
-
-on userspace-reboot-requested
-  setprop sys.usb.config ""
-  setprop sys.usb.state ""
diff --git a/trusty/keymint/Android.bp b/trusty/keymint/Android.bp
index 92d9c6f..1b87d80 100644
--- a/trusty/keymint/Android.bp
+++ b/trusty/keymint/Android.bp
@@ -17,12 +17,10 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-rust_binary {
-    name: "android.hardware.security.keymint-service.rust.trusty",
+rust_defaults {
+    name: "android.hardware.security.keymint-service.rust.trusty.default",
     relative_install_path: "hw",
     vendor: true,
-    init_rc: ["android.hardware.security.keymint-service.rust.trusty.rc"],
-    vintf_fragments: ["android.hardware.security.keymint-service.rust.trusty.xml"],
     srcs: [
         "src/keymint_hal_main.rs",
     ],
@@ -37,7 +35,23 @@
         "liblog_rust",
     ],
     prefer_rlib: true,
+}
+
+rust_binary {
+    name: "android.hardware.security.keymint-service.rust.trusty",
+    defaults: ["android.hardware.security.keymint-service.rust.trusty.default"],
+    init_rc: ["android.hardware.security.keymint-service.rust.trusty.rc"],
+    vintf_fragments: ["android.hardware.security.keymint-service.rust.trusty.xml"],
     required: [
         "android.hardware.hardware_keystore.xml",
     ],
 }
+
+rust_binary {
+    name: "android.hardware.security.keymint-service.rust.trusty.nonsecure",
+    defaults: ["android.hardware.security.keymint-service.rust.trusty.default"],
+    features: ["nonsecure"],
+    rustlibs: [
+        "libkmr_hal_nonsecure",
+    ],
+}
diff --git a/trusty/keymint/src/keymint_hal_main.rs b/trusty/keymint/src/keymint_hal_main.rs
index 3c5627b..a0b1d79 100644
--- a/trusty/keymint/src/keymint_hal_main.rs
+++ b/trusty/keymint/src/keymint_hal_main.rs
@@ -18,7 +18,7 @@
 use kmr_hal::{
     extract_rsp, keymint, rpc, secureclock, send_hal_info, sharedsecret, SerializedChannel,
 };
-use log::{error, info};
+use log::{error, info, warn};
 use std::{
     ffi::CString,
     ops::DerefMut,
@@ -109,7 +109,11 @@
         error!("{}", panic_info);
     }));
 
-    info!("Trusty KM HAL service is starting.");
+    if cfg!(feature = "nonsecure") {
+        warn!("Non-secure Trusty KM HAL service is starting.");
+    } else {
+        info!("Trusty KM HAL service is starting.");
+    }
 
     info!("Starting thread pool now.");
     binder::ProcessState::start_thread_pool();
@@ -126,6 +130,29 @@
         )?;
     let tipc_channel = Arc::new(Mutex::new(TipcChannel(connection)));
 
+    #[cfg(feature = "nonsecure")]
+    {
+        // When the non-secure feature is enabled, retrieve root-of-trust information
+        // (with the exception of the verified boot key hash) from Android properties, and
+        // populate the TA with this information. On a real device, the bootloader should
+        // provide this data to the TA directly.
+        let boot_req = kmr_hal_nonsecure::get_boot_info();
+        info!("boot/HAL->TA: boot info is {:?}", boot_req);
+        kmr_hal::send_boot_info(tipc_channel.lock().unwrap().deref_mut(), boot_req)
+            .map_err(|e| HalServiceError(format!("Failed to send boot info: {:?}", e)))?;
+        // When the non-secure feature is enabled, also retrieve device ID information
+        // (except for IMEI/MEID values) from Android properties and populate the TA with
+        // this information. On a real device, a factory provisioning process would populate
+        // this information.
+        let attest_ids = kmr_hal_nonsecure::attestation_id_info();
+        if let Err(e) =
+            kmr_hal::send_attest_ids(tipc_channel.lock().unwrap().deref_mut(), attest_ids)
+        {
+            error!("Failed to send attestation ID info: {:?}", e);
+        }
+        info!("Successfully sent non-secure boot info and attestation IDs to the TA.");
+    }
+
     // Register the Keymint service
     let km_service = keymint::Device::new_as_binder(tipc_channel.clone());
     let km_service_name = format!("{}/{}", KM_SERVICE_NAME, SERVICE_INSTANCE);
diff --git a/trusty/libtrusty-rs/Android.bp b/trusty/libtrusty-rs/Android.bp
index 4fc162b..e289005 100644
--- a/trusty/libtrusty-rs/Android.bp
+++ b/trusty/libtrusty-rs/Android.bp
@@ -21,9 +21,10 @@
     crate_name: "trusty",
     vendor_available: true,
     srcs: [
-        "src/lib.rs"
+        "src/lib.rs",
     ],
     rustlibs: [
+        "liblog_rust",
         "libnix",
         "liblibc",
     ],
@@ -36,5 +37,5 @@
     rustlibs: [
         "libtrusty-rs",
         "liblibc",
-    ]
+    ],
 }
diff --git a/trusty/libtrusty-rs/src/lib.rs b/trusty/libtrusty-rs/src/lib.rs
index 22b894a..9237c8b 100644
--- a/trusty/libtrusty-rs/src/lib.rs
+++ b/trusty/libtrusty-rs/src/lib.rs
@@ -61,12 +61,18 @@
 //! ```
 
 use crate::sys::tipc_connect;
+use log::{trace, warn};
+use nix::sys::socket;
+use std::convert::From;
 use std::ffi::CString;
 use std::fs::File;
+use std::io;
 use std::io::prelude::*;
 use std::io::{ErrorKind, Result};
 use std::os::unix::prelude::AsRawFd;
 use std::path::Path;
+use std::thread;
+use std::time;
 
 mod sys;
 
@@ -98,7 +104,89 @@
     /// bytes. This is handled with a panic because the service names are all
     /// hard-coded constants, and so such an error should always be indicative of a
     /// bug in the calling code.
-    pub fn connect(device: impl AsRef<Path>, service: &str) -> Result<Self> {
+    pub fn connect(device: &str, service: &str) -> Result<Self> {
+        if let Some(cid_port_str) = device.strip_prefix("VSOCK:") {
+            Self::connect_vsock(cid_port_str, service)
+        } else {
+            Self::connect_tipc(device, service)
+        }
+    }
+
+    fn connect_vsock(type_cid_port_str: &str, service: &str) -> Result<Self> {
+        let cid_port_str;
+        let socket_type;
+        if let Some(stream_cid_port_str) = type_cid_port_str.strip_prefix("STREAM:") {
+            socket_type = socket::SockType::Stream;
+            cid_port_str = stream_cid_port_str;
+        } else if let Some(seqpacket_cid_port_str) = type_cid_port_str.strip_prefix("SEQPACKET:") {
+            socket_type = socket::SockType::SeqPacket;
+            cid_port_str = seqpacket_cid_port_str;
+        } else {
+            /*
+             * Default to SOCK_STREAM if neither type is specified.
+             *
+             * TODO: use SOCK_SEQPACKET by default instead of SOCK_STREAM when SOCK_SEQPACKET is fully
+             * supported since it matches tipc better. At the moment SOCK_SEQPACKET is not supported by
+             * crosvm. It is also significantly slower since the Linux kernel implementation (as of
+             * v6.7-rc1) sends credit update packets every time it receives a data packet while the
+             * SOCK_STREAM version skips these unless the remaining buffer space is "low".
+             */
+            socket_type = socket::SockType::Stream;
+            cid_port_str = type_cid_port_str;
+        }
+
+        let [cid, port]: [u32; 2] = cid_port_str
+            .split(':')
+            .map(|v| v.parse::<u32>().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e)))
+            .collect::<Result<Vec<u32>>>()?
+            .try_into()
+            .map_err(|e| {
+                io::Error::new(io::ErrorKind::InvalidInput, format!("Wrong number of args: {e:?}"))
+            })?;
+
+        trace!("got cid, port: {cid}, {port}");
+        let s = socket::socket(
+            socket::AddressFamily::Vsock,
+            socket_type,
+            socket::SockFlag::SOCK_CLOEXEC,
+            None,
+        )?;
+        trace!("got socket");
+        let sa = socket::VsockAddr::new(cid, port);
+        trace!("got sa");
+
+        //let connect_timeout = libc::timeval {tv_sec: 60, tv_usec: 0};
+        // TODO: Set AF_VSOCK/SO_VM_SOCKETS_CONNECT_TIMEOUT sockopt.
+
+        let mut retry = 10;
+        loop {
+            let res = socket::connect(s.as_raw_fd(), &sa);
+            if res.is_ok() || retry <= 0 {
+                res?;
+                break;
+            }
+            warn!("vsock:{cid}:{port} connect failed {res:?}, {retry} retries remaining");
+            retry -= 1;
+            thread::sleep(time::Duration::from_secs(5));
+        }
+        trace!("connected");
+        // TODO: Current vsock tipc bridge in trusty expects a port name in the
+        // first packet. We need to replace this with a protocol that also does DICE
+        // based authentication.
+        // `s` is a valid file descriptor because it came from socket::socket.
+        let mut channel = Self(File::from(s));
+        channel.send(service.as_bytes())?;
+        trace!("sent tipc port name");
+
+        // Work around lack of seq packet support. Read a status byte to prevent
+        // the caller from sending more data until srv_name has been read.
+        let mut status = [0; 1];
+        channel.recv_no_alloc(&mut status)?;
+        trace!("got status byte: {status:?}");
+        Ok(channel)
+    }
+
+    fn connect_tipc(device: impl AsRef<Path>, service: &str) -> Result<Self> {
         let file = File::options().read(true).write(true).open(device)?;
 
         let srv_name = CString::new(service).expect("Service name contained null bytes");
@@ -108,7 +196,7 @@
             tipc_connect(file.as_raw_fd(), srv_name.as_ptr())?;
         }
 
-        Ok(TipcChannel(file))
+        Ok(Self(file))
     }
 
     /// Sends a message to the connected service.
diff --git a/trusty/libtrusty/trusty.c b/trusty/libtrusty/trusty.c
index f44f8b4..63262a0 100644
--- a/trusty/libtrusty/trusty.c
+++ b/trusty/libtrusty/trusty.c
@@ -23,16 +23,161 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
 #include <unistd.h>
 
+#include <linux/vm_sockets.h> /* must be after sys/socket.h */
 #include <log/log.h>
 
 #include <trusty/ipc.h>
 
+static const char* strip_prefix(const char* str, const char* prefix) {
+    size_t prefix_len = strlen(prefix);
+    if (strncmp(str, prefix, prefix_len) == 0) {
+        return str + prefix_len;
+    } else {
+        return NULL;
+    }
+}
+
+static bool use_vsock_connection = false;
+static int tipc_vsock_connect(const char* type_cid_port_str, const char* srv_name) {
+    int ret;
+    const char* cid_port_str;
+    char* port_str;
+    char* end_str;
+    int socket_type;
+    if ((cid_port_str = strip_prefix(type_cid_port_str, "STREAM:"))) {
+        socket_type = SOCK_STREAM;
+    } else if ((cid_port_str = strip_prefix(type_cid_port_str, "SEQPACKET:"))) {
+        socket_type = SOCK_SEQPACKET;
+    } else {
+        /*
+         * Default to SOCK_STREAM if neither type is specified.
+         *
+         * TODO: use SOCK_SEQPACKET by default instead of SOCK_STREAM when SOCK_SEQPACKET is fully
+         * supported since it matches tipc better. At the moment SOCK_SEQPACKET is not supported by
+         * crosvm. It is also significantly slower since the Linux kernel implementation (as of
+         * v6.7-rc1) sends credit update packets every time it receives a data packet while the
+         * SOCK_STREAM version skips these unless the remaining buffer space is "low".
+         */
+        socket_type = SOCK_STREAM;
+        cid_port_str = type_cid_port_str;
+    }
+    long cid = strtol(cid_port_str, &port_str, 0);
+    if (port_str[0] != ':') {
+        ALOGE("%s: invalid VSOCK str, \"%s\", need cid:port missing : after cid\n", __func__,
+              cid_port_str);
+        return -EINVAL;
+    }
+    long port = strtol(port_str + 1, &end_str, 0);
+    if (end_str[0] != '\0') {
+        ALOGE("%s: invalid VSOCK str, \"%s\", need cid:port got %ld:%ld\n", __func__, cid_port_str,
+              cid, port);
+        return -EINVAL;
+    }
+    int fd = socket(AF_VSOCK, socket_type, 0);
+    if (fd < 0) {
+        ret = -errno;
+        ALOGE("%s: can't get vsock %ld:%ld socket for tipc service \"%s\" (err=%d)\n", __func__,
+              cid, port, srv_name, errno);
+        return ret < 0 ? ret : -1;
+    }
+    struct timeval connect_timeout = {.tv_sec = 60, .tv_usec = 0};
+    ret = setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_CONNECT_TIMEOUT, &connect_timeout,
+                     sizeof(connect_timeout));
+    if (ret) {
+        ALOGE("%s: vsock %ld:%ld: Failed to set connect timeout (err=%d)\n", __func__, cid, port,
+              errno);
+        /* failed to set longer timeout, but try to connect anyway */
+    }
+    struct sockaddr_vm sa = {
+            .svm_family = AF_VSOCK,
+            .svm_port = port,
+            .svm_cid = cid,
+    };
+    int retry = 10;
+    do {
+        ret = TEMP_FAILURE_RETRY(connect(fd, (struct sockaddr*)&sa, sizeof(sa)));
+        if (ret && (errno == ENODEV || errno == ESOCKTNOSUPPORT) && --retry) {
+            /*
+             * The kernel returns ESOCKTNOSUPPORT instead of ENODEV if the socket type is
+             * SOCK_SEQPACKET and the guest CID we are trying to connect to is not ready yet.
+             */
+            ALOGE("%s: Can't connect to vsock %ld:%ld for tipc service \"%s\" (err=%d) %d retries "
+                  "remaining\n",
+                  __func__, cid, port, srv_name, errno, retry);
+            sleep(1);
+        } else {
+            retry = 0;
+        }
+    } while (retry);
+    if (ret) {
+        ret = -errno;
+        ALOGE("%s: Can't connect to vsock %ld:%ld for tipc service \"%s\" (err=%d)\n", __func__,
+              cid, port, srv_name, errno);
+        close(fd);
+        return ret < 0 ? ret : -1;
+    }
+    /*
+     * TODO: Current vsock tipc bridge in trusty expects a port name in the
+     * first packet. We need to replace this with a protocol that also does DICE
+     * based authentication.
+     */
+    ret = TEMP_FAILURE_RETRY(write(fd, srv_name, strlen(srv_name)));
+    if (ret != strlen(srv_name)) {
+        ret = -errno;
+        ALOGE("%s: vsock %ld:%ld: failed to send tipc service name \"%s\" (err=%d)\n", __func__,
+              cid, port, srv_name, errno);
+        close(fd);
+        return ret < 0 ? ret : -1;
+    }
+    /*
+     * Work around lack of seq packet support. Read a status byte to prevent
+     * the caller from sending more data until srv_name has been read.
+     */
+    int8_t status;
+    ret = TEMP_FAILURE_RETRY(read(fd, &status, sizeof(status)));
+    if (ret != sizeof(status)) {
+        ALOGE("%s: vsock %ld:%ld: failed to read status byte for connect to tipc service name "
+              "\"%s\" (err=%d)\n",
+              __func__, cid, port, srv_name, errno);
+        close(fd);
+        return ret < 0 ? ret : -1;
+    }
+    use_vsock_connection = true;
+    return fd;
+}
+
+static size_t tipc_vsock_send(int fd, const struct iovec* iov, int iovcnt, struct trusty_shm* shms,
+                              int shmcnt) {
+    int ret;
+
+    (void)shms;
+    if (shmcnt != 0) {
+        ALOGE("%s: vsock does not yet support passing fds\n", __func__);
+        return -ENOTSUP;
+    }
+    ret = TEMP_FAILURE_RETRY(writev(fd, iov, iovcnt));
+    if (ret < 0) {
+        ret = -errno;
+        ALOGE("%s: failed to send message (err=%d)\n", __func__, errno);
+        return ret < 0 ? ret : -1;
+    }
+
+    return ret;
+}
+
 int tipc_connect(const char* dev_name, const char* srv_name) {
     int fd;
     int rc;
 
+    const char* type_cid_port_str = strip_prefix(dev_name, "VSOCK:");
+    if (type_cid_port_str) {
+        return tipc_vsock_connect(type_cid_port_str, srv_name);
+    }
+
     fd = TEMP_FAILURE_RETRY(open(dev_name, O_RDWR));
     if (fd < 0) {
         rc = -errno;
@@ -54,6 +199,9 @@
 
 ssize_t tipc_send(int fd, const struct iovec* iov, int iovcnt, struct trusty_shm* shms,
                   int shmcnt) {
+    if (use_vsock_connection) {
+        return tipc_vsock_send(fd, iov, iovcnt, shms, shmcnt);
+    }
     struct tipc_send_msg_req req;
     req.iov = (__u64)iov;
     req.iov_cnt = (__u64)iovcnt;