Merge changes from topic "CowSequenceOp"

* changes:
  libsnapshot: Switch merge to CowRevMergeOpItr
  libsnapshot: Add seq op support to inspect_cow
  libsnapshot: Add CowRevMergeOpIter
  libsnapshot: Add IsOrderedOp
  libsnapshot: Cleanup iterators
  libsnapshot: Add Sequence Ops
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 8555b3d..d7e8f32 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -8,6 +8,7 @@
         "-Wall",
         "-Wextra",
         "-Werror",
+        "-Wno-gcc-compat",
         "-Wno-unused-argument",
         "-Wno-unused-function",
         "-Wno-nullability-completeness",
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index 530e0e8..b302918 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -47,19 +47,30 @@
 
 using android::base::ReadFileToString;
 using android::base::SendFileDescriptors;
+using android::base::StringAppendV;
 using android::base::unique_fd;
 using android::base::WriteStringToFd;
 
-static bool send_signal(pid_t pid, const DebuggerdDumpType dump_type) {
-  const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
-  sigval val;
-  val.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0;
+#define TAG "libdebuggerd_client: "
 
-  if (sigqueue(pid, signal, val) != 0) {
-    PLOG(ERROR) << "libdebuggerd_client: failed to send signal to pid " << pid;
-    return false;
+// Log an error both to the log (via LOG(ERROR)) and to the given fd.
+static void log_error(int fd, int errno_value, const char* format, ...) __printflike(3, 4) {
+  std::string message(TAG);
+
+  va_list ap;
+  va_start(ap, format);
+  StringAppendV(&message, format, ap);
+  va_end(ap);
+
+  if (errno_value != 0) {
+    message = message + ": " + strerror(errno_value);
   }
-  return true;
+
+  if (fd != -1) {
+    dprintf(fd, "%s\n", message.c_str());
+  }
+
+  LOG(ERROR) << message;
 }
 
 template <typename Duration>
@@ -74,13 +85,11 @@
  * Returns the wchan data for each thread in the process,
  * or empty string if unable to obtain any data.
  */
-static std::string get_wchan_data(pid_t pid) {
-  std::stringstream buffer;
+static std::string get_wchan_data(int fd, pid_t pid) {
   std::vector<pid_t> tids;
-
   if (!android::procinfo::GetProcessTids(pid, &tids)) {
-    LOG(WARNING) << "libdebuggerd_client: Failed to get process tids";
-    return buffer.str();
+    log_error(fd, 0, "failed to get process tids");
+    return "";
   }
 
   std::stringstream data;
@@ -88,12 +97,13 @@
     std::string path = "/proc/" + std::to_string(pid) + "/task/" + std::to_string(tid) + "/wchan";
     std::string wchan_str;
     if (!ReadFileToString(path, &wchan_str, true)) {
-      PLOG(WARNING) << "libdebuggerd_client: Failed to read \"" << path << "\"";
+      log_error(fd, errno, "failed to read \"%s\"", path.c_str());
       continue;
     }
     data << "sysTid=" << std::left << std::setw(10) << tid << wchan_str << "\n";
   }
 
+  std::stringstream buffer;
   if (std::string str = data.str(); !str.empty()) {
     buffer << "\n----- Waiting Channels: pid " << pid << " at " << get_timestamp() << " -----\n"
            << "Cmd line: " << android::base::Join(get_command_line(pid), " ") << "\n";
@@ -101,16 +111,9 @@
     buffer << "----- end " << std::to_string(pid) << " -----\n";
     buffer << "\n";
   }
-
   return buffer.str();
 }
 
-static void dump_wchan_data(const std::string& data, int fd, pid_t pid) {
-  if (!WriteStringToFd(data, fd)) {
-    LOG(WARNING) << "libdebuggerd_client: Failed to dump wchan data for pid: " << pid;
-  }
-}
-
 bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms,
                             unique_fd output_fd) {
   pid_t pid = tid;
@@ -119,51 +122,51 @@
     android::procinfo::ProcessInfo procinfo;
     std::string error;
     if (!android::procinfo::GetProcessInfo(tid, &procinfo, &error)) {
-      LOG(ERROR) << "libdebugged_client: failed to get process info: " << error;
+      log_error(output_fd, 0, "failed to get process info: %s", error.c_str());
       return false;
     }
     pid = procinfo.pid;
   }
 
-  LOG(INFO) << "libdebuggerd_client: started dumping process " << pid;
-  unique_fd sockfd;
-  const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
-  auto time_left = [&end]() { return end - std::chrono::steady_clock::now(); };
-  auto set_timeout = [timeout_ms, &time_left](int sockfd) {
-    if (timeout_ms <= 0) {
-      return sockfd;
-    }
+  LOG(INFO) << TAG "started dumping process " << pid;
 
-    auto remaining = time_left();
+  // Rather than try to deal with poll() all the way through the flow, we update
+  // the socket timeout between each step (and only use poll() during the final
+  // copy loop).
+  const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
+  auto update_timeout = [timeout_ms, &output_fd](int sockfd, auto end) {
+    if (timeout_ms <= 0) return true;
+
+    auto remaining = end - std::chrono::steady_clock::now();
     if (remaining < decltype(remaining)::zero()) {
-      LOG(ERROR) << "libdebuggerd_client: timeout expired";
-      return -1;
+      log_error(output_fd, 0, "timeout expired");
+      return false;
     }
 
     struct timeval timeout;
     populate_timeval(&timeout, remaining);
-
     if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0) {
-      PLOG(ERROR) << "libdebuggerd_client: failed to set receive timeout";
-      return -1;
+      log_error(output_fd, errno, "failed to set receive timeout");
+      return false;
     }
     if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0) {
-      PLOG(ERROR) << "libdebuggerd_client: failed to set send timeout";
-      return -1;
+      log_error(output_fd, errno, "failed to set send timeout");
+      return false;
     }
-
-    return sockfd;
+    return true;
   };
 
-  sockfd.reset(socket(AF_LOCAL, SOCK_SEQPACKET, 0));
+  unique_fd sockfd(socket(AF_LOCAL, SOCK_SEQPACKET, 0));
   if (sockfd == -1) {
-    PLOG(ERROR) << "libdebugger_client: failed to create socket";
+    log_error(output_fd, errno, "failed to create socket");
     return false;
   }
 
-  if (socket_local_client_connect(set_timeout(sockfd.get()), kTombstonedInterceptSocketName,
+  if (!update_timeout(sockfd, end)) return false;
+
+  if (socket_local_client_connect(sockfd.get(), kTombstonedInterceptSocketName,
                                   ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET) == -1) {
-    PLOG(ERROR) << "libdebuggerd_client: failed to connect to tombstoned";
+    log_error(output_fd, errno, "failed to connect to tombstoned");
     return false;
   }
 
@@ -171,15 +174,11 @@
       .dump_type = dump_type,
       .pid = pid,
   };
-  if (!set_timeout(sockfd)) {
-    PLOG(ERROR) << "libdebugger_client: failed to set timeout";
-    return false;
-  }
 
   // Create an intermediate pipe to pass to the other end.
   unique_fd pipe_read, pipe_write;
   if (!Pipe(&pipe_read, &pipe_write)) {
-    PLOG(ERROR) << "libdebuggerd_client: failed to create pipe";
+    log_error(output_fd, errno, "failed to create pipe");
     return false;
   }
 
@@ -194,71 +193,69 @@
   }
 
   if (fcntl(pipe_read.get(), F_SETPIPE_SZ, pipe_buffer_size) != pipe_buffer_size) {
-    PLOG(ERROR) << "failed to set pipe buffer size";
+    log_error(output_fd, errno, "failed to set pipe buffer size");
   }
 
