Merge "sparse_fuzzer: Bug fix"
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 a5e2413..f4ba347 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -113,8 +113,14 @@
// Enable GWP-ASan at the start of this process. GWP-ASan is enabled using
// process sampling, so we need to ensure we force GWP-ASan on.
__attribute__((constructor)) static void enable_gwp_asan() {
- bool force = true;
- android_mallopt(M_INITIALIZE_GWP_ASAN, &force, sizeof(force));
+ android_mallopt_gwp_asan_options_t opts;
+ // No, we're not an app, but let's turn ourselves on without sampling.
+ // Technically, if someone's using the *.default_app sysprops, they'll adjust
+ // our settings, but I don't think this will be common on a device that's
+ // running debuggerd_tests.
+ opts.desire = android_mallopt_gwp_asan_options_t::Action::TURN_ON_FOR_APP;
+ opts.program_name = "";
+ android_mallopt(M_INITIALIZE_GWP_ASAN, &opts, sizeof(android_mallopt_gwp_asan_options_t));
}
static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd,
@@ -377,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([]() {
@@ -408,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([]() {
@@ -486,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;
@@ -2021,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/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index b2077ba..3d96627 100644
--- a/debuggerd/libdebuggerd/gwp_asan.cpp
+++ b/debuggerd/libdebuggerd/gwp_asan.cpp
@@ -43,10 +43,13 @@
static const gwp_asan::AllocationMetadata* retrieve_gwp_asan_metadata(
unwindstack::Memory* process_memory, const gwp_asan::AllocatorState& state,
uintptr_t metadata_addr) {
- if (state.MaxSimultaneousAllocations > 1024) {
+ // 1 million GWP-ASan slots would take 4.1GiB of space. Thankfully, copying
+ // the metadata for that amount of slots is only 532MiB, and this really will
+ // only be used with some ridiculous torture-tests.
+ if (state.MaxSimultaneousAllocations > 1000000) {
ALOGE(
"Error when retrieving GWP-ASan metadata, MSA from state (%zu) "
- "exceeds maximum allowed (1024).",
+ "exceeds maximum allowed (1,000,000).",
state.MaxSimultaneousAllocations);
return nullptr;
}
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/fastboot.bash b/fastboot/fastboot.bash
index e9bf9e9..911071a 100644
--- a/fastboot/fastboot.bash
+++ b/fastboot/fastboot.bash
@@ -109,7 +109,7 @@
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $i -eq $COMP_CWORD ]]; then
- partitions="boot bootloader dtbo init_boot modem odm odm_dlkm oem product pvmfw radio recovery system system_dlkm vbmeta vendor vendor_dlkm"
+ partitions="boot bootloader dtbo init_boot modem odm odm_dlkm oem product pvmfw radio recovery system system_dlkm vbmeta vendor vendor_dlkm vendor_kernel_boot"
COMPREPLY=( $(compgen -W "$partitions" -- $cur) )
else
_fastboot_util_complete_local_file "${cur}" '!*.img'
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 39d86f9..79c3ac7 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -186,6 +186,11 @@
"vendor_dlkm.img", "vendor_dlkm.sig",
"vendor_dlkm",
true, ImageType::Normal },
+ { "vendor_kernel_boot",
+ "vendor_kernel_boot.img",
+ "vendor_kernel_boot.sig",
+ "vendor_kernel_boot",
+ true, ImageType::BootCritical },
{ nullptr, "vendor_other.img", "vendor.sig", "vendor", true, ImageType::Normal },
// clang-format on
};
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/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/bootchart.cpp b/init/bootchart.cpp
index b7db9b6..f46fb09 100644
--- a/init/bootchart.cpp
+++ b/init/bootchart.cpp
@@ -140,6 +140,20 @@
static void bootchart_thread_main() {
LOG(INFO) << "Bootcharting started";
+ // Unshare the mount namespace of this thread so that the init process itself can switch
+ // the mount namespace later while this thread is still running.
+ // Otherwise, setns() call invoked as part of `enter_default_mount_ns` fails with EINVAL.
+ //
+ // Note that after unshare()'ing the mount namespace from the main thread, this thread won't
+ // receive mount/unmount events from the other mount namespace unless the events are happening
+ // from under a sharable mount.
+ //
+ // The bootchart thread is safe to unshare the mount namespace because it only reads from /proc
+ // and write to /data which are not private mounts.
+ if (unshare(CLONE_NEWNS) == -1) {
+ PLOG(ERROR) << "Cannot create mount namespace";
+ return;
+ }
// Open log files.
auto stat_log = fopen_unique("/data/bootchart/proc_stat.log", "we");
if (!stat_log) return;
diff --git a/init/compare-bootcharts.py b/init/compare-bootcharts.py
index 2057b55..009b639 100755
--- a/init/compare-bootcharts.py
+++ b/init/compare-bootcharts.py
@@ -56,24 +56,24 @@
]
jw = jiffy_record['jiffy_to_wallclock']
- print "process: baseline experiment (delta)"
- print " - Unit is ms (a jiffy is %d ms on the system)" % jw
- print "------------------------------------"
+ print("process: baseline experiment (delta)")
+ print(" - Unit is ms (a jiffy is %d ms on the system)" % jw)
+ print("------------------------------------")
for p in processes_of_interest:
# e.g., 32-bit system doesn't have zygote64
if p in process_map1 and p in process_map2:
- print "%s: %d %d (%+d)" % (
+ print("%s: %d %d (%+d)" % (
p, process_map1[p]['start_time'] * jw,
process_map2[p]['start_time'] * jw,
(process_map2[p]['start_time'] -
- process_map1[p]['start_time']) * jw)
+ process_map1[p]['start_time']) * jw))
# Print the last tick for the bootanimation process
- print "bootanimation ends at: %d %d (%+d)" % (
+ print("bootanimation ends at: %d %d (%+d)" % (
process_map1['/system/bin/bootanimation']['last_tick'] * jw,
process_map2['/system/bin/bootanimation']['last_tick'] * jw,
(process_map2['/system/bin/bootanimation']['last_tick'] -
- process_map1['/system/bin/bootanimation']['last_tick']) * jw)
+ process_map1['/system/bin/bootanimation']['last_tick']) * jw))
def parse_proc_file(pathname, process_map, jiffy_record=None):
# Uncompress bootchart.tgz
@@ -83,7 +83,7 @@
f = tf.extractfile('proc_ps.log')
# Break proc_ps into chunks based on timestamps
- blocks = f.read().split('\n\n')
+ blocks = f.read().decode('utf-8').split('\n\n')
for b in blocks:
lines = b.split('\n')
if not lines[0]:
@@ -133,7 +133,7 @@
def main():
if len(sys.argv) != 3:
- print "Usage: %s base_bootchart_dir exp_bootchart_dir" % sys.argv[0]
+ print("Usage: %s base_bootchart_dir exp_bootchart_dir" % sys.argv[0])
sys.exit(1)
process_map1 = {}
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/reboot.cpp b/init/reboot.cpp
index 6aa9912..41cf748 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -18,6 +18,7 @@
#include <dirent.h>
#include <fcntl.h>
+#include <linux/f2fs.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <mntent.h>
@@ -218,7 +219,7 @@
<< stat;
}
-static bool IsDataMounted() {
+static bool IsDataMounted(const std::string& fstype) {
std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(setmntent("/proc/mounts", "re"), endmntent);
if (fp == nullptr) {
PLOG(ERROR) << "Failed to open /proc/mounts";
@@ -227,7 +228,7 @@
mntent* mentry;
while ((mentry = getmntent(fp.get())) != nullptr) {
if (mentry->mnt_dir == "/data"s) {
- return true;
+ return fstype == "*" || mentry->mnt_type == fstype;
}
}
return false;
@@ -633,7 +634,7 @@
// If /data isn't mounted then we can skip the extra reboot steps below, since we don't need to
// worry about unmounting it.
- if (!IsDataMounted()) {
+ if (!IsDataMounted("*")) {
sync();
RebootSystem(cmd, reboot_target);
abort();
@@ -758,6 +759,16 @@
sem_post(&reboot_semaphore);
// Reboot regardless of umount status. If umount fails, fsck after reboot will fix it.
+ if (IsDataMounted("f2fs")) {
+ uint32_t flag = F2FS_GOING_DOWN_FULLSYNC;
+ unique_fd fd(TEMP_FAILURE_RETRY(open("/data", O_RDONLY)));
+ int ret = ioctl(fd, F2FS_IOC_SHUTDOWN, &flag);
+ if (ret) {
+ PLOG(ERROR) << "Shutdown /data: ";
+ } else {
+ LOG(INFO) << "Shutdown /data";
+ }
+ }
RebootSystem(cmd, reboot_target);
abort();
}
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 c6bf708..586e2cf 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -298,21 +298,31 @@
static UeventdConfiguration GetConfiguration() {
auto hardware = android::base::GetProperty("ro.hardware", "");
- std::vector<std::string> legacy_paths{"/vendor/ueventd.rc", "/odm/ueventd.rc",
- "/ueventd." + hardware + ".rc"};
+
+ struct LegacyPathInfo {
+ std::string legacy_path;
+ std::string preferred;
+ };
+ std::vector<LegacyPathInfo> legacy_paths{
+ {"/vendor/ueventd.rc", "/vendor/etc/ueventd.rc"},
+ {"/odm/ueventd.rc", "/odm/etc/ueventd.rc"},
+ {"/ueventd." + hardware + ".rc", "another ueventd.rc file"}};
std::vector<std::string> canonical{"/system/etc/ueventd.rc"};
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());
+ for (const auto& info : legacy_paths) {
+ canonical.push_back(info.legacy_path);
+ }
} else {
// Warn if newer device is using legacy paths.
- for (const auto& path : legacy_paths) {
- if (access(path.c_str(), F_OK) == 0) {
+ for (const auto& info : legacy_paths) {
+ if (access(info.legacy_path.c_str(), F_OK) == 0) {
LOG(FATAL_WITHOUT_ABORT)
<< "Legacy ueventd configuration file detected and will not be parsed: "
- << path;
+ << info.legacy_path << ". Please move your configuration to "
+ << info.preferred << " instead.";
}
}
}
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 54772b6..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);
}
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/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 27060ae..e1c5934 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -806,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) {
@@ -814,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) {
@@ -832,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/libutils/Android.bp b/libutils/Android.bp
index a9bd7d9..1b29285 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -188,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/VectorImpl.cpp b/libutils/VectorImpl.cpp
index c97a19b..d951b8b 100644
--- a/libutils/VectorImpl.cpp
+++ b/libutils/VectorImpl.cpp
@@ -279,14 +279,12 @@
ssize_t VectorImpl::removeItemsAt(size_t index, size_t count)
{
- ALOG_ASSERT((index+count)<=size(),
- "[%p] remove: index=%d, count=%d, size=%d",
- this, (int)index, (int)count, (int)size());
-
- if ((index+count) > size())
- return BAD_VALUE;
- _shrink(index, count);
- return index;
+ size_t end;
+ LOG_ALWAYS_FATAL_IF(__builtin_add_overflow(index, count, &end), "overflow: index=%zu count=%zu",
+ index, count);
+ if (end > size()) return BAD_VALUE;
+ _shrink(index, count);
+ return index;
}
void VectorImpl::finish_vector()
diff --git a/libutils/Vector_test.cpp b/libutils/Vector_test.cpp
index 5336c40..6d90eaa 100644
--- a/libutils/Vector_test.cpp
+++ b/libutils/Vector_test.cpp
@@ -136,4 +136,13 @@
}
}
+TEST_F(VectorTest, removeItemsAt_overflow) {
+ android::Vector<int> v;
+ for (int i = 0; i < 666; i++) v.add(i);
+
+ ASSERT_DEATH(v.removeItemsAt(SIZE_MAX, 666), "overflow");
+ ASSERT_DEATH(v.removeItemsAt(666, SIZE_MAX), "overflow");
+ ASSERT_DEATH(v.removeItemsAt(SIZE_MAX, SIZE_MAX), "overflow");
+}
+
} // namespace android
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 7ad1c3c..aae28dc 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -824,9 +824,15 @@
# 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
- # Directory for VirtualizationService temporary image files.
- mkdir /data/misc/virtualizationservice 0700 virtualizationservice virtualizationservice
+ 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. Always create
+ # a fresh new empty directory to remove any stale files from the previous
+ # boot.
+ rmdir /data/misc/virtualizationservice
+ mkdir /data/misc/virtualizationservice 0700 system system
mkdir /data/preloads 0775 system system encryption=None
@@ -1303,3 +1309,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");
+}