Merge "[GWP-ASan] Enable debuggerd to pull more allocation metadata."
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index dcf92be..9b96f36 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,8 +1,10 @@
 [Builtin Hooks]
 clang_format = true
+rustfmt = true
 
 [Builtin Hooks Options]
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+rustfmt = --config-path=rustfmt.toml
 
 [Hook Scripts]
 aosp_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "."
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index e0c138b..ad0231d 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -24,6 +24,10 @@
     export_include_dirs: ["common/include"],
     recovery_available: true,
     vendor_ramdisk_available: true,
+    apex_available: [
+        "com.android.virt",
+        "//apex_available:platform",
+   ],
 }
 
 cc_library_shared {
@@ -44,6 +48,10 @@
         "libbase",
         "libcutils",
     ],
+    apex_available: [
+        "com.android.virt",
+        "//apex_available:platform",
+    ],
 
     export_header_lib_headers: ["libdebuggerd_common_headers"],
     export_include_dirs: ["tombstoned/include"],
diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING
index d5327db..394447b 100644
--- a/debuggerd/TEST_MAPPING
+++ b/debuggerd/TEST_MAPPING
@@ -2,6 +2,14 @@
   "presubmit": [
     {
       "name": "debuggerd_test"
+    },
+    {
+      "name": "libtombstoned_client_rust_test"
+    }
+  ],
+  "hwasan-presubmit": [
+    {
+      "name": "debuggerd_test"
     }
   ]
 }
diff --git a/debuggerd/crasher/Android.bp b/debuggerd/crasher/Android.bp
index 23b106e..799163e 100644
--- a/debuggerd/crasher/Android.bp
+++ b/debuggerd/crasher/Android.bp
@@ -45,6 +45,8 @@
     shared_libs: [
         "libbase",
         "liblog",
+    ],
+    static_libs: [
         "libseccomp_policy",
     ],
     multilib: {
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index db30b8f..55490b5 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -39,6 +39,8 @@
 #include "debuggerd/handler.h"
 #endif
 
+extern "C" void android_set_abort_message(const char* msg);
+
 #if defined(__arm__)
 // See https://www.kernel.org/doc/Documentation/arm/kernel_user_helpers.txt for details.
 #define __kuser_helper_version (*(int32_t*) 0xffff0ffc)
@@ -182,6 +184,8 @@
     fprintf(stderr, "  leak                  leak memory until we get OOM-killed\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "  abort                 call abort()\n");
+    fprintf(stderr, "  abort_with_msg        call abort() setting an abort message\n");
+    fprintf(stderr, "  abort_with_null_msg   call abort() setting a null abort message\n");
     fprintf(stderr, "  assert                call assert() without a function\n");
     fprintf(stderr, "  assert2               call assert() with a function\n");
     fprintf(stderr, "  exit                  call exit(1)\n");
@@ -259,6 +263,12 @@
       return crash(42);
     } else if (!strcasecmp(arg, "abort")) {
       maybe_abort();
+    } else if (!strcasecmp(arg, "abort_with_msg")) {
+      android_set_abort_message("Aborting due to crasher");
+      maybe_abort();
+    } else if (!strcasecmp(arg, "abort_with_null")) {
+      android_set_abort_message(nullptr);
+      maybe_abort();
     } else if (!strcasecmp(arg, "assert")) {
       __assert("some_file.c", 123, "false");
     } else if (!strcasecmp(arg, "assert2")) {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index a52bf21..f4ba347 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -383,6 +383,8 @@
 #if !defined(__aarch64__)
   GTEST_SKIP() << "Requires aarch64";
 #endif
+  // HWASan crashes with SIGABRT on tag mismatch.
+  SKIP_WITH_HWASAN;
   int intercept_result;
   unique_fd output_fd;
   StartProcess([]() {
@@ -414,6 +416,10 @@
 #if defined(__i386__)
   GTEST_SKIP() << "architecture does not pass arguments in registers";
 #endif
+  // The memory dump in HWASan crashes sadly shows the memory near the registers
+  // in the HWASan dump function, rather the faulting context. This is a known
+  // issue.
+  SKIP_WITH_HWASAN;
   int intercept_result;
   unique_fd output_fd;
   StartProcess([]() {
@@ -492,6 +498,8 @@
     // instead of GWP-ASan.
     GTEST_SKIP() << "Skipped on MTE.";
   }
+  // Skip this test on HWASan, which will reliably catch test errors as well.
+  SKIP_WITH_HWASAN;
 
   GwpAsanTestParameters params = GetParam();
   LogcatCollector logcat_collector;
@@ -2027,6 +2035,9 @@
 
 // Verify that a fault address after the last map is properly handled.
 TEST_F(CrasherTest, fault_address_after_last_map) {
+  // This makes assumptions about the memory layout that are not true in HWASan
+  // processes.
+  SKIP_WITH_HWASAN;
   uintptr_t crash_uptr = untag_address(UINTPTR_MAX - 15);
   StartProcess([crash_uptr]() {
     ASSERT_EQ(0, crash_call(crash_uptr));
diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp
index 4c1f9eb..c8b25ae 100644
--- a/debuggerd/handler/debuggerd_fallback.cpp
+++ b/debuggerd/handler/debuggerd_fallback.cpp
@@ -73,14 +73,13 @@
     thread.registers.reset(
         unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext));
 
-    // TODO: Create this once and store it in a global?
-    unwindstack::UnwinderFromPid unwinder(kMaxFrames, getpid());
     // Do not use the thread cache here because it will call pthread_key_create
     // which doesn't work in linker code. See b/189803009.
     // Use a normal cached object because the process is stopped, and there
     // is no chance of data changing between reads.
     auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid());
-    unwinder.SetProcessMemory(process_memory);
+    // TODO: Create this once and store it in a global?
+    unwindstack::UnwinderFromPid unwinder(kMaxFrames, getpid(), process_memory);
     dump_backtrace_thread(output_fd, &unwinder, thread);
   }
   __linker_disable_fallback_allocator();
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 14caaf6..eda7182 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -101,10 +101,10 @@
     }
   }
 
-  unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch());
   auto process_memory =
       unwindstack::Memory::CreateProcessMemoryCached(getpid());
-  unwinder.SetProcessMemory(process_memory);
+  unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch(), nullptr,
+                                        process_memory);
   if (!unwinder.Init()) {
     async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to init unwinder object");
     return;
diff --git a/debuggerd/rust/tombstoned_client/Android.bp b/debuggerd/rust/tombstoned_client/Android.bp
new file mode 100644
index 0000000..2007f39
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/Android.bp
@@ -0,0 +1,59 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libtombstoned_client_wrapper",
+    srcs: [
+        "wrapper.cpp",
+    ],
+    generated_sources: [
+        "libtombstoned_client_rust_bridge_code"
+    ],
+    header_libs: [
+        "libbase_headers",
+        "libdebuggerd_common_headers",
+    ],
+    shared_libs: [
+        "libtombstoned_client",
+    ],
+    apex_available: ["com.android.virt"],
+}
+
+rust_defaults {
+    name: "libtombstoned_client_rust_defaults",
+    crate_name: "tombstoned_client",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libcxx",
+        "libthiserror",
+    ],
+    static_libs: [
+        "libtombstoned_client_wrapper",
+    ],
+    shared_libs: [
+        "libtombstoned_client",
+    ],
+}
+
+rust_library {
+    name: "libtombstoned_client_rust",
+    defaults: ["libtombstoned_client_rust_defaults"],
+    apex_available: ["com.android.virt"],
+}
+
+rust_test {
+    name: "libtombstoned_client_rust_test",
+    defaults: ["libtombstoned_client_rust_defaults"],
+    require_root: true,
+    test_suites: ["device-tests"],
+}
+
+genrule {
+    name: "libtombstoned_client_rust_bridge_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["src/lib.rs"],
+    out: ["libtombstoned_client_cxx_generated.cc"],
+}
diff --git a/debuggerd/rust/tombstoned_client/src/lib.rs b/debuggerd/rust/tombstoned_client/src/lib.rs
new file mode 100644
index 0000000..5c8abef
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/src/lib.rs
@@ -0,0 +1,153 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Rust wrapper for tombstoned client.
+
+pub use ffi::DebuggerdDumpType;
+use std::fs::File;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use thiserror::Error;
+
+/// Error communicating with tombstoned.
+#[derive(Clone, Debug, Error, Eq, PartialEq)]
+#[error("Error communicating with tombstoned")]
+pub struct Error;
+
+/// File descriptors for communicating with tombstoned.
+pub struct TombstonedConnection {
+    /// The socket connection to tombstoned.
+    ///
+    /// This is actually a Unix SOCK_SEQPACKET socket not a file, but the Rust standard library
+    /// doesn't have an appropriate type and it's not really worth bringing in a dependency on `uds`
+    /// or something when all we do is pass it back to C++ or close it.
+    tombstoned_socket: File,
+    /// The file descriptor for text output.
+    pub text_output: Option<File>,
+    /// The file descriptor for proto output.
+    pub proto_output: Option<File>,
+}
+
+impl TombstonedConnection {
+    unsafe fn from_raw_fds(
+        tombstoned_socket: RawFd,
+        text_output_fd: RawFd,
+        proto_output_fd: RawFd,
+    ) -> Self {
+        Self {
+            tombstoned_socket: File::from_raw_fd(tombstoned_socket),
+            text_output: if text_output_fd >= 0 {
+                Some(File::from_raw_fd(text_output_fd))
+            } else {
+                None
+            },
+            proto_output: if proto_output_fd >= 0 {
+                Some(File::from_raw_fd(proto_output_fd))
+            } else {
+                None
+            },
+        }
+    }
+
+    /// Connects to tombstoned.
+    pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error> {
+        let mut tombstoned_socket = -1;
+        let mut text_output_fd = -1;
+        let mut proto_output_fd = -1;
+        if ffi::tombstoned_connect_files(
+            pid,
+            &mut tombstoned_socket,
+            &mut text_output_fd,
+            &mut proto_output_fd,
+            dump_type,
+        ) {
+            Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) })
+        } else {
+            Err(Error)
+        }
+    }
+
+    /// Notifies tombstoned that the dump is complete.
+    pub fn notify_completion(&self) -> Result<(), Error> {
+        if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) {
+            Ok(())
+        } else {
+            Err(Error)
+        }
+    }
+}
+
+#[cxx::bridge]
+mod ffi {
+    /// The type of dump.
+    enum DebuggerdDumpType {
+        /// A native backtrace.
+        #[cxx_name = "kDebuggerdNativeBacktrace"]
+        NativeBacktrace,
+        /// A tombstone.
+        #[cxx_name = "kDebuggerdTombstone"]
+        Tombstone,
+        /// A Java backtrace.
+        #[cxx_name = "kDebuggerdJavaBacktrace"]
+        JavaBacktrace,
+        /// Any intercept.
+        #[cxx_name = "kDebuggerdAnyIntercept"]
+        AnyIntercept,
+        /// A tombstone proto.
+        #[cxx_name = "kDebuggerdTombstoneProto"]
+        TombstoneProto,
+    }
+
+    unsafe extern "C++" {
+        include!("wrapper.hpp");
+
+        type DebuggerdDumpType;
+
+        fn tombstoned_connect_files(
+            pid: i32,
+            tombstoned_socket: &mut i32,
+            text_output_fd: &mut i32,
+            proto_output_fd: &mut i32,
+            dump_type: DebuggerdDumpType,
+        ) -> bool;
+
+        fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool;
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::{io::Write, process};
+
+    // Verify that we can connect to tombstoned, write something to the file descriptor it returns,
+    // and notify completion, without any errors.
+    #[test]
+    fn test() {
+        let connection =
+            TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone)
+                .expect("Failed to connect to tombstoned.");
+
+        assert!(connection.proto_output.is_none());
+        connection
+            .text_output
+            .as_ref()
+            .expect("No text output FD returned.")
+            .write_all(b"test data")
+            .expect("Failed to write to text output FD.");
+
+        connection
+            .notify_completion()
+            .expect("Failed to notify completion.");
+    }
+}
diff --git a/debuggerd/rust/tombstoned_client/wrapper.cpp b/debuggerd/rust/tombstoned_client/wrapper.cpp
new file mode 100644
index 0000000..7492329
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/wrapper.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wrapper.hpp"
+
+#include <android-base/unique_fd.h>
+
+#include "tombstoned/tombstoned.h"
+
+using android::base::unique_fd;
+
+bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd,
+                              int& proto_output_fd, DebuggerdDumpType dump_type) {
+  unique_fd tombstoned_socket_unique, text_output_unique, proto_output_unique;
+
+  bool result = tombstoned_connect(pid, &tombstoned_socket_unique, &text_output_unique,
+                                   &proto_output_unique, dump_type);
+  if (result) {
+    tombstoned_socket = tombstoned_socket_unique.release();
+    text_output_fd = text_output_unique.release();
+    proto_output_fd = proto_output_unique.release();
+  }
+
+  return result;
+}
diff --git a/debuggerd/rust/tombstoned_client/wrapper.hpp b/debuggerd/rust/tombstoned_client/wrapper.hpp
new file mode 100644
index 0000000..95d3865
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/wrapper.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+#include "tombstoned/tombstoned.h"
+
+bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd,
+                              int& proto_output_fd, DebuggerdDumpType dump_type);
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 22f8363..06ffe0f 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -76,7 +76,7 @@
 
 }  // namespace
 