-  ssize_t rc = SendFileDescriptors(set_timeout(sockfd), &req, sizeof(req), pipe_write.get());
+  if (!update_timeout(sockfd, end)) return false;
+  ssize_t rc = SendFileDescriptors(sockfd, &req, sizeof(req), pipe_write.get());
   pipe_write.reset();
   if (rc != sizeof(req)) {
-    PLOG(ERROR) << "libdebuggerd_client: failed to send output fd to tombstoned";
+    log_error(output_fd, errno, "failed to send output fd to tombstoned");
     return false;
   }
 
+  auto get_response = [&output_fd](const char* kind, int sockfd, InterceptResponse* response) {
+    ssize_t rc = TEMP_FAILURE_RETRY(recv(sockfd, response, sizeof(*response), MSG_TRUNC));
+    if (rc == 0) {
+      log_error(output_fd, 0, "failed to read %s response from tombstoned: timeout reached?", kind);
+      return false;
+    } else if (rc == -1) {
+      log_error(output_fd, errno, "failed to read %s response from tombstoned", kind);
+      return false;
+    } else if (rc != sizeof(*response)) {
+      log_error(output_fd, 0,
+                "received packet of unexpected length from tombstoned while reading %s response: "
+                "expected %zd, received %zd",
+                kind, sizeof(response), rc);
+      return false;
+    }
+    return true;
+  };
+
   // Check to make sure we've successfully registered.
   InterceptResponse response;
-  rc = TEMP_FAILURE_RETRY(recv(set_timeout(sockfd.get()), &response, sizeof(response), MSG_TRUNC));
-  if (rc == 0) {
-    LOG(ERROR) << "libdebuggerd_client: failed to read initial response from tombstoned: "
-               << "timeout reached?";
-    return false;
-  } else if (rc == -1) {
-    PLOG(ERROR) << "libdebuggerd_client: failed to read initial response from tombstoned";
-    return false;
-  } else if (rc != sizeof(response)) {
-    LOG(ERROR) << "libdebuggerd_client: received packet of unexpected length from tombstoned while "
-                  "reading initial response: expected "
-               << sizeof(response) << ", received " << rc;
-    return false;
-  }
-
+  if (!update_timeout(sockfd, end)) return false;
+  if (!get_response("initial", sockfd, &response)) return false;
   if (response.status != InterceptStatus::kRegistered) {
-    LOG(ERROR) << "libdebuggerd_client: unexpected registration response: "
-               << static_cast<int>(response.status);
+    log_error(output_fd, 0, "unexpected registration response: %d",
+              static_cast<int>(response.status));
     return false;
   }
 
-  if (!send_signal(tid, dump_type)) {
+  // Send the signal.
+  const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
+  sigval val = {.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0};
+  if (sigqueue(pid, signal, val) != 0) {
+    log_error(output_fd, errno, "failed to send signal to pid %d", pid);
     return false;
   }
 
-  rc = TEMP_FAILURE_RETRY(recv(set_timeout(sockfd.get()), &response, sizeof(response), MSG_TRUNC));
-  if (rc == 0) {
-    LOG(ERROR) << "libdebuggerd_client: failed to read status response from tombstoned: "
-                  "timeout reached?";
-    return false;
-  } else if (rc == -1) {
-    PLOG(ERROR) << "libdebuggerd_client: failed to read status response from tombstoned";
-    return false;
-  } else if (rc != sizeof(response)) {
-    LOG(ERROR) << "libdebuggerd_client: received packet of unexpected length from tombstoned while "
-                  "reading confirmation response: expected "
-               << sizeof(response) << ", received " << rc;
-    return false;
-  }
-
+  if (!update_timeout(sockfd, end)) return false;
+  if (!get_response("status", sockfd, &response)) return false;
   if (response.status != InterceptStatus::kStarted) {
     response.error_message[sizeof(response.error_message) - 1] = '\0';
-    LOG(ERROR) << "libdebuggerd_client: tombstoned reported failure: " << response.error_message;
+    log_error(output_fd, 0, "tombstoned reported failure: %s", response.error_message);
     return false;
   }
 
   // Forward output from the pipe to the output fd.
   while (true) {
-    auto remaining_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_left()).count();
+    auto remaining = end - std::chrono::steady_clock::now();
+    auto remaining_ms = std::chrono::duration_cast<std::chrono::milliseconds>(remaining).count();
     if (timeout_ms <= 0) {
       remaining_ms = -1;
     } else if (remaining_ms < 0) {
-      LOG(ERROR) << "libdebuggerd_client: timeout expired";
+      log_error(output_fd, 0, "timeout expired");
       return false;
     }
 
@@ -271,11 +268,11 @@
       if (errno == EINTR) {
         continue;
       } else {
-        PLOG(ERROR) << "libdebuggerd_client: error while polling";
+        log_error(output_fd, errno, "error while polling");
         return false;
       }
     } else if (rc == 0) {
-      LOG(ERROR) << "libdebuggerd_client: timeout expired";
+      log_error(output_fd, 0, "timeout expired");
       return false;
     }
 
@@ -285,17 +282,17 @@
       // Done.
       break;
     } else if (rc == -1) {
-      PLOG(ERROR) << "libdebuggerd_client: error while reading";
+      log_error(output_fd, errno, "error while reading");
       return false;
     }
 
     if (!android::base::WriteFully(output_fd.get(), buf, rc)) {
-      PLOG(ERROR) << "libdebuggerd_client: error while writing";
+      log_error(output_fd, errno, "error while writing");
       return false;
     }
   }
 
-  LOG(INFO) << "libdebuggerd_client: done dumping process " << pid;
+  LOG(INFO) << TAG "done dumping process " << pid;
 
   return true;
 }
@@ -313,14 +310,16 @@
 
   // debuggerd_trigger_dump results in every thread in the process being interrupted
   // by a signal, so we need to fetch the wchan data before calling that.
-  std::string wchan_data = get_wchan_data(tid);
+  std::string wchan_data = get_wchan_data(fd, tid);
 
   int timeout_ms = timeout_secs > 0 ? timeout_secs * 1000 : 0;
   int ret = debuggerd_trigger_dump(tid, dump_type, timeout_ms, std::move(copy)) ? 0 : -1;
 
   // Dump wchan data, since only privileged processes (CAP_SYS_ADMIN) can read
   // kernel stack traces (/proc/*/stack).
-  dump_wchan_data(wchan_data, fd, tid);
+  if (!WriteStringToFd(wchan_data, fd)) {
+    LOG(WARNING) << TAG "Failed to dump wchan data for pid: " << tid;
+  }
 
   return ret;
 }
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 24804d0..abda071 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -58,6 +58,7 @@
 #include <scoped_minijail.h>
 
 #include "debuggerd/handler.h"
