Merge "Add pointer dereference in debuggerd error msg"
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 5da1b40..d20de6b 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -368,6 +368,7 @@
name: "crash_dump",
srcs: [
"crash_dump.cpp",
+ "tombstone_handler.cpp",
"util.cpp",
],
defaults: ["debuggerd_defaults"],
@@ -509,6 +510,9 @@
arm64: {
src: "seccomp_policy/crash_dump.arm.policy",
},
+ riscv64: {
+ enabled: false,
+ },
x86: {
src: "seccomp_policy/crash_dump.x86_64.policy",
},
diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING
index 394447b..8633cb8 100644
--- a/debuggerd/TEST_MAPPING
+++ b/debuggerd/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"name": "libtombstoned_client_rust_test"
+ },
+ {
+ "name": "MicrodroidHostTestCases"
}
],
"hwasan-presubmit": [
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 442392d..3563436 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -59,7 +59,7 @@
#include "libdebuggerd/utility.h"
#include "debuggerd/handler.h"
-#include "tombstoned/tombstoned.h"
+#include "tombstone_handler.h"
#include "protocol.h"
#include "util.h"
@@ -142,7 +142,8 @@
return false;
}
-static bool activity_manager_notify(pid_t pid, int signal, const std::string& amfd_data) {
+static bool activity_manager_notify(pid_t pid, int signal, const std::string& amfd_data,
+ bool recoverable_gwp_asan_crash) {
ATRACE_CALL();
android::base::unique_fd amfd(socket_local_client(
"/data/system/ndebugsocket", ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM));
@@ -165,19 +166,32 @@
return false;
}
- // Activity Manager protocol: binary 32-bit network-byte-order ints for the
- // pid and signal number, followed by the raw text of the dump, culminating
- // in a zero byte that marks end-of-data.
+ // Activity Manager protocol:
+ // - 32-bit network-byte-order: pid
+ // - 32-bit network-byte-order: signal number
+ // - byte: recoverable_gwp_asan_crash
+ // - bytes: raw text of the dump
+ // - null terminator
+
uint32_t datum = htonl(pid);
- if (!android::base::WriteFully(amfd, &datum, 4)) {
+ if (!android::base::WriteFully(amfd, &datum, sizeof(datum))) {
PLOG(ERROR) << "AM pid write failed";
return false;
}
+
datum = htonl(signal);
- if (!android::base::WriteFully(amfd, &datum, 4)) {
- PLOG(ERROR) << "AM signal write failed";
+ if (!android::base::WriteFully(amfd, &datum, sizeof(datum))) {
+ PLOG(ERROR) << "AM signo write failed";
return false;
}
+
+ uint8_t recoverable_gwp_asan_crash_byte = recoverable_gwp_asan_crash ? 1 : 0;
+ if (!android::base::WriteFully(amfd, &recoverable_gwp_asan_crash_byte,
+ sizeof(recoverable_gwp_asan_crash_byte))) {
+ PLOG(ERROR) << "AM recoverable_gwp_asan_crash_byte write failed";
+ return false;
+ }
+
if (!android::base::WriteFully(amfd, amfd_data.c_str(), amfd_data.size() + 1)) {
PLOG(ERROR) << "AM data write failed";
return false;
@@ -215,8 +229,8 @@
// If we abort before we get an output fd, contact tombstoned to let any
// potential listeners know that we failed.
if (!g_tombstoned_connected) {
- if (!tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd, &g_proto_fd,
- kDebuggerdAnyIntercept)) {
+ if (!connect_tombstone_server(g_target_thread, &g_tombstoned_socket, &g_output_fd,
+ &g_proto_fd, kDebuggerdAnyIntercept)) {
// We failed to connect, not much we can do.
LOG(ERROR) << "failed to connected to tombstoned to report failure";
_exit(1);
@@ -589,8 +603,8 @@
{
ATRACE_NAME("tombstoned_connect");
LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type;
- g_tombstoned_connected = tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd,
- &g_proto_fd, dump_type);
+ g_tombstoned_connected = connect_tombstone_server(g_target_thread, &g_tombstoned_socket,
+ &g_output_fd, &g_proto_fd, dump_type);
}
if (g_tombstoned_connected) {
@@ -651,10 +665,10 @@
}
}
- if (fatal_signal && !recoverable_gwp_asan_crash) {
+ if (fatal_signal) {
// Don't try to notify ActivityManager if it just crashed, or we might hang until timeout.
if (thread_info[target_process].thread_name != "system_server") {
- activity_manager_notify(target_process, signo, amfd_data);
+ activity_manager_notify(target_process, signo, amfd_data, recoverable_gwp_asan_crash);
}
}
@@ -673,7 +687,8 @@
// Close stdout before we notify tombstoned of completion.
close(STDOUT_FILENO);
- if (g_tombstoned_connected && !tombstoned_notify_completion(g_tombstoned_socket.get())) {
+ if (g_tombstoned_connected &&
+ !notify_completion(g_tombstoned_socket.get(), g_output_fd.get(), g_proto_fd.get())) {
LOG(ERROR) << "failed to notify tombstoned of completion";
}
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 4043a6e..6a19878 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -164,7 +164,8 @@
}
noinline void readdir_null() {
- readdir(nullptr);
+ DIR* sneaky_null = nullptr;
+ readdir(sneaky_null);
}
noinline int strlen_null() {
diff --git a/debuggerd/crasher/riscv64/crashglue.S b/debuggerd/crasher/riscv64/crashglue.S
index 47dd93b..42f59b3 100644
--- a/debuggerd/crasher/riscv64/crashglue.S
+++ b/debuggerd/crasher/riscv64/crashglue.S
@@ -1,45 +1,56 @@
-
- .globl crash1
- .globl crashnostack
-
+.globl crash1
crash1:
- li x0,0xdead0000+0
- li x1,0xdead0000+1
- li x2,0xdead0000+2
- li x3,0xdead0000+3
- li x4,0xdead0000+4
- li x5,0xdead0000+5
- li x6,0xdead0000+6
- li x7,0xdead0000+7
- li x8,0xdead0000+8
- li x9,0xdead0000+9
- li x10,0xdead0000+10
- li x11,0xdead0000+11
- li x12,0xdead0000+12
- li x13,0xdead0000+13
- li x14,0xdead0000+14
- li x15,0xdead0000+15
- li x16,0xdead0000+16
- li x17,0xdead0000+17
- li x18,0xdead0000+18
- li x19,0xdead0000+19
- li x20,0xdead0000+20
- li x21,0xdead0000+21
- li x22,0xdead0000+22
- li x23,0xdead0000+23
- li x24,0xdead0000+24
- li x25,0xdead0000+25
- li x26,0xdead0000+26
- li x27,0xdead0000+27
- li x28,0xdead0000+28
- # don't trash the stack otherwise the signal handler won't run
- #li $29,0xdead0000+29
- li x30,0xdead0000+30
- li x31,0xdead0000+31
+ .cfi_startproc
+ addi sp, sp, -16
+ .cfi_def_cfa_offset 16
+ sd ra, 8(sp)
+ .cfi_offset ra, -8
+ li x0,0xa5a50000
+ li x1,0xa5a50001
+ li x2,0xa5a50002
+ li x3,0xa5a50003
+ li x4,0xa5a50004
+ li x5,0xa5a50005
+ li x6,0xa5a50006
+ li x7,0xa5a50007
+ li x8,0xa5a50008
+ li x9,0xa5a50009
+ li x10,0xa5a50010
+ li x11,0xa5a50011
+ li x12,0xa5a50012
+ li x13,0xa5a50013
+ li x14,0xa5a50014
+ li x15,0xa5a50015
+ li x16,0xa5a50016
+ li x17,0xa5a50017
+ li x18,0xa5a50018
+ li x19,0xa5a50019
+ li x20,0xa5a50020
+ li x21,0xa5a50021
+ li x22,0xa5a50022
+ li x23,0xa5a50023
+ li x24,0xa5a50024
+ li x25,0xa5a50025
+ li x26,0xa5a50026
+ li x27,0xa5a50027
+ li x28,0xa5a50028
+ li x29,0xa5a50029
+ li x30,0xa5a50030
+ li x31,0xa5a50031
+
+ li sp, 0
+ ld t2, 0(zero)
j .
+ .cfi_endproc
+.globl crashnostack
crashnostack:
- li sp, 0
+ .cfi_startproc
+ mv t1, sp
+ .cfi_def_cfa_register t1
+ li sp, 0
+ ld t2, 0(zero)
j .
+ .cfi_endproc
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 517f2df..a00a202 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -2437,35 +2437,42 @@
#if defined(__arm__)
asm volatile(
"mov r1, %[base]\n"
- "mov r2, 0\n"
- "str r3, [r2]\n"
+ "mov r2, #0\n"
+ "str r2, [r2]\n"
: [base] "+r"(ptr)
:
- : "r1", "r2", "r3", "memory");
+ : "r1", "r2", "memory");
#elif defined(__aarch64__)
asm volatile(
"mov x1, %[base]\n"
- "mov x2, 0\n"
- "str x3, [x2]\n"
+ "mov x2, #0\n"
+ "str xzr, [x2]\n"
: [base] "+r"(ptr)
:
- : "x1", "x2", "x3", "memory");
+ : "x1", "x2", "memory");
+#elif defined(__riscv)
+ // TODO: x1 is ra (the link register) on riscv64, so this might have
+ // unintended consequences, but we'll need to change the .cfi_escape if so.
+ asm volatile(
+ "mv x1, %[base]\n"
+ "sw zero, 0(zero)\n"
+ : [base] "+r"(ptr)
+ :
+ : "x1", "memory");
#elif defined(__i386__)
asm volatile(
"mov %[base], %%ecx\n"
- "movl $0, %%edi\n"
- "movl 0(%%edi), %%edx\n"
+ "movl $0, 0\n"
: [base] "+r"(ptr)
:
- : "edi", "ecx", "edx", "memory");
+ : "ecx", "memory");
#elif defined(__x86_64__)
asm volatile(
"mov %[base], %%rdx\n"
- "movq 0, %%rdi\n"
- "movq 0(%%rdi), %%rcx\n"
+ "movq $0, 0\n"
: [base] "+r"(ptr)
:
- : "rcx", "rdx", "rdi", "memory");
+ : "rdx", "memory");
#else
#error "Unsupported architecture"
#endif
diff --git a/debuggerd/tombstone_handler.cpp b/debuggerd/tombstone_handler.cpp
new file mode 100644
index 0000000..09df6d9
--- /dev/null
+++ b/debuggerd/tombstone_handler.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2023, 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 "tombstoned/tombstoned.h"
+
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
+#include <linux/vm_sockets.h>
+#include "util.h"
+
+using android::base::unique_fd;
+
+/*
+ Port number that VirtualMachineService listens on connections from the guest VMs.
+ Kep in sync with IVirtualMachineService.aidl
+*/
+const unsigned int VM_TOMBSTONES_SERVICE_PORT = 2000;
+
+static bool is_microdroid() {
+ return android::base::GetProperty("ro.hardware", "") == "microdroid";
+}
+
+static bool connect_tombstone_server_microdroid(unique_fd* text_output_fd,
+ unique_fd* proto_output_fd,
+ DebuggerdDumpType dump_type) {
+ // We do not wait for the property to be set, the default behaviour is not export tombstones.
+ if (!android::base::GetBoolProperty("microdroid_manager.export_tombstones.enabled", false)) {
+ LOG(WARNING) << "exporting tombstones is not enabled";
+ return false;
+ }
+
+ // Microdroid supports handling requests originating from crash_dump which
+ // supports limited dump types. Java traces and incept management are not supported.
+ switch (dump_type) {
+ case kDebuggerdNativeBacktrace:
+ case kDebuggerdTombstone:
+ case kDebuggerdTombstoneProto:
+ break;
+
+ default:
+ LOG(WARNING) << "Requested dump type: " << dump_type << " "
+ << "not supported";
+ }
+
+ int fd1 = TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0));
+ int fd2 = TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0));
+ if (fd1 < 0 || fd2 < 0) {
+ LOG(WARNING) << "Unable to create virtual socket for writing tombstones";
+ return false;
+ }
+
+ unique_fd vsock_output_fd(fd1), vsock_proto_fd(fd2);
+
+ struct sockaddr_vm sa = (struct sockaddr_vm){
+ .svm_family = AF_VSOCK,
+ .svm_port = VM_TOMBSTONES_SERVICE_PORT,
+ .svm_cid = VMADDR_CID_HOST,
+ };
+
+ if (TEMP_FAILURE_RETRY(connect(vsock_output_fd, (struct sockaddr*)&sa, sizeof(sa))) < 0) {
+ PLOG(WARNING) << "Unable to connect to tombstone service in host";
+ return false;
+ }
+
+ if (dump_type == kDebuggerdTombstoneProto) {
+ if (TEMP_FAILURE_RETRY(connect(vsock_proto_fd, (struct sockaddr*)&sa, sizeof(sa))) < 0) {
+ PLOG(WARNING) << "Unable to connect to tombstone service in host";
+ return false;
+ }
+ }
+
+ *text_output_fd = std::move(vsock_output_fd);
+ if (proto_output_fd) {
+ *proto_output_fd = std::move(vsock_proto_fd);
+ }
+ return true;
+}
+
+static bool notify_completion_microdroid(int vsock_out, int vsock_proto) {
+ if (shutdown(vsock_out, SHUT_WR) || shutdown(vsock_proto, SHUT_WR)) return false;
+ return true;
+}
+bool connect_tombstone_server(pid_t pid, unique_fd* tombstoned_socket, unique_fd* text_output_fd,
+ unique_fd* proto_output_fd, DebuggerdDumpType dump_type) {
+ if (is_microdroid()) {
+ return connect_tombstone_server_microdroid(text_output_fd, proto_output_fd, dump_type);
+ }
+ return tombstoned_connect(pid, tombstoned_socket, text_output_fd, proto_output_fd, dump_type);
+}
+
+bool notify_completion(int tombstoned_socket, int vsock_out, int vsock_proto) {
+ if (is_microdroid()) {
+ return notify_completion_microdroid(vsock_out, vsock_proto);
+ }
+ return tombstoned_notify_completion(tombstoned_socket);
+}
diff --git a/debuggerd/tombstone_handler.h b/debuggerd/tombstone_handler.h
new file mode 100644
index 0000000..8726bd3
--- /dev/null
+++ b/debuggerd/tombstone_handler.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/unique_fd.h>
+#include "dump_type.h"
+
+bool connect_tombstone_server(pid_t pid, android::base::unique_fd* tombstoned_socket,
+ android::base::unique_fd* text_output_fd,
+ android::base::unique_fd* proto_output_fd,
+ DebuggerdDumpType dump_type);
+
+bool notify_completion(int tombstoned_socket, int vsock_out, int vsock_proto);
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index a9a822c..42269fe 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -988,7 +988,7 @@
}
const int files = sparse_file_resparse(s, max_size, nullptr, 0);
- if (files < 0) die("Failed to resparse");
+ if (files < 0) die("Failed to compute resparse boundaries");
auto temp = std::make_unique<sparse_file*[]>(files);
const int rv = sparse_file_resparse(s, max_size, temp.get(), files);
@@ -1057,6 +1057,10 @@
if (sparse_file* s = sparse_file_import(fd.get(), false, false)) {
buf->image_size = sparse_file_len(s, false, false);
+ if (buf->image_size < 0) {
+ LOG(ERROR) << "Could not compute length of sparse file";
+ return false;
+ }
sparse_file_destroy(s);
} else {
buf->image_size = sz;
@@ -1172,15 +1176,6 @@
return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
}
-static std::string fb_fix_numeric_var(std::string var) {
- // Some bootloaders (angler, for example), send spurious leading whitespace.
- var = android::base::Trim(var);
- // Some bootloaders (hammerhead, for example) use implicit hex.
- // This code used to use strtol with base 16.
- if (!android::base::StartsWith(var, "0x")) var = "0x" + var;
- return var;
-}
-
static uint64_t get_partition_size(const std::string& partition) {
std::string partition_size_str;
if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) {
@@ -1202,10 +1197,10 @@
}
static void copy_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
- if (buf->sz < AVB_FOOTER_SIZE) {
+ if (buf->sz < AVB_FOOTER_SIZE || is_logical(partition) ||
+ should_flash_in_userspace(partition)) {
return;
}
-
// If overflows and negative, it should be < buf->sz.
int64_t partition_size = static_cast<int64_t>(get_partition_size(partition));
@@ -1253,17 +1248,16 @@
for (size_t i = 0; i < files.size(); i++) {
sparse_file* s = files[i].get();
int64_t sz = sparse_file_len(s, true, false);
+ if (sz < 0) {
+ LOG(FATAL) << "Could not compute length of sparse image for " << partition;
+ }
fb->FlashPartition(partition, s, sz, i + 1, files.size());
}
}
static void flash_buf(const std::string& partition, struct fastboot_buffer* buf,
const bool apply_vbmeta) {
- if (partition == "boot" || partition == "boot_a" || partition == "boot_b" ||
- partition == "init_boot" || partition == "init_boot_a" || partition == "init_boot_b" ||
- partition == "recovery" || partition == "recovery_a" || partition == "recovery_b") {
- copy_avb_footer(partition, buf);
- }
+ copy_avb_footer(partition, buf);
// Rewrite vbmeta if that's what we're flashing and modification has been requested.
if (g_disable_verity || g_disable_verification) {
@@ -1593,11 +1587,6 @@
void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
- // If the image uses the default slot, or the user specified "all", then
- // the paired string will be empty. If the image requests a specific slot
- // (for example, system_other) it is specified instead.
- using ImageEntry = std::pair<const Image*, std::string>;
-
std::vector<ImageEntry> boot_images_;
std::vector<ImageEntry> os_images_;
FlashingPlan* fp_;
@@ -1626,17 +1615,15 @@
// or in bootloader fastboot.
FlashImages(boot_images_);
- auto flash_super_task = FlashSuperLayoutTask::Initialize(fp_, os_images_);
+ std::vector<std::unique_ptr<Task>> tasks;
- if (flash_super_task) {
- flash_super_task->Run();
+ if (auto flash_super_task = FlashSuperLayoutTask::Initialize(fp_, os_images_)) {
+ tasks.emplace_back(std::move(flash_super_task));
} else {
// Sync the super partition. This will reboot to userspace fastboot if needed.
- std::unique_ptr<UpdateSuperTask> update_super_task = std::make_unique<UpdateSuperTask>(fp_);
- update_super_task->Run();
+ tasks.emplace_back(std::make_unique<UpdateSuperTask>(fp_));
// Resize any logical partition to 0, so each partition is reset to 0
// extents, and will achieve more optimal allocation.
- std::vector<std::unique_ptr<ResizeTask>> resize_tasks;
for (const auto& [image, slot] : os_images_) {
// Retrofit devices have two super partitions, named super_a and super_b.
// On these devices, secondary slots must be flashed as physical
@@ -1646,17 +1633,14 @@
std::string partition_name = image->part_name + "_"s + slot;
if (image->IsSecondary() && is_logical(partition_name)) {
fp_->fb->DeletePartition(partition_name);
- std::unique_ptr<DeleteTask> delete_task =
- std::make_unique<DeleteTask>(fp_, partition_name);
- delete_task->Run();
}
+ tasks.emplace_back(std::make_unique<DeleteTask>(fp_, partition_name));
}
- resize_tasks.emplace_back(
- std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
+ tasks.emplace_back(std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
}
- for (auto& i : resize_tasks) {
- i->Run();
- }
+ }
+ for (auto& task : tasks) {
+ task->Run();
}
FlashImages(os_images_);
}
diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp
index 1b59e4a..a898070 100644
--- a/fastboot/fuzzer/Android.bp
+++ b/fastboot/fuzzer/Android.bp
@@ -58,5 +58,13 @@
"android-media-fuzzing-reports@google.com",
],
componentid: 533764,
+ hotlists: [
+ "4593311",
+ ],
+ description: "The fuzzer targets the APIs of libfastboot library",
+ vector: "local_no_privileges_required",
+ service_privilege: "host_only",
+ users: "single_user",
+ fuzzed_code_usage: "shipped",
},
}
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 44008e5..9d4cb75 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -19,12 +19,9 @@
#include "filesystem.h"
#include "super_flash_helper.h"
-using namespace std::string_literals;
+#include <android-base/parseint.h>
-FlashTask::FlashTask(const std::string& slot, const std::string& pname, const bool apply_vbmeta)
- : pname_(pname), fname_(find_item(pname)), slot_(slot), apply_vbmeta_(apply_vbmeta) {
- if (fname_.empty()) die("cannot determine image filename for '%s'", pname_.c_str());
-}
+using namespace std::string_literals;
FlashTask::FlashTask(const std::string& _slot, const std::string& _pname, const std::string& _fname,
const bool apply_vbmeta)
: pname_(_pname), fname_(_fname), slot_(_slot), apply_vbmeta_(apply_vbmeta) {}
@@ -71,14 +68,18 @@
FlashSuperLayoutTask::FlashSuperLayoutTask(const std::string& super_name,
std::unique_ptr<SuperFlashHelper> helper,
- SparsePtr sparse_layout)
+ SparsePtr sparse_layout, uint64_t super_size)
: super_name_(super_name),
helper_(std::move(helper)),
- sparse_layout_(std::move(sparse_layout)) {}
+ sparse_layout_(std::move(sparse_layout)),
+ super_size_(super_size) {}
void FlashSuperLayoutTask::Run() {
+ // Use the reported super partition size as the upper limit, rather than
+ // sparse_file_len, which (1) can fail and (2) is kind of expensive, since
+ // it will map in all of the embedded fds.
std::vector<SparsePtr> files;
- if (int limit = get_sparse_limit(sparse_file_len(sparse_layout_.get(), false, false))) {
+ if (int limit = get_sparse_limit(super_size_)) {
files = resparse_file(sparse_layout_.get(), limit);
} else {
files.emplace_back(std::move(sparse_layout_));
@@ -112,12 +113,19 @@
if (fp->fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
super_name = "super";
}
- std::string partition_size_str;
+ uint64_t partition_size;
+ std::string partition_size_str;
if (fp->fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
return nullptr;
}
+ partition_size_str = fb_fix_numeric_var(partition_size_str);
+ if (!android::base::ParseUint(partition_size_str, &partition_size)) {
+ LOG(VERBOSE) << "Could not parse " << super_name << " size: " << partition_size_str;
+ return nullptr;
+ }
+
std::unique_ptr<SuperFlashHelper> helper = std::make_unique<SuperFlashHelper>(*fp->source);
if (!helper->Open(fd)) {
return nullptr;
@@ -141,7 +149,8 @@
};
os_images.erase(std::remove_if(os_images.begin(), os_images.end(), remove_if_callback),
os_images.end());
- return std::make_unique<FlashSuperLayoutTask>(super_name, std::move(helper), std::move(s));
+ return std::make_unique<FlashSuperLayoutTask>(super_name, std::move(helper), std::move(s),
+ partition_size);
}
UpdateSuperTask::UpdateSuperTask(FlashingPlan* fp) : fp_(fp) {}
@@ -192,9 +201,13 @@
void WipeTask::Run() {
std::string partition_type;
if (fp_->fb->GetVar("partition-type:" + pname_, &partition_type) != fastboot::SUCCESS) {
+ LOG(ERROR) << "wipe task partition not found: " << pname_;
return;
}
if (partition_type.empty()) return;
- fp_->fb->Erase(pname_);
+ if (fp_->fb->Erase(pname_) != fastboot::SUCCESS) {
+ LOG(ERROR) << "wipe task erase failed with partition: " << pname_;
+ return;
+ }
fb_perform_format(pname_, 1, partition_type, "", fp_->fs_options);
}
diff --git a/fastboot/task.h b/fastboot/task.h
index 264e43f..e89f85b 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -32,7 +32,6 @@
class FlashTask : public Task {
public:
- FlashTask(const std::string& slot, const std::string& pname, const bool apply_vbmeta);
FlashTask(const std::string& slot, const std::string& pname, const std::string& fname,
const bool apply_vbmeta);
@@ -59,7 +58,7 @@
class FlashSuperLayoutTask : public Task {
public:
FlashSuperLayoutTask(const std::string& super_name, std::unique_ptr<SuperFlashHelper> helper,
- SparsePtr sparse_layout);
+ SparsePtr sparse_layout, uint64_t super_size);
static std::unique_ptr<FlashSuperLayoutTask> Initialize(FlashingPlan* fp,
std::vector<ImageEntry>& os_images);
using ImageEntry = std::pair<const Image*, std::string>;
@@ -69,6 +68,7 @@
const std::string super_name_;
std::unique_ptr<SuperFlashHelper> helper_;
SparsePtr sparse_layout_;
+ uint64_t super_size_;
};
class UpdateSuperTask : public Task {
diff --git a/fastboot/util.cpp b/fastboot/util.cpp
index ded54a5..e03012a 100644
--- a/fastboot/util.cpp
+++ b/fastboot/util.cpp
@@ -33,6 +33,9 @@
#include <sys/stat.h>
#include <sys/time.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
#include "util.h"
using android::base::borrowed_fd;
@@ -106,3 +109,12 @@
}
return sb.st_size;
}
+
+std::string fb_fix_numeric_var(std::string var) {
+ // Some bootloaders (angler, for example), send spurious leading whitespace.
+ var = android::base::Trim(var);
+ // Some bootloaders (hammerhead, for example) use implicit hex.
+ // This code used to use strtol with base 16.
+ if (!android::base::StartsWith(var, "0x")) var = "0x" + var;
+ return var;
+}
diff --git a/fastboot/util.h b/fastboot/util.h
index 290d0d5..fdbc1d6 100644
--- a/fastboot/util.h
+++ b/fastboot/util.h
@@ -30,6 +30,7 @@
const std::string& partition_name);
bool is_sparse_file(android::base::borrowed_fd fd);
int64_t get_file_size(android::base::borrowed_fd fd);
+std::string fb_fix_numeric_var(std::string var);
class ImageSource {
public:
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index 85dbb36..90b65ce 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -101,7 +101,6 @@
if (wait_for_verity_dev) timeout = 1s;
std::string dev_path;
- const std::string mount_point(Basename(fstab_entry->mount_point));
const std::string device_name(GetVerityDeviceName(*fstab_entry));
android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();
if (!dm.CreateDevice(device_name, table, &dev_path, timeout)) {
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 9261482..7fcaac5 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -62,6 +62,7 @@
"dm-snapshot-merge/snapuserd_worker.cpp",
"dm-snapshot-merge/snapuserd_readahead.cpp",
"snapuserd_buffer.cpp",
+ "user-space-merge/handler_manager.cpp",
"user-space-merge/snapuserd_core.cpp",
"user-space-merge/snapuserd_dm_user.cpp",
"user-space-merge/snapuserd_merge.cpp",
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index bfe93eb..0557214 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -114,7 +114,7 @@
return false;
}
auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
- if (!handler || !user_server_.StartHandler(handler)) {
+ if (!handler || !user_server_.StartHandler(parts[0])) {
return false;
}
}
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
new file mode 100644
index 0000000..c5150c4
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -0,0 +1,371 @@
+// Copyright (C) 2023 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 "handler_manager.h"
+
+#include <sys/eventfd.h>
+
+#include <android-base/logging.h>
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+static constexpr uint8_t kMaxMergeThreads = 2;
+
+void HandlerThread::FreeResources() {
+ // Each worker thread holds a reference to snapuserd.
+ // Clear them so that all the resources
+ // held by snapuserd is released
+ if (snapuserd_) {
+ snapuserd_->FreeResources();
+ snapuserd_ = nullptr;
+ }
+}
+
+SnapshotHandlerManager::SnapshotHandlerManager() {
+ monitor_merge_event_fd_.reset(eventfd(0, EFD_CLOEXEC));
+ if (monitor_merge_event_fd_ == -1) {
+ PLOG(FATAL) << "monitor_merge_event_fd_: failed to create eventfd";
+ }
+}
+
+std::shared_ptr<HandlerThread> SnapshotHandlerManager::AddHandler(
+ const std::string& misc_name, const std::string& cow_device_path,
+ const std::string& backing_device, const std::string& base_path_merge,
+ int num_worker_threads, bool use_iouring, bool perform_verification) {
+ auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
+ base_path_merge, num_worker_threads,
+ use_iouring, perform_verification);
+ if (!snapuserd->InitCowDevice()) {
+ LOG(ERROR) << "Failed to initialize Snapuserd";
+ return nullptr;
+ }
+
+ if (!snapuserd->InitializeWorkers()) {
+ LOG(ERROR) << "Failed to initialize workers";
+ return nullptr;
+ }
+
+ auto handler = std::make_shared<HandlerThread>(snapuserd);
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ if (FindHandler(&lock, misc_name) != dm_users_.end()) {
+ LOG(ERROR) << "Handler already exists: " << misc_name;
+ return nullptr;
+ }
+ dm_users_.push_back(handler);
+ }
+ return handler;
+}
+
+bool SnapshotHandlerManager::StartHandler(const std::string& misc_name) {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, misc_name);
+ if (iter == dm_users_.end()) {
+ LOG(ERROR) << "Could not find handler: " << misc_name;
+ return false;
+ }
+ if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) {
+ LOG(ERROR) << "Tried to re-attach control device: " << misc_name;
+ return false;
+ }
+ if (!StartHandler(*iter)) {
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotHandlerManager::StartHandler(const std::shared_ptr<HandlerThread>& handler) {
+ if (handler->snapuserd()->IsAttached()) {
+ LOG(ERROR) << "Handler already attached";
+ return false;
+ }
+
+ handler->snapuserd()->AttachControlDevice();
+
+ handler->thread() = std::thread(std::bind(&SnapshotHandlerManager::RunThread, this, handler));
+ return true;
+}
+
+bool SnapshotHandlerManager::DeleteHandler(const std::string& misc_name) {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, misc_name);
+ if (iter == dm_users_.end()) {
+ // After merge is completed, we swap dm-user table with
+ // the underlying dm-linear base device. Hence, worker
+ // threads would have terminted and was removed from
+ // the list.
+ LOG(DEBUG) << "Could not find handler: " << misc_name;
+ return true;
+ }
+
+ if (!(*iter)->ThreadTerminated()) {
+ (*iter)->snapuserd()->NotifyIOTerminated();
+ }
+ }
+ if (!RemoveAndJoinHandler(misc_name)) {
+ return false;
+ }
+ return true;
+}
+
+void SnapshotHandlerManager::RunThread(std::shared_ptr<HandlerThread> handler) {
+ LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
+
+ if (!handler->snapuserd()->Start()) {
+ LOG(ERROR) << " Failed to launch all worker threads";
+ }
+
+ handler->snapuserd()->CloseFds();
+ bool merge_completed = handler->snapuserd()->CheckMergeCompletionStatus();
+ handler->snapuserd()->UnmapBufferRegion();
+
+ auto misc_name = handler->misc_name();
+ LOG(INFO) << "Handler thread about to exit: " << misc_name;
+
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ if (merge_completed) {
+ num_partitions_merge_complete_ += 1;
+ active_merge_threads_ -= 1;
+ WakeupMonitorMergeThread();
+ }
+ handler->SetThreadTerminated();
+ auto iter = FindHandler(&lock, handler->misc_name());
+ if (iter == dm_users_.end()) {
+ // RemoveAndJoinHandler() already removed us from the list, and is
+ // now waiting on a join(), so just return. Additionally, release
+ // all the resources held by snapuserd object which are shared
+ // by worker threads. This should be done when the last reference
+ // of "handler" is released; but we will explicitly release here
+ // to make sure snapuserd object is freed as it is the biggest
+ // consumer of memory in the daemon.
+ handler->FreeResources();
+ LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
+ return;
+ }
+
+ LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name;
+
+ if (handler->snapuserd()->IsAttached()) {
+ handler->thread().detach();
+ }
+
+ // Important: free resources within the lock. This ensures that if
+ // WaitForDelete() is called, the handler is either in the list, or
+ // it's not and its resources are guaranteed to be freed.
+ handler->FreeResources();
+ dm_users_.erase(iter);
+ }
+}
+
+bool SnapshotHandlerManager::InitiateMerge(const std::string& misc_name) {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, misc_name);
+ if (iter == dm_users_.end()) {
+ LOG(ERROR) << "Could not find handler: " << misc_name;
+ return false;
+ }
+
+ return StartMerge(&lock, *iter);
+}
+
+bool SnapshotHandlerManager::StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
+ const std::shared_ptr<HandlerThread>& handler) {
+ CHECK(proof_of_lock);
+
+ if (!handler->snapuserd()->IsAttached()) {
+ LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
+ return false;
+ }
+
+ handler->snapuserd()->MonitorMerge();
+
+ if (!is_merge_monitor_started_) {
+ std::thread(&SnapshotHandlerManager::MonitorMerge, this).detach();
+ is_merge_monitor_started_ = true;
+ }
+
+ merge_handlers_.push(handler);
+ WakeupMonitorMergeThread();
+ return true;
+}
+
+void SnapshotHandlerManager::WakeupMonitorMergeThread() {
+ uint64_t notify = 1;
+ ssize_t rc = TEMP_FAILURE_RETRY(write(monitor_merge_event_fd_.get(), ¬ify, sizeof(notify)));
+ if (rc < 0) {
+ PLOG(FATAL) << "failed to notify monitor merge thread";
+ }
+}
+
+void SnapshotHandlerManager::MonitorMerge() {
+ while (!stop_monitor_merge_thread_) {
+ uint64_t testVal;
+ ssize_t ret =
+ TEMP_FAILURE_RETRY(read(monitor_merge_event_fd_.get(), &testVal, sizeof(testVal)));
+ if (ret == -1) {
+ PLOG(FATAL) << "Failed to read from eventfd";
+ } else if (ret == 0) {
+ LOG(FATAL) << "Hit EOF on eventfd";
+ }
+
+ LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_;
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ while (active_merge_threads_ < kMaxMergeThreads && merge_handlers_.size() > 0) {
+ auto handler = merge_handlers_.front();
+ merge_handlers_.pop();
+
+ if (!handler->snapuserd()) {
+ LOG(INFO) << "MonitorMerge: skipping deleted handler: " << handler->misc_name();
+ continue;
+ }
+
+ LOG(INFO) << "Starting merge for partition: "
+ << handler->snapuserd()->GetMiscName();
+ handler->snapuserd()->InitiateMerge();
+ active_merge_threads_ += 1;
+ }
+ }
+ }
+
+ LOG(INFO) << "Exiting MonitorMerge: size: " << merge_handlers_.size();
+}
+
+std::string SnapshotHandlerManager::GetMergeStatus(const std::string& misc_name) {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, misc_name);
+ if (iter == dm_users_.end()) {
+ LOG(ERROR) << "Could not find handler: " << misc_name;
+ return {};
+ }
+
+ return (*iter)->snapuserd()->GetMergeStatus();
+}
+
+double SnapshotHandlerManager::GetMergePercentage() {
+ std::lock_guard<std::mutex> lock(lock_);
+
+ double percentage = 0.0;
+ int n = 0;
+
+ for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+ auto& th = (*iter)->thread();
+ if (th.joinable()) {
+ // Merge percentage by individual partitions wherein merge is still
+ // in-progress
+ percentage += (*iter)->snapuserd()->GetMergePercentage();
+ n += 1;
+ }
+ }
+
+ // Calculate final merge including those partitions where merge was already
+ // completed - num_partitions_merge_complete_ will track them when each
+ // thread exists in RunThread.
+ int total_partitions = n + num_partitions_merge_complete_;
+
+ if (total_partitions) {
+ percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
+ }
+
+ LOG(DEBUG) << "Merge %: " << percentage
+ << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
+ << " total_partitions: " << total_partitions << " n: " << n;
+ return percentage;
+}
+
+bool SnapshotHandlerManager::GetVerificationStatus() {
+ std::lock_guard<std::mutex> lock(lock_);
+
+ bool status = true;
+ for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+ auto& th = (*iter)->thread();
+ if (th.joinable() && status) {
+ status = (*iter)->snapuserd()->CheckPartitionVerification() && status;
+ } else {
+ // return immediately if there is a failure
+ return false;
+ }
+ }
+
+ return status;
+}
+
+bool SnapshotHandlerManager::RemoveAndJoinHandler(const std::string& misc_name) {
+ std::shared_ptr<HandlerThread> handler;
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+
+ auto iter = FindHandler(&lock, misc_name);
+ if (iter == dm_users_.end()) {
+ // Client already deleted.
+ return true;
+ }
+ handler = std::move(*iter);
+ dm_users_.erase(iter);
+ }
+
+ auto& th = handler->thread();
+ if (th.joinable()) {
+ th.join();
+ }
+ return true;
+}
+
+void SnapshotHandlerManager::TerminateMergeThreads() {
+ std::lock_guard<std::mutex> guard(lock_);
+
+ for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+ if (!(*iter)->ThreadTerminated()) {
+ (*iter)->snapuserd()->NotifyIOTerminated();
+ }
+ }
+}
+
+void SnapshotHandlerManager::JoinAllThreads() {
+ // Acquire the thread list within the lock.
+ std::vector<std::shared_ptr<HandlerThread>> dm_users;
+ {
+ std::lock_guard<std::mutex> guard(lock_);
+ dm_users = std::move(dm_users_);
+ }
+
+ for (auto& client : dm_users) {
+ auto& th = client->thread();
+
+ if (th.joinable()) th.join();
+ }
+
+ stop_monitor_merge_thread_ = true;
+ WakeupMonitorMergeThread();
+}
+
+auto SnapshotHandlerManager::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+ const std::string& misc_name) -> HandlerList::iterator {
+ CHECK(proof_of_lock);
+
+ for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+ if ((*iter)->misc_name() == misc_name) {
+ return iter;
+ }
+ }
+ return dm_users_.end();
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
new file mode 100644
index 0000000..b7ddac1
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
@@ -0,0 +1,131 @@
+// Copyright (C) 2023 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 <memory>
+#include <queue>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace snapshot {
+
+class SnapshotHandler;
+
+class HandlerThread {
+ public:
+ explicit HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd);
+
+ void FreeResources();
+ const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
+ std::thread& thread() { return thread_; }
+
+ const std::string& misc_name() const { return misc_name_; }
+ bool ThreadTerminated() { return thread_terminated_; }
+ void SetThreadTerminated() { thread_terminated_ = true; }
+
+ private:
+ std::thread thread_;
+ std::shared_ptr<SnapshotHandler> snapuserd_;
+ std::string misc_name_;
+ bool thread_terminated_ = false;
+};
+
+class ISnapshotHandlerManager {
+ public:
+ virtual ~ISnapshotHandlerManager() {}
+
+ // Add a new snapshot handler but do not start serving requests yet.
+ virtual std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
+ const std::string& cow_device_path,
+ const std::string& backing_device,
+ const std::string& base_path_merge,
+ int num_worker_threads, bool use_iouring,
+ bool perform_verification) = 0;
+
+ // Start serving requests on a snapshot handler.
+ virtual bool StartHandler(const std::string& misc_name) = 0;
+
+ // Stop serving requests on a snapshot handler and remove it.
+ virtual bool DeleteHandler(const std::string& misc_name) = 0;
+
+ // Begin merging blocks on the given snapshot handler.
+ virtual bool InitiateMerge(const std::string& misc_name) = 0;
+
+ // Return a string containing a status code indicating the merge status
+ // on the handler. Returns empty on error.
+ virtual std::string GetMergeStatus(const std::string& misc_name) = 0;
+
+ // Wait until all handlers have terminated.
+ virtual void JoinAllThreads() = 0;
+
+ // Stop any in-progress merge threads.
+ virtual void TerminateMergeThreads() = 0;
+
+ // Returns the merge progress across all merging snapshot handlers.
+ virtual double GetMergePercentage() = 0;
+
+ // Returns whether all snapshots have verified.
+ virtual bool GetVerificationStatus() = 0;
+};
+
+class SnapshotHandlerManager final : public ISnapshotHandlerManager {
+ public:
+ SnapshotHandlerManager();
+ std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
+ const std::string& cow_device_path,
+ const std::string& backing_device,
+ const std::string& base_path_merge,
+ int num_worker_threads, bool use_iouring,
+ bool perform_verification) override;
+ bool StartHandler(const std::string& misc_name) override;
+ bool DeleteHandler(const std::string& misc_name) override;
+ bool InitiateMerge(const std::string& misc_name) override;
+ std::string GetMergeStatus(const std::string& misc_name) override;
+ void JoinAllThreads() override;
+ void TerminateMergeThreads() override;
+ double GetMergePercentage() override;
+ bool GetVerificationStatus() override;
+
+ private:
+ bool StartHandler(const std::shared_ptr<HandlerThread>& handler);
+ void RunThread(std::shared_ptr<HandlerThread> handler);
+ bool StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
+ const std::shared_ptr<HandlerThread>& handler);
+ void MonitorMerge();
+ void WakeupMonitorMergeThread();
+ bool RemoveAndJoinHandler(const std::string& misc_name);
+
+ // Find a HandlerThread within a lock.
+ using HandlerList = std::vector<std::shared_ptr<HandlerThread>>;
+ HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+ const std::string& misc_name);
+
+ std::mutex lock_;
+ HandlerList dm_users_;
+
+ bool is_merge_monitor_started_ = false;
+ bool stop_monitor_merge_thread_ = false;
+ int active_merge_threads_ = 0;
+ int num_partitions_merge_complete_ = 0;
+ std::queue<std::shared_ptr<HandlerThread>> merge_handlers_;
+ android::base::unique_fd monitor_merge_event_fd_;
+};
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index b7ce210..d87990a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -45,28 +45,9 @@
using android::base::borrowed_fd;
using android::base::unique_fd;
-DaemonOps UserSnapshotServer::Resolveop(std::string& input) {
- if (input == "init") return DaemonOps::INIT;
- if (input == "start") return DaemonOps::START;
- if (input == "stop") return DaemonOps::STOP;
- if (input == "query") return DaemonOps::QUERY;
- if (input == "delete") return DaemonOps::DELETE;
- if (input == "detach") return DaemonOps::DETACH;
- if (input == "supports") return DaemonOps::SUPPORTS;
- if (input == "initiate_merge") return DaemonOps::INITIATE;
- if (input == "merge_percent") return DaemonOps::PERCENTAGE;
- if (input == "getstatus") return DaemonOps::GETSTATUS;
- if (input == "update-verify") return DaemonOps::UPDATE_VERIFY;
-
- return DaemonOps::INVALID;
-}
-
UserSnapshotServer::UserSnapshotServer() {
- monitor_merge_event_fd_.reset(eventfd(0, EFD_CLOEXEC));
- if (monitor_merge_event_fd_ == -1) {
- PLOG(FATAL) << "monitor_merge_event_fd_: failed to create eventfd";
- }
terminating_ = false;
+ handlers_ = std::make_unique<SnapshotHandlerManager>();
}
UserSnapshotServer::~UserSnapshotServer() {
@@ -99,7 +80,7 @@
void UserSnapshotServer::ShutdownThreads() {
terminating_ = true;
- JoinAllThreads();
+ handlers_->JoinAllThreads();
}
HandlerThread::HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd)
@@ -135,226 +116,118 @@
std::vector<std::string> out;
Parsemsg(str, delim, out);
- DaemonOps op = Resolveop(out[0]);
- switch (op) {
- case DaemonOps::INIT: {
- // Message format:
- // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
- //
- // Reads the metadata and send the number of sectors
- if (out.size() != 5) {
- LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
- return Sendmsg(fd, "fail");
- }
-
- auto handler = AddHandler(out[1], out[2], out[3], out[4]);
- if (!handler) {
- return Sendmsg(fd, "fail");
- }
-
- auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
- return Sendmsg(fd, retval);
- }
- case DaemonOps::START: {
- // Message format:
- // start,<misc_name>
- //
- // Start the new thread which binds to dm-user misc device
- if (out.size() != 2) {
- LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
- return Sendmsg(fd, "fail");
- }
-
- std::lock_guard<std::mutex> lock(lock_);
- auto iter = FindHandler(&lock, out[1]);
- if (iter == dm_users_.end()) {
- LOG(ERROR) << "Could not find handler: " << out[1];
- return Sendmsg(fd, "fail");
- }
- if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) {
- LOG(ERROR) << "Tried to re-attach control device: " << out[1];
- return Sendmsg(fd, "fail");
- }
- if (!StartHandler(*iter)) {
- return Sendmsg(fd, "fail");
- }
- return Sendmsg(fd, "success");
- }
- case DaemonOps::STOP: {
- // Message format: stop
- //
- // Stop all the threads gracefully and then shutdown the
- // main thread
- SetTerminating();
- ShutdownThreads();
- return true;
- }
- case DaemonOps::QUERY: {
- // Message format: query
- //
- // As part of transition, Second stage daemon will be
- // created before terminating the first stage daemon. Hence,
- // for a brief period client may have to distiguish between
- // first stage daemon and second stage daemon.
- //
- // Second stage daemon is marked as active and hence will
- // be ready to receive control message.
- return Sendmsg(fd, GetDaemonStatus());
- }
- case DaemonOps::DELETE: {
- // Message format:
- // delete,<misc_name>
- if (out.size() != 2) {
- LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
- return Sendmsg(fd, "fail");
- }
- {
- std::lock_guard<std::mutex> lock(lock_);
- auto iter = FindHandler(&lock, out[1]);
- if (iter == dm_users_.end()) {
- // After merge is completed, we swap dm-user table with
- // the underlying dm-linear base device. Hence, worker
- // threads would have terminted and was removed from
- // the list.
- LOG(DEBUG) << "Could not find handler: " << out[1];
- return Sendmsg(fd, "success");
- }
-
- if (!(*iter)->ThreadTerminated()) {
- (*iter)->snapuserd()->NotifyIOTerminated();
- }
- }
- if (!RemoveAndJoinHandler(out[1])) {
- return Sendmsg(fd, "fail");
- }
- return Sendmsg(fd, "success");
- }
- case DaemonOps::DETACH: {
- std::lock_guard<std::mutex> lock(lock_);
- TerminateMergeThreads(&lock);
- terminating_ = true;
- return true;
- }
- case DaemonOps::SUPPORTS: {
- if (out.size() != 2) {
- LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
- return Sendmsg(fd, "fail");
- }
- if (out[1] == "second_stage_socket_handoff") {
- return Sendmsg(fd, "success");
- }
+ const auto& cmd = out[0];
+ if (cmd == "init") {
+ // Message format:
+ // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
+ //
+ // Reads the metadata and send the number of sectors
+ if (out.size() != 5) {
+ LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
}
- case DaemonOps::INITIATE: {
- if (out.size() != 2) {
- LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
- return Sendmsg(fd, "fail");
- }
- if (out[0] == "initiate_merge") {
- std::lock_guard<std::mutex> lock(lock_);
- auto iter = FindHandler(&lock, out[1]);
- if (iter == dm_users_.end()) {
- LOG(ERROR) << "Could not find handler: " << out[1];
- return Sendmsg(fd, "fail");
- }
- if (!StartMerge(&lock, *iter)) {
- return Sendmsg(fd, "fail");
- }
-
- return Sendmsg(fd, "success");
- }
+ auto handler = AddHandler(out[1], out[2], out[3], out[4]);
+ if (!handler) {
return Sendmsg(fd, "fail");
}
- case DaemonOps::PERCENTAGE: {
- std::lock_guard<std::mutex> lock(lock_);
- double percentage = GetMergePercentage(&lock);
- return Sendmsg(fd, std::to_string(percentage));
+ auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
+ return Sendmsg(fd, retval);
+ } else if (cmd == "start") {
+ // Message format:
+ // start,<misc_name>
+ //
+ // Start the new thread which binds to dm-user misc device
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
+ return Sendmsg(fd, "fail");
}
- case DaemonOps::GETSTATUS: {
- // Message format:
- // getstatus,<misc_name>
- if (out.size() != 2) {
- LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
- return Sendmsg(fd, "snapshot-merge-failed");
- }
- {
- std::lock_guard<std::mutex> lock(lock_);
- auto iter = FindHandler(&lock, out[1]);
- if (iter == dm_users_.end()) {
- LOG(ERROR) << "Could not find handler: " << out[1];
- return Sendmsg(fd, "snapshot-merge-failed");
- }
- std::string merge_status = GetMergeStatus(*iter);
- return Sendmsg(fd, merge_status);
- }
+ if (!handlers_->StartHandler(out[1])) {
+ return Sendmsg(fd, "fail");
}
- case DaemonOps::UPDATE_VERIFY: {
- std::lock_guard<std::mutex> lock(lock_);
- if (!UpdateVerification(&lock)) {
- return Sendmsg(fd, "fail");
- }
-
+ return Sendmsg(fd, "success");
+ } else if (cmd == "stop") {
+ // Message format: stop
+ //
+ // Stop all the threads gracefully and then shutdown the
+ // main thread
+ SetTerminating();
+ ShutdownThreads();
+ return true;
+ } else if (cmd == "query") {
+ // Message format: query
+ //
+ // As part of transition, Second stage daemon will be
+ // created before terminating the first stage daemon. Hence,
+ // for a brief period client may have to distiguish between
+ // first stage daemon and second stage daemon.
+ //
+ // Second stage daemon is marked as active and hence will
+ // be ready to receive control message.
+ return Sendmsg(fd, GetDaemonStatus());
+ } else if (cmd == "delete") {
+ // Message format:
+ // delete,<misc_name>
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+ return Sendmsg(fd, "fail");
+ }
+ if (!handlers_->DeleteHandler(out[1])) {
+ return Sendmsg(fd, "fail");
+ }
+ return Sendmsg(fd, "success");
+ } else if (cmd == "detach") {
+ handlers_->TerminateMergeThreads();
+ terminating_ = true;
+ return true;
+ } else if (cmd == "supports") {
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
+ return Sendmsg(fd, "fail");
+ }
+ if (out[1] == "second_stage_socket_handoff") {
return Sendmsg(fd, "success");
}
- default: {
- LOG(ERROR) << "Received unknown message type from client";
- Sendmsg(fd, "fail");
- return false;
+ return Sendmsg(fd, "fail");
+ } else if (cmd == "initiate_merge") {
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
+ return Sendmsg(fd, "fail");
}
- }
-}
-
-void UserSnapshotServer::RunThread(std::shared_ptr<HandlerThread> handler) {
- LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
-
- if (!handler->snapuserd()->Start()) {
- LOG(ERROR) << " Failed to launch all worker threads";
- }
-
- handler->snapuserd()->CloseFds();
- bool merge_completed = handler->snapuserd()->CheckMergeCompletionStatus();
- handler->snapuserd()->UnmapBufferRegion();
-
- auto misc_name = handler->misc_name();
- LOG(INFO) << "Handler thread about to exit: " << misc_name;
-
- {
- std::lock_guard<std::mutex> lock(lock_);
- if (merge_completed) {
- num_partitions_merge_complete_ += 1;
- active_merge_threads_ -= 1;
- WakeupMonitorMergeThread();
+ if (out[0] == "initiate_merge") {
+ if (!handlers_->InitiateMerge(out[1])) {
+ return Sendmsg(fd, "fail");
+ }
+ return Sendmsg(fd, "success");
}
- handler->SetThreadTerminated();
- auto iter = FindHandler(&lock, handler->misc_name());
- if (iter == dm_users_.end()) {
- // RemoveAndJoinHandler() already removed us from the list, and is
- // now waiting on a join(), so just return. Additionally, release
- // all the resources held by snapuserd object which are shared
- // by worker threads. This should be done when the last reference
- // of "handler" is released; but we will explicitly release here
- // to make sure snapuserd object is freed as it is the biggest
- // consumer of memory in the daemon.
- handler->FreeResources();
- LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
- return;
+ return Sendmsg(fd, "fail");
+ } else if (cmd == "merge_percent") {
+ double percentage = handlers_->GetMergePercentage();
+ return Sendmsg(fd, std::to_string(percentage));
+ } else if (cmd == "getstatus") {
+ // Message format:
+ // getstatus,<misc_name>
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+ return Sendmsg(fd, "snapshot-merge-failed");
}
-
- LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name;
-
- if (handler->snapuserd()->IsAttached()) {
- handler->thread().detach();
+ auto status = handlers_->GetMergeStatus(out[1]);
+ if (status.empty()) {
+ return Sendmsg(fd, "snapshot-merge-failed");
}
-
- // Important: free resources within the lock. This ensures that if
- // WaitForDelete() is called, the handler is either in the list, or
- // it's not and its resources are guaranteed to be freed.
- handler->FreeResources();
- dm_users_.erase(iter);
+ return Sendmsg(fd, status);
+ } else if (cmd == "update-verify") {
+ if (!handlers_->GetVerificationStatus()) {
+ return Sendmsg(fd, "fail");
+ }
+ return Sendmsg(fd, "success");
+ } else {
+ LOG(ERROR) << "Received unknown message type from client";
+ Sendmsg(fd, "fail");
+ return false;
}
}
@@ -423,28 +296,10 @@
}
}
- JoinAllThreads();
+ handlers_->JoinAllThreads();
return true;
}
-void UserSnapshotServer::JoinAllThreads() {
- // Acquire the thread list within the lock.
- std::vector<std::shared_ptr<HandlerThread>> dm_users;
- {
- std::lock_guard<std::mutex> guard(lock_);
- dm_users = std::move(dm_users_);
- }
-
- for (auto& client : dm_users) {
- auto& th = client->thread();
-
- if (th.joinable()) th.join();
- }
-
- stop_monitor_merge_thread_ = true;
- WakeupMonitorMergeThread();
-}
-
void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
struct pollfd p = {};
p.fd = fd.get();
@@ -506,185 +361,13 @@
perform_verification = false;
}
- auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
- base_path_merge, num_worker_threads,
- io_uring_enabled_, perform_verification);
- if (!snapuserd->InitCowDevice()) {
- LOG(ERROR) << "Failed to initialize Snapuserd";
- return nullptr;
- }
-
- if (!snapuserd->InitializeWorkers()) {
- LOG(ERROR) << "Failed to initialize workers";
- return nullptr;
- }
-
- auto handler = std::make_shared<HandlerThread>(snapuserd);
- {
- std::lock_guard<std::mutex> lock(lock_);
- if (FindHandler(&lock, misc_name) != dm_users_.end()) {
- LOG(ERROR) << "Handler already exists: " << misc_name;
- return nullptr;
- }
- dm_users_.push_back(handler);
- }
- return handler;
-}
-
-bool UserSnapshotServer::StartHandler(const std::shared_ptr<HandlerThread>& handler) {
- if (handler->snapuserd()->IsAttached()) {
- LOG(ERROR) << "Handler already attached";
- return false;
- }
-
- handler->snapuserd()->AttachControlDevice();
-
- handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler));
- return true;
-}
-
-bool UserSnapshotServer::StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
- const std::shared_ptr<HandlerThread>& handler) {
- CHECK(proof_of_lock);
-
- if (!handler->snapuserd()->IsAttached()) {
- LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
- return false;
- }
-
- handler->snapuserd()->MonitorMerge();
-
- if (!is_merge_monitor_started_.has_value()) {
- std::thread(&UserSnapshotServer::MonitorMerge, this).detach();
- is_merge_monitor_started_ = true;
- }
-
- merge_handlers_.push(handler);
- WakeupMonitorMergeThread();
- return true;
-}
-
-auto UserSnapshotServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
- const std::string& misc_name) -> HandlerList::iterator {
- CHECK(proof_of_lock);
-
- for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
- if ((*iter)->misc_name() == misc_name) {
- return iter;
- }
- }
- return dm_users_.end();
-}
-
-void UserSnapshotServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
- CHECK(proof_of_lock);
-
- for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
- if (!(*iter)->ThreadTerminated()) {
- (*iter)->snapuserd()->NotifyIOTerminated();
- }
- }
-}
-
-std::string UserSnapshotServer::GetMergeStatus(const std::shared_ptr<HandlerThread>& handler) {
- return handler->snapuserd()->GetMergeStatus();
-}
-
-double UserSnapshotServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
- CHECK(proof_of_lock);
- double percentage = 0.0;
- int n = 0;
-
- for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
- auto& th = (*iter)->thread();
- if (th.joinable()) {
- // Merge percentage by individual partitions wherein merge is still
- // in-progress
- percentage += (*iter)->snapuserd()->GetMergePercentage();
- n += 1;
- }
- }
-
- // Calculate final merge including those partitions where merge was already
- // completed - num_partitions_merge_complete_ will track them when each
- // thread exists in RunThread.
- int total_partitions = n + num_partitions_merge_complete_;
-
- if (total_partitions) {
- percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
- }
-
- LOG(DEBUG) << "Merge %: " << percentage
- << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
- << " total_partitions: " << total_partitions << " n: " << n;
- return percentage;
-}
-
-bool UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) {
- std::shared_ptr<HandlerThread> handler;
- {
- std::lock_guard<std::mutex> lock(lock_);
-
- auto iter = FindHandler(&lock, misc_name);
- if (iter == dm_users_.end()) {
- // Client already deleted.
- return true;
- }
- handler = std::move(*iter);
- dm_users_.erase(iter);
- }
-
- auto& th = handler->thread();
- if (th.joinable()) {
- th.join();
- }
- return true;
-}
-
-void UserSnapshotServer::WakeupMonitorMergeThread() {
- uint64_t notify = 1;
- ssize_t rc = TEMP_FAILURE_RETRY(write(monitor_merge_event_fd_.get(), ¬ify, sizeof(notify)));
- if (rc < 0) {
- PLOG(FATAL) << "failed to notify monitor merge thread";
- }
-}
-
-void UserSnapshotServer::MonitorMerge() {
- while (!stop_monitor_merge_thread_) {
- uint64_t testVal;
- ssize_t ret =
- TEMP_FAILURE_RETRY(read(monitor_merge_event_fd_.get(), &testVal, sizeof(testVal)));
- if (ret == -1) {
- PLOG(FATAL) << "Failed to read from eventfd";
- } else if (ret == 0) {
- LOG(FATAL) << "Hit EOF on eventfd";
- }
-
- LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_;
- {
- std::lock_guard<std::mutex> lock(lock_);
- while (active_merge_threads_ < kMaxMergeThreads && merge_handlers_.size() > 0) {
- auto handler = merge_handlers_.front();
- merge_handlers_.pop();
-
- if (!handler->snapuserd()) {
- LOG(INFO) << "MonitorMerge: skipping deleted handler: " << handler->misc_name();
- continue;
- }
-
- LOG(INFO) << "Starting merge for partition: "
- << handler->snapuserd()->GetMiscName();
- handler->snapuserd()->InitiateMerge();
- active_merge_threads_ += 1;
- }
- }
- }
-
- LOG(INFO) << "Exiting MonitorMerge: size: " << merge_handlers_.size();
+ return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge,
+ num_worker_threads, io_uring_enabled_, perform_verification);
}
bool UserSnapshotServer::WaitForSocket() {
- auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
+ auto scope_guard =
+ android::base::make_scope_guard([this]() -> void { handlers_->JoinAllThreads(); });
auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
@@ -781,21 +464,8 @@
return true;
}
-bool UserSnapshotServer::UpdateVerification(std::lock_guard<std::mutex>* proof_of_lock) {
- CHECK(proof_of_lock);
-
- bool status = true;
- for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
- auto& th = (*iter)->thread();
- if (th.joinable() && status) {
- status = (*iter)->snapuserd()->CheckPartitionVerification() && status;
- } else {
- // return immediately if there is a failure
- return false;
- }
- }
-
- return status;
+bool UserSnapshotServer::StartHandler(const std::string& misc_name) {
+ return handlers_->StartHandler(misc_name);
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index 12c3903..988c01a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -31,6 +31,7 @@
#include <vector>
#include <android-base/unique_fd.h>
+#include "handler_manager.h"
#include "snapuserd_core.h"
namespace android {
@@ -39,48 +40,6 @@
static constexpr uint32_t kMaxPacketSize = 512;
static constexpr uint8_t kMaxMergeThreads = 2;
-enum class DaemonOps {
- INIT,
- START,
- QUERY,
- STOP,
- DELETE,
- DETACH,
- SUPPORTS,
- INITIATE,
- PERCENTAGE,
- GETSTATUS,
- UPDATE_VERIFY,
- INVALID,
-};
-
-class HandlerThread {
- public:
- explicit HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd);
-
- void FreeResources() {
- // Each worker thread holds a reference to snapuserd.
- // Clear them so that all the resources
- // held by snapuserd is released
- if (snapuserd_) {
- snapuserd_->FreeResources();
- snapuserd_ = nullptr;
- }
- }
- const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
- std::thread& thread() { return thread_; }
-
- const std::string& misc_name() const { return misc_name_; }
- bool ThreadTerminated() { return thread_terminated_; }
- void SetThreadTerminated() { thread_terminated_ = true; }
-
- private:
- std::thread thread_;
- std::shared_ptr<SnapshotHandler> snapuserd_;
- std::string misc_name_;
- bool thread_terminated_ = false;
-};
-
class UserSnapshotServer {
private:
android::base::unique_fd sockfd_;
@@ -88,21 +47,12 @@
volatile bool received_socket_signal_ = false;
std::vector<struct pollfd> watched_fds_;
bool is_socket_present_ = false;
- int num_partitions_merge_complete_ = 0;
- int active_merge_threads_ = 0;
- bool stop_monitor_merge_thread_ = false;
bool is_server_running_ = false;
bool io_uring_enabled_ = false;
- std::optional<bool> is_merge_monitor_started_;
-
- android::base::unique_fd monitor_merge_event_fd_;
+ std::unique_ptr<ISnapshotHandlerManager> handlers_;
std::mutex lock_;
- using HandlerList = std::vector<std::shared_ptr<HandlerThread>>;
- HandlerList dm_users_;
- std::queue<std::shared_ptr<HandlerThread>> merge_handlers_;
-
void AddWatchedFd(android::base::borrowed_fd fd, int events);
void AcceptClient();
bool HandleClient(android::base::borrowed_fd fd, int revents);
@@ -111,28 +61,14 @@
bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
void ShutdownThreads();
- bool RemoveAndJoinHandler(const std::string& control_device);
- DaemonOps Resolveop(std::string& input);
std::string GetDaemonStatus();
void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
bool IsTerminating() { return terminating_; }
- void RunThread(std::shared_ptr<HandlerThread> handler);
- void MonitorMerge();
-
void JoinAllThreads();
bool StartWithSocket(bool start_listening);
- // Find a HandlerThread within a lock.
- HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
- const std::string& misc_name);
-
- double GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock);
- void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
-
- bool UpdateVerification(std::lock_guard<std::mutex>* proof_of_lock);
-
public:
UserSnapshotServer();
~UserSnapshotServer();
@@ -147,12 +83,8 @@
const std::string& cow_device_path,
const std::string& backing_device,
const std::string& base_path_merge);
- bool StartHandler(const std::shared_ptr<HandlerThread>& handler);
- bool StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
- const std::shared_ptr<HandlerThread>& handler);
- std::string GetMergeStatus(const std::shared_ptr<HandlerThread>& handler);
+ bool StartHandler(const std::string& misc_name);
- void WakeupMonitorMergeThread();
void SetTerminating() { terminating_ = true; }
void ReceivedSocketSignal() { received_socket_signal_ = true; }
void SetServerRunning() { is_server_running_ = true; }
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index a0527e8..26af13b 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -620,6 +620,18 @@
kick_animation(&batt_anim_);
}
health_info_ = health_info;
+
+ if (property_get_bool("ro.charger_mode_autoboot", false)) {
+ if (health_info_.battery_level >= boot_min_cap_) {
+ if (property_get_bool("ro.enable_boot_charger_mode", false)) {
+ LOGW("booting from charger mode\n");
+ property_set("sys.boot_from_charger_mode", "1");
+ } else {
+ LOGW("Battery SOC = %d%%, Automatically rebooting\n", health_info_.battery_level);
+ reboot(RB_AUTOBOOT);
+ }
+ }
+ }
}
int Charger::OnPrepareToWait(void) {
diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp
index 525a880..1d94a96 100644
--- a/libmodprobe/Android.bp
+++ b/libmodprobe/Android.bp
@@ -10,6 +10,7 @@
vendor_available: true,
ramdisk_available: true,
recovery_available: true,
+ host_supported: true,
srcs: [
"libmodprobe.cpp",
"libmodprobe_ext.cpp",
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index f7af08b..38eb92f 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -446,14 +446,9 @@
static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries,
int* max_processes) {
- if (uid < 0) {
- LOG(ERROR) << __func__ << ": invalid UID " << uid;
- return -1;
- }
- if (initialPid <= 0) {
- LOG(ERROR) << __func__ << ": invalid PID " << initialPid;
- return -1;
- }
+ CHECK_GE(uid, 0);
+ CHECK_GT(initialPid, 0);
+
std::string hierarchy_root_path;
if (CgroupsAvailable()) {
CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
@@ -590,7 +585,8 @@
}
int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
- std::string cgroup;
+ CHECK_GE(uid, 0);
+ CHECK_GT(initialPid, 0);
if (memControl && !UsePerAppMemcg()) {
PLOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
@@ -608,6 +604,7 @@
}
}
+ std::string cgroup;
CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
return createProcessGroupInternal(uid, initialPid, cgroup, true);
}
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 3e4393d..d013ec8 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -1,13 +1,6 @@
{
"Cgroups": [
{
- "Controller": "blkio",
- "Path": "/dev/blkio",
- "Mode": "0775",
- "UID": "system",
- "GID": "system"
- },
- {
"Controller": "cpu",
"Path": "/dev/cpuctl",
"Mode": "0755",
@@ -39,6 +32,12 @@
{
"Controller": "freezer",
"Path": "."
+ },
+ {
+ "Controller": "io",
+ "Path": ".",
+ "NeedsActivation": true,
+ "Optional": true
}
]
}
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index e44d3bf..12f7b44 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -457,14 +457,6 @@
"Name": "LowIoPriority",
"Actions": [
{
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "blkio",
- "Path": "background"
- }
- },
- {
"Name": "SetAttribute",
"Params":
{
@@ -497,14 +489,6 @@
"Name": "NormalIoPriority",
"Actions": [
{
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "blkio",
- "Path": ""
- }
- },
- {
"Name": "SetAttribute",
"Params":
{
@@ -537,14 +521,6 @@
"Name": "HighIoPriority",
"Actions": [
{
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "blkio",
- "Path": ""
- }
- },
- {
"Name": "SetAttribute",
"Params":
{
@@ -577,14 +553,6 @@
"Name": "MaxIoPriority",
"Actions": [
{
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "blkio",
- "Path": ""
- }
- },
- {
"Name": "SetAttribute",
"Params":
{
diff --git a/libsparse/backed_block.cpp b/libsparse/backed_block.cpp
index 6229e7c..a0d1cde 100644
--- a/libsparse/backed_block.cpp
+++ b/libsparse/backed_block.cpp
@@ -315,6 +315,10 @@
bb->len = len;
bb->type = BACKED_BLOCK_FILE;
bb->file.filename = strdup(filename);
+ if (!bb->file.filename) {
+ free(bb);
+ return -ENOMEM;
+ }
bb->file.offset = offset;
bb->next = nullptr;
@@ -359,14 +363,17 @@
new_bb->len = bb->len - max_len;
new_bb->block = bb->block + max_len / bbl->block_size;
new_bb->next = bb->next;
- bb->next = new_bb;
- bb->len = max_len;
switch (bb->type) {
case BACKED_BLOCK_DATA:
new_bb->data.data = (char*)bb->data.data + max_len;
break;
case BACKED_BLOCK_FILE:
+ new_bb->file.filename = strdup(bb->file.filename);
+ if (!new_bb->file.filename) {
+ free(new_bb);
+ return -ENOMEM;
+ }
new_bb->file.offset += max_len;
break;
case BACKED_BLOCK_FD:
@@ -376,5 +383,7 @@
break;
}
+ bb->next = new_bb;
+ bb->len = max_len;
return 0;
}
diff --git a/libsparse/output_file.cpp b/libsparse/output_file.cpp
index cb5d730..08312e4 100644
--- a/libsparse/output_file.cpp
+++ b/libsparse/output_file.cpp
@@ -58,6 +58,8 @@
#define container_of(inner, outer_t, elem) ((outer_t*)((char*)(inner)-offsetof(outer_t, elem)))
+static constexpr size_t kMaxMmapSize = 256 * 1024 * 1024;
+
struct output_file_ops {
int (*open)(struct output_file*, int fd);
int (*skip)(struct output_file*, int64_t);
@@ -71,6 +73,7 @@
int (*write_fill_chunk)(struct output_file* out, uint64_t len, uint32_t fill_val);
int (*write_skip_chunk)(struct output_file* out, uint64_t len);
int (*write_end_chunk)(struct output_file* out);
+ int (*write_fd_chunk)(struct output_file* out, uint64_t len, int fd, int64_t offset);
};
struct output_file {
@@ -318,6 +321,26 @@
return 0;
}
+template <typename T>
+static bool write_fd_chunk_range(int fd, int64_t offset, uint64_t len, T callback) {
+ uint64_t bytes_written = 0;
+ int64_t current_offset = offset;
+ while (bytes_written < len) {
+ size_t mmap_size = std::min(static_cast<uint64_t>(kMaxMmapSize), len - bytes_written);
+ auto m = android::base::MappedFile::FromFd(fd, current_offset, mmap_size, PROT_READ);
+ if (!m) {
+ error("failed to mmap region of length %zu", mmap_size);
+ return false;
+ }
+ if (!callback(m->data(), mmap_size)) {
+ return false;
+ }
+ bytes_written += mmap_size;
+ current_offset += mmap_size;
+ }
+ return true;
+}
+
static int write_sparse_skip_chunk(struct output_file* out, uint64_t skip_len) {
chunk_header_t chunk_header;
int ret;
@@ -424,6 +447,61 @@
return 0;
}
+static int write_sparse_fd_chunk(struct output_file* out, uint64_t len, int fd, int64_t offset) {
+ chunk_header_t chunk_header;
+ uint64_t rnd_up_len, zero_len;
+ int ret;
+
+ /* Round up the data length to a multiple of the block size */
+ rnd_up_len = ALIGN(len, out->block_size);
+ zero_len = rnd_up_len - len;
+
+ /* Finally we can safely emit a chunk of data */
+ chunk_header.chunk_type = CHUNK_TYPE_RAW;
+ chunk_header.reserved1 = 0;
+ chunk_header.chunk_sz = rnd_up_len / out->block_size;
+ chunk_header.total_sz = CHUNK_HEADER_LEN + rnd_up_len;
+ ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
+
+ if (ret < 0) return -1;
+ bool ok = write_fd_chunk_range(fd, offset, len, [&ret, out](char* data, size_t size) -> bool {
+ ret = out->ops->write(out, data, size);
+ if (ret < 0) return false;
+ if (out->use_crc) {
+ out->crc32 = sparse_crc32(out->crc32, data, size);
+ }
+ return true;
+ });
+ if (!ok) return -1;
+ if (zero_len) {
+ uint64_t len = zero_len;
+ uint64_t write_len;
+ while (len) {
+ write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
+ ret = out->ops->write(out, out->zero_buf, write_len);
+ if (ret < 0) {
+ return ret;
+ }
+ len -= write_len;
+ }
+
+ if (out->use_crc) {
+ uint64_t len = zero_len;
+ uint64_t write_len;
+ while (len) {
+ write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
+ out->crc32 = sparse_crc32(out->crc32, out->zero_buf, write_len);
+ len -= write_len;
+ }
+ }
+ }
+
+ out->cur_out_ptr += rnd_up_len;
+ out->chunk_cnt++;
+
+ return 0;
+}
+
int write_sparse_end_chunk(struct output_file* out) {
chunk_header_t chunk_header;
int ret;
@@ -454,6 +532,7 @@
.write_fill_chunk = write_sparse_fill_chunk,
.write_skip_chunk = write_sparse_skip_chunk,
.write_end_chunk = write_sparse_end_chunk,
+ .write_fd_chunk = write_sparse_fd_chunk,
};
static int write_normal_data_chunk(struct output_file* out, uint64_t len, void* data) {
@@ -495,6 +574,23 @@
return 0;
}
+static int write_normal_fd_chunk(struct output_file* out, uint64_t len, int fd, int64_t offset) {
+ int ret;
+ uint64_t rnd_up_len = ALIGN(len, out->block_size);
+
+ bool ok = write_fd_chunk_range(fd, offset, len, [&ret, out](char* data, size_t size) -> bool {
+ ret = out->ops->write(out, data, size);
+ return ret >= 0;
+ });
+ if (!ok) return ret;
+
+ if (rnd_up_len > len) {
+ ret = out->ops->skip(out, rnd_up_len - len);
+ }
+
+ return ret;
+}
+
static int write_normal_skip_chunk(struct output_file* out, uint64_t len) {
return out->ops->skip(out, len);
}
@@ -508,6 +604,7 @@
.write_fill_chunk = write_normal_fill_chunk,
.write_skip_chunk = write_normal_skip_chunk,
.write_end_chunk = write_normal_end_chunk,
+ .write_fd_chunk = write_normal_fd_chunk,
};
void output_file_close(struct output_file* out) {
@@ -670,10 +767,7 @@
}
int write_fd_chunk(struct output_file* out, uint64_t len, int fd, int64_t offset) {
- auto m = android::base::MappedFile::FromFd(fd, offset, len, PROT_READ);
- if (!m) return -errno;
-
- return out->sparse_ops->write_data_chunk(out, m->size(), m->data());
+ return out->sparse_ops->write_fd_chunk(out, len, fd, offset);
}
/* Write a contiguous region of data blocks from a file */
diff --git a/libsparse/sparse.cpp b/libsparse/sparse.cpp
index 396e7eb..ca7e5fe 100644
--- a/libsparse/sparse.cpp
+++ b/libsparse/sparse.cpp
@@ -260,8 +260,8 @@
return s->block_size;
}
-static struct backed_block* move_chunks_up_to_len(struct sparse_file* from, struct sparse_file* to,
- unsigned int len) {
+static int move_chunks_up_to_len(struct sparse_file* from, struct sparse_file* to, unsigned int len,
+ backed_block** out_bb) {
int64_t count = 0;
struct output_file* out_counter;
struct backed_block* last_bb = nullptr;
@@ -282,7 +282,7 @@
out_counter = output_file_open_callback(out_counter_write, &count, to->block_size, to->len, false,
true, 0, false);
if (!out_counter) {
- return nullptr;
+ return -1;
}
for (bb = start; bb; bb = backed_block_iter_next(bb)) {
@@ -319,7 +319,8 @@
out:
output_file_close(out_counter);
- return bb;
+ *out_bb = bb;
+ return 0;
}
int sparse_file_resparse(struct sparse_file* in_s, unsigned int max_len, struct sparse_file** out_s,
@@ -337,7 +338,15 @@
do {
s = sparse_file_new(in_s->block_size, in_s->len);
- bb = move_chunks_up_to_len(in_s, s, max_len);
+ if (move_chunks_up_to_len(in_s, s, max_len, &bb) < 0) {
+ sparse_file_destroy(s);
+ for (int i = 0; i < c && i < out_s_count; i++) {
+ sparse_file_destroy(out_s[i]);
+ out_s[i] = nullptr;
+ }
+ sparse_file_destroy(tmp);
+ return -1;
+ }
if (c < out_s_count) {
out_s[c] = s;
diff --git a/libstats/expresslog/Counter.cpp b/libstats/expresslog/Counter.cpp
index bee1303..9382041 100644
--- a/libstats/expresslog/Counter.cpp
+++ b/libstats/expresslog/Counter.cpp
@@ -28,5 +28,10 @@
stats_write(EXPRESS_EVENT_REPORTED, metricIdHash, amount);
}
+void Counter::logIncrementWithUid(const char* metricName, int32_t uid, int64_t amount) {
+ const int64_t metricIdHash = farmhash::Fingerprint64(metricName, strlen(metricName));
+ stats_write(EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
+}
+
} // namespace expresslog
} // namespace android
diff --git a/libstats/expresslog/Histogram.cpp b/libstats/expresslog/Histogram.cpp
index cb29a00..50bb343 100644
--- a/libstats/expresslog/Histogram.cpp
+++ b/libstats/expresslog/Histogram.cpp
@@ -71,5 +71,10 @@
stats_write(EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, /*count*/ 1, binIndex);
}
+void Histogram::logSampleWithUid(int32_t uid, float sample) const {
+ const int binIndex = mBinOptions->getBinForSample(sample);
+ stats_write(EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, /*count*/ 1, binIndex, uid);
+}
+
} // namespace expresslog
} // namespace android
diff --git a/libstats/expresslog/include/Counter.h b/libstats/expresslog/include/Counter.h
index 57328f5..8d0ab6a 100644
--- a/libstats/expresslog/include/Counter.h
+++ b/libstats/expresslog/include/Counter.h
@@ -24,6 +24,8 @@
class Counter final {
public:
static void logIncrement(const char* metricId, int64_t amount = 1);
+
+ static void logIncrementWithUid(const char* metricId, int32_t uid, int64_t amount = 1);
};
} // namespace expresslog
diff --git a/libstats/expresslog/include/Histogram.h b/libstats/expresslog/include/Histogram.h
index 8fdc1b6..49aee3d 100644
--- a/libstats/expresslog/include/Histogram.h
+++ b/libstats/expresslog/include/Histogram.h
@@ -72,6 +72,11 @@
*/
void logSample(float sample) const;
+ /**
+ * Logs increment sample count for automatically calculated bin with uid
+ */
+ void logSampleWithUid(int32_t uid, float sample) const;
+
private:
const int64_t mMetricIdHash;
const std::shared_ptr<BinOptions> mBinOptions;
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 68191bb..b165778 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -221,26 +221,6 @@
write /dev/stune/nnapi-hal/schedtune.boost 1
write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
- # Create blkio group and apply initial settings.
- # This feature needs kernel to support it, and the
- # device's init.rc must actually set the correct values.
- mkdir /dev/blkio/background
- chown system system /dev/blkio
- chown system system /dev/blkio/background
- chown system system /dev/blkio/tasks
- chown system system /dev/blkio/background/tasks
- chown system system /dev/blkio/cgroup.procs
- chown system system /dev/blkio/background/cgroup.procs
- chmod 0664 /dev/blkio/tasks
- chmod 0664 /dev/blkio/background/tasks
- chmod 0664 /dev/blkio/cgroup.procs
- chmod 0664 /dev/blkio/background/cgroup.procs
- write /dev/blkio/blkio.weight 1000
- write /dev/blkio/background/blkio.weight 200
- write /dev/blkio/background/blkio.bfq.weight 10
- write /dev/blkio/blkio.group_idle 0
- write /dev/blkio/background/blkio.group_idle 0
-
restorecon_recursive /mnt
mount configfs none /config nodev noexec nosuid
diff --git a/trusty/stats/test/Android.bp b/trusty/stats/test/Android.bp
new file mode 100644
index 0000000..6b2bce9
--- /dev/null
+++ b/trusty/stats/test/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 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"],
+}
+
+cc_test {
+ name: "trusty_stats_test",
+ vendor: true,
+ srcs: [
+ "stats_test.cpp",
+ ],
+ static_libs: [
+ "libgmock",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libtrusty",
+ "libbinder",
+ "libbinder_trusty",
+ "libutils",
+
+ // AIDL interface deps versions, please refer to below link
+ // https://source.android.com/docs/core/architecture/aidl/stable-aidl#module-naming-rules
+ "android.frameworks.stats-V1-cpp",
+ "android.trusty.stats.nw.setter-cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ require_root: true,
+ proprietary: true,
+}
diff --git a/trusty/stats/test/README.md b/trusty/stats/test/README.md
new file mode 100644
index 0000000..45e6af8
--- /dev/null
+++ b/trusty/stats/test/README.md
@@ -0,0 +1,97 @@
+# Development Notes
+
+* First get [repo_pull.py and gerrit.py](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/) from aosp.
+
+* Although this repo is not currently in Trusty’s manifest, it’s sufficient to copy these two python scripts to the root of the Trusty project and run them from there. Make sure to follow the [repo_pull installation](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/#installation) steps if necessary.
+
+## Build
+
+Build Android:
+
+```sh
+source build/envsetup.sh
+lunch qemu_trusty_arm64-userdebug
+m
+```
+
+Build Trusty:
+
+```sh
+./trusty/vendor/google/aosp/scripts/build.py qemu-generic-arm64-test-debug --skip-tests 2>stderr.log
+```
+
+## Trusty PORT_TEST
+
+On QEmu:
+
+```sh
+./build-root/build-qemu-generic-arm64-test-debug/run --headless --boot-test "com.android.trusty.stats.test" --verbose
+```
+
+On device: (Build for your device's debug target on both Adroid and Trusty)
+
+```sh
+/vendor/bin/trusty-ut-ctrl -D /dev/trusty-ipc-dev0 "com.android.trusty.stats.test"
+```
+
+On device, in a loop:
+
+```sh
+cat << 'EOF' > metrics.sh
+#!/system/bin/sh
+TIMES=${1:-0}
+X=0
+while [ "$TIMES" -eq 0 -o "$TIMES" -gt "$X" ]
+do
+ echo "######################## stats.test $X " $(( X++ ));
+ /vendor/bin/trusty-ut-ctrl -D /dev/trusty-ipc-dev0 "com.android.trusty.stats.test"
+done
+EOF
+
+adb wait-for-device
+adb push metrics.sh /data/user/test/metrics.sh
+adb shell sh /data/user/test/metrics.sh
+```
+
+## Android Native Test
+
+On QEmu:
+
+```sh
+./build-root/build-qemu-generic-arm64-test-debug/run --headless --android $ANDROID_PROJECT_ROOT --shell-command "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test" --verbose
+```
+
+On device: (Build for your device's debug target on both Adroid and Trusty)
+
+```sh
+/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+```
+
+On device, in a loop:
+
+```sh
+cat << 'EOF' > metrics-nw.sh
+#!/system/bin/sh
+TIMES=${1:-0}
+X=0
+while [ "$TIMES" -eq 0 -o "$TIMES" -gt "$X" ]
+do
+ echo "######################## stats.test $X " $(( X++ ));
+ /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+done
+EOF
+
+adb wait-for-device
+adb push metrics.sh /data/user/test/metrics-nw.sh
+adb shell sh /data/user/test/metrics-nw.sh
+```
+
+## Trusty Backtrace analysis
+
+
+```
+$ export A2L=./prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-addr2line
+$ export OD=./prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-objdump
+$ $OD -d -C build-root/build-qemu-generic-arm64-test-debug/user_tasks/trusty/user/base/app/metrics/metrics.syms.elf > objdump.lst
+$ $A2L -e build-root/build-qemu-generic-arm64-test-debug/user_tasks/trusty/user/base/app/metrics/metrics.syms.elf 0xe5104
+```
diff --git a/trusty/stats/test/stats_test.cpp b/trusty/stats/test/stats_test.cpp
new file mode 100644
index 0000000..1edddeb
--- /dev/null
+++ b/trusty/stats/test/stats_test.cpp
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <errno.h>
+#include <getopt.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <condition_variable>
+#include <cstddef>
+#include <mutex>
+#include <queue>
+
+#include <android-base/expected.h>
+#include <android-base/logging.h>
+#include <android/frameworks/stats/BnStats.h>
+#include <android/frameworks/stats/IStats.h>
+#include <android/trusty/stats/nw/setter/IStatsSetter.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcSession.h>
+#include <binder/RpcTransportRaw.h>
+#include <binder/RpcTransportTipcAndroid.h>
+#include <binder/RpcTrusty.h>
+#include <trusty/tipc.h>
+
+/** DOC:
+ * ./build-root/build-qemu-generic-arm64-test-debug/run \
+ * --android $ANDROID_PROJECT_ROOT \
+ * --headless --shell-command \
+ * "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test"
+ *
+ * adb -s emulator-5554 shell \
+ * /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+ */
+using ::android::base::unique_fd;
+using ::android::binder::Status;
+using ::android::frameworks::stats::BnStats;
+using ::android::frameworks::stats::IStats;
+using ::android::frameworks::stats::VendorAtom;
+using ::android::frameworks::stats::VendorAtomValue;
+using ::android::trusty::stats::nw::setter::IStatsSetter;
+
+constexpr const char kTrustyDefaultDeviceName[] = "/dev/trusty-ipc-dev0";
+constexpr const char kTrustyStatsSetterTest[] =
+ "com.android.frameworks.stats.trusty.test.relayer.istats_setter";
+constexpr const char kTrustyStatsSetterMetrics[] =
+ "com.android.frameworks.stats.trusty.metrics.istats_setter";
+constexpr const char kTrustyStatsPortTest[] = "com.android.trusty.stats.test";
+constexpr const char kTrustyCrashPortTest[] = "com.android.trusty.crashtest";
+constexpr const char kTrustyCrasherUuid[] = "7ee4dddc-177a-420a-96ea-5d413d88228e:crasher";
+
+enum TrustyAtoms : int32_t {
+ TrustyAppCrashed = 100072,
+ TrustyError = 100145,
+ TrustyStorageError = 100146
+};
+
+enum TestMsgHeader : int32_t {
+ TEST_PASSED = 0,
+ TEST_FAILED = 1,
+ TEST_MESSAGE = 2,
+};
+
+namespace android {
+namespace trusty {
+namespace stats {
+
+class Stats : public BnStats {
+ public:
+ Stats() : BnStats() {}
+
+ Status reportVendorAtom(const VendorAtom& vendorAtom) override {
+ const char* atomIdStr = vendorAtomStr(vendorAtom.atomId);
+ ALOGD("Vendor atom reported of type: %s\n", atomIdStr);
+ std::lock_guard lock(mLock);
+ mQueueVendorAtom.push(vendorAtom);
+ mCondVar.notify_one();
+ return Status::ok();
+ }
+
+ status_t getVendorAtom(VendorAtom* pVendorAtom, int64_t waitForMs) {
+ std::unique_lock lock(mLock);
+ while (mQueueVendorAtom.empty()) {
+ auto rc = mCondVar.wait_for(lock, std::chrono::milliseconds(waitForMs));
+ if (rc == std::cv_status::timeout) {
+ return TIMED_OUT;
+ }
+ }
+ *pVendorAtom = mQueueVendorAtom.front();
+ mQueueVendorAtom.pop();
+ return NO_ERROR;
+ }
+
+ private:
+ const char* vendorAtomStr(int32_t atomId) {
+ switch (atomId) {
+ case TrustyAtoms::TrustyAppCrashed:
+ return "TrustyAtoms::TrustyAppCrashed";
+ case TrustyAtoms::TrustyError:
+ return "TrustyAtoms::TrustyError";
+ case TrustyAtoms::TrustyStorageError:
+ return "TrustyAtoms::TrustyStorageError";
+ default:
+ return "unknown TrustyAtoms type";
+ }
+ }
+ std::mutex mLock;
+ std::condition_variable mCondVar;
+ std::queue<VendorAtom> mQueueVendorAtom;
+};
+
+class TrustyStatsTestBase : public ::testing::Test {
+ protected:
+ TrustyStatsTestBase(std::string&& portNameStatsSetter, std::string&& portNamePortTest)
+ : mPortTestFd(-1),
+ mPortNameStatsSetter(std::move(portNameStatsSetter)),
+ mPortNamePortTest(std::move(portNamePortTest)) {}
+
+ void SetUp() override {
+ // Commenting out the server portion because we do not have any direct
+ // incoming call Calls from TA are currently being handled on the mSession's
+ // extra thread. android::sp<::android::RpcServer> server =
+ // ::android::RpcServer::make(::android::RpcTransportCtxFactoryRaw::make());
+
+ mStats = android::sp<Stats>::make();
+ // Increasing number of incoming threads on mSession to be able to receive
+ // callbacks
+ auto session_initializer = [](sp<RpcSession>& session) {
+ session->setMaxIncomingThreads(1);
+ };
+
+ ASSERT_FALSE(mSession);
+ mSession = RpcTrustyConnectWithSessionInitializer(
+ kTrustyDefaultDeviceName, mPortNameStatsSetter.c_str(), session_initializer);
+ ASSERT_TRUE(mSession);
+
+ auto root = mSession->getRootObject();
+ ASSERT_TRUE(root);
+ auto statsSetter = IStatsSetter::asInterface(root);
+ ASSERT_TRUE(statsSetter);
+ statsSetter->setInterface(mStats);
+ }
+ void TearDown() override {
+ // close connection to unitest app
+ if (mPortTestFd != -1) {
+ tipc_close(mPortTestFd);
+ }
+ mPortTestFd = -1;
+
+ if (mSession) {
+ // shutdownAndWait here races with sending out the DecStrong
+ // messages after reportVendorAtom returns, so we delay it a little
+ // bit to give the messages time to go out over the transport
+ usleep(50000);
+ ASSERT_TRUE(mSession->shutdownAndWait(true));
+ }
+ mSession.clear();
+ mStats.clear();
+ }
+ void StartPortTest() {
+ // connect to unitest app
+ mPortTestFd = tipc_connect(kTrustyDefaultDeviceName, mPortNamePortTest.c_str());
+ if (mPortTestFd < 0) {
+ ALOGE("Failed to connect to '%s' app: %s\n", kTrustyStatsPortTest,
+ strerror(-mPortTestFd));
+ }
+ ASSERT_GT(mPortTestFd, 0);
+ }
+ void WaitPortTestDone() {
+ // wait for test to complete
+ char rxBuf[1024];
+ const char prolog[] = "Trusty PORT_TEST:";
+ strncpy(rxBuf, prolog, sizeof(prolog) - 1);
+ char* pRxBuf = rxBuf + sizeof(prolog) - 1;
+ size_t remainingBufSize = sizeof(rxBuf) - sizeof(prolog) - 1;
+
+ ASSERT_NE(mPortTestFd, -1);
+ for (;;) {
+ int rc = read(mPortTestFd, pRxBuf, remainingBufSize);
+ ASSERT_GT(rc, 0);
+ ASSERT_LT(rc, (int)remainingBufSize);
+ if (pRxBuf[0] == TEST_PASSED) {
+ break;
+ } else if (pRxBuf[0] == TEST_FAILED) {
+ break;
+ } else if (pRxBuf[0] == TEST_MESSAGE) {
+ pRxBuf[0] = ' ';
+ write(STDOUT_FILENO, rxBuf, rc + sizeof(prolog) - 1);
+ } else {
+ ALOGE("Bad message header: %d\n", rxBuf[0]);
+ break;
+ }
+ }
+ ASSERT_EQ(pRxBuf[0], TEST_PASSED);
+ }
+
+ android::sp<Stats> mStats;
+
+ private:
+ android::sp<RpcSession> mSession;
+ int mPortTestFd;
+ std::string mPortNameStatsSetter;
+ std::string mPortNamePortTest;
+};
+
+class TrustyStatsTest : public TrustyStatsTestBase {
+ protected:
+ TrustyStatsTest() : TrustyStatsTestBase(kTrustyStatsSetterTest, kTrustyStatsPortTest) {}
+};
+
+class TrustyMetricsCrashTest : public TrustyStatsTestBase {
+ protected:
+ TrustyMetricsCrashTest()
+ : TrustyStatsTestBase(kTrustyStatsSetterMetrics, kTrustyCrashPortTest) {}
+};
+
+TEST_F(TrustyStatsTest, CheckAtoms) {
+ int atomAppCrashedCnt = 0;
+ int atomStorageErrorCnt = 0;
+ int atomTrustyErrorCnt = 0;
+ uint64_t blockForMs = 500;
+ StartPortTest();
+ WaitPortTestDone();
+ for (;;) {
+ VendorAtom vendorAtom;
+ auto status = mStats->getVendorAtom(&vendorAtom, blockForMs);
+ ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT));
+ if (status == TIMED_OUT) {
+ // No more atoms
+ break;
+ }
+
+ ASSERT_THAT(vendorAtom.atomId,
+ ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed),
+ ::testing::Eq(TrustyAtoms::TrustyError),
+ ::testing::Eq(TrustyAtoms::TrustyStorageError)));
+ ASSERT_STREQ(String8(vendorAtom.reverseDomainName), "google.android.trusty");
+ switch (vendorAtom.atomId) {
+ case TrustyAtoms::TrustyAppCrashed:
+ ++atomAppCrashedCnt;
+ ASSERT_STREQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()),
+ "5247d19b-cf09-4272-a450-3ef20dbefc14");
+ break;
+ case TrustyAtoms::TrustyStorageError:
+ ++atomStorageErrorCnt;
+ ASSERT_EQ(vendorAtom.values[0].get<VendorAtomValue::intValue>(), 5);
+ ASSERT_STREQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()),
+ "5247d19b-cf09-4272-a450-3ef20dbefc14");
+ ASSERT_STREQ(String8(vendorAtom.values[2].get<VendorAtomValue::stringValue>()),
+ "5247d19b-cf09-4272-a450-3ef20dbefc14");
+ ASSERT_EQ(vendorAtom.values[3].get<VendorAtomValue::intValue>(), 1);
+ ASSERT_EQ(vendorAtom.values[4].get<VendorAtomValue::intValue>(), 3);
+ ASSERT_EQ(vendorAtom.values[5].get<VendorAtomValue::longValue>(),
+ 0x4BCDEFABBAFEDCBALL);
+ ASSERT_EQ(vendorAtom.values[6].get<VendorAtomValue::intValue>(), 4);
+ ASSERT_EQ(vendorAtom.values[7].get<VendorAtomValue::longValue>(), 1023);
+ break;
+ case TrustyAtoms::TrustyError:
+ ++atomTrustyErrorCnt;
+ break;
+ default:
+ FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId;
+ break;
+ }
+ };
+ ASSERT_EQ(atomAppCrashedCnt, 1);
+ ASSERT_EQ(atomStorageErrorCnt, 1);
+ ASSERT_EQ(atomTrustyErrorCnt, 0);
+}
+
+TEST_F(TrustyMetricsCrashTest, CheckTrustyCrashAtoms) {
+ const std::vector<uint32_t> kExpectedCrashReasonsArm64{
+ 0x00000001U, // exit_failure (twice)
+ 0x00000001U,
+ 0x92000004U, // read_null_ptr
+ 0xf200002aU, // brk_instruction
+ 0x92000004U, // read_bad_ptr
+ 0x92000044U, // crash_write_bad_ptr
+ 0x9200004fU, // crash_write_ro_ptr
+ 0x8200000fU, // crash_exec_rodata
+ 0x8200000fU, // crash_exec_data
+ };
+ const std::vector<uint32_t> kExpectedCrashReasonsArm32{
+ 0x00000001U, // exit_failure (twice)
+ 0x00000001U,
+ 0x20000007U, // read_null_ptr
+ 0x20000007U, // read_bad_ptr
+ 0x20000807U, // crash_write_bad_ptr
+ 0x2000080fU, // crash_write_ro_ptr
+ 0x3000000fU, // crash_exec_rodata
+ 0x3000000fU, // crash_exec_data
+ };
+
+ int expectedAtomCnt = 7;
+ int atomAppCrashedCnt = 0;
+ int atomStorageErrorCnt = 0;
+ int atomTrustyErrorCnt = 0;
+ std::vector<uint32_t> atomCrashReasons;
+ uint64_t blockForMs = 500;
+ StartPortTest();
+ WaitPortTestDone();
+ for (;;) {
+ VendorAtom vendorAtom;
+ auto status = mStats->getVendorAtom(&vendorAtom, blockForMs);
+ ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT));
+ if (status == TIMED_OUT) {
+ // No more atoms
+ break;
+ }
+
+ ASSERT_THAT(vendorAtom.atomId,
+ ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed),
+ ::testing::Eq(TrustyAtoms::TrustyError),
+ ::testing::Eq(TrustyAtoms::TrustyStorageError)));
+ ASSERT_STREQ(String8(vendorAtom.reverseDomainName), "google.android.trusty");
+
+ switch (vendorAtom.atomId) {
+ case TrustyAtoms::TrustyAppCrashed:
+ ++atomAppCrashedCnt;
+ ASSERT_STREQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()),
+ kTrustyCrasherUuid);
+ atomCrashReasons.push_back(vendorAtom.values[1].get<VendorAtomValue::intValue>());
+ break;
+ case TrustyAtoms::TrustyStorageError:
+ ++atomStorageErrorCnt;
+ break;
+ case TrustyAtoms::TrustyError:
+ ++atomTrustyErrorCnt;
+ ASSERT_STREQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()), "");
+ break;
+ default:
+ FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId;
+ }
+ }
+ ASSERT_GE(atomAppCrashedCnt, expectedAtomCnt - 1);
+ ASSERT_EQ(atomStorageErrorCnt, 0);
+ // There is one dropped event left over from Trusty boot,
+ // it may show up here
+ ASSERT_LE(atomTrustyErrorCnt, 1);
+ ASSERT_THAT(atomCrashReasons,
+ ::testing::AnyOf(kExpectedCrashReasonsArm64, kExpectedCrashReasonsArm32));
+};
+
+} // namespace stats
+} // namespace trusty
+} // namespace android