-int FlashRawDataChunk(int fd, const char* data, size_t len) {
+int FlashRawDataChunk(PartitionHandle* handle, const char* data, size_t len) {
     size_t ret = 0;
     const size_t max_write_size = 1048576;
     void* aligned_buffer;
@@ -91,7 +91,15 @@
     while (ret < len) {
         int this_len = std::min(max_write_size, len - ret);
         memcpy(aligned_buffer_unique_ptr.get(), data, this_len);
-        int this_ret = write(fd, aligned_buffer_unique_ptr.get(), this_len);
+        // In case of non 4KB aligned writes, reopen without O_DIRECT flag
+        if (this_len & 0xFFF) {
+            if (handle->Reset(O_WRONLY) != true) {
+                PLOG(ERROR) << "Failed to reset file descriptor";
+                return -1;
+            }
+        }
+
+        int this_ret = write(handle->fd(), aligned_buffer_unique_ptr.get(), this_len);
         if (this_ret < 0) {
             PLOG(ERROR) << "Failed to flash data of len " << len;
             return -1;
@@ -102,8 +110,8 @@
     return 0;
 }
 
-int FlashRawData(int fd, const std::vector<char>& downloaded_data) {
-    int ret = FlashRawDataChunk(fd, downloaded_data.data(), downloaded_data.size());
+int FlashRawData(PartitionHandle* handle, const std::vector<char>& downloaded_data) {
+    int ret = FlashRawDataChunk(handle, downloaded_data.data(), downloaded_data.size());
     if (ret < 0) {
         return -errno;
     }
@@ -111,30 +119,30 @@
 }
 
 int WriteCallback(void* priv, const void* data, size_t len) {
-    int fd = reinterpret_cast<long long>(priv);
+    PartitionHandle* handle = reinterpret_cast<PartitionHandle*>(priv);
     if (!data) {
-        return lseek64(fd, len, SEEK_CUR) >= 0 ? 0 : -errno;
+        return lseek64(handle->fd(), len, SEEK_CUR) >= 0 ? 0 : -errno;
     }
-    return FlashRawDataChunk(fd, reinterpret_cast<const char*>(data), len);
+    return FlashRawDataChunk(handle, reinterpret_cast<const char*>(data), len);
 }
 
-int FlashSparseData(int fd, std::vector<char>& downloaded_data) {
+int FlashSparseData(PartitionHandle* handle, std::vector<char>& downloaded_data) {
     struct sparse_file* file = sparse_file_import_buf(downloaded_data.data(),
                                                       downloaded_data.size(), true, false);
     if (!file) {
         // Invalid sparse format
         return -EINVAL;
     }
-    return sparse_file_callback(file, false, false, WriteCallback, reinterpret_cast<void*>(fd));
+    return sparse_file_callback(file, false, false, WriteCallback, reinterpret_cast<void*>(handle));
 }
 
-int FlashBlockDevice(int fd, std::vector<char>& downloaded_data) {
-    lseek64(fd, 0, SEEK_SET);
+int FlashBlockDevice(PartitionHandle* handle, std::vector<char>& downloaded_data) {
+    lseek64(handle->fd(), 0, SEEK_SET);
     if (downloaded_data.size() >= sizeof(SPARSE_HEADER_MAGIC) &&
         *reinterpret_cast<uint32_t*>(downloaded_data.data()) == SPARSE_HEADER_MAGIC) {
-        return FlashSparseData(fd, downloaded_data);
+        return FlashSparseData(handle, downloaded_data);
     } else {
-        return FlashRawData(fd, downloaded_data);
+        return FlashRawData(handle, downloaded_data);
     }
 }
 
@@ -181,7 +189,7 @@
     if (android::base::GetProperty("ro.system.build.type", "") != "user") {
         WipeOverlayfsForPartition(device, partition_name);
     }
-    int result = FlashBlockDevice(handle.fd(), data);
+    int result = FlashBlockDevice(&handle, data);
     sync();
     return result;
 }
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 97b5ad4..3302c43 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -90,14 +90,7 @@
         return false;
     }
 
-    flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
-    unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
-    if (fd < 0) {
-        PLOG(ERROR) << "Failed to open block device: " << handle->path();
-        return false;
-    }
-    handle->set_fd(std::move(fd));
-    return true;
+    return handle->Open(flags);
 }
 
 std::optional<std::string> FindPhysicalPartition(const std::string& name) {
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index 1d81b7a..6e1453f 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -18,6 +18,8 @@
 #include <optional>
 #include <string>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <android/hardware/boot/1.0/IBootControl.h>
 #include <fstab/fstab.h>
@@ -44,11 +46,51 @@
     }
     const std::string& path() const { return path_; }
     int fd() const { return fd_.get(); }
-    void set_fd(android::base::unique_fd&& fd) { fd_ = std::move(fd); }
+    bool Open(int flags) {
+        flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
 
+        // Attempts to open a second device can fail with EBUSY if the device is already open.
+        // Explicitly close any previously opened devices as unique_fd won't close them until
+        // after the attempt to open.
+        fd_.reset();
+
+        fd_ = android::base::unique_fd(TEMP_FAILURE_RETRY(open(path_.c_str(), flags)));
+        if (fd_ < 0) {
+            PLOG(ERROR) << "Failed to open block device: " << path_;
+            return false;
+        }
+        flags_ = flags;
+
+        return true;
+    }
+    bool Reset(int flags) {
+        if (fd_.ok() && (flags | O_EXCL | O_CLOEXEC | O_BINARY) == flags_) {
+            return true;
+        }
+
+        off_t offset = fd_.ok() ? lseek(fd_.get(), 0, SEEK_CUR) : 0;
+        if (offset < 0) {
+            PLOG(ERROR) << "Failed lseek on block device: " << path_;
+            return false;
+        }
+
+        sync();
+
+        if (Open(flags) == false) {
+            return false;
+        }
+
+        if (lseek(fd_.get(), offset, SEEK_SET) != offset) {
+            PLOG(ERROR) << "Failed lseek on block device: " << path_;
+            return false;
+        }
+
+        return true;
+    }
   private:
     std::string path_;
     android::base::unique_fd fd_;
+    int flags_;
     std::function<void()> closer_;
 };
 
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index b8b9262..8c719c8 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -653,6 +653,7 @@
                 entry->blk_device = partition;
                 // AVB keys for DSU should always be under kDsuKeysDir.
                 entry->avb_keys = kDsuKeysDir;
+                entry->fs_mgr_flags.logical = true;
             }
             // Make sure the ext4 is included to support GSI.
             auto partition_ext4 =
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 72827eb..1759cf9 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android-base/properties.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
@@ -31,6 +32,7 @@
 using ::testing::ElementsAre;
 using ::testing::NiceMock;
 using ::testing::Return;
+using android::base::GetProperty;
 
 class Environment : public ::testing::Environment {
   public:
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index e67fb33..d123304 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -20,7 +20,10 @@
 #include <sys/syscall.h>
 
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
+#include <fs_mgr.h>
+#include <fstab/fstab.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
@@ -38,6 +41,7 @@
 using ::testing::_;
 using ::testing::Return;
 using unique_fd = android::base::unique_fd;
+using android::base::GetProperty;
 
 // Our tests assume a 128KiB disk with two 512 byte metadata slots.
 static const size_t kDiskSize = 131072;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 38b47d5..11da568 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -538,6 +538,9 @@
     // Unmap a COW and remove it from a MetadataBuilder.
     void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata);
 
+    // Remove invalid snapshots if any
+    void RemoveInvalidSnapshots(LockedFile* lock);
+
     // Unmap and remove all known snapshots.
     bool RemoveAllSnapshots(LockedFile* lock);
 
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 797d627..a83f535 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -218,7 +218,10 @@
     if (!file) return false;
 
     UpdateState state = ReadUpdateState(file.get());
-    if (state == UpdateState::None) return true;
+    if (state == UpdateState::None) {
+        RemoveInvalidSnapshots(file.get());
+        return true;
+    }
 
     if (state == UpdateState::Initiated) {
         LOG(INFO) << "Update has been initiated, now canceling";
@@ -1903,6 +1906,33 @@
     return true;
 }
 
+void SnapshotManager::RemoveInvalidSnapshots(LockedFile* lock) {
+    std::vector<std::string> snapshots;
+
+    // Remove the stale snapshot metadata
+    //
+    // We make sure that all the three cases
+    // are valid before removing the snapshot metadata:
+    //
+    // 1: dm state is active
+    // 2: Root fs is not mounted off as a snapshot device
+    // 3: Snapshot slot suffix should match current device slot
+    if (!ListSnapshots(lock, &snapshots, device_->GetSlotSuffix()) || snapshots.empty()) {
+        return;
+    }
+
+    // We indeed have some invalid snapshots
+    for (const auto& name : snapshots) {
+        if (dm_.GetState(name) == DmDeviceState::ACTIVE && !IsSnapshotDevice(name)) {
+            if (!DeleteSnapshot(lock, name)) {
+                LOG(ERROR) << "Failed to delete invalid snapshot: " << name;
+            } else {
+                LOG(INFO) << "Invalid snapshot: " << name << " deleted";
+            }
+        }
+    }
+}
+
 bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
     std::vector<std::string> snapshots;
     if (!ListSnapshots(lock, &snapshots)) {
@@ -3205,15 +3235,27 @@
     status.set_compression_enabled(cow_creator.compression_enabled);
     if (cow_creator.compression_enabled) {
         if (!device()->IsTestDevice()) {
+            bool userSnapshotsEnabled = IsUserspaceSnapshotsEnabled();
+            const std::string UNKNOWN = "unknown";
+            const std::string vendor_release = android::base::GetProperty(
+                    "ro.vendor.build.version.release_or_codename", UNKNOWN);
+
+            // No user-space snapshots if vendor partition is on Android 12
+            if (vendor_release.find("12") != std::string::npos) {
+                LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
+                          << vendor_release;
+                userSnapshotsEnabled = false;
+            }
+
             // Userspace snapshots is enabled only if compression is enabled
-            status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
-            if (IsUserspaceSnapshotsEnabled()) {
+            status.set_userspace_snapshots(userSnapshotsEnabled);
+            if (userSnapshotsEnabled) {
                 is_snapshot_userspace_ = true;
                 status.set_io_uring_enabled(IsIouringEnabled());
-                LOG(INFO) << "User-space snapshots enabled";
+                LOG(INFO) << "Userspace snapshots enabled";
             } else {
                 is_snapshot_userspace_ = false;
-                LOG(INFO) << "User-space snapshots disabled";
+                LOG(INFO) << "Userspace snapshots disabled";
             }
 
             // Terminate stale daemon if any
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index b86a802..484a9c4 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
@@ -932,7 +932,6 @@
 
     ASSERT_EQ(area_sz, 2);
 
-    size_t new_chunk = 263;
     // Verify the partially filled area
     void* buffer = snapuserd_->GetExceptionBuffer(1);
     loff_t offset = 0;
@@ -941,7 +940,6 @@
         de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
         ASSERT_EQ(de->old_chunk, i);
         offset += sizeof(struct disk_exception);
-        new_chunk += 1;
     }
 
     de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index c31772b..2f7775c 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -34,6 +34,17 @@
 namespace snapshot {
 
 bool Daemon::IsUserspaceSnapshotsEnabled() {
+    const std::string UNKNOWN = "unknown";
+    const std::string vendor_release =
+            android::base::GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN);
+
+    // No user-space snapshots if vendor partition is on Android 12
+    if (vendor_release.find("12") != std::string::npos) {
+        LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
+                  << vendor_release;
+        return false;
+    }
+
     return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
 }
 
diff --git a/init/Android.bp b/init/Android.bp
index c39d163..dd67d04 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -532,8 +532,8 @@
     cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location check_builtins.cpp) > $(out)",
 }
 
-cc_binary {
-    name: "host_init_verifier",
+cc_defaults {
+    name: "init_host_defaults",
     host_supported: true,
     cflags: [
         "-Wall",
@@ -556,7 +556,6 @@
         "libprocessgroup",
         "libprotobuf-cpp-lite",
     ],
-    srcs: init_common_sources + init_host_sources,
     proto: {
         type: "lite",
     },
@@ -574,6 +573,26 @@
     },
 }
 
+cc_binary {
+    name: "host_init_verifier",
+    defaults: ["init_host_defaults"],
+    srcs: init_common_sources + init_host_sources,
+}
+
+cc_library_host_static {
+    name: "libinit_host",
+    defaults: ["init_host_defaults"],
+    srcs: init_common_sources,
+    export_include_dirs: ["."],
+    proto: {
+        export_proto_headers: true,
+    },
+    visibility: [
+        // host_apex_verifier performs a subset of init.rc validation
+        "//system/apex/tools",
+    ],
+}
+
 sh_binary {
     name: "extra_free_kbytes.sh",
     src: "extra_free_kbytes.sh",
diff --git a/init/OWNERS b/init/OWNERS
index 9e70e7d..4604d06 100644
--- a/init/OWNERS
+++ b/init/OWNERS
@@ -1 +1,2 @@
 dvander@google.com
+jiyong@google.com
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 0eb894b..01db4f5 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1306,58 +1306,11 @@
     }
     globfree(&glob_result);
 
-    // Compare all files /apex/path.#rc and /apex/path.rc with the same "/apex/path" prefix,
-    // choosing the one with the highest # that doesn't exceed the system's SDK.
-    // (.rc == .0rc for ranking purposes)
-    //
     int active_sdk = android::base::GetIntProperty("ro.build.version.sdk", INT_MAX);
 
-    std::map<std::string, std::pair<std::string, int>> script_map;
-
-    for (const auto& c : configs) {
-        int sdk = 0;
-        const std::vector<std::string> parts = android::base::Split(c, ".");
-        std::string base;
-        if (parts.size() < 2) {
-            continue;
-        }
-
-        // parts[size()-1], aka the suffix, should be "rc" or "#rc"
-        // any other pattern gets discarded
-
-        const auto& suffix = parts[parts.size() - 1];
-        if (suffix == "rc") {
-            sdk = 0;
-        } else {
-            char trailer[9] = {0};
-            int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer);
-            if (r != 2) {
-                continue;
-            }
-            if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) {
-                continue;
-            }
-        }
-
-        if (sdk < 0 || sdk > active_sdk) {
-            continue;
-        }
-
-        base = parts[0];
-        for (unsigned int i = 1; i < parts.size() - 1; i++) {
-            base = base + "." + parts[i];
-        }
-
-        // is this preferred over what we already have
-        auto it = script_map.find(base);
-        if (it == script_map.end() || it->second.second < sdk) {
-            script_map[base] = std::make_pair(c, sdk);
-        }
-    }
-
     bool success = true;