+#include "libdebuggerd/utility.h"
 #include "protocol.h"
 #include "tombstoned/tombstoned.h"
 #include "util.h"
@@ -526,6 +527,8 @@
   std::vector<std::string> log_sources(2);
   ConsumeFd(std::move(output_fd), &log_sources[0]);
   logcat_collector.Collect(&log_sources[1]);
+  // Tag dump only available in the tombstone, not logcat.
+  ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
 
   for (const auto& result : log_sources) {
     ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
@@ -597,6 +600,12 @@
   ConsumeFd(std::move(output_fd), &log_sources[0]);
   logcat_collector.Collect(&log_sources[1]);
 
+  // Tag dump only in tombstone, not logcat, and tagging is not used for
+  // overflow protection in the scudo secondary (guard pages are used instead).
+  if (GetParam() < 0x10000) {
+    ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
+  }
+
   for (const auto& result : log_sources) {
     ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
     ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
@@ -637,6 +646,7 @@
                            std::to_string(GetParam()) + R"(-byte allocation)");
   ASSERT_MATCH(result, R"((^|\s)allocated by thread .*
       #00 pc)");
+  ASSERT_MATCH(result, "Memory tags around the fault address");
 #else
   GTEST_SKIP() << "Requires aarch64";
 #endif
@@ -686,6 +696,9 @@
   ConsumeFd(std::move(output_fd), &log_sources[0]);
   logcat_collector.Collect(&log_sources[1]);
 
+  // Tag dump only in the tombstone, not logcat.
+  ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
+
   for (const auto& result : log_sources) {
     ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
     ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, "
@@ -706,21 +719,26 @@
 
 #if defined(__aarch64__)
 static uintptr_t CreateTagMapping() {
-  uintptr_t mapping =
-      reinterpret_cast<uintptr_t>(mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE | PROT_MTE,
-                                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
-  if (reinterpret_cast<void*>(mapping) == MAP_FAILED) {
+  // 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.
+  size_t page_size = getpagesize();
+  void* mapping = mmap(nullptr, page_size * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  uintptr_t mapping_uptr = reinterpret_cast<uintptr_t>(mapping);
+  if (mapping == MAP_FAILED) {
     return 0;
   }
-  __asm__ __volatile__(".arch_extension mte; stg %0, [%0]"
-                       :
-                       : "r"(mapping + (1ULL << 56))
-                       : "memory");
-  return mapping;
+  mprotect(reinterpret_cast<void*>(mapping_uptr + page_size), page_size,
+           PROT_READ | PROT_WRITE | PROT_MTE);
+  // Stripe the mapping, where even granules get tag '1', and odd granules get tag '0'.
+  for (uintptr_t offset = 0; offset < page_size; offset += 2 * kTagGranuleSize) {
+    uintptr_t tagged_addr = mapping_uptr + page_size + offset + (1ULL << 56);
+    __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : : "r"(tagged_addr) : "memory");
+  }
+  return mapping_uptr + page_size;
 }
 #endif
 
-TEST_F(CrasherTest, mte_tag_dump) {
+TEST_F(CrasherTest, mte_register_tag_dump) {
 #if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
@@ -753,6 +771,107 @@
 #endif
 }
 
+TEST_F(CrasherTest, mte_fault_tag_dump_front_truncated) {
+#if defined(__aarch64__)
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&]() {
+    SetTagCheckingLevelSync();
+    volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+    p[0] = 0;  // Untagged pointer, tagged memory.
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(Memory tags around the fault address.*
+\s*=>0x[0-9a-f]+000:\[1\] 0  1  0)");
+#else
+  GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_F(CrasherTest, mte_fault_tag_dump) {
+#if defined(__aarch64__)
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&]() {
+    SetTagCheckingLevelSync();
+    volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+    p[320] = 0;  // Untagged pointer, tagged memory.
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(Memory tags around the fault address.*
+\s*0x[0-9a-f]+: 1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0
+\s*=>0x[0-9a-f]+: 1  0  1  0 \[1\] 0  1  0  1  0  1  0  1  0  1  0
+\s*0x[0-9a-f]+: 1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0
+)");
+#else
+  GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_F(CrasherTest, mte_fault_tag_dump_rear_truncated) {
+#if defined(__aarch64__)
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&]() {
+    SetTagCheckingLevelSync();
+    size_t page_size = getpagesize();
+    volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+    p[page_size - kTagGranuleSize * 2] = 0;  // Untagged pointer, tagged memory.
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(Memory tags around the fault address)");
+  ASSERT_MATCH(result,
+               R"(\s*0x[0-9a-f]+: 1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0
+\s*=>0x[0-9a-f]+: 1  0  1  0  1  0  1  0  1  0  1  0  1  0 \[1\] 0
+
+)");  // Ensure truncation happened and there's a newline after the tag fault.
+#else
+  GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
 TEST_F(CrasherTest, LD_PRELOAD) {
   int intercept_result;
   unique_fd output_fd;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index c490fb1..24ae169 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -96,4 +96,8 @@
 // Number of bytes per MTE granule.
 constexpr size_t kTagGranuleSize = 16;
 
+// Number of rows and columns to display in an MTE tag dump.
+constexpr size_t kNumTagColumns = 16;
+constexpr size_t kNumTagRows = 16;
+
 #endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index abd1f12..ff12017 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -362,8 +362,10 @@
             dump.set_mapping_name(map_info->name());
           }
 
-          char buf[256];
-          uint8_t tags[256 / kTagGranuleSize];
+          constexpr size_t kNumBytesAroundRegister = 256;
+          constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize;
+          char buf[kNumBytesAroundRegister];
+          uint8_t tags[kNumTagsAroundRegister];
           size_t start_offset = 0;
           ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
           if (bytes == -1) {
@@ -377,7 +379,19 @@
           }
 
           dump.set_memory(buf, bytes);
-          dump.set_tags(tags, bytes / kTagGranuleSize);
+
+          bool has_tags = false;
+#if defined(__aarch64__)
+          for (size_t i = 0; i < kNumTagsAroundRegister; ++i) {
+            if (tags[i] != 0) {
+              has_tags = true;
+            }
+          }
+#endif  // defined(__aarch64__)
+
+          if (has_tags) {
+            dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister);
+          }
 
           *thread.add_memory_dump() = std::move(dump);
         }
@@ -531,6 +545,50 @@
   dump_log_file(tombstone, "main", pid);
 }
 
+static void dump_tags_around_fault_addr(Signal* signal, const Tombstone& tombstone,
+                                        unwindstack::Unwinder* unwinder, uintptr_t fault_addr) {
+  if (tombstone.arch() != Architecture::ARM64) return;
+
+  fault_addr = untag_address(fault_addr);
+  constexpr size_t kNumGranules = kNumTagRows * kNumTagColumns;
+  constexpr size_t kBytesToRead = kNumGranules * kTagGranuleSize;
+
+  // If the low part of the tag dump would underflow to the high address space, it's probably not
+  // a valid address for us to dump tags from.
+  if (fault_addr < kBytesToRead / 2) return;
+
+  unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
+
+  constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1);
+  size_t start_address = (fault_addr & kRowStartMask) - kBytesToRead / 2;
+  MemoryDump tag_dump;
+  size_t granules_to_read = kNumGranules;
+
+  // Attempt to read the first tag. If reading fails, this likely indicates the
+  // lowest touched page is inaccessible or not marked with PROT_MTE.
+  // Fast-forward over pages until one has tags, or we exhaust the search range.
+  while (memory->ReadTag(start_address) < 0) {
+    size_t page_size = sysconf(_SC_PAGE_SIZE);
+    size_t bytes_to_next_page = page_size - (start_address % page_size);
+    if (bytes_to_next_page >= granules_to_read * kTagGranuleSize) return;
+    start_address += bytes_to_next_page;
+    granules_to_read -= bytes_to_next_page / kTagGranuleSize;
+  }
+  tag_dump.set_begin_address(start_address);
+
+  std::string* mte_tags = tag_dump.mutable_arm_mte_metadata()->mutable_memory_tags();
+
+  for (size_t i = 0; i < granules_to_read; ++i) {
+    long tag = memory->ReadTag(start_address + i * kTagGranuleSize);
+    if (tag < 0) break;
+    mte_tags->push_back(static_cast<uint8_t>(tag));
+  }
+
+  if (!mte_tags->empty()) {
+    *signal->mutable_fault_adjacent_metadata() = tag_dump;
+  }
+}
+
 static std::optional<uint64_t> read_uptime_secs() {
   std::string uptime;
   if (!android::base::ReadFileToString("/proc/uptime", &uptime)) {
@@ -594,7 +652,9 @@
 
   if (process_info.has_fault_address) {
     sig.set_has_fault_address(true);
-    sig.set_fault_address(process_info.maybe_tagged_fault_address);
+    uintptr_t fault_addr = process_info.maybe_tagged_fault_address;
+    sig.set_fault_address(fault_addr);
+    dump_tags_around_fault_addr(&sig, result, unwinder, fault_addr);
   }
 
   *result.mutable_signal_info() = sig;
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index a932d48..053299a 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -29,6 +29,7 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <async_safe/log.h>
+#include <bionic/macros.h>
 
 #include "tombstone.pb.h"
 
@@ -193,8 +194,11 @@
     uint64_t addr = mem.begin_address();
     for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) {
       uint64_t tagged_addr = addr;
-      if (mem.tags().size() > offset / kTagGranuleSize) {
-        tagged_addr |= static_cast<uint64_t>(mem.tags()[offset / kTagGranuleSize]) << 56;
+      if (mem.has_arm_mte_metadata() &&
+          mem.arm_mte_metadata().memory_tags().size() > offset / kTagGranuleSize) {
+        tagged_addr |=
+            static_cast<uint64_t>(mem.arm_mte_metadata().memory_tags()[offset / kTagGranuleSize])
+            << 56;
       }
       std::string line = StringPrintf("    %0*" PRIx64, word_size * 2, tagged_addr + offset);
 
@@ -232,6 +236,60 @@
   print_thread_memory_dump(callback, tombstone, thread);
 }
 
+static void print_tag_dump(CallbackType callback, const Tombstone& tombstone) {
+  if (!tombstone.has_signal_info()) return;
+
+  const Signal& signal = tombstone.signal_info();
+
+  if (!signal.has_fault_address() || !signal.has_fault_adjacent_metadata()) {
+    return;
+  }
+
+  const MemoryDump& memory_dump = signal.fault_adjacent_metadata();
+
+  if (!memory_dump.has_arm_mte_metadata() || memory_dump.arm_mte_metadata().memory_tags().empty()) {
+    return;
+  }
+
+  const std::string& tags = memory_dump.arm_mte_metadata().memory_tags();
+
+  CBS("");
+  CBS("Memory tags around the fault address (0x%" PRIx64 "), one tag per %zu bytes:",
+      signal.fault_address(), kTagGranuleSize);
+  constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1);
+
+  size_t tag_index = 0;
+  size_t num_tags = tags.length();
+  uintptr_t fault_granule = untag_address(signal.fault_address()) & ~(kTagGranuleSize - 1);
+  for (size_t row = 0; tag_index < num_tags; ++row) {
+    uintptr_t row_addr =
+        (memory_dump.begin_address() + row * kNumTagColumns * kTagGranuleSize) & kRowStartMask;
+    std::string row_contents;
+    bool row_has_fault = false;
+
+    for (size_t column = 0; column < kNumTagColumns; ++column) {
+      uintptr_t granule_addr = row_addr + column * kTagGranuleSize;
+      if (granule_addr < memory_dump.begin_address() ||
+          granule_addr >= memory_dump.begin_address() + num_tags * kTagGranuleSize) {
+        row_contents += " . ";
+      } else if (granule_addr == fault_granule) {
+        row_contents += StringPrintf("[%1hhx]", tags[tag_index++]);
+        row_has_fault = true;
+      } else {
+        row_contents += StringPrintf(" %1hhx ", tags[tag_index++]);
+      }
+    }
+
+    if (row_contents.back() == ' ') row_contents.pop_back();
+
+    if (row_has_fault) {
+      CBS("    =>0x%" PRIxPTR ":%s", row_addr, row_contents.c_str());
+    } else {
+      CBS("      0x%" PRIxPTR ":%s", row_addr, row_contents.c_str());
+    }
+  }
+}
+
 static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
                               const Thread& thread) {
   print_thread_header(callback, tombstone, thread, true);
@@ -299,6 +357,8 @@
     }
   }
 
+  print_tag_dump(callback, tombstone);
+
   print_thread_memory_dump(callback, tombstone, thread);
 
   CBS("");
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 2c645b5..a7506b7 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -402,6 +402,8 @@
         case TRAP_HWBKPT: return "TRAP_HWBKPT";
         case TRAP_UNK:
           return "TRAP_UNDIAGNOSED";
+        case TRAP_PERF:
+          return "TRAP_PERF";
       }
       if ((si->si_code & 0xff) == SIGTRAP) {
         switch ((si->si_code >> 8) & 0xff) {
@@ -423,7 +425,7 @@
             return "PTRACE_EVENT_STOP";
         }
       }
-      static_assert(NSIGTRAP == TRAP_UNK, "missing TRAP_* si_code");
+      static_assert(NSIGTRAP == TRAP_PERF, "missing TRAP_* si_code");
       break;
   }
   // Then the other codes...
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index 22fc30e..a701212 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -56,8 +56,11 @@
 
   bool has_fault_address = 8;
   uint64 fault_address = 9;
+  // Note, may or may not contain the dump of the actual memory contents. Currently, on arm64, we
+  // only include metadata, and not the contents.
+  MemoryDump fault_adjacent_metadata = 10;
 
-  reserved 10 to 999;
+  reserved 11 to 999;
 }
 
 message HeapObject {
@@ -142,14 +145,22 @@
   reserved 9 to 999;
 }
 
+message ArmMTEMetadata {
+  // One memory tag per granule (e.g. every 16 bytes) of regular memory.
+  bytes memory_tags = 1;
+  reserved 2 to 999;
+}
+
 message MemoryDump {
   string register_name = 1;
   string mapping_name = 2;
   uint64 begin_address = 3;
   bytes memory = 4;
-  bytes tags = 5;
+  oneof metadata {
+    ArmMTEMetadata arm_mte_metadata = 6;
+  }
 
-  reserved 6 to 999;
+  reserved 5, 7 to 999;
 }
 
 message MemoryMapping {
diff --git a/fs_mgr/fs_mgr_boot_config.cpp b/fs_mgr/fs_mgr_boot_config.cpp
index e3ef232..75d1e0d 100644
--- a/fs_mgr/fs_mgr_boot_config.cpp
+++ b/fs_mgr/fs_mgr_boot_config.cpp
@@ -91,12 +91,6 @@
         if (key == bootconfig_key) {
             *out_val = value;
             return true;
-        } else if (android_key == "hardware" && android_key == key) {
-            // bootconfig doesn't allow subkeys and values to coexist, so
-            // "androidboot.hardware" cannot be used. It is replaced in
-            // bootconfig with "hardware"
-            *out_val = value;
-            return true;
         }
     }
 
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index c4874b8..a5eda29 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -170,19 +170,18 @@
     return access("/system/bin/recovery", F_OK) == 0;
 }
 
-bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table, std::string* path,
-                                const std::chrono::milliseconds& timeout_ms) {
+bool DeviceMapper::CreateEmptyDevice(const std::string& name) {
     std::string uuid = GenerateUuid();
-    if (!CreateDevice(name, uuid)) {
-        return false;
-    }
+    return CreateDevice(name, uuid);
+}
 
+bool DeviceMapper::WaitForDevice(const std::string& name,
+                                 const std::chrono::milliseconds& timeout_ms, std::string* path) {
     // We use the unique path for testing whether the device is ready. After
     // that, it's safe to use the dm-N path which is compatible with callers
     // that expect it to be formatted as such.
     std::string unique_path;
-    if (!LoadTableAndActivate(name, table) || !GetDeviceUniquePath(name, &unique_path) ||
-        !GetDmDevicePathByName(name, path)) {
+    if (!GetDeviceUniquePath(name, &unique_path) || !GetDmDevicePathByName(name, path)) {
         DeleteDevice(name);
         return false;
     }
@@ -208,6 +207,25 @@
     return true;
 }
 
+bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table, std::string* path,
+                                const std::chrono::milliseconds& timeout_ms) {
+    if (!CreateEmptyDevice(name)) {
+        return false;
+    }
+
+    if (!LoadTableAndActivate(name, table)) {
+        DeleteDevice(name);
+        return false;
+    }
+
+    if (!WaitForDevice(name, timeout_ms, path)) {
+        DeleteDevice(name);
+        return false;
+    }
+
+    return true;
+}
+
 bool DeviceMapper::GetDeviceUniquePath(const std::string& name, std::string* path) {
     struct dm_ioctl io;
     InitIo(&io, name);
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index 8006db2..8314ec5 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -29,6 +29,7 @@
 #include <thread>
 
 #include <android-base/file.h>
+#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
@@ -679,3 +680,17 @@
     ASSERT_NE(0, access(path.c_str(), F_OK));
     ASSERT_EQ(ENOENT, errno);
 }
+
+TEST(libdm, CreateEmptyDevice) {
+    DeviceMapper& dm = DeviceMapper::Instance();
+    ASSERT_TRUE(dm.CreateEmptyDevice("empty-device"));
+    auto guard = android::base::make_scope_guard([&]() { dm.DeleteDevice("empty-device", 5s); });
+
+    // Empty device should be in suspended state.
+    ASSERT_EQ(DmDeviceState::SUSPENDED, dm.GetState("empty-device"));
+
+    std::string path;
+    ASSERT_TRUE(dm.WaitForDevice("empty-device", 5s, &path));
+    // Path should exist.
+    ASSERT_EQ(0, access(path.c_str(), F_OK));
+}
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 70b14fa..8fcdf74 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -115,6 +115,19 @@
     // - ACTIVE: resumes the device.
     bool ChangeState(const std::string& name, DmDeviceState state);
 
+    // Creates empty device.
+    // This supports a use case when a caller doesn't need a device straight away, but instead
+    // asks kernel to create it beforehand, thus avoiding blocking itself from waiting for ueventd
+    // to create user space paths.
+    // Callers are expected to then activate their device by calling LoadTableAndActivate function.
+    // To avoid race conditions, callers must still synchronize with ueventd by calling
+    // WaitForDevice function.
+    bool CreateEmptyDevice(const std::string& name);
+
+    // Waits for device paths to be created in the user space.
+    bool WaitForDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+                       std::string* path);
+
     // Creates a device, loads the given table, and activates it. If the device
     // is not able to be activated, it is destroyed, and false is returned.
     // After creation, |path| contains the result of calling
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index aa1f415..acfaa84 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -311,7 +311,6 @@
         "android.hardware.boot@1.0",
         "android.hardware.boot@1.1",
         "libbase",