-    for (const auto& m : script_map) {
-        success &= parser.ParseConfigFile(m.second.first);
+    for (const auto& c : parser.FilterVersionedConfigs(configs, active_sdk)) {
+        success &= parser.ParseConfigFile(c);
     }
     ServiceList::GetInstance().MarkServicesUpdate();
     if (success) {
diff --git a/init/epoll.cpp b/init/epoll.cpp
index 0580f86..74d8aac 100644
--- a/init/epoll.cpp
+++ b/init/epoll.cpp
@@ -23,8 +23,6 @@
 #include <functional>
 #include <map>
 
-#include <android-base/logging.h>
-
 namespace android {
 namespace init {
 
@@ -44,11 +42,8 @@
     if (!events) {
         return Error() << "Must specify events";
     }
-
-    Info info;
-    info.events = events;
-    info.handler = std::make_shared<decltype(handler)>(std::move(handler));
-    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(info));
+    auto sp = std::make_shared<decltype(handler)>(std::move(handler));
+    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(sp));
     if (!inserted) {
         return Error() << "Cannot specify two epoll handlers for a given FD";
     }
@@ -89,14 +84,8 @@
     }
     std::vector<std::shared_ptr<Handler>> pending_functions;
     for (int i = 0; i < num_events; ++i) {
-        auto& info = *reinterpret_cast<Info*>(ev[i].data.ptr);
-        if ((info.events & (EPOLLIN | EPOLLPRI)) == (EPOLLIN | EPOLLPRI) &&
-            (ev[i].events & EPOLLIN) != ev[i].events) {
-            // This handler wants to know about exception events, and just got one.
-            // Log something informational.
-            LOG(ERROR) << "Received unexpected epoll event set: " << ev[i].events;
-        }
-        pending_functions.emplace_back(info.handler);
+        auto sp = *reinterpret_cast<std::shared_ptr<Handler>*>(ev[i].data.ptr);
+        pending_functions.emplace_back(std::move(sp));
     }
 
     return pending_functions;
diff --git a/init/epoll.h b/init/epoll.h
index f58ae8d..0df5289 100644
--- a/init/epoll.h
+++ b/init/epoll.h
@@ -46,13 +46,8 @@
             std::optional<std::chrono::milliseconds> timeout);
 
   private:
-    struct Info {
-        std::shared_ptr<Handler> handler;
-        uint32_t events;
-    };
-
     android::base::unique_fd epoll_fd_;
-    std::map<int, Info> epoll_handlers_;
+    std::map<int, std::shared_ptr<Handler>> epoll_handlers_;
 };
 
 }  // namespace init
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 13ee37d..d050ed7 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -113,10 +113,10 @@
         LOG(INFO) << "hard linking " << src << " to " << dst << " succeeded";
         return;
     }
-    PLOG(FATAL) << "hard linking " << src << " to " << dst << " failed, falling back to copy.";
+    PLOG(FATAL) << "hard linking " << src << " to " << dst << " failed";
 }
 
-// Move e2fsck before switching root, so that it is available at the same path
+// Move snapuserd before switching root, so that it is available at the same path
 // after switching root.
 void PrepareSwitchRoot() {
     constexpr const char* src = "/system/bin/snapuserd";
diff --git a/init/init.cpp b/init/init.cpp
index 5a0b3a6..038f172 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -661,8 +661,7 @@
         PLOG(FATAL) << "failed to create signalfd";
     }
 
-    constexpr int flags = EPOLLIN | EPOLLPRI;
-    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd, flags); !result.ok()) {
+    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result.ok()) {
         LOG(FATAL) << result.error();
     }
 }
@@ -796,6 +795,10 @@
         InstallRebootSignalHandlers();
     }
 
+    // No threads should be spin up until signalfd
+    // is registered. If the threads are indeed required,
+    // each of these threads _should_ make sure SIGCHLD signal
+    // is blocked. See b/223076262
     boot_clock::time_point start_time = boot_clock::now();
 
     trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };
diff --git a/init/parser.cpp b/init/parser.cpp
index 5c18551..abc2017 100644
--- a/init/parser.cpp
+++ b/init/parser.cpp
@@ -18,6 +18,8 @@
 
 #include <dirent.h>
 
+#include <map>
+
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -154,6 +156,58 @@
     return true;
 }
 
+std::vector<std::string> Parser::FilterVersionedConfigs(const std::vector<std::string>& configs,
+                                                        int active_sdk) {
+    std::vector<std::string> filtered_configs;
+
+    std::map<std::string, std::pair<std::string, int>> script_map;
+    for (const auto& c : configs) {
+        int sdk = 0;
+        const std::vector<std::string> parts = android::base::Split(c, ".");
+        std::string base;
+        if (parts.size() < 2) {
+            continue;
+        }
+
+        // parts[size()-1], aka the suffix, should be "rc" or "#rc"
+        // any other pattern gets discarded
+
+        const auto& suffix = parts[parts.size() - 1];
+        if (suffix == "rc") {
+            sdk = 0;
+        } else {
+            char trailer[9] = {0};
+            int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer);
+            if (r != 2) {
+                continue;
+            }
+            if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) {
+                continue;
+            }
+        }
+
+        if (sdk < 0 || sdk > active_sdk) {
+            continue;
+        }
+
+        base = parts[0];
+        for (unsigned int i = 1; i < parts.size() - 1; i++) {
+            base = base + "." + parts[i];
+        }
+
+        // is this preferred over what we already have
+        auto it = script_map.find(base);
+        if (it == script_map.end() || it->second.second < sdk) {
+            script_map[base] = std::make_pair(c, sdk);
+        }
+    }
+
+    for (const auto& m : script_map) {
+        filtered_configs.push_back(m.second.first);
+    }
+    return filtered_configs;
+}
+
 bool Parser::ParseConfigDir(const std::string& path) {
     LOG(INFO) << "Parsing directory " << path << "...";
     std::unique_ptr<DIR, decltype(&closedir)> config_dir(opendir(path.c_str()), closedir);
diff --git a/init/parser.h b/init/parser.h
index 95b0cd7..2f4108f 100644
--- a/init/parser.h
+++ b/init/parser.h
@@ -76,6 +76,12 @@
     void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
     void AddSingleLineParser(const std::string& prefix, LineCallback callback);
 
+    // Compare all files */path.#rc and */path.rc with the same path prefix.
+    // Keep the one with the highest # that doesn't exceed the system's SDK.
+    // (.rc == .0rc for ranking purposes)
+    std::vector<std::string> FilterVersionedConfigs(const std::vector<std::string>& configs,
+                                                    int active_sdk);
+
     // Host init verifier check file permissions.
     bool ParseConfigFileInsecure(const std::string& path);
 
diff --git a/init/service.cpp b/init/service.cpp
index 2ebf87e..077477a 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -491,10 +491,15 @@
 
     // Wait until the cgroups have been created and until the cgroup controllers have been
     // activated.
-    if (std::byte byte; read((*pipefd)[0], &byte, 1) < 0) {
+    char byte = 0;
+    if (read((*pipefd)[0], &byte, 1) < 0) {
         PLOG(ERROR) << "failed to read from notification channel";
     }
     pipefd.reset();
+    if (!byte) {
+        LOG(FATAL) << "Service '" << name_  << "' failed to start due to a fatal error";
+        _exit(EXIT_FAILURE);
+    }
 
     if (task_profiles_.size() > 0 && !SetTaskProfiles(getpid(), task_profiles_)) {
         LOG(ERROR) << "failed to set task profiles";
@@ -647,9 +652,14 @@
                       limit_percent_ != -1 || !limit_property_.empty();
     errno = -createProcessGroup(proc_attr_.uid, pid_, use_memcg);
     if (errno != 0) {
-        PLOG(ERROR) << "createProcessGroup(" << proc_attr_.uid << ", " << pid_
-                    << ") failed for service '" << name_ << "'";
-    } else if (use_memcg) {
+        if (char byte = 0; write((*pipefd)[1], &byte, 1) < 0) {
+            return ErrnoError() << "sending notification failed";
+        }
+        return Error() << "createProcessGroup(" << proc_attr_.uid << ", " << pid_
+                       << ") failed for service '" << name_ << "'";
+    }
+
+    if (use_memcg) {
         ConfigureMemcg();
     }
 
@@ -657,7 +667,7 @@
         LmkdRegister(name_, proc_attr_.uid, pid_, oom_score_adjust_);
     }
 
-    if (write((*pipefd)[1], "", 1) < 0) {
+    if (char byte = 1; write((*pipefd)[1], &byte, 1) < 0) {
         return ErrnoError() << "sending notification failed";
     }
 
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 35bd415..9e914ee 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -27,6 +27,7 @@
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 #include <hidl-util/FQName.h>
+#include <processgroup/processgroup.h>
 #include <system/thread_defs.h>
 
 #include "lmkd_service.h"
@@ -395,7 +396,15 @@
 
 Result<void> ServiceParser::ParseTaskProfiles(std::vector<std::string>&& args) {
     args.erase(args.begin());
-    service_->task_profiles_ = std::move(args);
+    if (service_->task_profiles_.empty()) {
+        service_->task_profiles_ = std::move(args);
+    } else {
+        // Some task profiles might have been added during writepid conversions
+        service_->task_profiles_.insert(service_->task_profiles_.end(),
+                                        std::make_move_iterator(args.begin()),
+                                        std::make_move_iterator(args.end()));
+        args.clear();
+    }
     return {};
 }
 
@@ -521,8 +530,37 @@
     return {};
 }
 
+// Convert legacy paths used to migrate processes between cgroups using writepid command.
+// We can't get these paths from TaskProfiles because profile definitions are changing
+// when we migrate to cgroups v2 while these hardcoded paths stay the same.
+static std::optional<const std::string> ConvertTaskFileToProfile(const std::string& file) {
+    static const std::map<const std::string, const std::string> map = {
+            {"/dev/stune/top-app/tasks", "MaxPerformance"},
+            {"/dev/stune/foreground/tasks", "HighPerformance"},
+            {"/dev/cpuset/camera-daemon/tasks", "CameraServiceCapacity"},
+            {"/dev/cpuset/foreground/tasks", "ProcessCapacityHigh"},
+            {"/dev/cpuset/system-background/tasks", "ServiceCapacityLow"},
+            {"/dev/stune/nnapi-hal/tasks", "NNApiHALPerformance"},
+            {"/dev/blkio/background/tasks", "LowIoPriority"},
+    };
+    auto iter = map.find(file);
+    return iter == map.end() ? std::nullopt : std::make_optional<const std::string>(iter->second);
+}
+
 Result<void> ServiceParser::ParseWritepid(std::vector<std::string>&& args) {
     args.erase(args.begin());
+    // Convert any cgroup writes into appropriate task_profiles
+    for (auto iter = args.begin(); iter != args.end();) {
+        auto task_profile = ConvertTaskFileToProfile(*iter);
+        if (task_profile) {
+            LOG(WARNING) << "'writepid " << *iter << "' is converted into 'task_profiles "
+                         << task_profile.value() << "' for service " << service_->name();
+            service_->task_profiles_.push_back(task_profile.value());
+            iter = args.erase(iter);
+        } else {
+            ++iter;
+        }
+    }
     service_->writepid_files_ = std::move(args);
     return {};
 }
diff --git a/init/service_utils.cpp b/init/service_utils.cpp
index 263cb73..eed5c65 100644
--- a/init/service_utils.cpp
+++ b/init/service_utils.cpp
@@ -18,6 +18,7 @@
 
 #include <fcntl.h>
 #include <grp.h>
+#include <map>
 #include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/wait.h>
@@ -305,6 +306,16 @@
     } else {
         LOG(ERROR) << "cpuset cgroup controller is not mounted!";
     }
+
+    // Issue a warning whenever writepid is being used with a cgroup. This can't be done during
+    // command parsing because cgroups might not be configured at the time or parsing.
+    for (const auto& file : *files) {
+        if (CgroupGetControllerFromPath(file, nullptr)) {
+            LOG(WARNING) << "writepid usage with cgroups path '" << file
+                         << "' is obsolete, please use task_profiles!";
+        }
+    }
+
     std::string pid_str = std::to_string(getpid());
     for (const auto& file : *files) {
         if (!WriteStringToFile(pid_str, file)) {
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index 6fc64df..9b2c7d9 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -95,10 +95,7 @@
         LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
     }
 
-    if (!service) {
-        LOG(INFO) << name << " did not have an associated service entry and will not be reaped";
-        return pid;
-    }
+    if (!service) return pid;
 
     service->Reap(siginfo);
 
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 68c6b51..c6bf708 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -303,7 +303,7 @@
 
     std::vector<std::string> canonical{"/system/etc/ueventd.rc"};
 
-    if (android::base::GetIntProperty("ro.product.first_api_level", 10000) <= __ANDROID_API_S__) {
+    if (android::base::GetIntProperty("ro.product.first_api_level", 10000) < __ANDROID_API_T__) {
         // TODO: Remove these legacy paths once Android S is no longer supported.
         canonical.insert(canonical.end(), legacy_paths.begin(), legacy_paths.end());
     } else {
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index 17a0070..98ae0d4 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -209,6 +209,37 @@
 }
 
 /**
+ * Trace the beginning of an asynchronous event. In addition to the name and a
+ * cookie as in ATRACE_ASYNC_BEGIN/ATRACE_ASYNC_END, a track name argument is
+ * provided, which is the name of the row where this async event should be
+ * recorded. The track name, name, and cookie used to begin an event must be
+ * used to end it.
+ */
+#define ATRACE_ASYNC_FOR_TRACK_BEGIN(track_name, name, cookie) \
+    atrace_async_for_track_begin(ATRACE_TAG, track_name, name, cookie)
+static inline void atrace_async_for_track_begin(uint64_t tag, const char* track_name,
+                                                const char* name, int32_t cookie) {
+    if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+        void atrace_async_for_track_begin_body(const char*, const char*, int32_t);
+        atrace_async_for_track_begin_body(track_name, name, cookie);
+    }
+}
+
+/**
+ * Trace the end of an asynchronous event.
+ * This should correspond to a previous ATRACE_ASYNC_FOR_TRACK_BEGIN.
+ */
+#define ATRACE_ASYNC_FOR_TRACK_END(track_name, name, cookie) \
+    atrace_async_for_track_end(ATRACE_TAG, track_name, name, cookie)
+static inline void atrace_async_for_track_end(uint64_t tag, const char* track_name,
+                                              const char* name, int32_t cookie) {
+    if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+        void atrace_async_for_track_end_body(const char*, const char*, int32_t);
+        atrace_async_for_track_end_body(track_name, name, cookie);
+    }
+}
+
+/**
  * Trace an instantaneous context. name is used to identify the context.
  *
  * An "instant" is an event with no defined duration. Visually is displayed like a single marker
@@ -227,17 +258,18 @@
 
 /**
  * Trace an instantaneous context. name is used to identify the context.
- * trackName is the name of the row where the event should be recorded.
+ * track_name is the name of the row where the event should be recorded.
  *
  * An "instant" is an event with no defined duration. Visually is displayed like a single marker
  * in the timeline (rather than a span, in the case of begin/end events).
  */
 #define ATRACE_INSTANT_FOR_TRACK(trackName, name) \
     atrace_instant_for_track(ATRACE_TAG, trackName, name)
-static inline void atrace_instant_for_track(uint64_t tag, const char* trackName, const char* name) {
+static inline void atrace_instant_for_track(uint64_t tag, const char* track_name,
+                                            const char* name) {
     if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
         void atrace_instant_for_track_body(const char*, const char*);
-        atrace_instant_for_track_body(trackName, name);
+        atrace_instant_for_track_body(track_name, name);
     }
 }
 
diff --git a/libcutils/trace-container.cpp b/libcutils/trace-container.cpp
index f3fdda4..8901e4a 100644
--- a/libcutils/trace-container.cpp
+++ b/libcutils/trace-container.cpp
@@ -229,6 +229,28 @@
     WRITE_MSG("F|%d|", "|%" PRId32, "", name, cookie);
 }
 