-        "libbinder",
         "libext2_uuid",
         "libext4_utils",
         "libfs_mgr_binder",
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index c0649ca..e2abdba 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -191,6 +191,9 @@
 
     // Merge failure code, filled if state == MergeFailed.
     MergeFailureCode merge_failure_code = 7;
+
+    // Source build fingerprint.
+    string source_build_fingerprint = 8;
 }
 
 // Next: 10
@@ -223,4 +226,7 @@
     // Merge failure code, filled if the merge failed at any time (regardless
     // of whether it succeeded at a later time).
     MergeFailureCode merge_failure_code = 9;
+
+    // The source fingerprint at the time the OTA was downloaded.
+    string source_build_fingerprint = 10;
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 94d5055..ec58cca 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -60,6 +60,7 @@
     MOCK_METHOD(bool, Dump, (std::ostream & os), (override));
     MOCK_METHOD(std::unique_ptr<AutoDevice>, EnsureMetadataMounted, (), (override));
     MOCK_METHOD(ISnapshotMergeStats*, GetSnapshotMergeStatsInstance, (), (override));
+    MOCK_METHOD(std::string, ReadSourceBuildFingerprint, (), (override));
 };
 
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
index 067f99c..3d384cc 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
@@ -35,13 +35,16 @@
     MOCK_METHOD(void, set_boot_complete_time_ms, (uint32_t), (override));
     MOCK_METHOD(void, set_boot_complete_to_merge_start_time_ms, (uint32_t), (override));
     MOCK_METHOD(void, set_merge_failure_code, (MergeFailureCode), (override));
+    MOCK_METHOD(void, set_source_build_fingerprint, (const std::string&), (override));
     MOCK_METHOD(uint64_t, cow_file_size, (), (override));
     MOCK_METHOD(uint64_t, total_cow_size_bytes, (), (override));
     MOCK_METHOD(uint64_t, estimated_cow_size_bytes, (), (override));
     MOCK_METHOD(uint32_t, boot_complete_time_ms, (), (override));
     MOCK_METHOD(uint32_t, boot_complete_to_merge_start_time_ms, (), (override));
+    MOCK_METHOD(std::string, source_build_fingerprint, (), (override));
     MOCK_METHOD(MergeFailureCode, merge_failure_code, (), (override));
     MOCK_METHOD(std::unique_ptr<Result>, Finish, (), (override));
+    MOCK_METHOD(bool, WriteState, (), (override));
 
     using ISnapshotMergeStats::Result;
     // Return nullptr if any failure.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 65034f7..15882b3 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -177,6 +177,9 @@
     // code. Otherwise, MergeFailureCode::Ok is returned.
     virtual MergeFailureCode ReadMergeFailureCode() = 0;
 
+    // If an update is in progress, return the source build fingerprint.
+    virtual std::string ReadSourceBuildFingerprint() = 0;
+
     // Find the status of the current update, if any.
     //
     // |progress| depends on the returned status:
@@ -369,6 +372,7 @@
     ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override;
     bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) override;
     bool UnmapAllSnapshots() override;
+    std::string ReadSourceBuildFingerprint() override;
 
     // We can't use WaitForFile during first-stage init, because ueventd is not
     // running and therefore will not automatically create symlinks. Instead,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index 4ce5077..8c2fec7 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -35,12 +35,14 @@
     virtual void set_boot_complete_time_ms(uint32_t ms) = 0;
     virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0;
     virtual void set_merge_failure_code(MergeFailureCode code) = 0;
+    virtual void set_source_build_fingerprint(const std::string& fingerprint) = 0;
     virtual uint64_t cow_file_size() = 0;
     virtual uint64_t total_cow_size_bytes() = 0;
     virtual uint64_t estimated_cow_size_bytes() = 0;
     virtual uint32_t boot_complete_time_ms() = 0;
     virtual uint32_t boot_complete_to_merge_start_time_ms() = 0;
     virtual MergeFailureCode merge_failure_code() = 0;
+    virtual std::string source_build_fingerprint() = 0;
 
     // Called when merge ends. Properly clean up permanent storage.
     class Result {
@@ -52,6 +54,10 @@
     };
     // Return nullptr if any failure.
     virtual std::unique_ptr<Result> Finish() = 0;
+
+    // Write out the current state. This should be called when data might be lost that
+    // cannot be recovered (eg the COW sizes).
+    virtual bool WriteState() = 0;
 };
 
 class SnapshotMergeStats : public ISnapshotMergeStats {
@@ -74,11 +80,13 @@
     uint32_t boot_complete_to_merge_start_time_ms() override;
     void set_merge_failure_code(MergeFailureCode code) override;
     MergeFailureCode merge_failure_code() override;
+    void set_source_build_fingerprint(const std::string& fingerprint) override;
+    std::string source_build_fingerprint() override;
     std::unique_ptr<Result> Finish() override;
+    bool WriteState() override;
 
   private:
     bool ReadState();
-    bool WriteState();
     bool DeleteState();
     SnapshotMergeStats(const std::string& path);
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index a7cd939..74b78c5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -57,6 +57,7 @@
     ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override;
     bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) override;
     bool UnmapAllSnapshots() override;
+    std::string ReadSourceBuildFingerprint() override;
 };
 
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index be732ec..0e36da1 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -716,7 +716,7 @@
         }
     }
 
-    SnapshotUpdateStatus initial_status;
+    SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
     initial_status.set_state(UpdateState::Merging);
     initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
     initial_status.set_total_sectors(initial_target_values.total_sectors);
@@ -2515,15 +2515,25 @@
     SnapshotUpdateStatus status;
     status.set_state(state);
 
-    if (state == UpdateState::MergeFailed) {
-        status.set_merge_failure_code(failure_code);
+    switch (state) {
+        case UpdateState::MergeFailed:
+            status.set_merge_failure_code(failure_code);
+            break;
+        case UpdateState::Initiated:
+            status.set_source_build_fingerprint(
+                    android::base::GetProperty("ro.build.fingerprint", ""));
+            break;
+        default:
+            break;
     }
 
     // If we're transitioning between two valid states (eg, we're not beginning
-    // or ending an OTA), then make sure to propagate the compression bit.
+    // or ending an OTA), then make sure to propagate the compression bit and
+    // build fingerprint.
     if (!(state == UpdateState::Initiated || state == UpdateState::None)) {
         SnapshotUpdateStatus old_status = ReadSnapshotUpdateStatus(lock);
         status.set_compression_enabled(old_status.compression_enabled());
+        status.set_source_build_fingerprint(old_status.source_build_fingerprint());
     }
     return WriteSnapshotUpdateStatus(lock, status);
 }
@@ -2838,7 +2848,7 @@
         }
     }
 
-    SnapshotUpdateStatus status = {};
+    SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
     status.set_state(update_state);
     status.set_compression_enabled(cow_creator.compression_enabled);
     if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
@@ -3264,9 +3274,10 @@
 
     std::stringstream ss;
 
+    auto update_status = ReadSnapshotUpdateStatus(file.get());
+
     ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
-    ss << "Compression: " << ReadSnapshotUpdateStatus(file.get()).compression_enabled()
-       << std::endl;
+    ss << "Compression: " << update_status.compression_enabled() << std::endl;
     ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
     ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
     ss << "Rollback indicator: "
@@ -3275,6 +3286,7 @@
     ss << "Forward merge indicator: "
        << (access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0 ? "exists" : strerror(errno))
        << std::endl;
+    ss << "Source build fingerprint: " << update_status.source_build_fingerprint() << std::endl;
 
     bool ok = true;
     std::vector<std::string> snapshots;
@@ -3792,5 +3804,13 @@
     return status.merge_failure_code();
 }
 
+std::string SnapshotManager::ReadSourceBuildFingerprint() {
+    auto lock = LockExclusive();
+    if (!lock) return {};
+
+    SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
+    return status.source_build_fingerprint();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 4a93d65..712eafb 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -91,7 +91,6 @@
 
 void SnapshotMergeStats::set_cow_file_size(uint64_t cow_file_size) {
     report_.set_cow_file_size(cow_file_size);
-    WriteState();
 }
 
 uint64_t SnapshotMergeStats::cow_file_size() {
@@ -138,6 +137,14 @@
     return report_.merge_failure_code();
 }
 
+void SnapshotMergeStats::set_source_build_fingerprint(const std::string& fingerprint) {
+    report_.set_source_build_fingerprint(fingerprint);
+}
+
+std::string SnapshotMergeStats::source_build_fingerprint() {
+    return report_.source_build_fingerprint();
+}
+
 class SnapshotMergeStatsResultImpl : public SnapshotMergeStats::Result {
   public:
     SnapshotMergeStatsResultImpl(const SnapshotMergeReport& report,
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 1a9eda5..a8d5b8a 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -136,7 +136,10 @@
     void set_boot_complete_to_merge_start_time_ms(uint32_t) override {}
     uint32_t boot_complete_to_merge_start_time_ms() override { return 0; }
     void set_merge_failure_code(MergeFailureCode) override {}
-    MergeFailureCode merge_failure_code() { return MergeFailureCode::Ok; }
+    MergeFailureCode merge_failure_code() override { return MergeFailureCode::Ok; }
+    void set_source_build_fingerprint(const std::string&) override {}
+    std::string source_build_fingerprint() override { return {}; }
+    bool WriteState() override { return false; }
 };
 
 ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -170,4 +173,9 @@
     return MergeFailureCode::Ok;
 }
 
+std::string SnapshotManagerStub::ReadSourceBuildFingerprint() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return {};
+}
+
 }  // namespace android::snapshot
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index a1b020a..94e1abb 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -127,7 +127,7 @@
         "androidboot.serialno = \"BLAHBLAHBLAH\"\n"
         "androidboot.slot_suffix = \"_a\"\n"
         "androidboot.hardware.platform = \"sdw813\"\n"