+void atrace_async_for_track_begin_body(const char* track_name, const char* name, int32_t cookie) {
+    if (CC_LIKELY(atrace_use_container_sock)) {
+        WRITE_MSG_IN_CONTAINER("T", "|", "|%d", track_name, name, cookie);
+        return;
+    }
+
+    if (atrace_marker_fd < 0) return;
+
+    WRITE_MSG("T|%d|", "|%" PRId32, track_name, name, cookie);
+}
+
+void atrace_async_for_track_end_body(const char* track_name, const char* name, int32_t cookie) {
+    if (CC_LIKELY(atrace_use_container_sock)) {
+        WRITE_MSG_IN_CONTAINER("U", "|", "|%d", track_name, name, cookie);
+        return;
+    }
+
+    if (atrace_marker_fd < 0) return;
+
+    WRITE_MSG("U|%d|", "|%" PRId32, track_name, name, cookie);
+}
+
 void atrace_instant_body(const char* name) {
     if (CC_LIKELY(atrace_use_container_sock)) {
         WRITE_MSG_IN_CONTAINER("I", "|", "%s", "", name, "");
diff --git a/libcutils/trace-dev.cpp b/libcutils/trace-dev.cpp
index 8bdeac5..eacc8ee 100644
--- a/libcutils/trace-dev.cpp
+++ b/libcutils/trace-dev.cpp
@@ -89,6 +89,14 @@
     WRITE_MSG("F|%d|", "|%" PRId32, "", name, cookie);
 }
 
+void atrace_async_for_track_begin_body(const char* track_name, const char* name, int32_t cookie) {
+    WRITE_MSG("T|%d|", "|%" PRId32, track_name, name, cookie);
+}
+
+void atrace_async_for_track_end_body(const char* track_name, const char* name, int32_t cookie) {
+    WRITE_MSG("U|%d|", "|%" PRId32, track_name, name, cookie);
+}
+
 void atrace_instant_body(const char* name) {
     WRITE_MSG("I|%d|", "%s", "", name, "");
 }
diff --git a/libcutils/trace-dev_test.cpp b/libcutils/trace-dev_test.cpp
index 29a5590..841674a 100644
--- a/libcutils/trace-dev_test.cpp
+++ b/libcutils/trace-dev_test.cpp
@@ -195,6 +195,226 @@
   ASSERT_STREQ(expected.c_str(), actual.c_str());
 }
 
+TEST_F(TraceDevTest, atrace_async_for_track_begin_body_normal) {
+    atrace_async_for_track_begin_body("fake_track", "fake_name", 12345);
+
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    std::string expected = android::base::StringPrintf("T|%d|fake_track|fake_name|12345", getpid());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_begin_body_exact_track_name) {
+    const int name_size = 5;
+    std::string expected = android::base::StringPrintf("T|%d|", getpid());
+    std::string track_name =
+            MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - name_size - 6);
+    atrace_async_for_track_begin_body(track_name.c_str(), "name", 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    expected += track_name + "|name|12345";
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+    // Add a single character and verify name truncation
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    track_name += '*';
+    expected = android::base::StringPrintf("T|%d|", getpid());
+    expected += track_name + "|nam|12345";
+    atrace_async_for_track_begin_body(track_name.c_str(), "name", 12345);
+    EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_begin_body_truncated_track_name) {
+    std::string expected = android::base::StringPrintf("T|%d|", getpid());
+    std::string track_name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_async_for_track_begin_body(track_name.c_str(), "name", 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 9;
+    expected += android::base::StringPrintf("%.*s|n|12345", expected_len, track_name.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_begin_body_exact_name) {
+    const int track_name_size = 11;
+    std::string expected = android::base::StringPrintf("T|%d|", getpid());
+    std::string name =
+            MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - track_name_size - 6);
+    atrace_async_for_track_begin_body("track_name", name.c_str(), 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    expected += "track_name|" + name + "|12345";
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+    // Add a single character and verify we get the same value as before.
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    name += '*';
+    atrace_async_for_track_begin_body("track_name", name.c_str(), 12345);
+    EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_begin_body_truncated_name) {
+    std::string expected = android::base::StringPrintf("T|%d|track_name|", getpid());
+    std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_async_for_track_begin_body("track_name", name.c_str(), 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 1 - 6;
+    expected += android::base::StringPrintf("%.*s|12345", expected_len, name.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_begin_body_truncated_both) {
+    std::string expected = android::base::StringPrintf("T|%d|", getpid());
+    std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    std::string track_name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_async_for_track_begin_body(track_name.c_str(), name.c_str(), 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 3 - 6;
+    expected += android::base::StringPrintf("%.*s|%.1s|12345", expected_len, track_name.c_str(),
+                                            name.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_end_body_normal) {
+    atrace_async_for_track_end_body("fake_track", "fake_name", 12345);
+
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    std::string expected = android::base::StringPrintf("U|%d|fake_track|fake_name|12345", getpid());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_end_body_exact_track_name) {
+    const int name_size = 5;
+    std::string expected = android::base::StringPrintf("U|%d|", getpid());
+    std::string track_name =
+            MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - name_size - 6);
+    atrace_async_for_track_end_body(track_name.c_str(), "name", 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    expected += track_name + "|name|12345";
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+    // Add a single character and verify name truncation
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    track_name += '*';
+    expected = android::base::StringPrintf("U|%d|", getpid());
+    expected += track_name + "|nam|12345";
+    atrace_async_for_track_end_body(track_name.c_str(), "name", 12345);
+    EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_end_body_truncated_track_name) {
+    std::string expected = android::base::StringPrintf("U|%d|", getpid());
+    std::string track_name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_async_for_track_end_body(track_name.c_str(), "name", 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 9;
+    expected += android::base::StringPrintf("%.*s|n|12345", expected_len, track_name.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_end_body_exact_name) {
+    const int track_name_size = 11;
+    std::string expected = android::base::StringPrintf("U|%d|", getpid());
+    std::string name =
+            MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - track_name_size - 6);
+    atrace_async_for_track_end_body("track_name", name.c_str(), 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    expected += "track_name|" + name + "|12345";
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+    // Add a single character and verify we get the same value as before.
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    name += '*';
+    atrace_async_for_track_end_body("track_name", name.c_str(), 12345);
+    EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_end_body_truncated_name) {
+    std::string expected = android::base::StringPrintf("U|%d|track_name|", getpid());
+    std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_async_for_track_end_body("track_name", name.c_str(), 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 1 - 6;
+    expected += android::base::StringPrintf("%.*s|12345", expected_len, name.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_async_for_track_end_body_truncated_both) {
+    std::string expected = android::base::StringPrintf("U|%d|", getpid());
+    std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    std::string track_name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+    atrace_async_for_track_end_body(track_name.c_str(), name.c_str(), 12345);
+
+    ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+    ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+    std::string actual;
+    ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+    int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 3 - 6;
+    expected += android::base::StringPrintf("%.*s|%.1s|12345", expected_len, track_name.c_str(),
+                                            name.c_str());
+    ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
 TEST_F(TraceDevTest, atrace_instant_body_normal) {
     atrace_instant_body("fake_name");
 
diff --git a/libcutils/trace-host.cpp b/libcutils/trace-host.cpp
index b01a0ec..c2a379b 100644
--- a/libcutils/trace-host.cpp
+++ b/libcutils/trace-host.cpp
@@ -28,9 +28,12 @@
 void atrace_end_body() { }
 void atrace_async_begin_body(const char* /*name*/, int32_t /*cookie*/) {}
 void atrace_async_end_body(const char* /*name*/, int32_t /*cookie*/) {}
+void atrace_async_for_track_begin_body(const char* /*track_name*/, const char* /*name*/,
+                                       int32_t /*cookie*/) {}
+void atrace_async_for_track_end_body(const char* /*track_name*/, const char* /*name*/,
+                                     int32_t /*cookie*/) {}
 void atrace_instant_body(const char* /*name*/) {}
-void atrace_instant_for_track_body(const char* /*trackName*/,
-                                   const char* /*name*/) {}
+void atrace_instant_for_track_body(const char* /*track_name*/, const char* /*name*/) {}
 void atrace_int_body(const char* /*name*/, int32_t /*value*/) {}
 void atrace_int64_body(const char* /*name*/, int64_t /*value*/) {}
 void atrace_init() {}
diff --git a/libdiskconfig/Android.bp b/libdiskconfig/Android.bp
index a3d643e..f523d4e 100644
--- a/libdiskconfig/Android.bp
+++ b/libdiskconfig/Android.bp
@@ -27,12 +27,8 @@
         darwin: {
             enabled: false,
         },
-        linux_glibc: {
+        host_linux: {
             cflags: [
-                "-O2",
-                "-g",
-                "-W",
-                "-Wall",
                 "-D_LARGEFILE64_SOURCE",
             ],
         },
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index b0fcb5f..e3a80e9 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -159,6 +159,21 @@
     return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache);
 }
 
+// C wrapper for SetProcessProfiles.
+// No need to have this in the header file because this function is specifically for crosvm. Crosvm
+// which is written in Rust has its own declaration of this foreign function and doesn't rely on the
+// header. See
+// https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3574427/5/src/linux/android.rs#12
+extern "C" bool android_set_process_profiles(uid_t uid, pid_t pid, size_t num_profiles,
+                                             const char* profiles[]) {
+    std::vector<std::string> profiles_;
+    profiles_.reserve(num_profiles);
+    for (size_t i = 0; i < num_profiles; i++) {
+        profiles_.emplace_back(profiles[i]);
+    }
+    return SetProcessProfiles(uid, pid, profiles_);
+}
+
 static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
     return StringPrintf("%s/uid_%d", cgroup, uid);
 }
@@ -464,7 +479,7 @@
     gid_t cgroup_gid = AID_SYSTEM;
     int ret = 0;
 
-    if (stat(cgroup.c_str(), &cgroup_stat) == 1) {
+    if (stat(cgroup.c_str(), &cgroup_stat) < 0) {
         PLOG(ERROR) << "Failed to get stats for " << cgroup;
     } else {
         cgroup_mode = cgroup_stat.st_mode;
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 7e03964..f5533c2 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -224,6 +224,19 @@
       ]
     },
     {
+      "Name": "VMCompilationPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpu",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
       "Name": "CpuPolicySpread",
       "Actions": [
         {
@@ -435,7 +448,6 @@
         }
       ]
     },
-
     {
       "Name": "LowIoPriority",
       "Actions": [
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 992cc2e..3831ef2 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -147,12 +147,17 @@
 static void MergeCgroupToDescriptors(std::map<std::string, CgroupDescriptor>* descriptors,
                                      const Json::Value& cgroup, const std::string& name,
                                      const std::string& root_path, int cgroups_version) {
+    const std::string cgroup_path = cgroup["Path"].asString();
     std::string path;
 
     if (!root_path.empty()) {
-        path = root_path + "/" + cgroup["Path"].asString();
+        path = root_path;
+        if (cgroup_path != ".") {
+            path += "/";
+            path += cgroup_path;
+        }
     } else {
-        path = cgroup["Path"].asString();
+        path = cgroup_path;
     }
 
     uint32_t controller_flags = 0;
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 78a316a..e1c5934 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -207,7 +207,7 @@
     }
 
     if (!WriteStringToFile(value_, path)) {
-        if (errno == ENOENT) {
+        if (access(path.c_str(), F_OK) < 0) {
             if (optional_) {
                 return true;
             } else {
@@ -215,6 +215,9 @@
                 return false;
             }
         }
+        // The PLOG() statement below uses the error code stored in `errno` by
+        // WriteStringToFile() because access() only overwrites `errno` if it fails
+        // and because this code is only reached if the access() function returns 0.
         PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
         return false;
     }
@@ -803,6 +806,7 @@
 
 bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
                                       const std::vector<std::string>& profiles, bool use_fd_cache) {
+    bool success = true;
     for (const auto& name : profiles) {
         TaskProfile* profile = GetProfile(name);
         if (profile != nullptr) {
@@ -811,16 +815,19 @@
             }
             if (!profile->ExecuteForProcess(uid, pid)) {
                 PLOG(WARNING) << "Failed to apply " << name << " process profile";
+                success = false;
             }
         } else {
-            PLOG(WARNING) << "Failed to find " << name << "process profile";
+            PLOG(WARNING) << "Failed to find " << name << " process profile";
+            success = false;
         }
     }
-    return true;
+    return success;
 }
 
 bool TaskProfiles::SetTaskProfiles(int tid, const std::vector<std::string>& profiles,
                                    bool use_fd_cache) {
+    bool success = true;
     for (const auto& name : profiles) {
         TaskProfile* profile = GetProfile(name);
         if (profile != nullptr) {
@@ -829,10 +836,12 @@
             }
             if (!profile->ExecuteForTask(tid)) {
                 PLOG(WARNING) << "Failed to apply " << name << " task profile";
+                success = false;
             }
         } else {
-            PLOG(WARNING) << "Failed to find " << name << "task profile";
+            PLOG(WARNING) << "Failed to find " << name << " task profile";
+            success = false;
         }
     }
-    return true;
+    return success;
 }
diff --git a/libstats/pull_rust/stats_pull.rs b/libstats/pull_rust/stats_pull.rs
index 174125e..09b2623 100644
--- a/libstats/pull_rust/stats_pull.rs
+++ b/libstats/pull_rust/stats_pull.rs
@@ -68,7 +68,7 @@
     }
 
     /// Calls AStatsManager_PullAtomMetadata_setAdditiveFields.
-    pub fn set_additive_fields(&mut self, additive_fields: &mut Vec<i32>) {
+    pub fn set_additive_fields(&mut self, additive_fields: &mut [i32]) {
         // Safety: Metadata::new ensures that self.metadata is a valid object.
         unsafe {
             AStatsManager_PullAtomMetadata_setAdditiveFields(
diff --git a/libsystem/include/system/graphics.h b/libsystem/include/system/graphics.h
index 1b6060a..a3c23b2 100644
--- a/libsystem/include/system/graphics.h
+++ b/libsystem/include/system/graphics.h
@@ -59,12 +59,14 @@
 
 /*
  * Structure for describing YCbCr formats for consumption by applications.
- * This is used with HAL_PIXEL_FORMAT_YCbCr_*_888.
+ * This is used with HAL_PIXEL_FORMAT_YCbCr_*.
  *
  * Buffer chroma subsampling is defined in the format.
  * e.g. HAL_PIXEL_FORMAT_YCbCr_420_888 has subsampling 4:2:0.
  *
- * Buffers must have a 8 bit depth.
+ * Buffers must have a byte aligned channel depth or a byte aligned packed
+ * channel depth (e.g. 10 bits packed into 16 bits for
+ * HAL_PIXEL_FORMAT_YCbCr_P010).
  *
  * y, cb, and cr point to the first byte of their respective planes.
  *
@@ -75,8 +77,8 @@
  * cstride is the stride of the chroma planes.
  *
  * chroma_step is the distance in bytes from one chroma pixel value to the
- * next.  This is 2 bytes for semiplanar (because chroma values are interleaved
- * and each chroma value is one byte) and 1 for planar.
+ * next.  This is `2 * channel depth` bytes for semiplanar (because chroma
+ * values are interleaved) and `1 * channel depth` bytes for planar.
  */
 
 struct android_ycbcr {
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 58af8e4..1b29285 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -48,13 +48,11 @@
             header_libs: ["libbacktrace_headers"],
             export_header_lib_headers: ["libbacktrace_headers"],
         },
-        linux_glibc: {
+        host_linux: {
             header_libs: ["libbacktrace_headers"],
             export_header_lib_headers: ["libbacktrace_headers"],
         },
         linux_bionic: {
-            header_libs: ["libbacktrace_headers"],
-            export_header_lib_headers: ["libbacktrace_headers"],
             enabled: true,
         },
         windows: {
@@ -190,6 +188,7 @@
     defaults: ["libutils_defaults"],
     // TODO(b/153609531): remove when no longer needed.
     native_bridge_supported: true,
+    min_sdk_version: "29",
 
     srcs: [
         "CallStack.cpp",
diff --git a/libutils/LruCache_test.cpp b/libutils/LruCache_test.cpp
index c4d917b..8b16947 100644
--- a/libutils/LruCache_test.cpp
+++ b/libutils/LruCache_test.cpp
@@ -298,8 +298,8 @@
 }
 
 TEST_F(LruCacheTest, Callback) {
-    LruCache<SimpleKey, StringValue> cache(100);
     EntryRemovedCallback callback;
+    LruCache<SimpleKey, StringValue> cache(100);
     cache.setOnEntryRemovedListener(&callback);
 
     cache.put(1, "one");
@@ -313,8 +313,8 @@
 }
 
 TEST_F(LruCacheTest, CallbackOnClear) {
-    LruCache<SimpleKey, StringValue> cache(100);
     EntryRemovedCallback callback;
+    LruCache<SimpleKey, StringValue> cache(100);
     cache.setOnEntryRemovedListener(&callback);
 
     cache.put(1, "one");
@@ -326,8 +326,8 @@
 }
 
 TEST_F(LruCacheTest, CallbackRemovesKeyWorksOK) {
-    LruCache<KeyWithPointer, StringValue> cache(1);
     InvalidateKeyCallback callback;
+    LruCache<KeyWithPointer, StringValue> cache(1);
     cache.setOnEntryRemovedListener(&callback);
     KeyWithPointer key1;
     key1.ptr = new int(1);
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 419b2de..3690389 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -313,7 +313,7 @@
 
     if (n > 0) {
         size_t oldLength = length();
-        if (n > std::numeric_limits<size_t>::max() - 1 ||
+        if (static_cast<size_t>(n) > std::numeric_limits<size_t>::max() - 1 ||
             oldLength > std::numeric_limits<size_t>::max() - n - 1) {
             return NO_MEMORY;
         }
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 7ad1c3c..d39a21c 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -824,9 +824,12 @@
     # directory used for on-device refresh metrics file.
     mkdir /data/misc/odrefresh 0777 system system
     # directory used for on-device signing key blob
-    mkdir /data/misc/odsign 0700 root root
+    mkdir /data/misc/odsign 0710 root system
+    # directory used for odsign metrics
+    mkdir /data/misc/odsign/metrics 0770 root system
+
     # Directory for VirtualizationService temporary image files.
-    mkdir /data/misc/virtualizationservice 0700 virtualizationservice virtualizationservice
+    mkdir /data/misc/virtualizationservice 0700 system system
 
     mkdir /data/preloads 0775 system system encryption=None
 
@@ -1303,3 +1306,15 @@
 
 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
+on property:persist.device_config.mglru_native.lru_gen_config=core
+  write /sys/kernel/mm/lru_gen/enabled 1
+on property:persist.device_config.mglru_native.lru_gen_config=core_and_mm_walk
+  write /sys/kernel/mm/lru_gen/enabled 3
+on property:persist.device_config.mglru_native.lru_gen_config=core_and_nonleaf_young
+  write /sys/kernel/mm/lru_gen/enabled 5
+on property:persist.device_config.mglru_native.lru_gen_config=all
+  write /sys/kernel/mm/lru_gen/enabled 7
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 3101974..a140c8c 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -67,9 +67,8 @@
 # CDMA radio interface MUX
 /dev/ppp                  0660   radio      vpn
 
-# Virtualization is managed by VirtualizationService.
-/dev/kvm                  0600   virtualizationservice root
-/dev/vhost-vsock          0600   virtualizationservice root
+/dev/kvm                  0600   system     system
+/dev/vhost-vsock          0600   system	    system
 
 # sysfs properties
 /sys/devices/platform/trusty.*      trusty_version        0440  root   log
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 120000
index 0000000..ee92d9e
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+../../build/soong/scripts/rustfmt.toml
\ No newline at end of file
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index c72af40..278499f 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -223,6 +223,9 @@
         case APPLOADER_ERR_INVALID_VERSION:
             LOG(ERROR) << "Error: invalid application version";
             break;
+        case APPLOADER_ERR_POLICY_VIOLATION:
+            LOG(ERROR) << "Error: loading denied by policy engine";
+            break;
         default:
             LOG(ERROR) << "Unrecognized error: " << resp.error;
             break;
diff --git a/trusty/apploader/apploader_ipc.h b/trusty/apploader/apploader_ipc.h
index 6cda7c1..306596e 100644
--- a/trusty/apploader/apploader_ipc.h
+++ b/trusty/apploader/apploader_ipc.h
@@ -56,6 +56,7 @@
     APPLOADER_ERR_ALREADY_EXISTS,
     APPLOADER_ERR_INTERNAL,
     APPLOADER_ERR_INVALID_VERSION,
+    APPLOADER_ERR_POLICY_VIOLATION,
 };
 
 /**
diff --git a/trusty/apploader/fuzz/Android.bp b/trusty/apploader/fuzz/Android.bp
index e37dab1..c961b36 100644
--- a/trusty/apploader/fuzz/Android.bp
+++ b/trusty/apploader/fuzz/Android.bp
@@ -25,7 +25,10 @@
         "-DTRUSTY_APP_PORT=\"com.android.trusty.apploader\"",
         "-DTRUSTY_APP_UUID=\"081ba88f-f1ee-452e-b5e8-a7e9ef173a97\"",
         "-DTRUSTY_APP_FILENAME=\"apploader.syms.elf\"",
-    ]
+    ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 }
 
 // Fuzz app package sent to apploader.
@@ -37,4 +40,7 @@
     shared_libs: [
         "libdmabufheap",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 }
diff --git a/trusty/confirmationui/fuzz/Android.bp b/trusty/confirmationui/fuzz/Android.bp
index ba57191..4780943 100644
--- a/trusty/confirmationui/fuzz/Android.bp
+++ b/trusty/confirmationui/fuzz/Android.bp
@@ -25,6 +25,9 @@
         "-DTRUSTY_APP_UUID=\"7dee2364-c036-425b-b086-df0f6c233c1b\"",
         "-DTRUSTY_APP_FILENAME=\"confirmationui.syms.elf\"",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 
 }
 
@@ -36,6 +39,9 @@
     shared_libs: [
         "libdmabufheap",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 
     // The initial corpus for this fuzzer was derived by dumping messages from/to
     // HAL to/from TA triggered by VtsHalConfirmationUIV1_0TargetTest.
diff --git a/trusty/gatekeeper/fuzz/Android.bp b/trusty/gatekeeper/fuzz/Android.bp
index d084cb6..67d0c0f 100644
--- a/trusty/gatekeeper/fuzz/Android.bp
+++ b/trusty/gatekeeper/fuzz/Android.bp
@@ -25,6 +25,9 @@
         "-DTRUSTY_APP_UUID=\"38ba0cdc-df0e-11e4-9869-233fb6ae4795\"",
         "-DTRUSTY_APP_FILENAME=\"gatekeeper.syms.elf\"",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 
     // The initial corpus for this fuzzer was derived by dumping messages from
     // the `secure_env` emulator interface for cuttlefish while enrolling a new
diff --git a/trusty/keymaster/fuzz/Android.bp b/trusty/keymaster/fuzz/Android.bp
index 8d7ee00..5f24bc6 100644
--- a/trusty/keymaster/fuzz/Android.bp
+++ b/trusty/keymaster/fuzz/Android.bp
@@ -25,6 +25,9 @@
         "-DTRUSTY_APP_UUID=\"5f902ace-5e5c-4cd8-ae54-87b88c22ddaf\"",
         "-DTRUSTY_APP_FILENAME=\"keymaster.syms.elf\"",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 
     // The initial corpus for this fuzzer was derived by dumping messages from
     // the `secure_env` emulator interface for cuttlefish while running the
diff --git a/trusty/libtrusty-rs/Android.bp b/trusty/libtrusty-rs/Android.bp
new file mode 100644
index 0000000..bc1dcf6
--- /dev/null
+++ b/trusty/libtrusty-rs/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+    name: "libtrusty-rs",
+    crate_name: "trusty",
+    srcs: [
+        "src/lib.rs"
+    ],
+    rustlibs: [
+        "libnix",
+        "liblibc",
+    ],
+}
+
+rust_test {
+    name: "libtrusty-rs-tests",
+    crate_name: "trusty_test",
+    srcs: ["tests/test.rs"],
+    rustlibs: [
+        "libtrusty-rs",
+        "liblibc",
+    ]
+}
diff --git a/trusty/libtrusty-rs/src/lib.rs b/trusty/libtrusty-rs/src/lib.rs
new file mode 100644
index 0000000..28ea075
--- /dev/null
+++ b/trusty/libtrusty-rs/src/lib.rs
@@ -0,0 +1,224 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Functionality for communicating with Trusty services.
+//!
+//! This crate provides the [`TipcChannel`] type, which allows you to establish a
+//! connection to a Trusty service and then communicate with that service.
+//!
+//! # Usage
+//!
+//! To connect to a Trusty service you need two things:
+//!
+//! * The filesystem path to the Trusty IPC device. This is usually
+//!   `/dev/trusty-ipc-dev0`, which is exposed in the constant [`DEFAULT_DEVICE`].
+//! * The port name defined by the service, e.g. `com.android.ipc-unittest.srv.echo`.
+//!
+//! Pass these values to [`TipcChannel::connect`] to establish a connection to a
+//! service.
+//!
+//! Once connected use the [`send`][TipcChannel::send] and [`recv`][TipcChannel::recv]
+//! methods to communicate with the service. Messages are passed as byte buffers, and
+//! each Trusty service has its own protocol for what data messages are expected to
+//! contain. Consult the documentation for the service you are communicating with to
+//! determine how to format outgoing messages and interpret incoming ones.
+//!
+//! The connection is closed automatically when [`TipcChannel`] is dropped.
+//!
+//! # Examples
+//!
+//! This example is a simplified version of the echo test from `tipc-test-rs`:
+//!
+//! ```no_run
+//! use trusty::{DEFAULT_DEVICE, TipcChannel};
+//! use std::io::{Read, Write};
+//!
+//! let mut chann = TipcChannel::connect(
+//!     DEFAULT_DEVICE,
+//!     "com.android.ipc-unittest.srv.echo",
+//! ).unwrap();
+//!
+//! chann.send("Hello, world!".as_bytes()).unwrap();
+//!
+//! let mut read_buf = Vec::new();
+//! let read_len = stream.recv(&mut read_buf).unwrap();
+//!
+//! let response = std::str::from_utf8(&read_buf[..read_len]).unwrap();
+//! assert_eq!("Hello, world!", response);
+//!
+//! // The connection is closed here.
+//! ```
+
+use crate::sys::tipc_connect;
+use std::ffi::CString;
+use std::fs::File;
+use std::io::prelude::*;
+use std::io::{ErrorKind, Result};
+use std::os::unix::prelude::AsRawFd;
+use std::path::Path;
+
+mod sys;
+
+/// The default filesystem path for the Trusty IPC device.
+pub const DEFAULT_DEVICE: &str = "/dev/trusty-ipc-dev0";
+
+/// The maximum size an incoming TIPC message can be.
+///
+/// This can be used to pre-allocate buffer space in order to ensure that your
+/// read buffer can always hold an incoming message.
+pub const MAX_MESSAGE_SIZE: usize = 4096;
+
+/// A channel for communicating with a Trusty service.
+///
+/// See the [crate-level documentation][crate] for usage details and examples.
+#[derive(Debug)]
+pub struct TipcChannel(File);
+
+impl TipcChannel {
+    /// Attempts to establish a connection to the specified Trusty service.
+    ///
+    /// The first argument is the path of the Trusty device in the local filesystem,
+    /// e.g. `/dev/trusty-ipc-dev0`. The second argument is the name of the service
+    /// to connect to, e.g. `com.android.ipc-unittest.srv.echo`.
+    ///
+    /// # Panics
+    ///
+    /// This function will panic if `service` contains any intermediate `NUL`
+    /// 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> {
+        let file = File::options().read(true).write(true).open(device)?;
+
+        let srv_name = CString::new(service).expect("Service name contained null bytes");
+        unsafe {
+            tipc_connect(file.as_raw_fd(), srv_name.as_ptr())?;
+        }
+
+        Ok(TipcChannel(file))
+    }
+
+    /// Sends a message to the connected service.
+    ///
+    /// The entire contents of `buf` will be sent as a single message to the
+    /// connected service.
+    pub fn send(&mut self, buf: &[u8]) -> Result<()> {
+        let write_len = self.0.write(buf)?;
+
+        // Verify that the expected number of bytes were written. The entire message
+        // should always be written with a single `write` call, or an error should have
+        // been returned if the message couldn't be written. An assertion failure here
+        // potentially means a bug in the kernel driver.
+        assert_eq!(
+            buf.len(),
+            write_len,
+            "Failed to send full message ({} of {} bytes written)",
+            write_len,
+            buf.len(),
+        );
+
+        Ok(())
+    }
+
+    /// Reads the next incoming message.
+    ///
+    /// Attempts to read the next incoming message from the connected service if any
+    /// exist. If the initial capacity of `buf` is not enough to hold the incoming
+    /// message the function repeatedly attempts to reserve additional space until
+    /// it is able to fully read the message.
+    ///
+    /// Blocks until there is an incoming message if there is not already a message
+    /// ready to be received.
+    ///
+    /// # Errors
+    ///
+    /// If this function encounters an error of the kind [`ErrorKind::Interrupted`]
+    /// then the error is ignored and the operation will be tried again.
+    ///
+    /// If this function encounters an error with the error code `EMSGSIZE` then
+    /// additional space will be reserved in `buf` and the operation will be tried
+    /// again.
+    ///
+    /// If any other read error is encountered then this function immediately
+    /// returns the error to the caller, and the length of `buf` is set to 0.
+    pub fn recv(&mut self, buf: &mut Vec<u8>) -> Result<()> {
+        // If no space has been allocated in the buffer reserve enough space to hold any
+        // incoming message.
+        if buf.capacity() == 0 {
+            buf.reserve(MAX_MESSAGE_SIZE);
+        }
+
+        loop {
+            // Resize the vec to make its full capacity available to write into.
+            buf.resize(buf.capacity(), 0);
+
+            match self.0.read(buf.as_mut_slice()) {
+                Ok(len) => {
+                    buf.truncate(len);
+                    return Ok(());
+                }
+
+                Err(err) => {
+                    if let Some(libc::EMSGSIZE) = err.raw_os_error() {
+                        // Ensure that we didn't get `EMSGSIZE` when we already had enough capacity
+                        // to contain the maximum message size. This should never happen, but if it
+                        // does we don't want to hang by looping infinitely.
+                        assert!(
+                            buf.capacity() < MAX_MESSAGE_SIZE,
+                            "Received `EMSGSIZE` error when buffer capacity was already at maximum",
+                        );
+
+                        // If we didn't have enough space to hold the incoming message, reserve
+                        // enough space to fit the maximum message size regardless of how much
+                        // capacity the buffer already had.
+                        buf.reserve(MAX_MESSAGE_SIZE - buf.capacity());
+                    } else if err.kind() == ErrorKind::Interrupted {
+                        // If we get an interrupted error the operation can be retried as-is, i.e.
+                        // we don't need to allocate additional space.
+                        continue;
+                    } else {
+                        buf.truncate(0);
+                        return Err(err);
+                    }
+                }
+            }
+        }
+    }
+
+    /// Reads the next incoming message without allocating.
+    ///
+    /// Returns the number of bytes in the received message, or any error that
+    /// occurred when reading the message.
+    ///
+    /// Blocks until there is an incoming message if there is not already a message
+    /// ready to be received.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error with native error code `EMSGSIZE` if `buf` isn't large
+    /// enough to contain the incoming message. Use
+    /// [`raw_os_error`][std::io::Error::raw_os_error] to check the error code to
+    /// determine if you need to increase the size of `buf`. If error code
+    /// `EMSGSIZE` is returned the incoming message will not be dropped, and a
+    /// subsequent call to `recv_no_alloc` can still read it.
+    ///
+    /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the read
+    /// operation should be retried if there is nothing else to do.
+    pub fn recv_no_alloc(&mut self, buf: &mut [u8]) -> Result<usize> {
+        self.0.read(buf)
+    }
+
+    // TODO: Add method that is equivalent to `tipc_send`, i.e. that supports
+    // sending shared memory buffers.
+}
diff --git a/trusty/libtrusty-rs/src/sys.rs b/trusty/libtrusty-rs/src/sys.rs
new file mode 100644
index 0000000..f1c8c5f
--- /dev/null
+++ b/trusty/libtrusty-rs/src/sys.rs
@@ -0,0 +1,31 @@
+// NOTE: The ioctl definitions are sequestered into this module because the
+// `ioctl_*!` macros provided by the nix crate generate public functions that we
+// don't want to be part of this crate's public API.
+//
+// NOTE: We are manually re-declaring the types and constants here instead of using
+// bindgen and a separate `-sys` crate because the defines used for the ioctl
+// numbers (`TIPC_IOC_CONNECT` and `TIPC_IOC_SEND_MSG`) can't currently be
+// translated by bindgen.
+
+use std::os::raw::c_char;
+
+const TIPC_IOC_MAGIC: u8 = b'r';
+
+// NOTE: We use `ioctl_write_ptr_bad!` here due to an error in how the ioctl
+// code is defined in `trusty/ipc.h`.
+//
+// If we were to do `ioctl_write_ptr!(TIPC_IOC_MAGIC, 0x80, c_char)` it would
+// generate a function that takes a `*const c_char` data arg and would use
+// `size_of::<c_char>()` when generating the ioctl number. However, in
+// `trusty/ipc.h` the definition for `TIPC_IOC_CONNECT` declares the ioctl with
+// `char*`, meaning we need to use `size_of::<*const c_char>()` to generate an
+// ioctl number that matches what Trusty expects.
+//
+// To maintain compatibility with the `trusty/ipc.h` and the kernel driver we
+// use `ioctl_write_ptr_bad!` and manually use `request_code_write!` to generate
+// the ioctl number using the correct size.
+nix::ioctl_write_ptr_bad!(
+    tipc_connect,
+    nix::request_code_write!(TIPC_IOC_MAGIC, 0x80, std::mem::size_of::<*const c_char>()),
+    c_char
+);
diff --git a/trusty/libtrusty-rs/tests/test.rs b/trusty/libtrusty-rs/tests/test.rs
new file mode 100644
index 0000000..6bff479
--- /dev/null
+++ b/trusty/libtrusty-rs/tests/test.rs
@@ -0,0 +1,86 @@
+use trusty::{TipcChannel, DEFAULT_DEVICE};
+
+const ECHO_NAME: &str = "com.android.ipc-unittest.srv.echo";
+
+#[test]
+fn recv_no_alloc() {
+    let mut connection = TipcChannel::connect(DEFAULT_DEVICE, ECHO_NAME)
+        .expect("Failed to connect to Trusty service");
+
+    // Send a message to the echo TA.
+    let send_buf = [7u8; 32];
+    connection.send(send_buf.as_slice()).unwrap();
+
+    // Receive the response message from the TA. The response message will be the
+    // same as the message we just sent.
+    let mut recv_buf = [0u8; 32];
+    let read_len = connection.recv_no_alloc(recv_buf.as_mut_slice()).unwrap();
+
+    assert_eq!(
+        send_buf.len(),
+        read_len,
+        "Received data was wrong size (expected {} bytes, received {})",
+        send_buf.len(),
+        read_len,
+    );
+    assert_eq!(send_buf, recv_buf, "Received data does not match sent data");
+}
+
+#[test]
+fn recv_small_buf() {
+    let mut connection = TipcChannel::connect(DEFAULT_DEVICE, ECHO_NAME)
+        .expect("Failed to connect to Trusty service");
+
+    // Send a long message to the echo service so that we can test receiving a long
+    // message.
+    let send_buf = [7u8; 2048];
+    connection.send(send_buf.as_slice()).unwrap();
+
+    // Attempt to receive the response message with a buffer that is too small to
+    // contain the message.
+    let mut recv_buf = [0u8; 32];
+    let err = connection.recv_no_alloc(recv_buf.as_mut_slice()).unwrap_err();
+
+    assert_eq!(
+        Some(libc::EMSGSIZE),
+        err.raw_os_error(),
+        "Unexpected error err when receiving incoming message: {:?}",
+        err,
+    );
+}
+
+#[test]
+fn recv_empty_vec() {
+    let mut connection = TipcChannel::connect(DEFAULT_DEVICE, ECHO_NAME)
+        .expect("Failed to connect to Trusty service");
+
+    // Send a message to the echo TA.
+    let send_buf = [7u8; 2048];
+    connection.send(send_buf.as_slice()).unwrap();
+
+    // Receive the response message. `recv_buf` is initially empty, and `recv` is
+    // responsible for allocating enough space to hold the message.
+    let mut recv_buf = Vec::new();
+    connection.recv(&mut recv_buf).unwrap();
+
+    assert_eq!(send_buf.as_slice(), recv_buf, "Received data does not match sent data");
+}
+
+#[test]
+fn recv_vec_existing_capacity() {
+    let mut connection = TipcChannel::connect(DEFAULT_DEVICE, ECHO_NAME)
+        .expect("Failed to connect to Trusty service");
+
+    // Send a message to the echo TA.
+    let send_buf = [7u8; 2048];
+    connection.send(send_buf.as_slice()).unwrap();
+
+    // Receive the response message into a buffer that already has enough capacity
+    // to hold the message. No additional capacity should be allocated when
+    // receiving the message.
+    let mut recv_buf = Vec::with_capacity(2048);
+    connection.recv(&mut recv_buf).unwrap();
+
+    assert_eq!(send_buf.as_slice(), recv_buf, "Received data does not match sent data");
+    assert_eq!(2048, recv_buf.capacity(), "Additional capacity was allocated when not needed");
+}