-        "hardware = \"foo\"\n"
+        "androidboot.hardware = \"foo\"\n"
         "androidboot.revision = \"EVT1.0\"\n"
         "androidboot.bootloader = \"burp-0.1-7521\"\n"
         "androidboot.hardware.sku = \"mary\"\n"
@@ -159,7 +159,7 @@
         {"androidboot.serialno", "BLAHBLAHBLAH"},
         {"androidboot.slot_suffix", "_a"},
         {"androidboot.hardware.platform", "sdw813"},
-        {"hardware", "foo"},
+        {"androidboot.hardware", "foo"},
         {"androidboot.revision", "EVT1.0"},
         {"androidboot.bootloader", "burp-0.1-7521"},
         {"androidboot.hardware.sku", "mary"},
diff --git a/init/devices.cpp b/init/devices.cpp
index ce6298a..56c6623 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -264,6 +264,8 @@
         setfscreatecon(secontext.c_str());
     }
 
+    gid_t new_group = -1;
+
     dev_t dev = makedev(major, minor);
     /* Temporarily change egid to avoid race condition setting the gid of the
      * device node. Unforunately changing the euid would prevent creation of
@@ -291,10 +293,21 @@
             PLOG(ERROR) << "Cannot set '" << secontext << "' SELinux label on '" << path
                         << "' device";
         }
+
+        struct stat s;
+        if (stat(path.c_str(), &s) == 0) {
+            if (gid != s.st_gid) {
+                new_group = gid;
+            }
+        } else {
+            PLOG(ERROR) << "Cannot stat " << path;
+        }
     }
 
 out:
-    chown(path.c_str(), uid, -1);
+    if (chown(path.c_str(), uid, new_group) < 0) {
+        PLOG(ERROR) << "Cannot chown " << path << " " << uid << " " << new_group;
+    }
     if (setegid(AID_ROOT)) {
         PLOG(FATAL) << "setegid(AID_ROOT) failed";
     }
diff --git a/init/mount_handler.cpp b/init/mount_handler.cpp
index 46f8331..f0d8d45 100644
--- a/init/mount_handler.cpp
+++ b/init/mount_handler.cpp
@@ -90,12 +90,18 @@
     auto mount_prop = entry.mount_point;
     if (mount_prop == "/") mount_prop = "/root";
     std::replace(mount_prop.begin(), mount_prop.end(), '/', '.');
-    mount_prop = "dev.mnt.blk" + mount_prop;
+    auto blk_mount_prop = "dev.mnt.blk" + mount_prop;
+    auto dev_mount_prop = "dev.mnt.dev" + mount_prop;
     // Set property even if its value does not change to trigger 'on property:'
     // handling, except for clearing non-existent or already clear property.
     // Goal is reduction of empty properties and associated triggers.
-    if (value.empty() && android::base::GetProperty(mount_prop, "").empty()) return;
-    android::base::SetProperty(mount_prop, value);
+    if (value.empty() && android::base::GetProperty(blk_mount_prop, "").empty()) return;
+    android::base::SetProperty(blk_mount_prop, value);
+    if (!value.empty()) {
+        android::base::SetProperty(dev_mount_prop, entry.blk_device.substr(strlen(devblock)));
+    } else {
+        android::base::SetProperty(dev_mount_prop, "");
+    }
 }
 
 }  // namespace
diff --git a/init/property_service.cpp b/init/property_service.cpp
index ff9da42..2d67bf5 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -1238,21 +1238,11 @@
     });
 }
 
-// bootconfig does not allow to populate `key=value` simultaneously with
-// `key.subkey=value` which does not work with the existing code for
-// `hardware` (e.g. we want both `ro.boot.hardware=value` and
-// `ro.boot.hardware.sku=value`) and for `qemu` (Android Stidio Emulator
-// specific).
-static bool IsAllowedBootconfigKey(const std::string_view key) {
-    return (key == "hardware"sv) || (key == "qemu"sv);
-}
 
 static void ProcessBootconfig() {
     ImportBootconfig([&](const std::string& key, const std::string& value) {
         if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
             InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
-        } else if (IsAllowedBootconfigKey(key)) {
-            InitPropertySet("ro.boot." + key, value);
         }
     });
 }
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 815d2bb..c824376 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -146,12 +146,6 @@
         std::this_thread::sleep_for(5ms);
     }
 
-    // With the exception of boot or shutdown, system uid_ folders are always populated. Spinning
-    // here would needlessly delay most pid removals. Additionally, once empty a uid_ cgroup won't
-    // have processes hanging on it (we've already spun for all its pid_), so there's no need to
-    // spin anyway.
-    rmdir(uid_path.c_str());
-
     return ret;
 }
 
@@ -230,7 +224,11 @@
  * transferred for the user/group passed as uid/gid before system_server can properly access them.
  */
 static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
-    if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
+    if (mkdir(path.c_str(), mode) == -1) {
+        if (errno == EEXIST) {
+            // Directory already exists and permissions have been set at the time it was created
+            return true;
+        }
         return false;
     }
 
diff --git a/libutils/String16.cpp b/libutils/String16.cpp
index faf90c2..c42cada 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -96,6 +96,12 @@
     acquire();
 }
 
+String16::String16(String16&& o) noexcept
+    : mString(o.mString)
+{
+    o.mString = getEmptyString();
+}
+
 String16::String16(const String16& o, size_t len, size_t begin)
     : mString(getEmptyString())
 {
@@ -126,6 +132,13 @@
     release();
 }
 
+String16& String16::operator=(String16&& other) noexcept {
+    release();
+    mString = other.mString;
+    other.mString = getEmptyString();
+    return *this;
+}
+
 size_t String16::size() const
 {
     if (isStaticString()) {
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index 54662ac..7d7230e 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -58,12 +58,27 @@
     EXPECT_STR16EQ(u"Verify me", another);
 }
 
+TEST(String16Test, CopyAssign) {
+    String16 tmp("Verify me");
+    String16 another;
+    another = tmp;
+    EXPECT_STR16EQ(u"Verify me", tmp);
+    EXPECT_STR16EQ(u"Verify me", another);
+}
+
 TEST(String16Test, Move) {
     String16 tmp("Verify me");
     String16 another(std::move(tmp));
     EXPECT_STR16EQ(u"Verify me", another);
 }
 
+TEST(String16Test, MoveAssign) {
+    String16 tmp("Verify me");
+    String16 another;
+    another = std::move(tmp);
+    EXPECT_STR16EQ(u"Verify me", another);
+}
+
 TEST(String16Test, Size) {
     String16 tmp("Verify me");
     EXPECT_EQ(9U, tmp.size());
@@ -174,10 +189,22 @@
     EXPECT_STR16EQ(u"Verify me", another);
 }
 
-TEST(String16Test, StringMoveFromStaticString) {
+TEST(String16Test, StringCopyAssignFromStaticString) {
     StaticString16 tmp(u"Verify me");
-    String16 another(std::move(tmp));
+    String16 another(u"nonstatic");
+    another = tmp;
     EXPECT_STR16EQ(u"Verify me", another);
+    EXPECT_TRUE(another.isStaticString());
+    EXPECT_STR16EQ(u"Verify me", tmp);
+    EXPECT_TRUE(tmp.isStaticString());
+}
+
+TEST(String16Test, StringMoveAssignFromStaticString) {
+    StaticString16 tmp(u"Verify me");
+    String16 another(u"nonstatic");
+    another = std::move(tmp);
+    EXPECT_STR16EQ(u"Verify me", another);
+    EXPECT_TRUE(another.isStaticString());
 }
 
 TEST(String16Test, EmptyStringIsStatic) {
diff --git a/libutils/include/utils/String16.h b/libutils/include/utils/String16.h
index 60d523a..3ef56a3 100644
--- a/libutils/include/utils/String16.h
+++ b/libutils/include/utils/String16.h
@@ -41,6 +41,7 @@
 public:
                                 String16();
                                 String16(const String16& o);
+                                String16(String16&& o) noexcept;
                                 String16(const String16& o,
                                          size_t len,
                                          size_t begin=0);
@@ -69,6 +70,7 @@
             status_t            append(const char16_t* other, size_t len);
 
     inline  String16&           operator=(const String16& other);
+            String16&           operator=(String16&& other) noexcept;
 
     inline  String16&           operator+=(const String16& other);
     inline  String16            operator+(const String16& other) const;
@@ -172,10 +174,6 @@
 
     template <size_t N>
     explicit constexpr String16(const StaticData<N>& s) : mString(s.data) {}
-
-public:
-    template <size_t N>
-    explicit constexpr String16(const StaticString16<N>& s) : mString(s.mString) {}
 };
 
 // String16 can be trivially moved using memcpy() because moving does not
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 81da0e8..a8bef42 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -438,6 +438,7 @@
     # Start logd before any other services run to ensure we capture all of their logs.
     start logd
     # Start lmkd before any other services run so that it can register them
+    write /proc/sys/vm/watermark_boost_factor 0
     chown root system /sys/module/lowmemorykiller/parameters/adj
     chmod 0664 /sys/module/lowmemorykiller/parameters/adj
     chown root system /sys/module/lowmemorykiller/parameters/minfree
@@ -1003,11 +1004,11 @@
 
     # to access F2FS sysfs on dm-<num> directly
     mkdir /dev/sys/fs/by-name 0755 system system
-    symlink /sys/fs/f2fs/${dev.mnt.blk.data} /dev/sys/fs/by-name/userdata
+    symlink /sys/fs/f2fs/${dev.mnt.dev.data} /dev/sys/fs/by-name/userdata
 
     # to access dm-<num> sysfs
     mkdir /dev/sys/block/by-name 0755 system system
-    symlink /sys/devices/virtual/block/${dev.mnt.blk.data} /dev/sys/block/by-name/userdata
+    symlink /sys/devices/virtual/block/${dev.mnt.dev.data} /dev/sys/block/by-name/userdata
 
     # F2FS tuning. Set cp_interval larger than dirty_expire_centisecs, 30 secs,
     # to avoid power consumption when system becomes mostly idle. Be careful
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index b7d7490..97e8d8e 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -18,9 +18,11 @@
         "awk",
         "bc",
         "bzip2",
+        "fsck.exfat",
         "ldd",
         "logwrapper",
         "mini-keyctl",
+        "mkfs.exfat",
         "mkshrc",
         "newfs_msdos",
         "reboot",
diff --git a/storaged/Android.bp b/storaged/Android.bp
index ec27a08..9d5cb48 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -30,7 +30,6 @@
         "libhidlbase",
         "liblog",
         "libprotobuf-cpp-lite",
-        "libsysutils",
         "libutils",
         "libz",
     ],
diff --git a/trusty/keymaster/keymint/TrustyKeyMintOperation.cpp b/trusty/keymaster/keymint/TrustyKeyMintOperation.cpp
index 41a21e9..9440724 100644
--- a/trusty/keymaster/keymint/TrustyKeyMintOperation.cpp
+++ b/trusty/keymaster/keymint/TrustyKeyMintOperation.cpp
@@ -34,6 +34,7 @@
 using ::keymaster::FinishOperationResponse;
 using ::keymaster::TAG_ASSOCIATED_DATA;
 using ::keymaster::TAG_AUTH_TOKEN;
+using ::keymaster::TAG_CONFIRMATION_TOKEN;
 using ::keymaster::UpdateOperationRequest;
 using ::keymaster::UpdateOperationResponse;
 using km_utils::authToken2AidlVec;
@@ -106,12 +107,12 @@
     return ScopedAStatus::ok();
 }
 
-ScopedAStatus TrustyKeyMintOperation::finish(
-        const optional<vector<uint8_t>>& input,      //
-        const optional<vector<uint8_t>>& signature,  //
-        const optional<HardwareAuthToken>& authToken,
-        const optional<TimeStampToken>& /* timestampToken */,
-        const optional<vector<uint8_t>>& /* confirmationToken */, vector<uint8_t>* output) {
+ScopedAStatus TrustyKeyMintOperation::finish(const optional<vector<uint8_t>>& input,      //
+                                             const optional<vector<uint8_t>>& signature,  //
+                                             const optional<HardwareAuthToken>& authToken,
+                                             const optional<TimeStampToken>& /* timestampToken */,
+                                             const optional<vector<uint8_t>>& confirmationToken,
+                                             vector<uint8_t>* output) {
     if (!output) {
         return ScopedAStatus(AStatus_fromServiceSpecificError(
                 static_cast<int32_t>(ErrorCode::OUTPUT_PARAMETER_NULL)));
@@ -119,6 +120,16 @@
     output->clear();
 
     FinishOperationRequest request(impl_->message_version());
+
+    if (authToken) {
+        auto tokenAsVec(authToken2AidlVec(*authToken));
+        request.additional_params.push_back(TAG_AUTH_TOKEN, tokenAsVec.data(), tokenAsVec.size());
+    }
+    if (confirmationToken) {
+        request.additional_params.push_back(TAG_CONFIRMATION_TOKEN, confirmationToken->data(),
+                                            confirmationToken->size());
+    }
+
     request.op_handle = opHandle_;
     if (signature) request.signature.Reinitialize(signature->data(), signature->size());
     size_t serialized_size = request.SerializedSize();
diff --git a/usbd/Android.bp b/usbd/Android.bp
index 22d171d..27db0fa 100644
--- a/usbd/Android.bp
+++ b/usbd/Android.bp
@@ -13,6 +13,5 @@
         "libutils",
         "libhardware",
         "android.hardware.usb.gadget@1.0",
-        "libcutils",
     ],
 }