Merge "Add AID_UPROBESTATS and set up its config dir." into main
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 267571b..7d20995 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -32,9 +32,10 @@
recovery_available: true,
vendor_ramdisk_available: true,
apex_available: [
+ "com.android.runtime",
"com.android.virt",
"//apex_available:platform",
- ],
+ ],
}
cc_library_shared {
@@ -85,6 +86,7 @@
export_header_lib_headers: ["libdebuggerd_common_headers"],
export_include_dirs: ["tombstoned/include"],
+ apex_available: ["com.android.runtime"],
}
// Core implementation, linked into libdebuggerd_handler and the dynamic linker.
@@ -110,6 +112,9 @@
export_header_lib_headers: ["libdebuggerd_common_headers"],
export_include_dirs: ["include"],
+ apex_available: [
+ "com.android.runtime",
+ ],
}
// Implementation with a no-op fallback.
@@ -186,6 +191,41 @@
export_include_dirs: ["include"],
}
+cc_library {
+ name: "libdebuggerd_tombstone_proto_to_text",
+ defaults: ["debuggerd_defaults"],
+ ramdisk_available: true,
+ recovery_available: true,
+ vendor_ramdisk_available: true,
+
+ local_include_dirs: ["libdebuggerd/include"],
+ export_include_dirs: ["libdebuggerd/include"],
+
+ srcs: [
+ "libdebuggerd/tombstone_proto_to_text.cpp",
+ ],
+
+ header_libs: [
+ "bionic_libc_platform_headers",
+ ],
+
+ static_libs: [
+ "libbase",
+ "liblog_for_runtime_apex",
+ "libunwindstack",
+ ],
+
+ whole_static_libs: [
+ "libtombstone_proto",
+ "libprotobuf-cpp-lite",
+ ],
+
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.runtime",
+ ],
+}
+
cc_library_static {
name: "libdebuggerd",
defaults: ["debuggerd_defaults"],
@@ -199,7 +239,6 @@
"libdebuggerd/open_files_list.cpp",
"libdebuggerd/tombstone.cpp",
"libdebuggerd/tombstone_proto.cpp",
- "libdebuggerd/tombstone_proto_to_text.cpp",
"libdebuggerd/utility.cpp",
],
@@ -225,6 +264,7 @@
],
whole_static_libs: [
+ "libdebuggerd_tombstone_proto_to_text",
"libasync_safe",
"gwp_asan_crash_handler",
"libtombstone_proto",
@@ -276,6 +316,9 @@
header_libs: ["scudo_headers"],
},
},
+ apex_available: [
+ "com.android.runtime",
+ ],
}
cc_binary {
@@ -309,7 +352,7 @@
"libdebuggerd/test/elf_fake.cpp",
"libdebuggerd/test/log_fake.cpp",
"libdebuggerd/test/open_files_list_test.cpp",
- "libdebuggerd/test/utility_test.cpp",
+ "libdebuggerd/test/tombstone_proto_to_text_test.cpp",
],
target: {
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 3563436..0899111 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -317,6 +317,7 @@
process_info->gwp_asan_state = crash_info->data.d.gwp_asan_state;
process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata;
process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot;
+ process_info->scudo_stack_depot_size = crash_info->data.d.scudo_stack_depot_size;
process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer;
process_info->scudo_ring_buffer_size = crash_info->data.d.scudo_ring_buffer_size;
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index 26726cf..0d4b91f 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -91,6 +91,15 @@
}
}
+ // unfreeze if pid is frozen.
+ const std::string freeze_file = android::base::StringPrintf(
+ "/sys/fs/cgroup/uid_%d/pid_%d/cgroup.freeze", proc_info.uid, proc_info.pid);
+ if (std::string freeze_status;
+ android::base::ReadFileToString(freeze_file, &freeze_status) && freeze_status[0] == '1') {
+ android::base::WriteStringToFile("0", freeze_file);
+ // we don't restore the frozen state as this is considered a benign change.
+ }
+
unique_fd output_fd(fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0));
if (output_fd.get() == -1) {
err(1, "failed to fcntl dup stdout");
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index a6d8226..c0522aa 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -2221,28 +2221,10 @@
ASSERT_MATCH(result, match_str);
}
-TEST(tombstoned, proto) {
- const pid_t self = getpid();
- unique_fd tombstoned_socket, text_fd, proto_fd;
- ASSERT_TRUE(
- tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto));
-
- tombstoned_notify_completion(tombstoned_socket.get());
-
- ASSERT_NE(-1, text_fd.get());
- ASSERT_NE(-1, proto_fd.get());
-
- struct stat text_st;
- ASSERT_EQ(0, fstat(text_fd.get(), &text_st));
-
- // Give tombstoned some time to link the files into place.
- std::this_thread::sleep_for(100ms);
-
- // Find the tombstone.
- std::optional<std::string> tombstone_file;
+void CheckForTombstone(const struct stat& text_st, std::optional<std::string>& tombstone_file) {
+ static std::regex tombstone_re("tombstone_\\d+");
std::unique_ptr<DIR, decltype(&closedir)> dir_h(opendir("/data/tombstones"), closedir);
ASSERT_TRUE(dir_h != nullptr);
- std::regex tombstone_re("tombstone_\\d+");
dirent* entry;
while ((entry = readdir(dir_h.get())) != nullptr) {
if (!std::regex_match(entry->d_name, tombstone_re)) {
@@ -2260,8 +2242,38 @@
break;
}
}
+}
- ASSERT_TRUE(tombstone_file);
+TEST(tombstoned, proto) {
+ const pid_t self = getpid();
+ unique_fd tombstoned_socket, text_fd, proto_fd;
+ ASSERT_TRUE(
+ tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto));
+
+ tombstoned_notify_completion(tombstoned_socket.get());
+
+ ASSERT_NE(-1, text_fd.get());
+ ASSERT_NE(-1, proto_fd.get());
+
+ struct stat text_st;
+ ASSERT_EQ(0, fstat(text_fd.get(), &text_st));
+
+ std::optional<std::string> tombstone_file;
+ // Allow up to 5 seconds for the tombstone to be written to the system.
+ const auto max_wait_time = std::chrono::seconds(5) * android::base::HwTimeoutMultiplier();
+ const auto start = std::chrono::high_resolution_clock::now();
+ while (true) {
+ std::this_thread::sleep_for(100ms);
+ CheckForTombstone(text_st, tombstone_file);
+ if (tombstone_file) {
+ break;
+ }
+ if (std::chrono::high_resolution_clock::now() - start > max_wait_time) {
+ break;
+ }
+ }
+
+ ASSERT_TRUE(tombstone_file) << "Timed out trying to find tombstone file.";
std::string proto_path = tombstone_file.value() + ".pb";
struct stat proto_fd_st;
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 01365f2..ea07ce2 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -395,6 +395,7 @@
ASSERT_SAME_OFFSET(scudo_region_info, scudo_region_info);
ASSERT_SAME_OFFSET(scudo_ring_buffer, scudo_ring_buffer);
ASSERT_SAME_OFFSET(scudo_ring_buffer_size, scudo_ring_buffer_size);
+ ASSERT_SAME_OFFSET(scudo_stack_depot_size, scudo_stack_depot_size);
ASSERT_SAME_OFFSET(recoverable_gwp_asan_crash, recoverable_gwp_asan_crash);
#undef ASSERT_SAME_OFFSET
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index ebb5372..de12fc6 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -44,6 +44,7 @@
const char* scudo_region_info;
const char* scudo_ring_buffer;
size_t scudo_ring_buffer_size;
+ size_t scudo_stack_depot_size;
bool recoverable_gwp_asan_crash;
};
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index 5a2a7ab..075b12c 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -51,6 +51,7 @@
uintptr_t scudo_region_info = 0;
uintptr_t scudo_ring_buffer = 0;
size_t scudo_ring_buffer_size = 0;
+ size_t scudo_stack_depot_size = 0;
bool has_fault_address = false;
uintptr_t untagged_fault_address = 0;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 198de37..26c2cd4 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -91,8 +91,6 @@
void get_signal_sender(char* buf, size_t n, const siginfo_t*);
const char* get_signame(const siginfo_t*);
const char* get_sigcode(const siginfo_t*);
-std::string describe_tagged_addr_ctrl(long ctrl);
-std::string describe_pac_enabled_keys(long keys);
// Number of bytes per MTE granule.
constexpr size_t kTagGranuleSize = 16;
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index 837f406..3fa3bd0 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -41,8 +41,6 @@
return;
}
- auto stack_depot = AllocAndReadFully(process_memory, process_info.scudo_stack_depot,
- __scudo_get_stack_depot_size());
auto region_info = AllocAndReadFully(process_memory, process_info.scudo_region_info,
__scudo_get_region_info_size());
std::unique_ptr<char[]> ring_buffer;
@@ -50,7 +48,12 @@
ring_buffer = AllocAndReadFully(process_memory, process_info.scudo_ring_buffer,
process_info.scudo_ring_buffer_size);
}
- if (!stack_depot || !region_info) {
+ std::unique_ptr<char[]> stack_depot;
+ if (process_info.scudo_stack_depot_size != 0) {
+ stack_depot = AllocAndReadFully(process_memory, process_info.scudo_stack_depot,
+ process_info.scudo_stack_depot_size);
+ }
+ if (!region_info) {
return;
}
@@ -78,7 +81,8 @@
}
__scudo_get_error_info(&error_info_, process_info.maybe_tagged_fault_address, stack_depot.get(),
- region_info.get(), ring_buffer.get(), memory.get(), memory_tags.get(),
+ process_info.scudo_stack_depot_size, region_info.get(), ring_buffer.get(),
+ process_info.scudo_ring_buffer_size, memory.get(), memory_tags.get(),
memory_begin, memory_end - memory_begin);
}
diff --git a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
new file mode 100644
index 0000000..ac92ac0
--- /dev/null
+++ b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+#include <sys/prctl.h>
+
+#include <string>
+
+#include <android-base/test_utils.h>
+
+#include "libdebuggerd/tombstone.h"
+#include "tombstone.pb.h"
+
+using CallbackType = std::function<void(const std::string& line, bool should_log)>;
+
+class TombstoneProtoToTextTest : public ::testing::Test {
+ public:
+ void SetUp() {
+ tombstone_.reset(new Tombstone);
+
+ tombstone_->set_arch(Architecture::ARM64);
+ tombstone_->set_build_fingerprint("Test fingerprint");
+ tombstone_->set_timestamp("1970-01-01 00:00:00");
+ tombstone_->set_pid(100);
+ tombstone_->set_tid(100);
+ tombstone_->set_uid(0);
+ tombstone_->set_selinux_label("none");
+
+ Signal signal;
+ signal.set_number(SIGSEGV);
+ signal.set_name("SIGSEGV");
+ signal.set_code(0);
+ signal.set_code_name("none");
+
+ *tombstone_->mutable_signal_info() = signal;
+
+ Thread thread;
+ thread.set_id(100);
+ thread.set_name("main");
+ thread.set_tagged_addr_ctrl(0);
+ thread.set_pac_enabled_keys(0);
+
+ auto& threads = *tombstone_->mutable_threads();
+ threads[100] = thread;
+ main_thread_ = &threads[100];
+ }
+
+ void ProtoToString() {
+ text_ = "";
+ EXPECT_TRUE(
+ tombstone_proto_to_text(*tombstone_, [this](const std::string& line, bool should_log) {
+ if (should_log) {
+ text_ += "LOG ";
+ }
+ text_ += line + '\n';
+ }));
+ }
+
+ Thread* main_thread_;
+ std::string text_;
+ std::unique_ptr<Tombstone> tombstone_;
+};
+
+TEST_F(TombstoneProtoToTextTest, tagged_addr_ctrl) {
+ main_thread_->set_tagged_addr_ctrl(0);
+ ProtoToString();
+ EXPECT_MATCH(text_, "LOG tagged_addr_ctrl: 0000000000000000\\n");
+
+ main_thread_->set_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE);
+ ProtoToString();
+ EXPECT_MATCH(text_, "LOG tagged_addr_ctrl: 0000000000000001 \\(PR_TAGGED_ADDR_ENABLE\\)\\n");
+
+ main_thread_->set_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+ (0xfffe << PR_MTE_TAG_SHIFT));
+ ProtoToString();
+ EXPECT_MATCH(text_,
+ "LOG tagged_addr_ctrl: 000000000007fff3 \\(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, "
+ "mask 0xfffe\\)\\n");
+
+ main_thread_->set_tagged_addr_ctrl(0xf0000000 | PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+ PR_MTE_TCF_ASYNC | (0xfffe << PR_MTE_TAG_SHIFT));
+ ProtoToString();
+ EXPECT_MATCH(text_,
+ "LOG tagged_addr_ctrl: 00000000f007fff7 \\(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, "
+ "PR_MTE_TCF_ASYNC, mask 0xfffe, unknown 0xf0000000\\)\\n");
+}
+
+TEST_F(TombstoneProtoToTextTest, pac_enabled_keys) {
+ main_thread_->set_pac_enabled_keys(0);
+ ProtoToString();
+ EXPECT_MATCH(text_, "LOG pac_enabled_keys: 0000000000000000\\n");
+
+ main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY);
+ ProtoToString();
+ EXPECT_MATCH(text_, "LOG pac_enabled_keys: 0000000000000001 \\(PR_PAC_APIAKEY\\)\\n");
+
+ main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY);
+ ProtoToString();
+ EXPECT_MATCH(text_,
+ "LOG pac_enabled_keys: 0000000000000009 \\(PR_PAC_APIAKEY, PR_PAC_APDBKEY\\)\\n");
+
+ main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY | 0x1000);
+ ProtoToString();
+ EXPECT_MATCH(text_,
+ "LOG pac_enabled_keys: 0000000000001009 \\(PR_PAC_APIAKEY, PR_PAC_APDBKEY, unknown "
+ "0x1000\\)\\n");
+}
diff --git a/debuggerd/libdebuggerd/test/utility_test.cpp b/debuggerd/libdebuggerd/test/utility_test.cpp
deleted file mode 100644
index dad3380..0000000
--- a/debuggerd/libdebuggerd/test/utility_test.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.
- */
-
-#include <gtest/gtest.h>
-#include <sys/prctl.h>
-
-#include "libdebuggerd/utility.h"
-
-TEST(UtilityTest, describe_tagged_addr_ctrl) {
- EXPECT_EQ("", describe_tagged_addr_ctrl(0));
- EXPECT_EQ(" (PR_TAGGED_ADDR_ENABLE)", describe_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE));
- EXPECT_EQ(" (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe)",
- describe_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
- (0xfffe << PR_MTE_TAG_SHIFT)));
- EXPECT_EQ(
- " (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, PR_MTE_TCF_ASYNC, mask 0xfffe, unknown "
- "0xf0000000)",
- describe_tagged_addr_ctrl(0xf0000000 | PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
- PR_MTE_TCF_ASYNC | (0xfffe << PR_MTE_TAG_SHIFT)));
-}
-
-TEST(UtilityTest, describe_pac_enabled_keys) {
- EXPECT_EQ("", describe_pac_enabled_keys(0));
- EXPECT_EQ(" (PR_PAC_APIAKEY)", describe_pac_enabled_keys(PR_PAC_APIAKEY));
- EXPECT_EQ(" (PR_PAC_APIAKEY, PR_PAC_APDBKEY)",
- describe_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY));
- EXPECT_EQ(" (PR_PAC_APIAKEY, PR_PAC_APDBKEY, unknown 0x1000)",
- describe_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY | 0x1000));
-}
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index e44dc10..ad91320 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -29,6 +29,7 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <bionic/macros.h>
+#include <sys/prctl.h>
#include "tombstone.pb.h"
@@ -40,6 +41,42 @@
#define CBS(...) CB(false, __VA_ARGS__)
using CallbackType = std::function<void(const std::string& line, bool should_log)>;
+#define DESCRIBE_FLAG(flag) \
+ if (value & flag) { \
+ desc += ", "; \
+ desc += #flag; \
+ value &= ~flag; \
+ }
+
+static std::string describe_end(long value, std::string& desc) {
+ if (value) {
+ desc += StringPrintf(", unknown 0x%lx", value);
+ }
+ return desc.empty() ? "" : " (" + desc.substr(2) + ")";
+}
+
+static std::string describe_tagged_addr_ctrl(long value) {
+ std::string desc;
+ DESCRIBE_FLAG(PR_TAGGED_ADDR_ENABLE);
+ DESCRIBE_FLAG(PR_MTE_TCF_SYNC);
+ DESCRIBE_FLAG(PR_MTE_TCF_ASYNC);
+ if (value & PR_MTE_TAG_MASK) {
+ desc += StringPrintf(", mask 0x%04lx", (value & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
+ value &= ~PR_MTE_TAG_MASK;
+ }
+ return describe_end(value, desc);
+}
+
+static std::string describe_pac_enabled_keys(long value) {
+ std::string desc;
+ DESCRIBE_FLAG(PR_PAC_APIAKEY);
+ DESCRIBE_FLAG(PR_PAC_APIBKEY);
+ DESCRIBE_FLAG(PR_PAC_APDAKEY);
+ DESCRIBE_FLAG(PR_PAC_APDBKEY);
+ DESCRIBE_FLAG(PR_PAC_APGAKEY);
+ return describe_end(value, desc);
+}
+
static const char* abi_string(const Tombstone& tombstone) {
switch (tombstone.arch()) {
case Architecture::ARM32:
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 15f09b3..742ac7c 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -445,42 +445,6 @@
return "?";
}
-#define DESCRIBE_FLAG(flag) \
- if (value & flag) { \
- desc += ", "; \
- desc += #flag; \
- value &= ~flag; \
- }
-
-static std::string describe_end(long value, std::string& desc) {
- if (value) {
- desc += StringPrintf(", unknown 0x%lx", value);
- }
- return desc.empty() ? "" : " (" + desc.substr(2) + ")";
-}
-
-std::string describe_tagged_addr_ctrl(long value) {
- std::string desc;
- DESCRIBE_FLAG(PR_TAGGED_ADDR_ENABLE);
- DESCRIBE_FLAG(PR_MTE_TCF_SYNC);
- DESCRIBE_FLAG(PR_MTE_TCF_ASYNC);
- if (value & PR_MTE_TAG_MASK) {
- desc += StringPrintf(", mask 0x%04lx", (value & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
- value &= ~PR_MTE_TAG_MASK;
- }
- return describe_end(value, desc);
-}
-
-std::string describe_pac_enabled_keys(long value) {
- std::string desc;
- DESCRIBE_FLAG(PR_PAC_APIAKEY);
- DESCRIBE_FLAG(PR_PAC_APIBKEY);
- DESCRIBE_FLAG(PR_PAC_APDAKEY);
- DESCRIBE_FLAG(PR_PAC_APDBKEY);
- DESCRIBE_FLAG(PR_PAC_APGAKEY);
- return describe_end(value, desc);
-}
-
void log_backtrace(log_t* log, unwindstack::AndroidUnwinder* unwinder,
unwindstack::AndroidUnwinderData& data, const char* prefix) {
std::set<std::string> unreadable_elf_files;
diff --git a/debuggerd/proto/Android.bp b/debuggerd/proto/Android.bp
index 804f805..7be5d61 100644
--- a/debuggerd/proto/Android.bp
+++ b/debuggerd/proto/Android.bp
@@ -39,3 +39,17 @@
recovery_available: true,
vendor_ramdisk_available: true,
}
+
+java_library_static {
+ name: "libtombstone_proto_java",
+ proto: {
+ type: "lite",
+ },
+ srcs: [
+ "tombstone.proto",
+ ],
+ jarjar_rules: "jarjar-rules.txt",
+ sdk_version: "current",
+ static_libs: ["libprotobuf-java-lite"],
+}
+
diff --git a/debuggerd/proto/jarjar-rules.txt b/debuggerd/proto/jarjar-rules.txt
new file mode 100644
index 0000000..66878a9
--- /dev/null
+++ b/debuggerd/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.protobuf.** com.android.server.os.protobuf.@1
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index 212d6dc..793428a 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -99,6 +99,7 @@
uintptr_t scudo_region_info;
uintptr_t scudo_ring_buffer;
size_t scudo_ring_buffer_size;
+ size_t scudo_stack_depot_size;
bool recoverable_gwp_asan_crash;
};
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index f85d1de..c0445f3 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -170,7 +170,7 @@
"android.hardware.fastboot@1.1",
"android.hardware.fastboot-V1-ndk",
"android.hardware.health@2.0",
- "android.hardware.health-V2-ndk",
+ "android.hardware.health-V3-ndk",
"libasyncio",
"libbase",
"libbinder_ndk",
diff --git a/fastboot/constants.h b/fastboot/constants.h
index a803307..af4d1eb 100644
--- a/fastboot/constants.h
+++ b/fastboot/constants.h
@@ -82,3 +82,5 @@
#define FB_VAR_TREBLE_ENABLED "treble-enabled"
#define FB_VAR_MAX_FETCH_SIZE "max-fetch-size"
#define FB_VAR_DMESG "dmesg"
+#define FB_VAR_BATTERY_SERIAL_NUMBER "battery-serial-number"
+#define FB_VAR_BATTERY_PART_STATUS "battery-part-status"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index bd936ae..e522f4d 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -147,6 +147,8 @@
{FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}},
{FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}},
{FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}},
+ {FB_VAR_BATTERY_SERIAL_NUMBER, {GetBatterySerialNumber, nullptr}},
+ {FB_VAR_BATTERY_PART_STATUS, {GetBatteryPartStatus, nullptr}},
};
static bool GetVarAll(FastbootDevice* device) {
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index 2847e35..77210ab 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -570,3 +570,79 @@
return true;
}
+
+bool GetBatterySerialNumber(FastbootDevice* device, const std::vector<std::string>&,
+ std::string* message) {
+ auto health_hal = device->health_hal();
+ if (!health_hal) {
+ return false;
+ }
+
+ if (GetDeviceLockStatus()) {
+ return device->WriteFail("Device is locked");
+ }
+
+ *message = "unsupported";
+
+ int32_t version = 0;
+ auto res = health_hal->getInterfaceVersion(&version);
+ if (!res.isOk()) {
+ return device->WriteFail("Unable to query battery data");
+ }
+ if (version >= 3) {
+ using aidl::android::hardware::health::BatteryHealthData;
+
+ BatteryHealthData data;
+ auto res = health_hal->getBatteryHealthData(&data);
+ if (!res.isOk()) {
+ return device->WriteFail("Unable to query battery data");
+ }
+ if (data.batterySerialNumber) {
+ *message = *data.batterySerialNumber;
+ }
+ }
+ return true;
+}
+
+bool GetBatteryPartStatus(FastbootDevice* device, const std::vector<std::string>&,
+ std::string* message) {
+ auto health_hal = device->health_hal();
+ if (!health_hal) {
+ return false;
+ }
+
+ using aidl::android::hardware::health::BatteryPartStatus;
+
+ BatteryPartStatus status = BatteryPartStatus::UNSUPPORTED;
+
+ int32_t version = 0;
+ auto res = health_hal->getInterfaceVersion(&version);
+ if (!res.isOk()) {
+ return device->WriteFail("Unable to query battery data");
+ }
+ if (version >= 3) {
+ using aidl::android::hardware::health::BatteryHealthData;
+
+ BatteryHealthData data;
+ auto res = health_hal->getBatteryHealthData(&data);
+ if (!res.isOk()) {
+ return device->WriteFail("Unable to query battery data");
+ }
+ status = data.batteryPartStatus;
+ }
+ switch (status) {
+ case BatteryPartStatus::UNSUPPORTED:
+ *message = "unsupported";
+ break;
+ case BatteryPartStatus::ORIGINAL:
+ *message = "original";
+ break;
+ case BatteryPartStatus::REPLACED:
+ *message = "replaced";
+ break;
+ default:
+ *message = "unknown";
+ break;
+ }
+ return true;
+}
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index 9a46786..99d1355 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -67,6 +67,10 @@
std::string* message);
bool GetBatterySoCOk(FastbootDevice* device, const std::vector<std::string>& args,
std::string* message);
+bool GetBatterySerialNumber(FastbootDevice* device, const std::vector<std::string>& args,
+ std::string* message);
+bool GetBatteryPartStatus(FastbootDevice* device, const std::vector<std::string>& args,
+ std::string* message);
bool GetSuperPartitionName(FastbootDevice* device, const std::vector<std::string>& args,
std::string* message);
bool GetSnapshotUpdateStatus(FastbootDevice* device, const std::vector<std::string>& args,
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index ac2a20f..12a1ddc 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -402,7 +402,7 @@
transport = open_device(device.c_str(), false, false);
if (print) {
- PrintDevice(device.c_str(), transport ? "offline" : "fastboot");
+ PrintDevice(device.c_str(), transport ? "fastboot" : "offline");
}
if (transport) {
@@ -571,7 +571,8 @@
" Format a flash partition.\n"
" set_active SLOT Set the active slot.\n"
" oem [COMMAND...] Execute OEM-specific command.\n"
- " gsi wipe|disable Wipe or disable a GSI installation (fastbootd only).\n"
+ " gsi wipe|disable|status Wipe, disable or show status of a GSI installation\n"
+ " (fastbootd only).\n"
" wipe-super [SUPER_EMPTY] Wipe the super partition. This will reset it to\n"
" contain an empty set of default dynamic partitions.\n"
" create-logical-partition NAME SIZE\n"
@@ -1675,7 +1676,7 @@
}
for (size_t i = 0; i < tasks->size(); i++) {
if (auto flash_task = tasks->at(i)->AsFlashTask()) {
- if (FlashTask::IsDynamicParitition(fp->source.get(), flash_task)) {
+ if (FlashTask::IsDynamicPartition(fp->source.get(), flash_task)) {
if (!loc) {
loc = i;
}
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 25c5a6e..ea78a01 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -30,7 +30,7 @@
const bool apply_vbmeta, const FlashingPlan* fp)
: pname_(pname), fname_(fname), slot_(slot), apply_vbmeta_(apply_vbmeta), fp_(fp) {}
-bool FlashTask::IsDynamicParitition(const ImageSource* source, const FlashTask* task) {
+bool FlashTask::IsDynamicPartition(const ImageSource* source, const FlashTask* task) {
std::vector<char> contents;
if (!source->ReadFile("super_empty.img", &contents)) {
return false;
@@ -152,7 +152,7 @@
continue;
}
auto flash_task = tasks[i + 2]->AsFlashTask();
- if (!FlashTask::IsDynamicParitition(source, flash_task)) {
+ if (!FlashTask::IsDynamicPartition(source, flash_task)) {
continue;
}
return true;
@@ -224,7 +224,7 @@
auto remove_if_callback = [&](const auto& task) -> bool {
if (auto flash_task = task->AsFlashTask()) {
return helper->WillFlash(flash_task->GetPartitionAndSlot());
- } else if (auto update_super_task = task->AsUpdateSuperTask()) {
+ } else if (task->AsUpdateSuperTask()) {
return true;
} else if (auto reboot_task = task->AsRebootTask()) {
if (reboot_task->GetTarget() == "fastboot") {
diff --git a/fastboot/task.h b/fastboot/task.h
index a98c874..7a713cf 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -52,7 +52,7 @@
const bool apply_vbmeta, const FlashingPlan* fp);
virtual FlashTask* AsFlashTask() override { return this; }
- static bool IsDynamicParitition(const ImageSource* source, const FlashTask* task);
+ static bool IsDynamicPartition(const ImageSource* source, const FlashTask* task);
void Run() override;
std::string ToString() const override;
std::string GetPartition() const { return pname_; }
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
index 9cde1a8..519d4ed 100644
--- a/fastboot/task_test.cpp
+++ b/fastboot/task_test.cpp
@@ -233,7 +233,7 @@
<< "size of fastboot-info task list: " << fastboot_info_tasks.size()
<< " size of hardcoded task list: " << hardcoded_tasks.size();
}
-TEST_F(ParseTest, IsDynamicParitiontest) {
+TEST_F(ParseTest, IsDynamicPartitiontest) {
if (!get_android_product_out()) {
GTEST_SKIP();
}
@@ -258,7 +258,7 @@
ParseFastbootInfoLine(fp.get(), android::base::Tokenize(test.first, " "));
auto flash_task = task->AsFlashTask();
ASSERT_FALSE(flash_task == nullptr);
- ASSERT_EQ(FlashTask::IsDynamicParitition(fp->source.get(), flash_task), test.second);
+ ASSERT_EQ(FlashTask::IsDynamicPartition(fp->source.get(), flash_task), test.second);
}
}
@@ -354,11 +354,11 @@
const std::vector<std::unique_ptr<Task>>& tasks) {
bool contains_optimized_task = false;
for (auto& task : tasks) {
- if (auto optimized_task = task->AsOptimizedFlashSuperTask()) {
+ if (task->AsOptimizedFlashSuperTask()) {
contains_optimized_task = true;
}
if (auto flash_task = task->AsFlashTask()) {
- if (FlashTask::IsDynamicParitition(fp->source.get(), flash_task)) {
+ if (FlashTask::IsDynamicPartition(fp->source.get(), flash_task)) {
return false;
}
}
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index 324f50a..1989a5c 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -25,7 +25,7 @@
{
"name": "vab_legacy_tests"
},
- // TODO: b/279009697
+ // TODO(b/279009697):
//{"name": "vabc_legacy_tests"},
{
"name": "cow_api_test"
@@ -42,12 +42,9 @@
"name": "liblp_test"
},
{
- "name": "vts_libsnapshot_test"
- },
- {
"name": "vab_legacy_tests"
},
- // TODO: b/279009697
+ // TODO(b/279009697):
//{"name": "vabc_legacy_tests"}
{
"name": "snapuserd_test"
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index faea5eb..8c0c1ef 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -16,7 +16,6 @@
#include "fs_mgr.h"
-#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
@@ -701,6 +700,29 @@
}
//
+// Mechanism to allow fsck to be triggered by setting ro.preventative_fsck
+// Introduced to address b/305658663
+// If the property value is not equal to the flag file contents, trigger
+// fsck and store the property value in the flag file
+// If we want to trigger again, simply change the property value
+//
+static bool check_if_preventative_fsck_needed(const FstabEntry& entry) {
+ const char* flag_file = "/metadata/vold/preventative_fsck";
+ if (entry.mount_point != "/data") return false;
+
+ // Don't error check - both default to empty string, which is OK
+ std::string prop = android::base::GetProperty("ro.preventative_fsck", "");
+ std::string flag;
+ android::base::ReadFileToString(flag_file, &flag);
+ if (prop == flag) return false;
+ // fsck is run immediately, so assume it runs or there is some deeper problem
+ if (!android::base::WriteStringToFile(prop, flag_file))
+ PERROR << "Failed to write file " << flag_file;
+ LINFO << "Run preventative fsck on /data";
+ return true;
+}
+
+//
// Prepare the filesystem on the given block device to be mounted.
//
// If the "check" option was given in the fstab record, or it seems that the
@@ -750,7 +772,7 @@
}
}
- if (entry.fs_mgr_flags.check ||
+ if (check_if_preventative_fsck_needed(entry) || entry.fs_mgr_flags.check ||
(fs_stat & (FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED))) {
check_fs(blk_device, entry.fs_type, mount_point, &fs_stat);
}
@@ -796,9 +818,13 @@
// __mount(): wrapper around the mount() system call which also
// sets the underlying block device to read-only if the mount is read-only.
// See "man 2 mount" for return values.
-static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry) {
+static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry,
+ bool read_only = false) {
errno = 0;
unsigned long mountflags = entry.flags;
+ if (read_only) {
+ mountflags |= MS_RDONLY;
+ }
int ret = 0;
int save_errno = 0;
int gc_allowance = 0;
@@ -892,6 +918,10 @@
return true;
}
+static bool should_use_metadata_encryption(const FstabEntry& entry) {
+ return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption;
+}
+
// Tries to mount any of the consecutive fstab entries that match
// the mountpoint of the one given by fstab[start_idx].
//
@@ -899,8 +929,7 @@
// attempted_idx: On return, will indicate which fstab entry
// succeeded. In case of failure, it will be the start_idx.
// Sets errno to match the 1st mount failure on failure.
-static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx,
- int* attempted_idx) {
+static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx, int* attempted_idx) {
unsigned long i;
int mount_errno = 0;
bool mounted = false;
@@ -938,8 +967,15 @@
}
int retry_count = 2;
+ const auto read_only = should_use_metadata_encryption(fstab[i]);
+ if (read_only) {
+ LOG(INFO) << "Mount point " << fstab[i].blk_device << " @ " << fstab[i].mount_point
+ << " uses metadata encryption, which means we need to unmount it later and "
+ "call encryptFstab/encrypt_inplace. To avoid file operations before "
+ "encryption, we will mount it as read-only first";
+ }
while (retry_count-- > 0) {
- if (!__mount(fstab[i].blk_device, fstab[i].mount_point, fstab[i])) {
+ if (!__mount(fstab[i].blk_device, fstab[i].mount_point, fstab[i], read_only)) {
*attempted_idx = i;
mounted = true;
if (i != start_idx) {
@@ -1031,10 +1067,6 @@
return false;
}
-static bool should_use_metadata_encryption(const FstabEntry& entry) {
- return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption;
-}
-
// Check to see if a mountable volume has encryption requirements
static int handle_encryptable(const FstabEntry& entry) {
if (should_use_metadata_encryption(entry)) {
@@ -1244,17 +1276,27 @@
};
std::string fs_mgr_find_bow_device(const std::string& block_device) {
- if (block_device.substr(0, 5) != "/dev/") {
- LOG(ERROR) << "Expected block device, got " << block_device;
- return std::string();
+ // handle symlink such as "/dev/block/mapper/userdata"
+ std::string real_path;
+ if (!android::base::Realpath(block_device, &real_path)) {
+ real_path = block_device;
}
- std::string sys_dir = std::string("/sys/") + block_device.substr(5);
-
+ struct stat st;
+ if (stat(real_path.c_str(), &st) < 0) {
+ PLOG(ERROR) << "stat failed: " << real_path;
+ return std::string();
+ }
+ if (!S_ISBLK(st.st_mode)) {
+ PLOG(ERROR) << real_path << " is not block device";
+ return std::string();
+ }
+ std::string sys_dir = android::base::StringPrintf("/sys/dev/block/%u:%u", major(st.st_rdev),
+ minor(st.st_rdev));
for (;;) {
std::string name;
if (!android::base::ReadFileToString(sys_dir + "/dm/name", &name)) {
- PLOG(ERROR) << block_device << " is not dm device";
+ PLOG(ERROR) << real_path << " is not dm device";
return std::string();
}
@@ -1388,6 +1430,8 @@
return {FS_MGR_MNTALL_FAIL, userdata_mounted};
}
+ bool scratch_can_be_mounted = true;
+
// Keep i int to prevent unsigned integer overflow from (i = top_idx - 1),
// where top_idx is 0. It will give SIGABRT
for (int i = 0; i < static_cast<int>(fstab->size()); i++) {
@@ -1506,6 +1550,7 @@
}
encryptable = status;
if (status == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
+ fs_mgr_set_blk_ro(attempted_entry.blk_device, false);
if (!call_vdc({"cryptfs", "encryptFstab", attempted_entry.blk_device,
attempted_entry.mount_point, wiped ? "true" : "false",
attempted_entry.fs_type, attempted_entry.zoned_device},
@@ -1520,6 +1565,9 @@
if (current_entry.mount_point == "/data") {
userdata_mounted = true;
}
+
+ MountOverlayfs(attempted_entry, &scratch_can_be_mounted);
+
// Success! Go get the next one.
continue;
}
@@ -1604,10 +1652,6 @@
set_type_property(encryptable);
-#if ALLOW_ADBD_DISABLE_VERITY == 1 // "userdebug" build
- fs_mgr_overlayfs_mount_all(fstab);
-#endif
-
if (error_count) {
return {FS_MGR_MNTALL_FAIL, userdata_mounted};
} else {
@@ -1944,6 +1988,8 @@
if (retry_count <= 0) break; // run check_fs only once
if (!first_mount_errno) first_mount_errno = errno;
mount_errors++;
+ PERROR << "Cannot mount filesystem on " << n_blk_device << " at " << mount_point
+ << " with fstype " << fstab_entry.fs_type;
fs_stat |= FS_STAT_FULL_MOUNT_FAILED;
// try again after fsck
check_fs(n_blk_device, fstab_entry.fs_type, mount_point, &fs_stat);
diff --git a/fs_mgr/fs_mgr_format.cpp b/fs_mgr/fs_mgr_format.cpp
index 622f181..8e76150 100644
--- a/fs_mgr/fs_mgr_format.cpp
+++ b/fs_mgr/fs_mgr_format.cpp
@@ -136,6 +136,7 @@
/* Format the partition using the calculated length */
const auto size_str = std::to_string(dev_sz / getpagesize());
+ std::string block_size = std::to_string(getpagesize());
std::vector<const char*> args = {"/system/bin/make_f2fs", "-g", "android"};
if (needs_projid) {
@@ -154,6 +155,10 @@
args.push_back("-O");
args.push_back("extra_attr");
}
+ args.push_back("-w");
+ args.push_back(block_size.c_str());
+ args.push_back("-b");
+ args.push_back(block_size.c_str());
if (!zoned_device.empty()) {
args.push_back("-c");
args.push_back(zoned_device.c_str());
diff --git a/fs_mgr/fs_mgr_overlayfs_control.cpp b/fs_mgr/fs_mgr_overlayfs_control.cpp
index 50d8280..08ad80c 100644
--- a/fs_mgr/fs_mgr_overlayfs_control.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_control.cpp
@@ -219,6 +219,35 @@
return OverlayfsTeardownResult::Ok;
}
+bool GetOverlaysActiveFlag() {
+ auto slot_number = fs_mgr_overlayfs_slot_number();
+ const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name();
+
+ auto metadata = ReadMetadata(super_device, slot_number);
+ if (!metadata) {
+ return false;
+ }
+ return !!(metadata->header.flags & LP_HEADER_FLAG_OVERLAYS_ACTIVE);
+}
+
+bool SetOverlaysActiveFlag(bool flag) {
+ // Mark overlays as active in the partition table, to detect re-flash.
+ auto slot_number = fs_mgr_overlayfs_slot_number();
+ const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name();
+ auto builder = MetadataBuilder::New(super_device, slot_number);
+ if (!builder) {
+ LERROR << "open " << super_device << " metadata";
+ return false;
+ }
+ builder->SetOverlaysActiveFlag(flag);
+ auto metadata = builder->Export();
+ if (!metadata || !UpdatePartitionTable(super_device, *metadata.get(), slot_number)) {
+ LERROR << "update super metadata";
+ return false;
+ }
+ return true;
+}
+
OverlayfsTeardownResult fs_mgr_overlayfs_teardown_scratch(const std::string& overlay,
bool* change) {
// umount and delete kScratchMountPoint storage if we have logical partitions
@@ -232,6 +261,10 @@
return OverlayfsTeardownResult::Error;
}
+ // Note: we don't care if SetOverlaysActiveFlag fails, since
+ // the overlays are removed no matter what.
+ SetOverlaysActiveFlag(false);
+
bool was_mounted = fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false);
if (was_mounted) {
fs_mgr_overlayfs_umount_scratch();
@@ -347,33 +380,6 @@
return "";
}
-// This returns the scratch device that was detected during early boot (first-
-// stage init). If the device was created later, for example during setup for
-// the adb remount command, it can return an empty string since it does not
-// query ImageManager. (Note that ImageManager in first-stage init will always
-// use device-mapper, since /data is not available to use loop devices.)
-static std::string GetBootScratchDevice() {
- // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
- if (fs_mgr_is_dsu_running()) {
- return GetDsuScratchDevice();
- }
-
- auto& dm = DeviceMapper::Instance();
-
- // If there is a scratch partition allocated in /data or on super, we
- // automatically prioritize that over super_other or system_other.
- // Some devices, for example, have a write-protected eMMC and the
- // super partition cannot be used even if it exists.
- std::string device;
- auto partition_name = android::base::Basename(kScratchMountPoint);
- if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
- dm.GetDmDevicePathByName(partition_name, &device)) {
- return device;
- }
-
- return "";
-}
-
bool MakeScratchFilesystem(const std::string& scratch_device) {
// Force mkfs by design for overlay support of adb remount, simplify and
// thus do not rely on fsck to correct problems that could creep in.
@@ -383,6 +389,8 @@
fs_type = "f2fs";
command = kMkF2fs + " -w "s;
command += std::to_string(getpagesize());
+ command = kMkF2fs + " -b "s;
+ command += std::to_string(getpagesize());
command += " -f -d1 -l" + android::base::Basename(kScratchMountPoint);
} else if (!access(kMkExt4, X_OK) && fs_mgr_filesystem_available("ext4")) {
fs_type = "ext4";
@@ -473,6 +481,7 @@
}
}
}
+
// land the update back on to the partition
if (changed) {
auto metadata = builder->Export();
@@ -617,6 +626,12 @@
return false;
}
+ if (!SetOverlaysActiveFlag(true)) {
+ LOG(ERROR) << "Failed to update dynamic partition data";
+ fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr);
+ return false;
+ }
+
// If the partition exists, assume first that it can be mounted.
if (partition_exists) {
if (MountScratch(scratch_device)) {
@@ -881,21 +896,9 @@
return;
}
- bool want_scratch = false;
- for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
- if (fs_mgr_is_verity_enabled(entry)) {
- continue;
- }
- if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) {
- continue;
- }
- want_scratch = true;
- break;
- }
- if (!want_scratch) {
+ if (!GetOverlaysActiveFlag()) {
return;
}
-
if (ScratchIsOnData()) {
if (auto images = IImageManager::Open("remount", 0ms)) {
images->MapAllImages(init);
@@ -919,9 +922,39 @@
}
if (auto images = IImageManager::Open("remount", 0ms)) {
images->RemoveDisabledImages();
+ if (!GetOverlaysActiveFlag()) {
+ fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr);
+ }
}
}
+// This returns the scratch device that was detected during early boot (first-
+// stage init). If the device was created later, for example during setup for
+// the adb remount command, it can return an empty string since it does not
+// query ImageManager. (Note that ImageManager in first-stage init will always
+// use device-mapper, since /data is not available to use loop devices.)
+std::string GetBootScratchDevice() {
+ // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
+ if (fs_mgr_is_dsu_running()) {
+ return GetDsuScratchDevice();
+ }
+
+ auto& dm = DeviceMapper::Instance();
+
+ // If there is a scratch partition allocated in /data or on super, we
+ // automatically prioritize that over super_other or system_other.
+ // Some devices, for example, have a write-protected eMMC and the
+ // super partition cannot be used even if it exists.
+ std::string device;
+ auto partition_name = android::base::Basename(kScratchMountPoint);
+ if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
+ dm.GetDmDevicePathByName(partition_name, &device)) {
+ return device;
+ }
+
+ return "";
+}
+
void TeardownAllOverlayForMountPoint(const std::string& mount_point) {
if (!OverlayfsTeardownAllowed()) {
return;
diff --git a/fs_mgr/fs_mgr_overlayfs_control.h b/fs_mgr/fs_mgr_overlayfs_control.h
index b175101..3516c46 100644
--- a/fs_mgr/fs_mgr_overlayfs_control.h
+++ b/fs_mgr/fs_mgr_overlayfs_control.h
@@ -38,5 +38,7 @@
void CleanupOldScratchFiles();
+std::string GetBootScratchDevice();
+
} // namespace fs_mgr
} // namespace android
diff --git a/fs_mgr/fs_mgr_overlayfs_mount.cpp b/fs_mgr/fs_mgr_overlayfs_mount.cpp
index ae7ed4c..a1ec63b 100644
--- a/fs_mgr/fs_mgr_overlayfs_mount.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_mount.cpp
@@ -34,6 +34,7 @@
#include <android-base/file.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
+#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <ext4_utils/ext4_utils.h>
@@ -41,15 +42,14 @@
#include <fs_mgr/file_wait.h>
#include <fs_mgr_overlayfs.h>
#include <fstab/fstab.h>
-#include <libdm/dm.h>
#include <libgsi/libgsi.h>
#include <storage_literals/storage_literals.h>
+#include "fs_mgr_overlayfs_control.h"
#include "fs_mgr_overlayfs_mount.h"
#include "fs_mgr_priv.h"
using namespace std::literals;
-using namespace android::dm;
using namespace android::fs_mgr;
using namespace android::storage_literals;
@@ -58,6 +58,9 @@
constexpr char kCacheMountPoint[] = "/cache";
constexpr char kPhysicalDevice[] = "/dev/block/by-name/";
+// Mount tree to temporarily hold references to submounts.
+constexpr char kMoveMountTempDir[] = "/dev/remount";
+
constexpr char kLowerdirOption[] = "lowerdir=";
constexpr char kUpperdirOption[] = "upperdir=";
constexpr char kWorkdirOption[] = "workdir=";
@@ -95,7 +98,12 @@
if (mount_point.empty() || !android::base::Realpath(mount_point, &normalized_path)) {
return "";
}
- return android::base::StringReplace(normalized_path, "/", "@", true);
+ std::string_view sv(normalized_path);
+ if (sv != "/") {
+ android::base::ConsumePrefix(&sv, "/");
+ android::base::ConsumeSuffix(&sv, "/");
+ }
+ return android::base::StringReplace(sv, "/", "@", true);
}
static bool fs_mgr_is_dir(const std::string& path) {
@@ -284,10 +292,6 @@
if (ret) {
PERROR << "__mount(target=" << mount_point
<< ",flag=" << (shared_flag ? "MS_SHARED" : "MS_PRIVATE") << ")=" << ret;
- // If "/system" doesn't look like a mountpoint, retry with "/".
- if (errno == EINVAL && mount_point == "/system") {
- return fs_mgr_overlayfs_set_shared_mount("/", shared_flag);
- }
return false;
}
return true;
@@ -302,6 +306,25 @@
return true;
}
+static bool fs_mgr_overlayfs_mount(const std::string& mount_point, const std::string& options) {
+ auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
+ for (const auto& opt : android::base::Split(options, ",")) {
+ if (android::base::StartsWith(opt, kUpperdirOption)) {
+ report = report + "," + opt;
+ break;
+ }
+ }
+ report = report + ")=";
+ auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
+ options.c_str());
+ if (ret) {
+ PERROR << report << ret;
+ } else {
+ LINFO << report << ret;
+ }
+ return !ret;
+}
+
struct mount_info {
std::string mount_point;
bool shared_flag;
@@ -374,24 +397,23 @@
return info;
}
-static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
- const auto mount_point = fs_mgr_mount_point(entry.mount_point);
- const auto options = fs_mgr_get_overlayfs_options(entry);
+static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) {
+ const auto mount_point = fs_mgr_mount_point(fstab_entry.mount_point);
+ const auto options = fs_mgr_get_overlayfs_options(fstab_entry);
if (options.empty()) return false;
- auto retval = true;
-
struct MoveEntry {
std::string mount_point;
std::string dir;
bool shared_flag;
};
-
std::vector<MoveEntry> moved_mounts;
- auto parent_private = false;
- auto parent_made_private = false;
- auto dev_private = false;
- auto dev_made_private = false;
+
+ bool retval = true;
+ bool move_dir_shared = true;
+ bool parent_shared = true;
+ bool root_shared = true;
+ bool root_made_private = false;
// There could be multiple mount entries with the same mountpoint.
// Group these entries together with stable_sort, and keep only the last entry of a group.
@@ -411,18 +433,32 @@
// mountinfo is reversed twice, so still is in lexical sorted order.
for (const auto& entry : mountinfo) {
- if ((entry.mount_point == mount_point) && !entry.shared_flag) {
- parent_private = true;
+ if (entry.mount_point == kMoveMountTempDir) {
+ move_dir_shared = entry.shared_flag;
}
- if ((entry.mount_point == "/dev") && !entry.shared_flag) {
- dev_private = true;
+ if (entry.mount_point == mount_point ||
+ (mount_point == "/system" && entry.mount_point == "/")) {
+ parent_shared = entry.shared_flag;
}
+ if (entry.mount_point == "/") {
+ root_shared = entry.shared_flag;
+ }
+ }
+
+ // Precondition is that kMoveMountTempDir is MS_PRIVATE, otherwise don't try to move any
+ // submount in to or out of it.
+ if (move_dir_shared) {
+ mountinfo.clear();
}
// Need to make the original mountpoint MS_PRIVATE, so that the overlayfs can be MS_MOVE.
// This could happen if its parent mount is remounted later.
- if (!parent_private) {
- parent_made_private = fs_mgr_overlayfs_set_shared_mount(mount_point, false);
+ if (!fs_mgr_overlayfs_set_shared_mount(mount_point, false)) {
+ // If failed to set "/system" mount type, it might be due to "/system" not being a valid
+ // mountpoint after switch root. Retry with "/" in this case.
+ if (errno == EINVAL && mount_point == "/system") {
+ root_made_private = fs_mgr_overlayfs_set_shared_mount("/", false);
+ }
}
for (const auto& entry : mountinfo) {
@@ -440,8 +476,8 @@
// mountinfo is in lexical order, so no need to worry about |entry| being a parent mount of
// entries of |moved_mounts|.
- // use as the bound directory in /dev.
- MoveEntry new_entry{entry.mount_point, "/dev/TemporaryDir-XXXXXX", entry.shared_flag};
+ MoveEntry new_entry{entry.mount_point, kMoveMountTempDir + "/TemporaryDir-XXXXXX"s,
+ entry.shared_flag};
{
AutoSetFsCreateCon createcon;
auto new_context = fs_mgr_get_context(entry.mount_point);
@@ -475,32 +511,10 @@
moved_mounts.push_back(std::move(new_entry));
}
- // hijack __mount() report format to help triage
- auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
- const auto opt_list = android::base::Split(options, ",");
- for (const auto& opt : opt_list) {
- if (android::base::StartsWith(opt, kUpperdirOption)) {
- report = report + "," + opt;
- break;
- }
- }
- report = report + ")=";
-
- auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
- options.c_str());
- if (ret) {
- retval = false;
- PERROR << report << ret;
- } else {
- LINFO << report << ret;
- }
+ retval &= fs_mgr_overlayfs_mount(mount_point, options);
// Move submounts back.
for (const auto& entry : moved_mounts) {
- if (!dev_private && !dev_made_private) {
- dev_made_private = fs_mgr_overlayfs_set_shared_mount("/dev", false);
- }
-
if (!fs_mgr_overlayfs_move_mount(entry.dir, entry.mount_point)) {
retval = false;
} else if (entry.shared_flag &&
@@ -509,12 +523,13 @@
}
rmdir(entry.dir.c_str());
}
- if (dev_made_private) {
- fs_mgr_overlayfs_set_shared_mount("/dev", true);
- }
- if (parent_made_private) {
+ // If the original (overridden) mount was MS_SHARED, then set the overlayfs mount to MS_SHARED.
+ if (parent_shared) {
fs_mgr_overlayfs_set_shared_mount(mount_point, true);
}
+ if (root_shared && root_made_private) {
+ fs_mgr_overlayfs_set_shared_mount("/", true);
+ }
return retval;
}
@@ -582,45 +597,6 @@
return true;
}
-// Note: The scratch partition of DSU is managed by gsid, and should be initialized during
-// first-stage-mount. Just check if the DM device for DSU scratch partition is created or not.
-static std::string GetDsuScratchDevice() {
- auto& dm = DeviceMapper::Instance();
- std::string device;
- if (dm.GetState(android::gsi::kDsuScratch) != DmDeviceState::INVALID &&
- dm.GetDmDevicePathByName(android::gsi::kDsuScratch, &device)) {
- return device;
- }
- return "";
-}
-
-// This returns the scratch device that was detected during early boot (first-
-// stage init). If the device was created later, for example during setup for
-// the adb remount command, it can return an empty string since it does not
-// query ImageManager. (Note that ImageManager in first-stage init will always
-// use device-mapper, since /data is not available to use loop devices.)
-static std::string GetBootScratchDevice() {
- // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
- if (fs_mgr_is_dsu_running()) {
- return GetDsuScratchDevice();
- }
-
- auto& dm = DeviceMapper::Instance();
-
- // If there is a scratch partition allocated in /data or on super, we
- // automatically prioritize that over super_other or system_other.
- // Some devices, for example, have a write-protected eMMC and the
- // super partition cannot be used even if it exists.
- std::string device;
- auto partition_name = android::base::Basename(kScratchMountPoint);
- if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
- dm.GetDmDevicePathByName(partition_name, &device)) {
- return device;
- }
-
- return "";
-}
-
// NOTE: OverlayfsSetupAllowed() must be "stricter" than OverlayfsTeardownAllowed().
// Setup is allowed only if teardown is also allowed.
bool OverlayfsSetupAllowed(bool verbose) {
@@ -730,6 +706,25 @@
if (!OverlayfsSetupAllowed()) {
return false;
}
+
+ // Ensure kMoveMountTempDir is standalone mount tree with 'private' propagation by bind mounting
+ // to itself and set to MS_PRIVATE.
+ // Otherwise mounts moved in to it would have their propagation type changed unintentionally.
+ // Section 5d, https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
+ if (!fs_mgr_overlayfs_already_mounted(kMoveMountTempDir, false)) {
+ if (mkdir(kMoveMountTempDir, 0755) && errno != EEXIST) {
+ PERROR << "mkdir " << kMoveMountTempDir;
+ }
+ if (mount(kMoveMountTempDir, kMoveMountTempDir, nullptr, MS_BIND, nullptr)) {
+ PERROR << "bind mount " << kMoveMountTempDir;
+ }
+ }
+ fs_mgr_overlayfs_set_shared_mount(kMoveMountTempDir, false);
+ android::base::ScopeGuard umountDir([]() {
+ umount(kMoveMountTempDir);
+ rmdir(kMoveMountTempDir);
+ });
+
auto ret = true;
auto scratch_can_be_mounted = !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false);
for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
@@ -742,7 +737,7 @@
scratch_can_be_mounted = false;
TryMountScratch();
}
- ret &= fs_mgr_overlayfs_mount(entry);
+ ret &= fs_mgr_overlayfs_mount_one(entry);
}
return ret;
}
@@ -785,3 +780,38 @@
}
return false;
}
+
+namespace android {
+namespace fs_mgr {
+
+void MountOverlayfs(const FstabEntry& fstab_entry, bool* scratch_can_be_mounted) {
+ if (!OverlayfsSetupAllowed()) {
+ return;
+ }
+ const auto candidates = fs_mgr_overlayfs_candidate_list({fstab_entry});
+ if (candidates.empty()) {
+ return;
+ }
+ const auto& entry = candidates.front();
+ if (fs_mgr_is_verity_enabled(entry)) {
+ return;
+ }
+ const auto mount_point = fs_mgr_mount_point(entry.mount_point);
+ if (fs_mgr_overlayfs_already_mounted(mount_point)) {
+ return;
+ }
+ if (*scratch_can_be_mounted) {
+ *scratch_can_be_mounted = false;
+ if (!fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
+ TryMountScratch();
+ }
+ }
+ const auto options = fs_mgr_get_overlayfs_options(entry);
+ if (options.empty()) {
+ return;
+ }
+ fs_mgr_overlayfs_mount(mount_point, options);
+}
+
+} // namespace fs_mgr
+} // namespace android
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 7ba4d2b..733ba2f 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -380,8 +380,8 @@
// Now remount!
for (const auto& mnt_point : {mount_point, entry.mount_point}) {
- if (::mount(blk_device.c_str(), mnt_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
- nullptr) == 0) {
+ if (::mount(blk_device.c_str(), mnt_point.c_str(), entry.fs_type.c_str(),
+ MS_REMOUNT | MS_NOATIME, nullptr) == 0) {
LOG(INFO) << "Remounted " << mnt_point << " as RW";
return true;
}
diff --git a/fs_mgr/fs_mgr_roots.cpp b/fs_mgr/fs_mgr_roots.cpp
index 2ad8125..a697fb3 100644
--- a/fs_mgr/fs_mgr_roots.cpp
+++ b/fs_mgr/fs_mgr_roots.cpp
@@ -115,8 +115,8 @@
return true;
}
- static const std::vector<std::string> supported_fs{"ext4", "squashfs", "vfat", "f2fs", "erofs",
- "none"};
+ static const std::vector<std::string> supported_fs{"ext4", "squashfs", "vfat", "exfat", "f2fs",
+ "erofs", "none"};
if (std::find(supported_fs.begin(), supported_fs.end(), rec->fs_type) == supported_fs.end()) {
LERROR << "unknown fs_type \"" << rec->fs_type << "\" for " << mount_point;
return false;
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index bdaabbf..bf68b2c 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -30,6 +30,9 @@
namespace android {
namespace fs_mgr {
+// Mount the overlayfs override for |fstab_entry|.
+void MountOverlayfs(const FstabEntry& fstab_entry, bool* scratch_can_be_mounted);
+
void MapScratchPartitionIfNeeded(Fstab* fstab,
const std::function<bool(const std::set<std::string>&)>& init);
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index c522eaf..a0129c2 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -580,9 +580,18 @@
ASSERT_TRUE(dm.GetDmDevicePathByName("libdm-test-dm-linear", &path));
ASSERT_EQ(0, access(path.c_str(), F_OK));
+ std::string unique_path;
+ ASSERT_TRUE(dm.GetDeviceUniquePath("libdm-test-dm-linear", &unique_path));
+ ASSERT_EQ(0, access(unique_path.c_str(), F_OK));
+
ASSERT_TRUE(dm.DeleteDevice("libdm-test-dm-linear", 5s));
ASSERT_EQ(DmDeviceState::INVALID, dm.GetState("libdm-test-dm-linear"));
- ASSERT_NE(0, access(path.c_str(), F_OK));
+ // Check that unique path of this device has been deleteted.
+ // Previously this test case used to check that dev node (i.e. /dev/block/dm-XX) has been
+ // deleted. However, this introduces a race condition, ueventd will remove the unique symlink
+ // (i.e. /dev/block/mapper/by-uuid/...) **before** removing the device node, while DeleteDevice
+ // API synchronizes on the unique symlink being deleted.
+ ASSERT_NE(0, access(unique_path.c_str(), F_OK));
ASSERT_EQ(ENOENT, errno);
}
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index fb22423..be48de6 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -288,14 +288,82 @@
return false;
}
-AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta(const FstabEntry& fstab_entry,
- const std::vector<std::string>& preload_avb_key_blobs) {
+bool IsPublicKeyMatching(const FstabEntry& fstab_entry, const std::string& public_key_data,
+ const std::vector<std::string>& preload_avb_key_blobs) {
// At least one of the following should be provided for public key matching.
if (preload_avb_key_blobs.empty() && fstab_entry.avb_keys.empty()) {
LERROR << "avb_keys=/path/to/key(s) is missing for " << fstab_entry.mount_point;
- return nullptr;
+ return false;
}
+ // Expected key shouldn't be empty.
+ if (public_key_data.empty()) {
+ LERROR << "public key data shouldn't be empty for " << fstab_entry.mount_point;
+ return false;
+ }
+
+ // Performs key matching for preload_avb_key_blobs first, if it is present.
+ if (!preload_avb_key_blobs.empty()) {
+ if (std::find(preload_avb_key_blobs.begin(), preload_avb_key_blobs.end(),
+ public_key_data) != preload_avb_key_blobs.end()) {
+ return true;
+ }
+ }
+
+ // Performs key matching for fstab_entry.avb_keys if necessary.
+ // Note that it is intentional to match both preload_avb_key_blobs and fstab_entry.avb_keys.
+ // Some keys might only be available before init chroots into /system, e.g., /avb/key1
+ // in the first-stage ramdisk, while other keys might only be available after the chroot,
+ // e.g., /system/etc/avb/key2.
+ // fstab_entry.avb_keys might be either a directory containing multiple keys,
+ // or a string indicating multiple keys separated by ':'.
+ std::vector<std::string> allowed_avb_keys;
+ auto list_avb_keys_in_dir = ListFiles(fstab_entry.avb_keys);
+ if (list_avb_keys_in_dir.ok()) {
+ std::sort(list_avb_keys_in_dir->begin(), list_avb_keys_in_dir->end());
+ allowed_avb_keys = *list_avb_keys_in_dir;
+ } else {
+ allowed_avb_keys = Split(fstab_entry.avb_keys, ":");
+ }
+ return ValidatePublicKeyBlob(public_key_data, allowed_avb_keys);
+}
+
+bool IsHashtreeDescriptorRootDigestMatching(const FstabEntry& fstab_entry,
+ const std::vector<VBMetaData>& vbmeta_images,
+ const std::string& ab_suffix,
+ const std::string& ab_other_suffix) {
+ // Read expected value of hashtree descriptor root digest from fstab_entry.
+ std::string root_digest_expected;
+ if (!ReadFileToString(fstab_entry.avb_hashtree_digest, &root_digest_expected)) {
+ LERROR << "Failed to load expected root digest for " << fstab_entry.mount_point;
+ return false;
+ }
+
+ // Read actual hashtree descriptor from vbmeta image.
+ std::string partition_name = DeriveAvbPartitionName(fstab_entry, ab_suffix, ab_other_suffix);
+ if (partition_name.empty()) {
+ LERROR << "Failed to find partition name for " << fstab_entry.mount_point;
+ return false;
+ }
+ std::unique_ptr<FsAvbHashtreeDescriptor> hashtree_descriptor =
+ android::fs_mgr::GetHashtreeDescriptor(partition_name, vbmeta_images);
+ if (!hashtree_descriptor) {
+ LERROR << "Not found hashtree descriptor for " << fstab_entry.mount_point;
+ return false;
+ }
+
+ // Performs hashtree descriptor root digest matching.
+ if (hashtree_descriptor->root_digest != root_digest_expected) {
+ LERROR << "root digest (" << hashtree_descriptor->root_digest
+ << ") is different from expected value (" << root_digest_expected << ")";
+ return false;
+ }
+
+ return true;
+}
+
+AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta(const FstabEntry& fstab_entry,
+ const std::vector<std::string>& preload_avb_key_blobs) {
// Binds allow_verification_error and rollback_protection to device unlock state.
bool allow_verification_error = IsAvbPermissive();
bool rollback_protection = !allow_verification_error;
@@ -333,40 +401,24 @@
return nullptr;
}
- bool public_key_match = false;
- // Performs key matching for preload_avb_key_blobs first, if it is present.
- if (!public_key_data.empty() && !preload_avb_key_blobs.empty()) {
- if (std::find(preload_avb_key_blobs.begin(), preload_avb_key_blobs.end(),
- public_key_data) != preload_avb_key_blobs.end()) {
- public_key_match = true;
+ // Verify vbmeta image checking by either public key or hashtree descriptor root digest.
+ if (!preload_avb_key_blobs.empty() || !fstab_entry.avb_keys.empty()) {
+ if (!IsPublicKeyMatching(fstab_entry, public_key_data, preload_avb_key_blobs)) {
+ avb_handle->status_ = AvbHandleStatus::kVerificationError;
+ LWARNING << "Found unknown public key used to sign " << fstab_entry.mount_point;
+ if (!allow_verification_error) {
+ LERROR << "Unknown public key is not allowed";
+ return nullptr;
+ }
}
- }
- // Performs key matching for fstab_entry.avb_keys if necessary.
- // Note that it is intentional to match both preload_avb_key_blobs and fstab_entry.avb_keys.
- // Some keys might only be availble before init chroots into /system, e.g., /avb/key1
- // in the first-stage ramdisk, while other keys might only be available after the chroot,
- // e.g., /system/etc/avb/key2.
- if (!public_key_data.empty() && !public_key_match) {
- // fstab_entry.avb_keys might be either a directory containing multiple keys,
- // or a string indicating multiple keys separated by ':'.
- std::vector<std::string> allowed_avb_keys;
- auto list_avb_keys_in_dir = ListFiles(fstab_entry.avb_keys);
- if (list_avb_keys_in_dir.ok()) {
- std::sort(list_avb_keys_in_dir->begin(), list_avb_keys_in_dir->end());
- allowed_avb_keys = *list_avb_keys_in_dir;
- } else {
- allowed_avb_keys = Split(fstab_entry.avb_keys, ":");
- }
- if (ValidatePublicKeyBlob(public_key_data, allowed_avb_keys)) {
- public_key_match = true;
- }
- }
-
- if (!public_key_match) {
+ } else if (!IsHashtreeDescriptorRootDigestMatching(fstab_entry, avb_handle->vbmeta_images_,
+ avb_handle->slot_suffix_,
+ avb_handle->other_slot_suffix_)) {
avb_handle->status_ = AvbHandleStatus::kVerificationError;
- LWARNING << "Found unknown public key used to sign " << fstab_entry.mount_point;
+ LWARNING << "Found unknown hashtree descriptor root digest used on "
+ << fstab_entry.mount_point;
if (!allow_verification_error) {
- LERROR << "Unknown public key is not allowed";
+ LERROR << "Verification based on root digest failed. Vbmeta image is not allowed.";
return nullptr;
}
}
diff --git a/fs_mgr/libfstab/fstab.cpp b/fs_mgr/libfstab/fstab.cpp
index 32460b1..6fa22fe 100644
--- a/fs_mgr/libfstab/fstab.cpp
+++ b/fs_mgr/libfstab/fstab.cpp
@@ -286,6 +286,10 @@
}
} else if (StartsWith(flag, "avb_keys=")) { // must before the following "avb"
entry->avb_keys = arg;
+ } else if (StartsWith(flag, "avb_hashtree_digest=")) {
+ // "avb_hashtree_digest" must before the following "avb"
+ // The path where hex-encoded hashtree descriptor root digest is located.
+ entry->avb_hashtree_digest = arg;
} else if (StartsWith(flag, "avb")) {
entry->fs_mgr_flags.avb = true;
entry->vbmeta_partition = arg;
@@ -716,7 +720,7 @@
if (!ReadFstabFromFileCommon(path, fstab)) {
return false;
}
- if (path != kProcMountsPath) {
+ if (path != kProcMountsPath && !InRecovery()) {
if (!access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
std::string dsu_slot;
if (!android::gsi::GetActiveDsu(&dsu_slot)) {
diff --git a/fs_mgr/libfstab/include/fstab/fstab.h b/fs_mgr/libfstab/include/fstab/fstab.h
index 09471f0..5e4019c 100644
--- a/fs_mgr/libfstab/include/fstab/fstab.h
+++ b/fs_mgr/libfstab/include/fstab/fstab.h
@@ -57,6 +57,7 @@
uint64_t zram_backingdev_size = 0;
std::string avb_keys;
std::string lowerdir;
+ std::string avb_hashtree_digest;
struct FsMgrFlags {
bool wait : 1;
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 6cb2c51..4e6e97b 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -1211,6 +1211,15 @@
header_.flags |= LP_HEADER_FLAG_VIRTUAL_AB_DEVICE;
}
+void MetadataBuilder::SetOverlaysActiveFlag(bool flag) {
+ RequireExpandedMetadataHeader();
+ if (flag) {
+ header_.flags |= LP_HEADER_FLAG_OVERLAYS_ACTIVE;
+ } else {
+ header_.flags &= ~LP_HEADER_FLAG_OVERLAYS_ACTIVE;
+ }
+}
+
bool MetadataBuilder::IsABDevice() {
return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty();
}
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index 54f31bc..957b96b 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -346,6 +346,8 @@
void SetAutoSlotSuffixing();
// Set the LP_HEADER_FLAG_VIRTUAL_AB_DEVICE flag.
void SetVirtualABDeviceFlag();
+ // Set or unset the LP_HEADER_FLAG_OVERLAYS_ACTIVE flag.
+ void SetOverlaysActiveFlag(bool flag);
bool GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const;
bool UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& info);
diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h
index 41d8b0c..8d77097 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -240,6 +240,9 @@
*/
#define LP_HEADER_FLAG_VIRTUAL_AB_DEVICE 0x1
+/* This device has overlays activated via "adb remount". */
+#define LP_HEADER_FLAG_OVERLAYS_ACTIVE 0x2
+
/* This struct defines a logical partition entry, similar to what would be
* present in a GUID Partition Table.
*/
diff --git a/fs_mgr/liblp/super_layout_builder.cpp b/fs_mgr/liblp/super_layout_builder.cpp
index 5349e41..fd7416b 100644
--- a/fs_mgr/liblp/super_layout_builder.cpp
+++ b/fs_mgr/liblp/super_layout_builder.cpp
@@ -17,6 +17,8 @@
#include <liblp/liblp.h>
+#include <algorithm>
+
#include "images.h"
#include "utility.h"
#include "writer.h"
diff --git a/fs_mgr/liblp/utility.cpp b/fs_mgr/liblp/utility.cpp
index d8e171b..70c7b79 100644
--- a/fs_mgr/liblp/utility.cpp
+++ b/fs_mgr/liblp/utility.cpp
@@ -25,6 +25,7 @@
#include <sys/ioctl.h>
#endif
+#include <algorithm>
#include <map>
#include <string>
#include <vector>
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index fa04c43..7e97dc0 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -114,6 +114,9 @@
// Enable batching for COW writes
bool batched_writes = 14;
+
+ // Size of v3 operation buffer. Needs to be determined during writer initialization
+ uint64 estimated_ops_buffer_size = 15;
}
// Next: 8
@@ -250,4 +253,7 @@
// Whether this update attempt used io_uring.
bool iouring_used = 13;
+
+ // Size of v3 operation buffer. Needs to be determined during writer initialization
+ uint64 estimated_op_count_max = 14;
}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 75467cb..9401c66 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -16,8 +16,10 @@
#include <stdint.h>
+#include <limits>
#include <optional>
#include <string_view>
+#include <type_traits>
namespace android {
namespace snapshot {
@@ -32,7 +34,7 @@
static constexpr uint32_t kCowVersionManifest = 2;
static constexpr uint32_t kMinCowVersion = 1;
-static constexpr uint32_t kMaxCowVersion = 2;
+static constexpr uint32_t kMaxCowVersion = 3;
// Normally, this should be kMaxCowVersion. When a new version is under testing
// it may be the previous value of kMaxCowVersion.
@@ -105,11 +107,11 @@
static constexpr uint8_t kNumResumePoints = 4;
struct CowHeaderV3 : public CowHeader {
- // Location of sequence buffer in COW.
- uint64_t sequence_buffer_offset;
- // number of currently written resume points
+ // Number of sequence data stored (each of which is a 32 bit integer)
+ uint64_t sequence_data_count;
+ // Number of currently written resume points &&
uint32_t resume_point_count;
- // Size, in bytes, of the CowResumePoint buffer.
+ // Number of max resume points that can be written
uint32_t resume_point_max;
// Number of CowOperationV3 structs in the operation buffer, currently and total
// region size.
@@ -119,10 +121,30 @@
uint32_t compression_algorithm;
} __attribute__((packed));
+enum class CowOperationType : uint8_t {
+ kCowCopyOp = 1,
+ kCowReplaceOp = 2,
+ kCowZeroOp = 3,
+ kCowLabelOp = 4,
+ kCowClusterOp = 5,
+ kCowXorOp = 6,
+ kCowSequenceOp = 7,
+ kCowFooterOp = std::numeric_limits<uint8_t>::max(),
+};
+
+static constexpr CowOperationType kCowCopyOp = CowOperationType::kCowCopyOp;
+static constexpr CowOperationType kCowReplaceOp = CowOperationType::kCowReplaceOp;
+static constexpr CowOperationType kCowZeroOp = CowOperationType::kCowZeroOp;
+static constexpr CowOperationType kCowLabelOp = CowOperationType::kCowLabelOp;
+static constexpr CowOperationType kCowClusterOp = CowOperationType::kCowClusterOp;
+static constexpr CowOperationType kCowXorOp = CowOperationType::kCowXorOp;
+static constexpr CowOperationType kCowSequenceOp = CowOperationType::kCowSequenceOp;
+static constexpr CowOperationType kCowFooterOp = CowOperationType::kCowFooterOp;
+
// This structure is the same size of a normal Operation, but is repurposed for the footer.
struct CowFooterOperation {
// The operation code (always kCowFooterOp).
- uint8_t type;
+ CowOperationType type;
// If this operation reads from the data section of the COW, this contains
// the compression type of that data (see constants below).
@@ -141,7 +163,7 @@
// V2 version of COW. On disk format for older devices
struct CowOperationV2 {
// The operation code (see the constants and structures below).
- uint8_t type;
+ CowOperationType type;
// If this operation reads from the data section of the COW, this contains
// the compression type of that data (see constants below).
@@ -173,11 +195,12 @@
uint64_t source;
} __attribute__((packed));
+static constexpr uint64_t kCowOpSourceInfoDataMask = (1ULL << 48) - 1;
+static constexpr uint64_t kCowOpSourceInfoTypeBit = 60;
+static constexpr uint64_t kCowOpSourceInfoTypeNumBits = 4;
+static constexpr uint64_t kCowOpSourceInfoTypeMask = (1ULL << kCowOpSourceInfoTypeNumBits) - 1;
// The on disk format of cow (currently == CowOperation)
struct CowOperationV3 {
- // The operation code (see the constants and structures below).
- uint8_t type;
-
// If this operation reads from the data section of the COW, this contains
// the length.
uint16_t data_length;
@@ -185,6 +208,10 @@
// The block of data in the new image that this operation modifies.
uint32_t new_block;
+ // source_info with have the following layout
+ // |---4 bits ---| ---12 bits---| --- 48 bits ---|
+ // |--- type --- | -- unused -- | --- source --- |
+ //
// The value of |source| depends on the operation code.
//
// CopyOp: a 32-bit block location in the source image.
@@ -196,20 +223,34 @@
// For ops other than Label:
// Bits 47-62 are reserved and must be zero.
// A block is compressed if it’s data is < block_sz
- uint64_t source_info;
+ uint64_t source_info_;
+ constexpr uint64_t source() const { return source_info_ & kCowOpSourceInfoDataMask; }
+ constexpr void set_source(uint64_t source) {
+ // Clear the first 48 bit first
+ source_info_ &= ~kCowOpSourceInfoDataMask;
+ // Set the actual source field
+ source_info_ |= source & kCowOpSourceInfoDataMask;
+ }
+ constexpr CowOperationType type() const {
+ // this is a mask to grab the first 4 bits of a 64 bit integer
+ const auto type = (source_info_ >> kCowOpSourceInfoTypeBit) & kCowOpSourceInfoTypeMask;
+ return static_cast<CowOperationType>(type);
+ }
+ constexpr void set_type(CowOperationType type) {
+ // Clear the top 4 bits first
+ source_info_ &= ((1ULL << kCowOpSourceInfoTypeBit) - 1);
+ // set the actual type bits
+ source_info_ |= (static_cast<uint64_t>(type) & kCowOpSourceInfoTypeMask)
+ << kCowOpSourceInfoTypeBit;
+ }
} __attribute__((packed));
+// Ensure that getters/setters added to CowOperationV3 does not increases size
+// of CowOperationV3 struct(no virtual method tables added).
+static_assert(std::is_trivially_copyable_v<CowOperationV3>);
+static_assert(std::is_standard_layout_v<CowOperationV3>);
static_assert(sizeof(CowOperationV2) == sizeof(CowFooterOperation));
-static constexpr uint8_t kCowCopyOp = 1;
-static constexpr uint8_t kCowReplaceOp = 2;
-static constexpr uint8_t kCowZeroOp = 3;
-static constexpr uint8_t kCowLabelOp = 4;
-static constexpr uint8_t kCowClusterOp = 5;
-static constexpr uint8_t kCowXorOp = 6;
-static constexpr uint8_t kCowSequenceOp = 7;
-static constexpr uint8_t kCowFooterOp = -1;
-
enum CowCompressionAlgorithm : uint8_t {
kCowCompressNone = 0,
kCowCompressGz = 1,
@@ -226,25 +267,23 @@
static constexpr uint8_t kCowReadAheadInProgress = 1;
static constexpr uint8_t kCowReadAheadDone = 2;
-static constexpr uint64_t kCowOpSourceInfoDataMask = (1ULL << 48) - 1;
-
-static inline uint64_t GetCowOpSourceInfoData(const CowOperation& op) {
- return op.source_info & kCowOpSourceInfoDataMask;
-}
-
-static constexpr off_t GetOpOffset(uint32_t op_index, const CowHeaderV3 header) {
- return header.prefix.header_size + header.buffer_size +
- (header.resume_point_max * sizeof(ResumePoint)) + (op_index * sizeof(CowOperationV3));
-}
-static constexpr off_t GetDataOffset(const CowHeaderV3 header) {
- return header.prefix.header_size + header.buffer_size +
- (header.resume_point_max * sizeof(ResumePoint)) +
- header.op_count_max * sizeof(CowOperation);
-}
-static constexpr off_t GetResumeOffset(const CowHeaderV3 header) {
+static constexpr off_t GetSequenceOffset(const CowHeaderV3& header) {
return header.prefix.header_size + header.buffer_size;
}
+static constexpr off_t GetResumeOffset(const CowHeaderV3& header) {
+ return GetSequenceOffset(header) + (header.sequence_data_count * sizeof(uint32_t));
+}
+
+static constexpr off_t GetOpOffset(uint32_t op_index, const CowHeaderV3& header) {
+ return GetResumeOffset(header) + (header.resume_point_max * sizeof(ResumePoint)) +
+ (op_index * sizeof(CowOperationV3));
+}
+
+static constexpr off_t GetDataOffset(const CowHeaderV3& header) {
+ return GetOpOffset(header.op_count_max, header);
+}
+
struct CowFooter {
CowFooterOperation op;
uint8_t unused[64];
@@ -268,10 +307,12 @@
std::ostream& operator<<(std::ostream& os, CowOperationV2 const& arg);
-std::ostream& operator<<(std::ostream& os, CowOperation const& arg);
+std::ostream& operator<<(std::ostream& os, CowOperationV3 const& arg);
std::ostream& operator<<(std::ostream& os, ResumePoint const& arg);
+std::ostream& operator<<(std::ostream& os, CowOperationType cow_type);
+
int64_t GetNextOpOffset(const CowOperationV2& op, uint32_t cluster_size);
int64_t GetNextDataOffset(const CowOperationV2& op, uint32_t cluster_size);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index c87b32d..bf4c79f 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -16,8 +16,6 @@
#include <stdint.h>
-#include <deque>
-#include <functional>
#include <memory>
#include <optional>
#include <unordered_map>
@@ -169,6 +167,12 @@
private:
bool ParseV2(android::base::borrowed_fd fd, std::optional<uint64_t> label);
bool PrepMergeOps();
+ // sequence data is stored as an operation with actual data residing in the data offset.
+ bool GetSequenceDataV2(std::vector<uint32_t>* merge_op_blocks, std::vector<int>* other_ops,
+ std::unordered_map<uint32_t, int>* block_map);
+ // v3 of the cow writes sequence data within its own separate sequence buffer.
+ bool GetSequenceData(std::vector<uint32_t>* merge_op_blocks, std::vector<int>* other_ops,
+ std::unordered_map<uint32_t, int>* block_map);
uint64_t FindNumCopyops();
uint8_t GetCompressionType();
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 5b1e56c..7df976d 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -33,7 +33,10 @@
namespace android {
namespace snapshot {
-
+struct CowSizeInfo {
+ uint64_t cow_size;
+ uint64_t op_count_max;
+};
struct CowOptions {
uint32_t block_size = 4096;
std::string compression;
@@ -56,7 +59,7 @@
bool batch_write = false;
// Size of the cow operation buffer; used in v3 only.
- uint32_t op_count_max = 0;
+ uint64_t op_count_max = 0;
};
// Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -92,8 +95,9 @@
// to ensure that the correct headers and footers are written.
virtual bool Finalize() = 0;
- // Return number of bytes the cow image occupies on disk.
- virtual uint64_t GetCowSize() = 0;
+ // Return number of bytes the cow image occupies on disk + the size of sequence && ops buffer
+ // The latter two fields are used in v3 cow format and left as 0 for v2 cow format
+ virtual CowSizeInfo GetCowSizeInfo() const = 0;
virtual uint32_t GetBlockSize() const = 0;
virtual std::optional<uint32_t> GetMaxBlocks() const = 0;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
index c58c654..8491fb0 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h
@@ -24,8 +24,7 @@
using FileDescriptor = chromeos_update_engine::FileDescriptor;
MOCK_METHOD(bool, Finalize, (), (override));
-
- MOCK_METHOD(uint64_t, GetCowSize, (), (override));
+ MOCK_METHOD(CowSizeInfo, GetCowSizeInfo, (), (const, override));
MOCK_METHOD(bool, AddCopy, (uint64_t, uint64_t, uint64_t), (override));
MOCK_METHOD(bool, AddRawBlocks, (uint64_t, const void*, size_t), (override));
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 08a79ba..d102863 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -826,6 +826,9 @@
bool DeleteDeviceIfExists(const std::string& name,
const std::chrono::milliseconds& timeout_ms = {});
+ // Set read-ahead size during OTA
+ void SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb);
+
android::dm::IDeviceMapper& dm_;
std::unique_ptr<IDeviceInfo> device_;
std::string metadata_dir_;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index 937065d..8d1786c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -14,7 +14,6 @@
// limitations under the License.
//
-#include <inttypes.h>
#include <libsnapshot/cow_format.h>
#include <sys/types.h>
#include <unistd.h>
@@ -30,7 +29,7 @@
using android::base::unique_fd;
-std::ostream& EmitCowTypeString(std::ostream& os, uint8_t cow_type) {
+std::ostream& EmitCowTypeString(std::ostream& os, CowOperationType cow_type) {
switch (cow_type) {
case kCowCopyOp:
return os << "kCowCopyOp";
@@ -53,6 +52,10 @@
}
}
+std::ostream& operator<<(std::ostream& os, CowOperationType cow_type) {
+ return EmitCowTypeString(os, cow_type);
+}
+
std::ostream& operator<<(std::ostream& os, CowOperationV2 const& op) {
os << "CowOperationV2(";
EmitCowTypeString(os, op.type) << ", ";
@@ -80,22 +83,21 @@
return os;
}
-std::ostream& operator<<(std::ostream& os, CowOperation const& op) {
+std::ostream& operator<<(std::ostream& os, CowOperationV3 const& op) {
os << "CowOperation(";
- EmitCowTypeString(os, op.type);
- if (op.type == kCowReplaceOp || op.type == kCowXorOp || op.type == kCowSequenceOp) {
+ EmitCowTypeString(os, op.type());
+ if (op.type() == kCowReplaceOp || op.type() == kCowXorOp || op.type() == kCowSequenceOp) {
os << ", data_length:" << op.data_length;
}
- if (op.type != kCowClusterOp && op.type != kCowSequenceOp && op.type != kCowLabelOp) {
+ if (op.type() != kCowClusterOp && op.type() != kCowSequenceOp && op.type() != kCowLabelOp) {
os << ", new_block:" << op.new_block;
}
- if (op.type == kCowXorOp || op.type == kCowReplaceOp || op.type == kCowCopyOp) {
- os << ", source:" << (op.source_info & kCowOpSourceInfoDataMask);
- } else if (op.type == kCowClusterOp) {
- os << ", cluster_data:" << (op.source_info & kCowOpSourceInfoDataMask);
- } else {
- os << ", label:0x" << android::base::StringPrintf("%" PRIx64, op.source_info);
+ if (op.type() == kCowXorOp || op.type() == kCowReplaceOp || op.type() == kCowCopyOp) {
+ os << ", source:" << op.source();
+ } else if (op.type() == kCowClusterOp) {
+ os << ", cluster_data:" << op.source();
}
+ // V3 op stores resume points in header, so CowOp can never be Label.
os << ")";
return os;
}
@@ -126,7 +128,7 @@
}
bool IsMetadataOp(const CowOperation& op) {
- switch (op.type) {
+ switch (op.type()) {
case kCowLabelOp:
case kCowClusterOp:
case kCowFooterOp:
@@ -138,7 +140,7 @@
}
bool IsOrderedOp(const CowOperation& op) {
- switch (op.type) {
+ switch (op.type()) {
case kCowCopyOp:
case kCowXorOp:
return true;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 8412879..1b4a971 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -24,6 +24,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <libsnapshot/cow_format.h>
#include <libsnapshot/cow_reader.h>
#include <zlib.h>
@@ -265,52 +266,31 @@
// Replace-op-4, Zero-op-9, Replace-op-5 }
//==============================================================
bool CowReader::PrepMergeOps() {
- auto merge_op_blocks = std::make_unique<std::vector<uint32_t>>();
std::vector<int> other_ops;
- auto seq_ops_set = std::unordered_set<uint32_t>();
- auto block_map = std::make_unique<std::unordered_map<uint32_t, int>>();
- size_t num_seqs = 0;
- size_t read;
+ std::vector<uint32_t> merge_op_blocks;
+ std::unordered_map<uint32_t, int> block_map;
- for (size_t i = 0; i < ops_->size(); i++) {
- auto& current_op = ops_->data()[i];
-
- if (current_op.type == kCowSequenceOp) {
- size_t seq_len = current_op.data_length / sizeof(uint32_t);
-
- merge_op_blocks->resize(merge_op_blocks->size() + seq_len);
- if (!GetRawBytes(¤t_op, &merge_op_blocks->data()[num_seqs],
- current_op.data_length, &read)) {
- PLOG(ERROR) << "Failed to read sequence op!";
- return false;
- }
- for (size_t j = num_seqs; j < num_seqs + seq_len; j++) {
- seq_ops_set.insert(merge_op_blocks->data()[j]);
- }
- num_seqs += seq_len;
- }
-
- if (IsMetadataOp(current_op)) {
- continue;
- }
-
- // Sequence ops must be the first ops in the stream.
- if (seq_ops_set.empty() && IsOrderedOp(current_op)) {
- merge_op_blocks->emplace_back(current_op.new_block);
- } else if (seq_ops_set.count(current_op.new_block) == 0) {
- other_ops.push_back(current_op.new_block);
- }
- block_map->insert({current_op.new_block, i});
+ switch (header_.prefix.major_version) {
+ case 1:
+ case 2:
+ GetSequenceDataV2(&merge_op_blocks, &other_ops, &block_map);
+ break;
+ case 3:
+ GetSequenceData(&merge_op_blocks, &other_ops, &block_map);
+ break;
+ default:
+ break;
}
- for (auto block : *merge_op_blocks) {
- if (block_map->count(block) == 0) {
+
+ for (auto block : merge_op_blocks) {
+ if (block_map.count(block) == 0) {
LOG(ERROR) << "Invalid Sequence Ops. Could not find Cow Op for new block " << block;
return false;
}
}
- if (merge_op_blocks->size() > header_.num_merge_ops) {
- num_ordered_ops_to_merge_ = merge_op_blocks->size() - header_.num_merge_ops;
+ if (merge_op_blocks.size() > header_.num_merge_ops) {
+ num_ordered_ops_to_merge_ = merge_op_blocks.size() - header_.num_merge_ops;
} else {
num_ordered_ops_to_merge_ = 0;
}
@@ -326,9 +306,9 @@
std::sort(other_ops.begin(), other_ops.end(), std::greater<int>());
}
- merge_op_blocks->insert(merge_op_blocks->end(), other_ops.begin(), other_ops.end());
+ merge_op_blocks.insert(merge_op_blocks.end(), other_ops.begin(), other_ops.end());
- num_total_data_ops_ = merge_op_blocks->size();
+ num_total_data_ops_ = merge_op_blocks.size();
if (header_.num_merge_ops > 0) {
merge_op_start_ = header_.num_merge_ops;
}
@@ -338,24 +318,94 @@
// the ops vector as required for merge operations.
auto merge_ops_buffer = std::make_shared<std::vector<CowOperation>>();
merge_ops_buffer->reserve(num_total_data_ops_);
- for (auto block : *merge_op_blocks) {
- merge_ops_buffer->emplace_back(ops_->data()[block_map->at(block)]);
+ for (auto block : merge_op_blocks) {
+ merge_ops_buffer->emplace_back(ops_->data()[block_map.at(block)]);
}
ops_->clear();
ops_ = merge_ops_buffer;
ops_->shrink_to_fit();
} else {
- for (auto block : *merge_op_blocks) {
- block_pos_index_->push_back(block_map->at(block));
+ for (auto block : merge_op_blocks) {
+ block_pos_index_->push_back(block_map.at(block));
}
}
- block_map->clear();
- merge_op_blocks->clear();
+ block_map.clear();
+ merge_op_blocks.clear();
return true;
}
+bool CowReader::GetSequenceDataV2(std::vector<uint32_t>* merge_op_blocks,
+ std::vector<int>* other_ops,
+ std::unordered_map<uint32_t, int>* block_map) {
+ auto seq_ops_set = std::unordered_set<uint32_t>();
+ size_t num_seqs = 0;
+ size_t read;
+ for (size_t i = 0; i < ops_->size(); i++) {
+ auto& current_op = ops_->data()[i];
+
+ if (current_op.type() == kCowSequenceOp) {
+ size_t seq_len = current_op.data_length / sizeof(uint32_t);
+
+ merge_op_blocks->resize(merge_op_blocks->size() + seq_len);
+ if (!GetRawBytes(¤t_op, &merge_op_blocks->data()[num_seqs],
+ current_op.data_length, &read)) {
+ PLOG(ERROR) << "Failed to read sequence op!";
+ return false;
+ }
+ for (size_t j = num_seqs; j < num_seqs + seq_len; j++) {
+ seq_ops_set.insert(merge_op_blocks->at(j));
+ }
+ num_seqs += seq_len;
+ }
+
+ if (IsMetadataOp(current_op)) {
+ continue;
+ }
+
+ // Sequence ops must be the first ops in the stream.
+ if (seq_ops_set.empty() && IsOrderedOp(current_op)) {
+ merge_op_blocks->emplace_back(current_op.new_block);
+ } else if (seq_ops_set.count(current_op.new_block) == 0) {
+ other_ops->push_back(current_op.new_block);
+ }
+ block_map->insert({current_op.new_block, i});
+ }
+ return false;
+}
+
+bool CowReader::GetSequenceData(std::vector<uint32_t>* merge_op_blocks, std::vector<int>* other_ops,
+ std::unordered_map<uint32_t, int>* block_map) {
+ std::unordered_set<uint32_t> seq_ops_set;
+ // read sequence ops data
+ merge_op_blocks->resize(header_.sequence_data_count);
+ if (!android::base::ReadFullyAtOffset(
+ fd_, merge_op_blocks->data(),
+ header_.sequence_data_count * sizeof(merge_op_blocks->at(0)),
+ GetSequenceOffset(header_))) {
+ PLOG(ERROR) << "failed to read sequence buffer. seq_data_count: "
+ << header_.sequence_data_count << " at offset: " << GetSequenceOffset(header_);
+ return false;
+ }
+ seq_ops_set.reserve(merge_op_blocks->size());
+ for (auto& i : *merge_op_blocks) {
+ seq_ops_set.insert(i);
+ }
+ // read ordered op data
+ for (size_t i = 0; i < ops_->size(); i++) {
+ auto& current_op = ops_->data()[i];
+ // Sequence ops must be the first ops in the stream.
+ if (seq_ops_set.empty()) {
+ merge_op_blocks->emplace_back(current_op.new_block);
+ } else if (seq_ops_set.count(current_op.new_block) == 0) {
+ other_ops->push_back(current_op.new_block);
+ }
+ block_map->insert({current_op.new_block, i});
+ }
+ return true;
+}
+
bool CowReader::VerifyMergeOps() {
auto itr = GetMergeOpIter(true);
std::unordered_map<uint64_t, const CowOperation*> overwritten_blocks;
@@ -587,11 +637,11 @@
}
bool CowReader::GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read) {
- switch (op->type) {
+ switch (op->type()) {
case kCowSequenceOp:
case kCowReplaceOp:
case kCowXorOp:
- return GetRawBytes(GetCowOpSourceInfoData(*op), buffer, len, read);
+ return GetRawBytes(op->source(), buffer, len, read);
default:
LOG(ERROR) << "Cannot get raw bytes of non-data op: " << *op;
return false;
@@ -680,10 +730,10 @@
}
uint64_t offset;
- if (op->type == kCowXorOp) {
+ if (op->type() == kCowXorOp) {
offset = xor_data_loc_->at(op->new_block);
} else {
- offset = GetCowOpSourceInfoData(*op);
+ offset = op->source();
}
if (!decompressor ||
((op->data_length == header_.block_size) && (header_.prefix.major_version == 3))) {
@@ -697,12 +747,12 @@
}
bool CowReader::GetSourceOffset(const CowOperation* op, uint64_t* source_offset) {
- switch (op->type) {
+ switch (op->type()) {
case kCowCopyOp:
- *source_offset = GetCowOpSourceInfoData(*op) * header_.block_size;
+ *source_offset = op->source() * header_.block_size;
return true;
case kCowXorOp:
- *source_offset = GetCowOpSourceInfoData(*op);
+ *source_offset = op->source();
return true;
default:
return false;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 993630b..3718851 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -199,7 +199,7 @@
if (!FLAGS_silent && FLAGS_show_ops) std::cout << *op << "\n";
- if ((FLAGS_decompress || extract_to >= 0) && op->type == kCowReplaceOp) {
+ if ((FLAGS_decompress || extract_to >= 0) && op->type() == kCowReplaceOp) {
if (reader.ReadData(op, buffer.data(), buffer.size()) < 0) {
std::cerr << "Failed to decompress for :" << *op << "\n";
success = false;
@@ -213,12 +213,12 @@
return false;
}
}
- } else if (extract_to >= 0 && !IsMetadataOp(*op) && op->type != kCowZeroOp) {
+ } else if (extract_to >= 0 && !IsMetadataOp(*op) && op->type() != kCowZeroOp) {
PLOG(ERROR) << "Cannot extract op yet: " << *op;
return false;
}
- if (op->type == kCowSequenceOp && FLAGS_show_merge_sequence) {
+ if (op->type() == kCowSequenceOp && FLAGS_show_merge_sequence) {
size_t read;
std::vector<uint32_t> merge_op_blocks;
size_t seq_len = op->data_length / sizeof(uint32_t);
@@ -236,13 +236,13 @@
}
}
- if (op->type == kCowCopyOp) {
+ if (op->type() == kCowCopyOp) {
copy_ops++;
- } else if (op->type == kCowReplaceOp) {
+ } else if (op->type() == kCowReplaceOp) {
replace_ops++;
- } else if (op->type == kCowZeroOp) {
+ } else if (op->type() == kCowZeroOp) {
zero_ops++;
- } else if (op->type == kCowXorOp) {
+ } else if (op->type() == kCowXorOp) {
xor_ops++;
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
index 08a43a4..fe977b7 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
@@ -205,7 +205,7 @@
const auto& v2_op = v2_ops_->at(i);
auto& new_op = out->ops->at(i);
- new_op.type = v2_op.type;
+ new_op.set_type(v2_op.type);
new_op.data_length = v2_op.data_length;
if (v2_op.new_block > std::numeric_limits<uint32_t>::max()) {
@@ -215,7 +215,7 @@
new_op.new_block = v2_op.new_block;
uint64_t source_info = v2_op.source;
- if (new_op.type != kCowLabelOp) {
+ if (new_op.type() != kCowLabelOp) {
source_info &= kCowOpSourceInfoDataMask;
if (source_info != v2_op.source) {
LOG(ERROR) << "Out-of-range source value in COW op: " << v2_op;
@@ -232,7 +232,7 @@
return false;
}
}
- new_op.source_info = source_info;
+ new_op.set_source(source_info);
}
out->header = header_;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
index 52c6348..036d335 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
@@ -79,7 +79,7 @@
return android::base::ReadFullyAtOffset(fd, resume_points_->data(),
header_.resume_point_count * sizeof(ResumePoint),
- header_.prefix.header_size + header_.buffer_size);
+ GetResumeOffset(header_));
}
std::optional<uint32_t> CowParserV3::FindResumeOp(const uint64_t label) {
@@ -112,8 +112,14 @@
xor_data_loc_ = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
for (auto op : *ops_) {
- if (op.type == kCowXorOp) {
+ if (op.type() == kCowXorOp) {
xor_data_loc_->insert({op.new_block, data_pos});
+ } else if (op.type() == kCowReplaceOp) {
+ if (data_pos != op.source()) {
+ LOG(ERROR) << "Invalid data location for operation " << op
+ << ", expected: " << data_pos;
+ return false;
+ }
}
data_pos += op.data_length;
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
index a3e40d9..4e90a0f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
@@ -147,7 +147,7 @@
op = ops_[chunk];
}
- if (!op || op->type == kCowCopyOp) {
+ if (!op || op->type() == kCowCopyOp) {
borrowed_fd fd = GetSourceFd();
if (fd < 0) {
// GetSourceFd sets errno.
@@ -169,15 +169,15 @@
// ReadFullyAtOffset sets errno.
return -1;
}
- } else if (op->type == kCowZeroOp) {
+ } else if (op->type() == kCowZeroOp) {
memset(buffer, 0, bytes_to_read);
- } else if (op->type == kCowReplaceOp) {
+ } else if (op->type() == kCowReplaceOp) {
if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
errno = EIO;
return -1;
}
- } else if (op->type == kCowXorOp) {
+ } else if (op->type() == kCowXorOp) {
borrowed_fd fd = GetSourceFd();
if (fd < 0) {
// GetSourceFd sets errno.
@@ -208,7 +208,7 @@
((char*)buffer)[i] ^= data[i];
}
} else {
- LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type);
+ LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type());
errno = EINVAL;
return -1;
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
index 2709059..1d1d24c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
@@ -85,10 +85,10 @@
size_t i = 0;
while (!iter->AtEnd()) {
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowCopyOp);
+ ASSERT_EQ(op->type(), kCowCopyOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 10 + i);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 1000 + i);
+ ASSERT_EQ(op->source(), 1000 + i);
iter->Next();
i += 1;
}
@@ -131,10 +131,10 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowCopyOp);
+ ASSERT_EQ(op->type(), kCowCopyOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 10);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 20);
+ ASSERT_EQ(op->source(), 20);
std::string sink(data.size(), '\0');
@@ -142,7 +142,7 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_EQ(op->data_length, 4096);
ASSERT_EQ(op->new_block, 50);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -153,19 +153,19 @@
op = iter->Get();
// Note: the zero operation gets split into two blocks.
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 51);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+ ASSERT_EQ(op->source(), 0);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 52);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+ ASSERT_EQ(op->source(), 0);
iter->Next();
ASSERT_TRUE(iter->AtEnd());
@@ -206,10 +206,10 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowCopyOp);
+ ASSERT_EQ(op->type(), kCowCopyOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 10);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 20);
+ ASSERT_EQ(op->source(), 20);
std::string sink(data.size(), '\0');
@@ -217,10 +217,10 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowXorOp);
+ ASSERT_EQ(op->type(), kCowXorOp);
ASSERT_EQ(op->data_length, 4096);
ASSERT_EQ(op->new_block, 50);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314); // 4096 * 24 + 10
+ ASSERT_EQ(op->source(), 98314); // 4096 * 24 + 10
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, data);
@@ -229,19 +229,19 @@
op = iter->Get();
// Note: the zero operation gets split into two blocks.
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 51);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+ ASSERT_EQ(op->source(), 0);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 52);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+ ASSERT_EQ(op->source(), 0);
iter->Next();
ASSERT_TRUE(iter->AtEnd());
@@ -273,7 +273,7 @@
std::string sink(data.size(), '\0');
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_EQ(op->data_length, 56); // compressed!
ASSERT_EQ(op->new_block, 50);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -325,16 +325,16 @@
while (!iter->AtEnd()) {
auto op = iter->Get();
- if (op->type == kCowXorOp) {
+ if (op->type() == kCowXorOp) {
total_blocks += 1;
std::string sink(xor_data.size(), '\0');
ASSERT_EQ(op->new_block, 50);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314); // 4096 * 24 + 10
+ ASSERT_EQ(op->source(), 98314); // 4096 * 24 + 10
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, xor_data);
}
- if (op->type == kCowReplaceOp) {
+ if (op->type() == kCowReplaceOp) {
total_blocks += 1;
if (op->new_block == 100) {
data.resize(options.block_size);
@@ -399,7 +399,7 @@
while (!iter->AtEnd()) {
auto op = iter->Get();
- if (op->type == kCowReplaceOp) {
+ if (op->type() == kCowReplaceOp) {
total_blocks += 1;
if (op->new_block == 50) {
data.resize(options.block_size);
@@ -519,7 +519,7 @@
std::string sink(data.size(), '\0');
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_EQ(op->data_length, 56); // compressed!
ASSERT_EQ(op->new_block, 50);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -529,7 +529,7 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowClusterOp);
+ ASSERT_EQ(op->type(), kCowClusterOp);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
@@ -546,7 +546,7 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowClusterOp);
+ ASSERT_EQ(op->type(), kCowClusterOp);
iter->Next();
ASSERT_TRUE(iter->AtEnd());
@@ -580,7 +580,7 @@
std::string sink(options.block_size, '\0');
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_EQ(op->new_block, 51);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
}
@@ -601,14 +601,14 @@
ASSERT_TRUE(writer.AddCopy(10, 20));
ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size()));
ASSERT_TRUE(writer.AddZeroBlocks(51, 2));
- auto size_before = writer.GetCowSize();
+ auto size_before = writer.GetCowSizeInfo().cow_size;
ASSERT_TRUE(writer.Finalize());
- auto size_after = writer.GetCowSize();
+ auto size_after = writer.GetCowSizeInfo().cow_size;
ASSERT_EQ(size_before, size_after);
struct stat buf;
ASSERT_GE(fstat(cow_->fd, &buf), 0) << strerror(errno);
- ASSERT_EQ(buf.st_size, writer.GetCowSize());
+ ASSERT_EQ(buf.st_size, writer.GetCowSizeInfo().cow_size);
}
TEST_F(CowTest, AppendLabelSmall) {
@@ -637,7 +637,7 @@
struct stat buf;
ASSERT_EQ(fstat(cow_->fd, &buf), 0);
- ASSERT_EQ(buf.st_size, writer->GetCowSize());
+ ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
// Read back both operations, and label.
CowReader reader;
@@ -653,7 +653,7 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, data);
@@ -663,14 +663,14 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 3);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 3);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, data2);
@@ -690,7 +690,7 @@
ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
ASSERT_TRUE(writer->AddLabel(1));
// Drop the tail end of the last op header, corrupting it.
- ftruncate(cow_->fd, writer->GetCowSize() - sizeof(CowFooter) - 3);
+ ftruncate(cow_->fd, writer->GetCowSizeInfo().cow_size - sizeof(CowFooter) - 3);
ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
@@ -705,7 +705,7 @@
struct stat buf;
ASSERT_EQ(fstat(cow_->fd, &buf), 0);
- ASSERT_EQ(buf.st_size, writer->GetCowSize());
+ ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
// Read back both operations.
CowReader reader;
@@ -716,14 +716,14 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 0);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
iter->Next();
@@ -763,7 +763,7 @@
struct stat buf;
ASSERT_EQ(fstat(cow_->fd, &buf), 0);
- ASSERT_EQ(buf.st_size, writer->GetCowSize());
+ ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
// Read back all valid operations
CowReader reader;
@@ -774,8 +774,8 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 5);
iter->Next();
ASSERT_TRUE(iter->AtEnd());
@@ -812,7 +812,7 @@
struct stat buf;
ASSERT_EQ(fstat(cow_->fd, &buf), 0);
- ASSERT_EQ(buf.st_size, writer->GetCowSize());
+ ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
// Read back all ops
CowReader reader;
@@ -825,7 +825,7 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, data.substr(0, options.block_size));
@@ -835,7 +835,7 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, data.substr(options.block_size, 2 * options.block_size));
@@ -843,26 +843,26 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 4);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 4);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 5);
iter->Next();
@@ -906,7 +906,7 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, data.substr(0, options.block_size));
@@ -914,52 +914,52 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 4);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 4);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowClusterOp);
+ ASSERT_EQ(op->type(), kCowClusterOp);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 5);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowCopyOp);
+ ASSERT_EQ(op->type(), kCowCopyOp);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowClusterOp);
+ ASSERT_EQ(op->type(), kCowClusterOp);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 6);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 6);
iter->Next();
@@ -989,7 +989,7 @@
struct stat buf;
ASSERT_EQ(fstat(cow_->fd, &buf), 0);
- ASSERT_EQ(buf.st_size, writer->GetCowSize());
+ ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size);
// Read back both operations, plus cluster op at end
CowReader reader;
@@ -1005,14 +1005,14 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 50);
+ ASSERT_EQ(op->type(), kCowLabelOp);
+ ASSERT_EQ(op->source(), 50);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, data2);
@@ -1020,7 +1020,7 @@
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowClusterOp);
+ ASSERT_EQ(op->type(), kCowClusterOp);
iter->Next();
@@ -1117,12 +1117,12 @@
num_in_cluster++;
max_in_cluster = std::max(max_in_cluster, num_in_cluster);
- if (op->type == kCowReplaceOp) {
+ if (op->type() == kCowReplaceOp) {
num_replace++;
ASSERT_EQ(op->new_block, num_replace);
ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
- } else if (op->type == kCowClusterOp) {
+ } else if (op->type() == kCowClusterOp) {
num_in_cluster = 0;
num_clusters++;
}
@@ -1178,12 +1178,12 @@
num_in_cluster++;
max_in_cluster = std::max(max_in_cluster, num_in_cluster);
- if (op->type == kCowReplaceOp) {
+ if (op->type() == kCowReplaceOp) {
num_replace++;
ASSERT_EQ(op->new_block, num_replace);
ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
- } else if (op->type == kCowClusterOp) {
+ } else if (op->type() == kCowClusterOp) {
num_in_cluster = 0;
num_clusters++;
}
@@ -1229,12 +1229,12 @@
num_in_cluster++;
max_in_cluster = std::max(max_in_cluster, num_in_cluster);
- if (op->type == kCowReplaceOp) {
+ if (op->type() == kCowReplaceOp) {
num_replace++;
ASSERT_EQ(op->new_block, num_replace);
ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
- } else if (op->type == kCowClusterOp) {
+ } else if (op->type() == kCowClusterOp) {
num_in_cluster = 0;
num_clusters++;
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index c41e07c..8cf46f4 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -1,10 +1,3 @@
-// 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,
@@ -15,17 +8,16 @@
#include <sys/stat.h>
#include <cstdio>
-#include <iostream>
+#include <limits>
#include <memory>
-#include <string_view>
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
+#include <libsnapshot/cow_format.h>
#include <libsnapshot/cow_reader.h>
#include <libsnapshot/cow_writer.h>
-#include "cow_decompress.h"
-#include "libsnapshot/cow_format.h"
#include "writer_v2.h"
#include "writer_v3.h"
@@ -99,7 +91,7 @@
options.op_count_max = 20;
auto writer = CreateCowWriter(3, options, GetCowFd());
ASSERT_FALSE(writer->AddZeroBlocks(1, 21));
- ASSERT_FALSE(writer->AddZeroBlocks(1, 1));
+ ASSERT_TRUE(writer->AddZeroBlocks(1, 20));
std::string data = "This is some data, believe it";
data.resize(options.block_size, '\0');
@@ -128,19 +120,19 @@
ASSERT_FALSE(iter->AtEnd());
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 1);
- ASSERT_EQ(op->source_info, 0);
+ ASSERT_EQ(op->source(), 0);
iter->Next();
ASSERT_FALSE(iter->AtEnd());
op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 2);
- ASSERT_EQ(op->source_info, 0);
+ ASSERT_EQ(op->source(), 0);
}
TEST_F(CowTestV3, ReplaceOp) {
@@ -171,7 +163,7 @@
auto op = iter->Get();
std::string sink(data.size(), '\0');
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_EQ(op->data_length, 4096);
ASSERT_EQ(op->new_block, 5);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -186,7 +178,7 @@
std::string data;
data.resize(options.block_size * 5);
for (int i = 0; i < data.size(); i++) {
- data[i] = char(rand() % 256);
+ data[i] = static_cast<char>('A' + i / options.block_size);
}
ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size()));
@@ -207,19 +199,20 @@
ASSERT_FALSE(iter->AtEnd());
size_t i = 0;
- std::string sink(data.size(), '\0');
while (!iter->AtEnd()) {
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ std::string sink(options.block_size, '\0');
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_EQ(op->data_length, options.block_size);
ASSERT_EQ(op->new_block, 5 + i);
- ASSERT_TRUE(
- ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+ ASSERT_TRUE(ReadData(reader, op, sink.data(), options.block_size));
+ ASSERT_EQ(std::string_view(sink),
+ std::string_view(data).substr(i * options.block_size, options.block_size))
+ << " readback data for " << i << "th block does not match";
iter->Next();
i++;
}
- ASSERT_EQ(sink, data);
ASSERT_EQ(i, 5);
}
@@ -249,10 +242,10 @@
size_t i = 0;
while (!iter->AtEnd()) {
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowCopyOp);
+ ASSERT_EQ(op->type(), kCowCopyOp);
ASSERT_EQ(op->data_length, 0);
ASSERT_EQ(op->new_block, 10 + i);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 1000 + i);
+ ASSERT_EQ(op->source(), 1000 + i);
iter->Next();
i += 1;
}
@@ -285,10 +278,10 @@
auto op = iter->Get();
std::string sink(data.size(), '\0');
- ASSERT_EQ(op->type, kCowXorOp);
+ ASSERT_EQ(op->type(), kCowXorOp);
ASSERT_EQ(op->data_length, 4096);
ASSERT_EQ(op->new_block, 50);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314); // 4096 * 24 + 10
+ ASSERT_EQ(op->source(), 98314); // 4096 * 24 + 10
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
ASSERT_EQ(sink, data);
}
@@ -325,10 +318,10 @@
while (!iter->AtEnd()) {
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowXorOp);
+ ASSERT_EQ(op->type(), kCowXorOp);
ASSERT_EQ(op->data_length, 4096);
ASSERT_EQ(op->new_block, 50 + i);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314 + (i * options.block_size)); // 4096 * 24 + 10
+ ASSERT_EQ(op->source(), 98314 + (i * options.block_size)); // 4096 * 24 + 10
ASSERT_TRUE(
ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
iter->Next();
@@ -374,49 +367,40 @@
ASSERT_NE(iter, nullptr);
ASSERT_FALSE(iter->AtEnd());
- size_t i = 0;
-
- while (i < 5) {
+ for (size_t i = 0; i < 5; i++) {
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowZeroOp);
+ ASSERT_EQ(op->type(), kCowZeroOp);
ASSERT_EQ(op->new_block, 10 + i);
iter->Next();
- i++;
}
- i = 0;
- while (i < 5) {
+ for (size_t i = 0; i < 5; i++) {
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowCopyOp);
+ ASSERT_EQ(op->type(), kCowCopyOp);
ASSERT_EQ(op->new_block, 15 + i);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 3 + i);
+ ASSERT_EQ(op->source(), 3 + i);
iter->Next();
- i++;
}
- i = 0;
std::string sink(data.size(), '\0');
- while (i < 5) {
+ for (size_t i = 0; i < 5; i++) {
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_EQ(op->new_block, 18 + i);
- ASSERT_TRUE(
- ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+ ASSERT_EQ(reader.ReadData(op, sink.data() + (i * options.block_size), options.block_size),
+ options.block_size);
iter->Next();
- i++;
}
ASSERT_EQ(sink, data);
- i = 0;
std::fill(sink.begin(), sink.end(), '\0');
- while (i < 5) {
+ for (size_t i = 0; i < 5; i++) {
auto op = iter->Get();
- ASSERT_EQ(op->type, kCowXorOp);
+ ASSERT_EQ(op->type(), kCowXorOp);
ASSERT_EQ(op->new_block, 50 + i);
- ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314 + (i * options.block_size)); // 4096 * 24 + 10
+ ASSERT_EQ(op->source(), 98314 + (i * options.block_size)); // 4096 * 24 + 10
ASSERT_TRUE(
ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
iter->Next();
- i++;
}
ASSERT_EQ(sink, data);
}
@@ -448,7 +432,7 @@
std::string sink(data.size(), '\0');
- ASSERT_EQ(op->type, kCowReplaceOp);
+ ASSERT_EQ(op->type(), kCowReplaceOp);
ASSERT_EQ(op->data_length, 56); // compressed!
ASSERT_EQ(op->new_block, 50);
ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -482,5 +466,237 @@
header = reader.header_v3();
ASSERT_EQ(header.op_count, 15);
}
+
+TEST_F(CowTestV3, BufferMetadataSyncTest) {
+ CowOptions options;
+ options.op_count_max = 100;
+ auto writer = CreateCowWriter(3, options, GetCowFd());
+ /*
+ Header metadafields
+ sequence_data_count = 0;
+ resume_point_count = 0;
+ resume_point_max = 4;
+ */
+ ASSERT_TRUE(writer->Finalize());
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+
+ auto header = reader.header_v3();
+ ASSERT_EQ(header.sequence_data_count, 0);
+ ASSERT_EQ(header.resume_point_count, 0);
+ ASSERT_EQ(header.resume_point_max, 4);
+
+ writer->AddLabel(0);
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ header = reader.header_v3();
+ ASSERT_EQ(header.sequence_data_count, 0);
+ ASSERT_EQ(header.resume_point_count, 1);
+ ASSERT_EQ(header.resume_point_max, 4);
+
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ header = reader.header_v3();
+
+ /*
+ Header metadafields
+ sequence_data_count = 1;
+ resume_point_count = 0;
+ resume_point_max = 4;
+ */
+}
+
+TEST_F(CowTestV3, SequenceTest) {
+ CowOptions options;
+ constexpr int seq_len = std::numeric_limits<uint16_t>::max() / sizeof(uint32_t) + 1;
+ options.op_count_max = seq_len;
+ auto writer = CreateCowWriter(3, options, GetCowFd());
+ // sequence data. This just an arbitrary set of integers that specify the merge order. The
+ // actual calculation is done by update_engine and passed to writer. All we care about here is
+ // writing that data correctly
+ uint32_t sequence[seq_len];
+ for (int i = 0; i < seq_len; i++) {
+ sequence[i] = i + 1;
+ }
+
+ ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
+ ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len - 1));
+ std::vector<uint8_t> data(writer->GetBlockSize());
+ for (size_t i = 0; i < data.size(); i++) {
+ data[i] = static_cast<uint8_t>(i & 0xFF);
+ }
+ ASSERT_TRUE(writer->AddRawBlocks(seq_len, data.data(), data.size()));
+ ASSERT_TRUE(writer->Finalize());
+
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ auto iter = reader.GetRevMergeOpIter();
+
+ for (int i = 0; i < seq_len; i++) {
+ ASSERT_TRUE(!iter->AtEnd());
+ const auto& op = iter->Get();
+
+ ASSERT_EQ(op->new_block, seq_len - i);
+ if (op->new_block == seq_len) {
+ std::vector<uint8_t> read_back(writer->GetBlockSize());
+ ASSERT_EQ(reader.ReadData(op, read_back.data(), read_back.size()),
+ static_cast<ssize_t>(read_back.size()));
+ ASSERT_EQ(read_back, data);
+ }
+
+ iter->Next();
+ }
+ ASSERT_TRUE(iter->AtEnd());
+}
+
+TEST_F(CowTestV3, MissingSeqOp) {
+ CowOptions options;
+ options.op_count_max = std::numeric_limits<uint32_t>::max();
+ auto writer = CreateCowWriter(3, options, GetCowFd());
+ const int seq_len = 10;
+ uint32_t sequence[seq_len];
+ for (int i = 0; i < seq_len; i++) {
+ sequence[i] = i + 1;
+ }
+ ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
+ ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len - 1));
+ ASSERT_TRUE(writer->Finalize());
+
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_FALSE(reader.Parse(cow_->fd));
+}
+
+TEST_F(CowTestV3, ResumeSeqOp) {
+ CowOptions options;
+ options.op_count_max = std::numeric_limits<uint32_t>::max();
+ auto writer = std::make_unique<CowWriterV3>(options, GetCowFd());
+ const int seq_len = 10;
+ uint32_t sequence[seq_len];
+ for (int i = 0; i < seq_len; i++) {
+ sequence[i] = i + 1;
+ }
+ ASSERT_TRUE(writer->Initialize());
+
+ ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
+ ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len / 2));
+ ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, 1));
+
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+ auto reader = std::make_unique<CowReader>();
+ ASSERT_TRUE(reader->Parse(cow_->fd, 1));
+ auto itr = reader->GetRevMergeOpIter();
+ ASSERT_TRUE(itr->AtEnd());
+
+ writer = std::make_unique<CowWriterV3>(options, GetCowFd());
+ ASSERT_TRUE(writer->Initialize({1}));
+ ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, seq_len / 2));
+ ASSERT_TRUE(writer->Finalize());
+
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ reader = std::make_unique<CowReader>();
+ ASSERT_TRUE(reader->Parse(cow_->fd));
+
+ auto iter = reader->GetRevMergeOpIter();
+
+ uint64_t expected_block = 10;
+ while (!iter->AtEnd() && expected_block > 0) {
+ ASSERT_FALSE(iter->AtEnd());
+ const auto& op = iter->Get();
+
+ ASSERT_EQ(op->new_block, expected_block);
+
+ iter->Next();
+ expected_block--;
+ }
+ ASSERT_EQ(expected_block, 0);
+ ASSERT_TRUE(iter->AtEnd());
+}
+
+TEST_F(CowTestV3, SetSourceManyTimes) {
+ CowOperationV3 op{};
+ op.set_source(1);
+ ASSERT_EQ(op.source(), 1);
+ op.set_source(2);
+ ASSERT_EQ(op.source(), 2);
+ op.set_source(4);
+ ASSERT_EQ(op.source(), 4);
+ op.set_source(8);
+ ASSERT_EQ(op.source(), 8);
+}
+
+TEST_F(CowTestV3, SetTypeManyTimes) {
+ CowOperationV3 op{};
+ op.set_type(kCowCopyOp);
+ ASSERT_EQ(op.type(), kCowCopyOp);
+ op.set_type(kCowReplaceOp);
+ ASSERT_EQ(op.type(), kCowReplaceOp);
+ op.set_type(kCowZeroOp);
+ ASSERT_EQ(op.type(), kCowZeroOp);
+ op.set_type(kCowXorOp);
+ ASSERT_EQ(op.type(), kCowXorOp);
+}
+
+TEST_F(CowTestV3, SetTypeSourceInverleave) {
+ CowOperationV3 op{};
+ op.set_type(kCowCopyOp);
+ ASSERT_EQ(op.type(), kCowCopyOp);
+ op.set_source(0x010203040506);
+ ASSERT_EQ(op.source(), 0x010203040506);
+ ASSERT_EQ(op.type(), kCowCopyOp);
+ op.set_type(kCowReplaceOp);
+ ASSERT_EQ(op.source(), 0x010203040506);
+ ASSERT_EQ(op.type(), kCowReplaceOp);
+}
+
+TEST_F(CowTestV3, CowSizeEstimate) {
+ CowOptions options{};
+ options.compression = "none";
+ auto estimator = android::snapshot::CreateCowEstimator(3, options);
+ ASSERT_TRUE(estimator->AddZeroBlocks(0, 1024 * 1024));
+ const auto cow_size = estimator->GetCowSizeInfo().cow_size;
+ options.op_count_max = 1024 * 1024;
+ options.max_blocks = 1024 * 1024;
+ CowWriterV3 writer(options, GetCowFd());
+ ASSERT_TRUE(writer.Initialize());
+ ASSERT_TRUE(writer.AddZeroBlocks(0, 1024 * 1024));
+
+ ASSERT_LE(writer.GetCowSizeInfo().cow_size, cow_size);
+}
+
+TEST_F(CowTestV3, CopyOpMany) {
+ CowOptions options;
+ options.op_count_max = 100;
+ CowWriterV3 writer(options, GetCowFd());
+ writer.Initialize();
+ ASSERT_TRUE(writer.AddCopy(100, 50, 50));
+ ASSERT_TRUE(writer.AddCopy(150, 100, 50));
+ ASSERT_TRUE(writer.Finalize());
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(GetCowFd()));
+ auto it = reader.GetOpIter();
+ for (size_t i = 0; i < 100; i++) {
+ ASSERT_FALSE(it->AtEnd()) << " op iterator ended at " << i;
+ const auto op = *it->Get();
+ ASSERT_EQ(op.type(), kCowCopyOp);
+ ASSERT_EQ(op.new_block, 100 + i);
+ it->Next();
+ }
+}
+
+TEST_F(CowTestV3, CheckOpCount) {
+ CowOptions options;
+ options.op_count_max = 20;
+ options.batch_write = true;
+ options.cluster_ops = 200;
+ auto writer = CreateCowWriter(3, options, GetCowFd());
+ ASSERT_TRUE(writer->AddZeroBlocks(0, 19));
+ ASSERT_FALSE(writer->AddZeroBlocks(0, 19));
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
index 37324c7..75cd111 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
@@ -369,7 +369,7 @@
}
bool CowWriterV2::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
- uint64_t old_block, uint16_t offset, uint8_t type) {
+ uint64_t old_block, uint16_t offset, CowOperationType type) {
CHECK(!merge_in_progress_);
const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
@@ -576,12 +576,14 @@
return Sync();
}
-uint64_t CowWriterV2::GetCowSize() {
+CowSizeInfo CowWriterV2::GetCowSizeInfo() const {
+ CowSizeInfo info;
if (current_data_size_ > 0) {
- return next_data_pos_ + sizeof(footer_);
+ info.cow_size = next_data_pos_ + sizeof(footer_);
} else {
- return next_op_pos_ + sizeof(footer_);
+ info.cow_size = next_op_pos_ + sizeof(footer_);
}
+ return info;
}
bool CowWriterV2::GetDataPos(uint64_t* pos) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
index 24170eb..05de2ad 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
@@ -27,7 +27,7 @@
bool Initialize(std::optional<uint64_t> label = {}) override;
bool Finalize() override;
- uint64_t GetCowSize() override;
+ CowSizeInfo GetCowSizeInfo() const override;
protected:
virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
@@ -42,7 +42,7 @@
bool EmitCluster();
bool EmitClusterIfNeeded();
bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
- uint16_t offset, uint8_t type);
+ uint16_t offset, CowOperationType type);
void SetupHeaders();
void SetupWriteOptions();
bool ParseOptions();
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index 6883c5e..be6b6da 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -34,10 +34,12 @@
#include <zlib.h>
#include <fcntl.h>
+#include <libsnapshot/cow_compress.h>
#include <libsnapshot_cow/parser_v3.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <unistd.h>
+#include <numeric>
// The info messages here are spammy, but as useful for update_engine. Disable
// them when running on the host.
@@ -54,11 +56,35 @@
using android::base::unique_fd;
+// Divide |x| by |y| and round up to the nearest integer.
+constexpr uint64_t DivRoundUp(uint64_t x, uint64_t y) {
+ return (x + y - 1) / y;
+}
+
CowWriterV3::CowWriterV3(const CowOptions& options, unique_fd&& fd)
- : CowWriterBase(options, std::move(fd)) {
+ : CowWriterBase(options, std::move(fd)), batch_size_(std::max<size_t>(options.cluster_ops, 1)) {
SetupHeaders();
}
+void CowWriterV3::InitWorkers() {
+ if (num_compress_threads_ <= 1) {
+ LOG_INFO << "Not creating new threads for compression.";
+ return;
+ }
+ compress_threads_.reserve(num_compress_threads_);
+ compress_threads_.clear();
+ threads_.reserve(num_compress_threads_);
+ threads_.clear();
+ for (size_t i = 0; i < num_compress_threads_; i++) {
+ std::unique_ptr<ICompressor> compressor =
+ ICompressor::Create(compression_, header_.block_size);
+ auto&& wt = compress_threads_.emplace_back(
+ std::make_unique<CompressWorker>(std::move(compressor), header_.block_size));
+ threads_.emplace_back(std::thread([wt = wt.get()]() { wt->RunThread(); }));
+ }
+ LOG(INFO) << num_compress_threads_ << " thread used for compression";
+}
+
void CowWriterV3::SetupHeaders() {
header_ = {};
header_.prefix.magic = kCowMagicNumber;
@@ -77,7 +103,8 @@
// v3 specific fields
// WIP: not quite sure how some of these are calculated yet, assuming buffer_size is determined
// during COW size estimation
- header_.sequence_buffer_offset = 0;
+ header_.sequence_data_count = 0;
+
header_.resume_point_count = 0;
header_.resume_point_max = kNumResumePoints;
header_.op_count = 0;
@@ -100,6 +127,7 @@
return false;
}
header_.compression_algorithm = *algorithm;
+ header_.op_count_max = options_.op_count_max;
if (parts.size() > 1) {
if (!android::base::ParseUint(parts[1], &compression_.compression_level)) {
@@ -112,11 +140,44 @@
}
compression_.algorithm = *algorithm;
- compressor_ = ICompressor::Create(compression_, header_.block_size);
+ if (compression_.algorithm != kCowCompressNone) {
+ compressor_ = ICompressor::Create(compression_, header_.block_size);
+ if (compressor_ == nullptr) {
+ LOG(ERROR) << "Failed to create compressor for " << compression_.algorithm;
+ return false;
+ }
+ if (options_.cluster_ops &&
+ (android::base::GetBoolProperty("ro.virtual_ab.batch_writes", false) ||
+ options_.batch_write)) {
+ batch_size_ = std::max<size_t>(options_.cluster_ops, 1);
+ data_vec_.reserve(batch_size_);
+ cached_data_.reserve(batch_size_);
+ cached_ops_.reserve(batch_size_);
+ }
+ }
+ if (batch_size_ > 1) {
+ LOG(INFO) << "Batch writes: enabled with batch size " << batch_size_;
+ } else {
+ LOG(INFO) << "Batch writes: disabled";
+ }
+ if (android::base::GetBoolProperty("ro.virtual_ab.compression.threads", false) &&
+ options_.num_compress_threads) {
+ num_compress_threads_ = options_.num_compress_threads;
+ }
+ InitWorkers();
return true;
}
-CowWriterV3::~CowWriterV3() {}
+CowWriterV3::~CowWriterV3() {
+ for (const auto& t : compress_threads_) {
+ t->Finalize();
+ }
+ for (auto& t : threads_) {
+ if (t.joinable()) {
+ t.join();
+ }
+ }
+}
bool CowWriterV3::Initialize(std::optional<uint64_t> label) {
if (!InitFd() || !ParseOptions()) {
@@ -163,7 +224,7 @@
return false;
}
}
- header_.op_count_max = options_.op_count_max;
+
resume_points_ = std::make_shared<std::vector<ResumePoint>>();
if (!Sync()) {
@@ -175,7 +236,7 @@
}
bool CowWriterV3::OpenForAppend(uint64_t label) {
- CowHeaderV3 header_v3;
+ CowHeaderV3 header_v3{};
if (!ReadCowHeader(fd_, &header_v3)) {
LOG(ERROR) << "Couldn't read Cow Header";
return false;
@@ -205,79 +266,121 @@
return true;
}
+bool CowWriterV3::CheckOpCount(size_t op_count) {
+ if (IsEstimating()) {
+ return true;
+ }
+ if (header_.op_count + cached_ops_.size() + op_count > header_.op_count_max) {
+ LOG(ERROR) << "Current number of ops on disk: " << header_.op_count
+ << ", number of ops cached in memory: " << cached_ops_.size()
+ << ", number of ops attempting to write: " << op_count
+ << ", this will exceed max op count " << header_.op_count_max;
+ return false;
+ }
+ return true;
+}
+
bool CowWriterV3::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
+ if (!CheckOpCount(num_blocks)) {
+ return false;
+ }
for (size_t i = 0; i < num_blocks; i++) {
- CowOperationV3 op = {};
- op.type = kCowCopyOp;
+ CowOperationV3& op = cached_ops_.emplace_back();
+ op.set_type(kCowCopyOp);
op.new_block = new_block + i;
- op.source_info = old_block + i;
- if (!WriteOperation(op)) {
+ op.set_source(old_block + i);
+ }
+
+ if (NeedsFlush()) {
+ if (!FlushCacheOps()) {
return false;
}
}
-
return true;
}
bool CowWriterV3::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
+ if (!CheckOpCount(size / header_.block_size)) {
+ return false;
+ }
return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp);
}
bool CowWriterV3::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
uint32_t old_block, uint16_t offset) {
+ if (!CheckOpCount(size / header_.block_size)) {
+ return false;
+ }
return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp);
}
+bool CowWriterV3::NeedsFlush() const {
+ // Allow bigger batch sizes for ops without data. A single CowOperationV3
+ // struct uses 14 bytes of memory, even if we cache 200 * 16 ops in memory,
+ // it's only ~44K.
+ return cached_data_.size() >= batch_size_ || cached_ops_.size() >= batch_size_ * 16;
+}
+
bool CowWriterV3::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
- uint64_t old_block, uint16_t offset, uint8_t type) {
+ uint64_t old_block, uint16_t offset, CowOperationType type) {
+ if (compression_.algorithm != kCowCompressNone && compressor_ == nullptr) {
+ LOG(ERROR) << "Compression algorithm is " << compression_.algorithm
+ << " but compressor is uninitialized.";
+ return false;
+ }
+ const auto bytes = reinterpret_cast<const uint8_t*>(data);
const size_t num_blocks = (size / header_.block_size);
- for (size_t i = 0; i < num_blocks; i++) {
- const uint8_t* const iter =
- reinterpret_cast<const uint8_t*>(data) + (header_.block_size * i);
- CowOperation op = {};
- op.new_block = new_block_start + i;
-
- op.type = type;
- if (type == kCowXorOp) {
- op.source_info = (old_block + i) * header_.block_size + offset;
- } else {
- op.source_info = next_data_pos_;
- }
- std::basic_string<uint8_t> compressed_data;
- const void* out_data = iter;
-
- op.data_length = header_.block_size;
-
- if (compression_.algorithm) {
- if (!compressor_) {
- PLOG(ERROR) << "Compressor not initialized";
- return false;
- }
- compressed_data = compressor_->Compress(out_data, header_.block_size);
- if (compressed_data.size() < op.data_length) {
- out_data = compressed_data.data();
- op.data_length = compressed_data.size();
- }
- }
- if (!WriteOperation(op, out_data, op.data_length)) {
- PLOG(ERROR) << "AddRawBlocks with compression: write failed. new block: "
- << new_block_start << " compression: " << compression_.algorithm;
+ for (size_t i = 0; i < num_blocks;) {
+ const auto blocks_to_write =
+ std::min<size_t>(batch_size_ - cached_data_.size(), num_blocks - i);
+ size_t compressed_bytes = 0;
+ auto&& blocks = CompressBlocks(blocks_to_write, bytes + header_.block_size * i);
+ if (blocks.size() != blocks_to_write) {
+ LOG(ERROR) << "Failed to compress blocks " << new_block_start + i << ", "
+ << blocks_to_write << ", actual number of blocks received from compressor "
+ << blocks.size();
return false;
}
+ for (size_t j = 0; j < blocks_to_write; j++) {
+ CowOperation& op = cached_ops_.emplace_back();
+ auto& vec = data_vec_.emplace_back();
+ auto& compressed_data = cached_data_.emplace_back(std::move(blocks[j]));
+ op.new_block = new_block_start + i + j;
+
+ op.set_type(type);
+ if (type == kCowXorOp) {
+ op.set_source((old_block + i + j) * header_.block_size + offset);
+ } else {
+ op.set_source(next_data_pos_ + compressed_bytes);
+ }
+ vec = {.iov_base = compressed_data.data(), .iov_len = compressed_data.size()};
+ op.data_length = vec.iov_len;
+ compressed_bytes += op.data_length;
+ }
+ if (NeedsFlush() && !FlushCacheOps()) {
+ LOG(ERROR) << "EmitBlocks with compression: write failed. new block: "
+ << new_block_start << " compression: " << compression_.algorithm
+ << ", op type: " << type;
+ return false;
+ }
+ i += blocks_to_write;
}
return true;
}
-bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
+bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, const uint64_t num_blocks) {
+ if (!CheckOpCount(num_blocks)) {
+ return false;
+ }
for (uint64_t i = 0; i < num_blocks; i++) {
- CowOperationV3 op;
- op.type = kCowZeroOp;
- op.data_length = 0;
+ auto& op = cached_ops_.emplace_back();
+ op.set_type(kCowZeroOp);
op.new_block = new_block_start + i;
- op.source_info = 0;
- if (!WriteOperation(op)) {
+ }
+ if (NeedsFlush()) {
+ if (!FlushCacheOps()) {
return false;
}
}
@@ -288,6 +391,10 @@
// remove all labels greater than this current one. we want to avoid the situation of adding
// in
// duplicate labels with differing op values
+ if (!FlushCacheOps()) {
+ LOG(ERROR) << "Failed to flush cached ops before emitting label " << label;
+ return false;
+ }
auto remove_if_callback = [&](const auto& resume_point) -> bool {
if (resume_point.label >= label) return true;
return false;
@@ -311,43 +418,149 @@
PLOG(ERROR) << "writing resume buffer failed";
return false;
}
- return Sync();
+ return Finalize();
}
bool CowWriterV3::EmitSequenceData(size_t num_ops, const uint32_t* data) {
- LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
- if (num_ops && data) return false;
- return false;
+ if (header_.op_count > 0 || !cached_ops_.empty()) {
+ LOG(ERROR) << "There's " << header_.op_count << " operations written to disk and "
+ << cached_ops_.size()
+ << " ops cached in memory. Writing sequence data is only allowed before all "
+ "operation writes.";
+ return false;
+ }
+
+ header_.sequence_data_count = num_ops;
+
+ // Ensure next_data_pos_ is updated as previously initialized + the newly added sequence buffer.
+ CHECK_EQ(next_data_pos_ + header_.sequence_data_count * sizeof(uint32_t),
+ GetDataOffset(header_));
+ next_data_pos_ = GetDataOffset(header_);
+
+ if (!android::base::WriteFullyAtOffset(fd_, data, sizeof(data[0]) * num_ops,
+ GetSequenceOffset(header_))) {
+ PLOG(ERROR) << "writing sequence buffer failed";
+ return false;
+ }
+ return true;
}
-bool CowWriterV3::WriteOperation(const CowOperationV3& op, const void* data, size_t size) {
+bool CowWriterV3::FlushCacheOps() {
+ if (cached_ops_.empty()) {
+ if (!data_vec_.empty()) {
+ LOG(ERROR) << "Cached ops is empty, but data iovec has size: " << data_vec_.size()
+ << " this is definitely a bug.";
+ return false;
+ }
+ return true;
+ }
+ size_t bytes_written = 0;
+
+ for (auto& op : cached_ops_) {
+ if (op.type() == kCowReplaceOp) {
+ op.set_source(next_data_pos_ + bytes_written);
+ }
+ bytes_written += op.data_length;
+ }
+ if (!WriteOperation({cached_ops_.data(), cached_ops_.size()},
+ {data_vec_.data(), data_vec_.size()})) {
+ LOG(ERROR) << "Failed to flush " << cached_ops_.size() << " ops to disk";
+ return false;
+ }
+ cached_ops_.clear();
+ cached_data_.clear();
+ data_vec_.clear();
+ return true;
+}
+
+std::vector<std::basic_string<uint8_t>> CowWriterV3::CompressBlocks(const size_t num_blocks,
+ const void* data) {
+ const size_t num_threads = (num_blocks == 1) ? 1 : num_compress_threads_;
+ const size_t blocks_per_thread = DivRoundUp(num_blocks, num_threads);
+ std::vector<std::basic_string<uint8_t>> compressed_buf;
+ compressed_buf.clear();
+ const uint8_t* const iter = reinterpret_cast<const uint8_t*>(data);
+ if (compression_.algorithm == kCowCompressNone) {
+ for (size_t i = 0; i < num_blocks; i++) {
+ auto& buf = compressed_buf.emplace_back();
+ buf.resize(header_.block_size);
+ std::memcpy(buf.data(), iter + i * header_.block_size, header_.block_size);
+ }
+ return compressed_buf;
+ }
+ if (num_threads <= 1) {
+ if (!CompressWorker::CompressBlocks(compressor_.get(), header_.block_size, data, num_blocks,
+ &compressed_buf)) {
+ return {};
+ }
+ } else {
+ // Submit the blocks per thread. The retrieval of
+ // compressed buffers has to be done in the same order.
+ // We should not poll for completed buffers in a different order as the
+ // buffers are tightly coupled with block ordering.
+ for (size_t i = 0; i < num_threads; i++) {
+ CompressWorker* worker = compress_threads_[i].get();
+ const auto blocks_in_batch =
+ std::min(num_blocks - i * blocks_per_thread, blocks_per_thread);
+ worker->EnqueueCompressBlocks(iter + i * blocks_per_thread * header_.block_size,
+ blocks_in_batch);
+ }
+
+ for (size_t i = 0; i < num_threads; i++) {
+ CompressWorker* worker = compress_threads_[i].get();
+ if (!worker->GetCompressedBuffers(&compressed_buf)) {
+ return {};
+ }
+ }
+ }
+ for (size_t i = 0; i < num_blocks; i++) {
+ auto& block = compressed_buf[i];
+ if (block.size() >= header_.block_size) {
+ block.resize(header_.block_size);
+ std::memcpy(block.data(), iter + header_.block_size * i, header_.block_size);
+ }
+ }
+
+ return compressed_buf;
+}
+
+bool CowWriterV3::WriteOperation(std::basic_string_view<CowOperationV3> ops,
+ std::basic_string_view<struct iovec> data) {
+ const auto total_data_size =
+ std::transform_reduce(data.begin(), data.end(), 0, std::plus<size_t>{},
+ [](const struct iovec& a) { return a.iov_len; });
if (IsEstimating()) {
- header_.op_count++;
- header_.op_count_max++;
- next_data_pos_ += op.data_length;
+ header_.op_count += ops.size();
+ if (header_.op_count > header_.op_count_max) {
+ // If we increment op_count_max, the offset of data section would
+ // change. So need to update |next_data_pos_|
+ next_data_pos_ += (header_.op_count - header_.op_count_max) * sizeof(CowOperationV3);
+ header_.op_count_max = header_.op_count;
+ }
+ next_data_pos_ += total_data_size;
return true;
}
- if (header_.op_count + 1 > header_.op_count_max) {
- LOG(ERROR) << "Maximum number of ops reached: " << header_.op_count_max;
+ if (header_.op_count + ops.size() > header_.op_count_max) {
+ LOG(ERROR) << "Current op count " << header_.op_count << ", attempting to write "
+ << ops.size() << " ops will exceed the max of " << header_.op_count_max;
return false;
}
-
const off_t offset = GetOpOffset(header_.op_count, header_);
- if (!android::base::WriteFullyAtOffset(fd_, &op, sizeof(op), offset)) {
- PLOG(ERROR) << "write failed for " << op << " at " << offset;
+ if (!android::base::WriteFullyAtOffset(fd_, ops.data(), ops.size() * sizeof(ops[0]), offset)) {
+ PLOG(ERROR) << "Write failed for " << ops.size() << " ops at " << offset;
return false;
}
- if (data && size > 0) {
- if (!android::base::WriteFullyAtOffset(fd_, data, size, next_data_pos_)) {
- PLOG(ERROR) << "write failed for data of size: " << size
- << " at offset: " << next_data_pos_;
+ if (!data.empty()) {
+ const auto ret = pwritev(fd_, data.data(), data.size(), next_data_pos_);
+ if (ret != total_data_size) {
+ PLOG(ERROR) << "write failed for data of size: " << data.size()
+ << " at offset: " << next_data_pos_ << " " << ret;
return false;
}
}
- header_.op_count++;
- next_data_pos_ += op.data_length;
- next_op_pos_ += sizeof(CowOperationV3);
+ header_.op_count += ops.size();
+ next_data_pos_ += total_data_size;
return true;
}
@@ -355,14 +568,20 @@
bool CowWriterV3::Finalize() {
CHECK_GE(header_.prefix.header_size, sizeof(CowHeaderV3));
CHECK_LE(header_.prefix.header_size, sizeof(header_));
+ if (!FlushCacheOps()) {
+ return false;
+ }
if (!android::base::WriteFullyAtOffset(fd_, &header_, header_.prefix.header_size, 0)) {
return false;
}
return Sync();
}
-uint64_t CowWriterV3::GetCowSize() {
- return next_data_pos_;
+CowSizeInfo CowWriterV3::GetCowSizeInfo() const {
+ CowSizeInfo info;
+ info.cow_size = next_data_pos_;
+ info.op_count_max = header_.op_count_max;
+ return info;
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 3dfc33c..b19af60 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -15,6 +15,9 @@
#pragma once
#include <android-base/logging.h>
+#include <string_view>
+#include <thread>
+#include <vector>
#include "writer_base.h"
@@ -28,7 +31,7 @@
bool Initialize(std::optional<uint64_t> label = {}) override;
bool Finalize() override;
- uint64_t GetCowSize() override;
+ CowSizeInfo GetCowSizeInfo() const override;
protected:
virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
@@ -41,15 +44,22 @@
private:
void SetupHeaders();
+ bool NeedsFlush() const;
bool ParseOptions();
bool OpenForWrite();
bool OpenForAppend(uint64_t label);
- bool WriteOperation(const CowOperationV3& op, const void* data = nullptr, size_t size = 0);
+ bool WriteOperation(std::basic_string_view<CowOperationV3> op,
+ std::basic_string_view<struct iovec> data);
bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
- uint16_t offset, uint8_t type);
- bool CompressBlocks(size_t num_blocks, const void* data);
+ uint16_t offset, CowOperationType type);
+ bool CheckOpCount(size_t op_count);
private:
+ std::vector<std::basic_string<uint8_t>> CompressBlocks(const size_t num_blocks,
+ const void* data);
+ bool ReadBackVerification();
+ bool FlushCacheOps();
+ void InitWorkers();
CowHeaderV3 header_{};
CowCompression compression_;
// in the case that we are using one thread for compression, we can store and re-use the same
@@ -59,13 +69,17 @@
// Resume points contain a laebl + cow_op_index.
std::shared_ptr<std::vector<ResumePoint>> resume_points_;
- uint64_t next_op_pos_ = 0;
uint64_t next_data_pos_ = 0;
- std::vector<std::basic_string<uint8_t>> compressed_buf_;
// in the case that we are using one thread for compression, we can store and re-use the same
// compressor
int num_compress_threads_ = 1;
+ size_t batch_size_ = 1;
+ std::vector<CowOperationV3> cached_ops_;
+ std::vector<std::basic_string<uint8_t>> cached_data_;
+ std::vector<struct iovec> data_vec_;
+
+ std::vector<std::thread> threads_;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 5bc7e65..c0d2073 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -217,6 +217,7 @@
if (update && update->has_estimate_cow_size()) {
ret.snapshot_status.set_estimated_cow_size(update->estimate_cow_size());
+ ret.snapshot_status.set_estimated_ops_buffer_size(update->estimate_op_count_max());
}
if (ret.snapshot_status.snapshot_size() == 0) {
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index e91e3b7..9eb41b2 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -30,6 +30,7 @@
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
@@ -82,12 +83,28 @@
using std::chrono::duration_cast;
using namespace std::chrono_literals;
using namespace std::string_literals;
+using android::base::Realpath;
+using android::base::StringPrintf;
static constexpr char kBootSnapshotsWithoutSlotSwitch[] =
"/metadata/ota/snapshot-boot-without-slot-switch";
static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot";
static constexpr char kRollbackIndicatorPath[] = "/metadata/ota/rollback-indicator";
static constexpr auto kUpdateStateCheckInterval = 2s;
+/*
+ * The readahead size is set to 32kb so that
+ * there is no significant memory pressure (/proc/pressure/memory) during boot.
+ * After OTA, during boot, partitions are scanned before marking slot as successful.
+ * This scan will trigger readahead both on source and COW block device thereby
+ * leading to Inactive(file) pages to be very high.
+ *
+ * A lower value may help reduce memory pressure further, however, that will
+ * increase the boot time. Thus, for device which don't care about OTA boot
+ * time, they could use O_DIRECT functionality wherein the I/O to the source
+ * block device will be O_DIRECT.
+ */
+static constexpr auto kCowReadAheadSizeKb = 32;
+static constexpr auto kSourceReadAheadSizeKb = 32;
// Note: IImageManager is an incomplete type in the header, so the default
// destructor doesn't work.
@@ -1748,6 +1765,9 @@
snapuserd_argv->emplace_back(std::move(message));
}
+ SetReadAheadSize(cow_image_device, kCowReadAheadSizeKb);
+ SetReadAheadSize(source_device, kSourceReadAheadSizeKb);
+
// Do not attempt to connect to the new snapuserd yet, it hasn't
// been started. We do however want to wait for the misc device
// to have been created.
@@ -3310,7 +3330,7 @@
// Terminate stale daemon if any
std::unique_ptr<SnapuserdClient> snapuserd_client = std::move(snapuserd_client_);
if (!snapuserd_client) {
- snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
+ snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s);
}
if (snapuserd_client) {
snapuserd_client->DetachSnapuserd();
@@ -3531,6 +3551,9 @@
options.scratch_space = false;
}
options.compression = it->second.compression_algorithm();
+ if (cow_version >= 3) {
+ options.op_count_max = it->second.estimated_ops_buffer_size();
+ }
auto writer = CreateCowWriter(cow_version, options, std::move(fd));
if (!writer->Finalize()) {
@@ -3641,7 +3664,8 @@
cow_options.compression = status.compression_algorithm();
cow_options.max_blocks = {status.device_size() / cow_options.block_size};
cow_options.batch_write = status.batched_writes();
- cow_options.num_compress_threads = status.enable_threading() ? 2 : 0;
+ cow_options.num_compress_threads = status.enable_threading() ? 2 : 1;
+ cow_options.op_count_max = status.estimated_ops_buffer_size();
// Disable scratch space for vts tests
if (device()->IsTestDevice()) {
cow_options.scratch_space = false;
@@ -4404,5 +4428,31 @@
return true;
}
+void SnapshotManager::SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb) {
+ std::string block_device;
+ if (!Realpath(entry_block_device, &block_device)) {
+ PLOG(ERROR) << "Failed to realpath " << entry_block_device;
+ return;
+ }
+
+ static constexpr std::string_view kDevBlockPrefix("/dev/block/");
+ if (!android::base::StartsWith(block_device, kDevBlockPrefix)) {
+ LOG(ERROR) << block_device << " is not a block device";
+ return;
+ }
+
+ std::string block_name = block_device.substr(kDevBlockPrefix.length());
+ std::string sys_partition =
+ android::base::StringPrintf("/sys/class/block/%s/partition", block_name.c_str());
+ struct stat info;
+ if (lstat(sys_partition.c_str(), &info) == 0) {
+ block_name += "/..";
+ }
+ std::string sys_ra = android::base::StringPrintf("/sys/class/block/%s/queue/read_ahead_kb",
+ block_name.c_str());
+ std::string size = std::to_string(size_kb);
+ android::base::WriteStringToFile(size, sys_ra.c_str());
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 4e6b5e1..e538d50 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -2362,8 +2362,10 @@
auto init = NewManagerForFirstStageMount("_b");
ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->EnsureSnapuserdConnected());
- init->set_use_first_stage_snapuserd(true);
+ if (snapuserd_required_) {
+ ASSERT_TRUE(init->EnsureSnapuserdConnected());
+ init->set_use_first_stage_snapuserd(true);
+ }
ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -2374,9 +2376,11 @@
ASSERT_TRUE(IsPartitionUnchanged(name));
}
- ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
- for (const auto& name : partitions) {
- ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init"));
+ if (snapuserd_required_) {
+ ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
+ for (const auto& name : partitions) {
+ ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init"));
+ }
}
// Initiate the merge and wait for it to be completed.
@@ -2860,15 +2864,23 @@
}
void KillSnapuserd() {
- auto status = android::base::GetProperty("init.svc.snapuserd", "stopped");
- if (status == "stopped") {
- return;
+ // Detach the daemon if it's alive
+ auto snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s);
+ if (snapuserd_client) {
+ snapuserd_client->DetachSnapuserd();
}
- auto snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
- if (!snapuserd_client) {
- return;
+
+ // Now stop the service - Init will send a SIGKILL to the daemon. However,
+ // process state will move from "running" to "stopping". Only after the
+ // process is reaped by init, the service state is moved to "stopped".
+ //
+ // Since the tests involve starting the daemon immediately, wait for the
+ // process to completely stop (aka. wait until init reaps the terminated
+ // process).
+ android::base::SetProperty("ctl.stop", "snapuserd");
+ if (!android::base::WaitForProperty("init.svc.snapuserd", "stopped", 10s)) {
+ LOG(ERROR) << "Timed out waiting for snapuserd to stop.";
}
- snapuserd_client->DetachSnapuserd();
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index ebaca2d..0396a55 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -227,8 +227,12 @@
if (file_offset >= dev_sz) {
break;
}
+
+ if (fsync(cfd.get()) < 0) {
+ PLOG(ERROR) << "Fsync failed at offset: " << file_offset << " size: " << to_read;
+ return false;
+ }
}
- fsync(cfd.get());
return true;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 1b0c563..bd296a3 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -86,10 +86,15 @@
"libext4_utils",
"libsnapshot_cow",
"liburing",
+ "libprocessgroup",
+ "libjsoncpp",
+ "libcgrouprc",
+ "libcgrouprc_format",
],
include_dirs: ["bionic/libc/kernel"],
export_include_dirs: ["include"],
header_libs: [
+ "libcutils_headers",
"libstorage_literals_headers",
],
ramdisk_available: true,
@@ -126,6 +131,10 @@
"liblog",
"libsnapshot_cow",
"libsnapuserd",
+ "libprocessgroup",
+ "libjsoncpp",
+ "libcgrouprc",
+ "libcgrouprc_format",
"libsnapuserd_client",
"libz",
"liblz4",
@@ -135,6 +144,7 @@
],
header_libs: [
+ "libcutils_headers",
"libstorage_literals_headers",
],
@@ -147,12 +157,6 @@
// snapuserd, which would lead to deadlock if we had to handle page
// faults for its code pages.
static_executable: true,
-
- // Snapuserd segfaults with ThinLTO
- // http://b/208565717
- lto: {
- never: true,
- },
}
cc_binary {
@@ -257,6 +261,10 @@
"libgtest",
"libsnapshot_cow",
"libsnapuserd",
+ "libprocessgroup",
+ "libjsoncpp",
+ "libcgrouprc",
+ "libcgrouprc_format",
"liburing",
"libz",
],
@@ -267,6 +275,7 @@
header_libs: [
"libstorage_literals_headers",
"libfiemap_headers",
+ "libcutils_headers",
],
test_options: {
min_shipping_api_level: 30,
@@ -326,6 +335,10 @@
"libgflags",
"libsnapshot_cow",
"libsnapuserd",
+ "libprocessgroup",
+ "libjsoncpp",
+ "libcgrouprc",
+ "libcgrouprc_format",
"liburing",
"libz",
],
@@ -336,5 +349,6 @@
header_libs: [
"libstorage_literals_headers",
"libfiemap_headers",
+ "libcutils_headers",
],
}
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 6dc082e..93bb0b2 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -406,9 +406,9 @@
break;
}
- if (cow_op->type == kCowReplaceOp) {
+ if (cow_op->type() == kCowReplaceOp) {
replace_ops++;
- } else if (cow_op->type == kCowZeroOp) {
+ } else if (cow_op->type() == kCowZeroOp) {
zero_ops++;
}
@@ -508,7 +508,7 @@
// the merge of operations are done based on the ops present
// in the file.
//===========================================================
- uint64_t block_source = GetCowOpSourceInfoData(*cow_op);
+ uint64_t block_source = cow_op->source();
if (prev_id.has_value()) {
if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source)) {
break;
@@ -540,7 +540,7 @@
chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
offset += sizeof(struct disk_exception);
num_ops += 1;
- if (cow_op->type == kCowCopyOp) {
+ if (cow_op->type() == kCowCopyOp) {
copy_ops++;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index ab0b309..f1b9245 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -172,7 +172,7 @@
}
void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
- uint64_t source_block = GetCowOpSourceInfoData(*cow_op);
+ uint64_t source_block = cow_op->source();
if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block)) {
overlap_ = true;
}
@@ -191,8 +191,8 @@
// Get the first block with offset
const CowOperation* cow_op = GetRAOpIter();
CHECK_NE(cow_op, nullptr);
- *source_offset = GetCowOpSourceInfoData(*cow_op);
- if (cow_op->type == kCowCopyOp) {
+ *source_offset = cow_op->source();
+ if (cow_op->type() == kCowCopyOp) {
*source_offset *= BLOCK_SZ;
}
RAIterNext();
@@ -210,8 +210,8 @@
while (!RAIterDone() && num_ops) {
const CowOperation* op = GetRAOpIter();
CHECK_NE(op, nullptr);
- uint64_t next_offset = GetCowOpSourceInfoData(*op);
- if (op->type == kCowCopyOp) {
+ uint64_t next_offset = op->source();
+ if (op->type() == kCowCopyOp) {
next_offset *= BLOCK_SZ;
}
if (next_offset + nr_consecutive * BLOCK_SZ != *source_offset) {
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 571b352..1f5d568 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -177,7 +177,7 @@
return false;
}
- switch (cow_op->type) {
+ switch (cow_op->type()) {
case kCowReplaceOp: {
return ProcessReplaceOp(cow_op);
}
@@ -191,7 +191,8 @@
}
default: {
- SNAP_LOG(ERROR) << "Unsupported operation-type found: " << cow_op->type;
+ SNAP_LOG(ERROR) << "Unsupported operation-type found: "
+ << static_cast<uint8_t>(cow_op->type());
}
}
return false;
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index 010beb3..ede92dd 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -17,11 +17,7 @@
#include <unistd.h>
#include <chrono>
-#include <cstring>
-#include <iostream>
#include <string>
-#include <thread>
-#include <vector>
#include <android-base/unique_fd.h>
@@ -53,9 +49,14 @@
explicit SnapuserdClient(android::base::unique_fd&& sockfd);
SnapuserdClient(){};
+ // Attempt to connect to snapsuerd, wait for the daemon to start if
+ // connection failed.
static std::unique_ptr<SnapuserdClient> Connect(const std::string& socket_name,
std::chrono::milliseconds timeout_ms);
-
+ // Attempt to connect to snapsuerd, but does not wait for the daemon to
+ // start.
+ static std::unique_ptr<SnapuserdClient> TryConnect(const std::string& socket_name,
+ std::chrono::milliseconds timeout_ms);
bool StopSnapuserd();
// Initializing a snapuserd handler is a three-step process:
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 3bed3a4..789c980 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -27,7 +27,7 @@
#include <unistd.h>
#include <chrono>
-#include <sstream>
+#include <thread>
#include <android-base/file.h>
#include <android-base/logging.h>
@@ -64,6 +64,40 @@
return errno == ECONNREFUSED || errno == EINTR || errno == ENOENT;
}
+std::unique_ptr<SnapuserdClient> SnapuserdClient::TryConnect(const std::string& socket_name,
+ std::chrono::milliseconds timeout_ms) {
+ unique_fd fd;
+ const auto start = std::chrono::steady_clock::now();
+ while (true) {
+ fd.reset(TEMP_FAILURE_RETRY(socket_local_client(
+ socket_name.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)));
+ if (fd >= 0) {
+ auto client = std::make_unique<SnapuserdClient>(std::move(fd));
+ if (!client->ValidateConnection()) {
+ return nullptr;
+ }
+ return client;
+ }
+ if (errno == ENOENT) {
+ LOG(INFO) << "Daemon socket " << socket_name
+ << " does not exist, return without waiting.";
+ return nullptr;
+ }
+ if (errno == ECONNREFUSED) {
+ const auto now = std::chrono::steady_clock::now();
+ const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+ if (elapsed >= timeout_ms) {
+ LOG(ERROR) << "Timed out connecting to snapuserd socket: " << socket_name;
+ return nullptr;
+ }
+ std::this_thread::sleep_for(10ms);
+ } else {
+ PLOG(ERROR) << "connect failed: " << socket_name;
+ return nullptr;
+ }
+ }
+}
+
std::unique_ptr<SnapuserdClient> SnapuserdClient::Connect(const std::string& socket_name,
std::chrono::milliseconds timeout_ms) {
unique_fd fd;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
index c5718d5..c85331b 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
@@ -41,7 +41,7 @@
bool Extractor::Init() {
auto opener = factory_.CreateTestOpener(control_name_);
handler_ = std::make_shared<SnapshotHandler>(control_name_, cow_path_, base_path_, base_path_,
- opener, 1, false, false);
+ opener, 1, false, false, false);
if (!handler_->InitCowDevice()) {
return false;
}
@@ -50,7 +50,7 @@
}
read_worker_ = std::make_unique<ReadWorker>(cow_path_, base_path_, control_name_, base_path_,
- handler_->GetSharedPtr(), opener);
+ handler_->GetSharedPtr(), opener, false);
if (!read_worker_->Init()) {
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
index ffd7a4b..711e704 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -51,10 +51,11 @@
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,
- std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads, bool use_iouring) {
- auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
- base_path_merge, opener, num_worker_threads,
- use_iouring, perform_verification_);
+ std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads, bool use_iouring,
+ bool o_direct) {
+ auto snapuserd = std::make_shared<SnapshotHandler>(
+ misc_name, cow_device_path, backing_device, base_path_merge, opener, num_worker_threads,
+ use_iouring, perform_verification_, o_direct);
if (!snapuserd->InitCowDevice()) {
LOG(ERROR) << "Failed to initialize Snapuserd";
return nullptr;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
index ff6ee8f..f23f07e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
@@ -57,7 +57,8 @@
const std::string& backing_device,
const std::string& base_path_merge,
std::shared_ptr<IBlockServerOpener> opener,
- int num_worker_threads, bool use_iouring) = 0;
+ int num_worker_threads, bool use_iouring,
+ bool o_direct) = 0;
// Start serving requests on a snapshot handler.
virtual bool StartHandler(const std::string& misc_name) = 0;
@@ -96,7 +97,8 @@
const std::string& backing_device,
const std::string& base_path_merge,
std::shared_ptr<IBlockServerOpener> opener,
- int num_worker_threads, bool use_iouring) override;
+ int num_worker_threads, bool use_iouring,
+ bool o_direct) 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;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
index 11b8d7c..1e7d0c0 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
@@ -80,16 +80,16 @@
}
bool MergeWorker::MergeReplaceZeroOps() {
- // Flush after merging 2MB. Since all ops are independent and there is no
+ // Flush after merging 1MB. Since all ops are independent and there is no
// dependency between COW ops, we will flush the data and the number
// of ops merged in COW block device. If there is a crash, we will
// end up replaying some of the COW ops which were already merged. That is
// ok.
//
- // Although increasing this greater than 2MB may help in improving merge
+ // Although increasing this greater than 1MB may help in improving merge
// times; however, on devices with low memory, this can be problematic
// when there are multiple merge threads in parallel.
- int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 2;
+ int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ);
int num_ops_merged = 0;
SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
@@ -114,13 +114,13 @@
SNAP_LOG(ERROR) << "AcquireBuffer failed in MergeReplaceOps";
return false;
}
- if (cow_op->type == kCowReplaceOp) {
+ if (cow_op->type() == kCowReplaceOp) {
if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
SNAP_LOG(ERROR) << "Failed to read COW in merge";
return false;
}
} else {
- CHECK(cow_op->type == kCowZeroOp);
+ CHECK(cow_op->type() == kCowZeroOp);
memset(buffer, 0, BLOCK_SZ);
}
}
@@ -557,10 +557,14 @@
return true;
}
- if (!SetThreadPriority(kNiceValueForMergeThreads)) {
+ if (!SetThreadPriority(ANDROID_PRIORITY_BACKGROUND)) {
SNAP_PLOG(ERROR) << "Failed to set thread priority";
}
+ if (!SetProfiles({"CPUSET_SP_BACKGROUND"})) {
+ SNAP_PLOG(ERROR) << "Failed to assign task profile to Mergeworker thread";
+ }
+
SNAP_LOG(INFO) << "Merge starting..";
bufsink_.Initialize(PAYLOAD_BUFFER_SZ);
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
index 5cb13e8..f1d4065 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
@@ -31,16 +31,19 @@
void ReadWorker::CloseFds() {
block_server_ = {};
backing_store_fd_ = {};
+ backing_store_direct_fd_ = {};
Worker::CloseFds();
}
ReadWorker::ReadWorker(const std::string& cow_device, const std::string& backing_device,
const std::string& misc_name, const std::string& base_path_merge,
std::shared_ptr<SnapshotHandler> snapuserd,
- std::shared_ptr<IBlockServerOpener> opener)
+ std::shared_ptr<IBlockServerOpener> opener, bool direct_read)
: Worker(cow_device, misc_name, base_path_merge, snapuserd),
backing_store_device_(backing_device),
- block_server_opener_(opener) {}
+ direct_read_(direct_read),
+ block_server_opener_(opener),
+ aligned_buffer_(std::unique_ptr<void, decltype(&::free)>(nullptr, &::free)) {}
// Start the replace operation. This will read the
// internal COW format and if the block is compressed,
@@ -61,9 +64,20 @@
}
SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
<< " Op: " << *cow_op;
+
+ if (direct_read_ && IsBlockAligned(offset)) {
+ if (!android::base::ReadFullyAtOffset(backing_store_direct_fd_, aligned_buffer_.get(),
+ BLOCK_SZ, offset)) {
+ SNAP_PLOG(ERROR) << "O_DIRECT Read failed at offset: " << offset;
+ return false;
+ }
+ std::memcpy(buffer, aligned_buffer_.get(), BLOCK_SZ);
+ return true;
+ }
+
if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
std::string op;
- if (cow_op->type == kCowCopyOp)
+ if (cow_op->type() == kCowCopyOp)
op = "Copy-op";
else {
op = "Xor-op";
@@ -133,7 +147,7 @@
}
case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
bool ret;
- if (cow_op->type == kCowCopyOp) {
+ if (cow_op->type() == kCowCopyOp) {
ret = ProcessCopyOp(cow_op, buffer);
} else {
ret = ProcessXorOp(cow_op, buffer);
@@ -167,7 +181,7 @@
return false;
}
- switch (cow_op->type) {
+ switch (cow_op->type()) {
case kCowReplaceOp: {
return ProcessReplaceOp(cow_op, buffer);
}
@@ -183,7 +197,8 @@
}
default: {
- SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+ SNAP_LOG(ERROR) << "Unknown operation-type found: "
+ << static_cast<uint8_t>(cow_op->type());
}
}
return false;
@@ -200,6 +215,24 @@
return false;
}
+ if (direct_read_) {
+ backing_store_direct_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY | O_DIRECT));
+ if (backing_store_direct_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed with O_DIRECT: " << backing_store_direct_fd_;
+ direct_read_ = false;
+ } else {
+ void* aligned_addr;
+ ssize_t page_size = getpagesize();
+ if (posix_memalign(&aligned_addr, page_size, page_size) < 0) {
+ direct_read_ = false;
+ SNAP_PLOG(ERROR) << "posix_memalign failed "
+ << " page_size: " << page_size << " read_sz: " << page_size;
+ } else {
+ aligned_buffer_.reset(aligned_addr);
+ }
+ }
+ }
+
block_server_ = block_server_opener_->Open(this, PAYLOAD_BUFFER_SZ);
if (!block_server_) {
SNAP_PLOG(ERROR) << "Unable to open block server";
@@ -213,7 +246,7 @@
pthread_setname_np(pthread_self(), "ReadWorker");
- if (!SetThreadPriority(kNiceValueForMergeThreads)) {
+ if (!SetThreadPriority(ANDROID_PRIORITY_NORMAL)) {
SNAP_PLOG(ERROR) << "Failed to set thread priority";
}
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
index 6dbae81..1aff50c 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
@@ -28,7 +28,7 @@
ReadWorker(const std::string& cow_device, const std::string& backing_device,
const std::string& misc_name, const std::string& base_path_merge,
std::shared_ptr<SnapshotHandler> snapuserd,
- std::shared_ptr<IBlockServerOpener> opener);
+ std::shared_ptr<IBlockServerOpener> opener, bool direct_read = false);
bool Run();
bool Init() override;
@@ -59,11 +59,14 @@
std::string backing_store_device_;
unique_fd backing_store_fd_;
+ unique_fd backing_store_direct_fd_;
+ bool direct_read_ = false;
std::shared_ptr<IBlockServerOpener> block_server_opener_;
std::unique_ptr<IBlockServer> block_server_;
std::basic_string<uint8_t> xor_buffer_;
+ std::unique_ptr<void, decltype(&::free)> aligned_buffer_;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 950d771..05ba047 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -36,7 +36,7 @@
SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
std::string backing_device, std::string base_path_merge,
std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads,
- bool use_iouring, bool perform_verification) {
+ bool use_iouring, bool perform_verification, bool o_direct) {
misc_name_ = std::move(misc_name);
cow_device_ = std::move(cow_device);
backing_store_device_ = std::move(backing_device);
@@ -45,13 +45,14 @@
num_worker_threads_ = num_worker_threads;
is_io_uring_enabled_ = use_iouring;
perform_verification_ = perform_verification;
+ o_direct_ = o_direct;
}
bool SnapshotHandler::InitializeWorkers() {
for (int i = 0; i < num_worker_threads_; i++) {
auto wt = std::make_unique<ReadWorker>(cow_device_, backing_store_device_, misc_name_,
base_path_merge_, GetSharedPtr(),
- block_server_opener_);
+ block_server_opener_, o_direct_);
if (!wt->Init()) {
SNAP_LOG(ERROR) << "Thread initialization failed";
return false;
@@ -199,13 +200,13 @@
while (!cowop_iter->AtEnd()) {
const CowOperation* cow_op = cowop_iter->Get();
- if (cow_op->type == kCowCopyOp) {
+ if (cow_op->type() == kCowCopyOp) {
copy_ops += 1;
- } else if (cow_op->type == kCowReplaceOp) {
+ } else if (cow_op->type() == kCowReplaceOp) {
replace_ops += 1;
- } else if (cow_op->type == kCowZeroOp) {
+ } else if (cow_op->type() == kCowZeroOp) {
zero_ops += 1;
- } else if (cow_op->type == kCowXorOp) {
+ } else if (cow_op->type() == kCowXorOp) {
xor_ops += 1;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index fa1e7a0..9b7238a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -47,6 +47,7 @@
#include <snapuserd/snapuserd_buffer.h>
#include <snapuserd/snapuserd_kernel.h>
#include <storage_literals/storage_literals.h>
+#include <system/thread_defs.h>
#include "snapuserd_readahead.h"
#include "snapuserd_verify.h"
@@ -62,8 +63,6 @@
static constexpr int kNumWorkerThreads = 4;
-static constexpr int kNiceValueForMergeThreads = -5;
-
#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
@@ -105,7 +104,7 @@
public:
SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device,
std::string base_path_merge, std::shared_ptr<IBlockServerOpener> opener,
- int num_workers, bool use_iouring, bool perform_verification);
+ int num_workers, bool use_iouring, bool perform_verification, bool o_direct);
bool InitCowDevice();
bool Start();
@@ -247,6 +246,7 @@
bool perform_verification_ = true;
bool resume_merge_ = false;
bool merge_complete_ = false;
+ bool o_direct_ = false;
std::unique_ptr<UpdateVerify> update_verify_;
std::shared_ptr<IBlockServerOpener> block_server_opener_;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index 998d233..2baf20d 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -77,7 +77,7 @@
SNAP_LOG(ERROR) << "PrepareNextReadAhead operation has no source offset: " << *cow_op;
return nr_consecutive;
}
- if (cow_op->type == kCowXorOp) {
+ if (cow_op->type() == kCowXorOp) {
xor_op_vec.push_back(cow_op);
}
@@ -106,7 +106,7 @@
break;
}
- if (op->type == kCowXorOp) {
+ if (op->type() == kCowXorOp) {
xor_op_vec.push_back(op);
}
@@ -778,10 +778,14 @@
InitializeIouring();
- if (!SetThreadPriority(kNiceValueForMergeThreads)) {
+ if (!SetThreadPriority(ANDROID_PRIORITY_BACKGROUND)) {
SNAP_PLOG(ERROR) << "Failed to set thread priority";
}
+ if (!SetProfiles({"CPUSET_SP_BACKGROUND"})) {
+ SNAP_PLOG(ERROR) << "Failed to assign task profile to readahead thread";
+ }
+
SNAP_LOG(INFO) << "ReadAhead processing.";
while (!RAIterDone()) {
if (!ReadAheadIOStart()) {
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 6eee357..0b881b6 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -346,7 +346,8 @@
std::shared_ptr<HandlerThread> UserSnapshotServer::AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
const std::string& backing_device,
- const std::string& base_path_merge) {
+ const std::string& base_path_merge,
+ const bool o_direct) {
// We will need multiple worker threads only during
// device boot after OTA. For all other purposes,
// one thread is sufficient. We don't want to consume
@@ -368,7 +369,7 @@
auto opener = block_server_factory_->CreateOpener(misc_name);
return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge,
- opener, num_worker_threads, io_uring_enabled_);
+ opener, num_worker_threads, io_uring_enabled_, o_direct);
}
bool UserSnapshotServer::WaitForSocket() {
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 9926071..3013c47 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -86,7 +86,8 @@
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);
+ const std::string& base_path_merge,
+ bool o_direct = false);
bool StartHandler(const std::string& misc_name);
void SetTerminating() { terminating_ = true; }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index 73c3cbf..8ddb0f4 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -59,10 +59,16 @@
using testing::AssertionFailure;
using testing::AssertionResult;
using testing::AssertionSuccess;
+using ::testing::TestWithParam;
-class SnapuserdTestBase : public ::testing::TestWithParam<bool> {
+struct TestParam {
+ bool io_uring;
+ bool o_direct;
+};
+
+class SnapuserdTestBase : public ::testing::TestWithParam<TestParam> {
protected:
- void SetUp() override;
+ virtual void SetUp() override;
void TearDown() override;
void CreateBaseDevice();
void CreateCowDevice();
@@ -628,9 +634,10 @@
auto factory = harness_->GetBlockServerFactory();
auto opener = factory->CreateOpener(system_device_ctrl_name_);
handlers_->DisableVerification();
- auto handler =
- handlers_->AddHandler(system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(),
- base_dev_->GetPath(), opener, 1, GetParam());
+ const TestParam params = GetParam();
+ auto handler = handlers_->AddHandler(system_device_ctrl_name_, cow_system_->path,
+ base_dev_->GetPath(), base_dev_->GetPath(), opener, 1,
+ params.io_uring, params.o_direct);
ASSERT_NE(handler, nullptr);
ASSERT_NE(handler->snapuserd(), nullptr);
#ifdef __ANDROID__
@@ -898,9 +905,10 @@
opener_ = factory_.CreateTestOpener(system_device_ctrl_name_);
ASSERT_NE(opener_, nullptr);
+ const TestParam params = GetParam();
handler_ = std::make_shared<SnapshotHandler>(system_device_ctrl_name_, cow_system_->path,
base_dev_->GetPath(), base_dev_->GetPath(),
- opener_, 1, false, false);
+ opener_, 1, false, false, params.o_direct);
ASSERT_TRUE(handler_->InitCowDevice());
ASSERT_TRUE(handler_->InitializeWorkers());
@@ -990,14 +998,28 @@
return {false, true};
}
-std::string IoUringConfigName(const testing::TestParamInfo<SnapuserdTest::ParamType>& info) {
- return info.param ? "io_uring" : "sync";
+std::vector<TestParam> GetTestConfigs() {
+ std::vector<TestParam> testParams;
+ std::vector<bool> uring_configs = GetIoUringConfigs();
+
+ for (bool config : uring_configs) {
+ TestParam param;
+ param.io_uring = config;
+ param.o_direct = false;
+ testParams.push_back(std::move(param));
+ }
+
+ for (bool config : uring_configs) {
+ TestParam param;
+ param.io_uring = config;
+ param.o_direct = true;
+ testParams.push_back(std::move(param));
+ }
+ return testParams;
}
-INSTANTIATE_TEST_SUITE_P(Io, SnapuserdTest, ::testing::ValuesIn(GetIoUringConfigs()),
- IoUringConfigName);
-INSTANTIATE_TEST_SUITE_P(Io, HandlerTest, ::testing::ValuesIn(GetIoUringConfigs()),
- IoUringConfigName);
+INSTANTIATE_TEST_SUITE_P(Io, SnapuserdTest, ::testing::ValuesIn(GetTestConfigs()));
+INSTANTIATE_TEST_SUITE_P(Io, HandlerTest, ::testing::ValuesIn(GetTestConfigs()));
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h
index d07d2f8..7c99085 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h
@@ -48,10 +48,27 @@
std::mutex m_lock_;
std::condition_variable m_cv_;
+ /*
+ * Scanning of partitions is an expensive operation both in terms of memory
+ * and CPU usage. The goal here is to scan the partitions fast enough without
+ * significant increase in the boot time.
+ *
+ * Partitions such as system, product which may be huge and may need multiple
+ * threads to speed up the verification process. Using multiple threads for
+ * all partitions may increase CPU usage significantly. Hence, limit that to
+ * 1 thread per partition.
+ *
+ * These numbers were derived by monitoring the memory and CPU pressure
+ * (/proc/pressure/{cpu,memory}; and monitoring the Inactive(file) and
+ * Active(file) pages from /proc/meminfo.
+ *
+ * Additionally, for low memory devices, it is advisible to use O_DIRECT
+ * fucntionality for source block device.
+ */
int kMinThreadsToVerify = 1;
- int kMaxThreadsToVerify = 4;
- uint64_t kThresholdSize = 512_MiB;
- uint64_t kBlockSizeVerify = 1_MiB;
+ int kMaxThreadsToVerify = 3;
+ uint64_t kThresholdSize = 750_MiB;
+ uint64_t kBlockSizeVerify = 2_MiB;
bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
void UpdatePartitionVerificationState(UpdateVerifyState state);
diff --git a/fs_mgr/libsnapshot/snapuserd/utility.cpp b/fs_mgr/libsnapshot/snapuserd/utility.cpp
index fcdb69d..684ca3d 100644
--- a/fs_mgr/libsnapshot/snapuserd/utility.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/utility.cpp
@@ -19,6 +19,9 @@
#include <unistd.h>
#include <android-base/file.h>
+#include <processgroup/processgroup.h>
+
+#include <private/android_filesystem_config.h>
namespace android {
namespace snapshot {
@@ -33,6 +36,17 @@
#endif
}
+bool SetProfiles([[maybe_unused]] std::initializer_list<std::string_view> profiles) {
+#ifdef __ANDROID__
+ if (setgid(AID_SYSTEM)) {
+ return false;
+ }
+ return SetTaskProfiles(gettid(), profiles);
+#else
+ return true;
+#endif
+}
+
bool KernelSupportsIoUring() {
struct utsname uts {};
unsigned int major, minor;
diff --git a/fs_mgr/libsnapshot/snapuserd/utility.h b/fs_mgr/libsnapshot/snapuserd/utility.h
index 255aee1..c3c3cba 100644
--- a/fs_mgr/libsnapshot/snapuserd/utility.h
+++ b/fs_mgr/libsnapshot/snapuserd/utility.h
@@ -14,10 +14,14 @@
#pragma once
+#include <initializer_list>
+#include <string_view>
+
namespace android {
namespace snapshot {
bool SetThreadPriority(int priority);
+bool SetProfiles(std::initializer_list<std::string_view> profiles);
bool KernelSupportsIoUring();
} // namespace snapshot
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index c87e564..7ac7a16 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -1233,6 +1233,12 @@
adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts </dev/null &&
die "/vendor is not RO"
+data_device=$(adb_sh awk '$2 == "/data" { print $1; exit }' /proc/mounts)
+RO=$(adb_sh grep " ro," /proc/mounts </dev/null |
+ grep -v "^${data_device}" |
+ skip_administrative_mounts |
+ awk '{ print $1 }')
+
T=$(adb_date)
adb remount >&2 ||
die -t "${T}" "adb remount"
@@ -1241,6 +1247,12 @@
adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts </dev/null ||
die -t "${T}" "/vendor is not RW"
+# Only find mounts that are remounted RO -> RW
+RW=$(adb_sh grep " rw," /proc/mounts </dev/null |
+ grep -v "^${data_device}" |
+ skip_administrative_mounts |
+ grep -E "^($(join_with '|' ${RO})) ")
+
scratch_on_super=false
if ${overlayfs_needed}; then
is_overlayfs_mounted /system ||
@@ -1287,27 +1299,19 @@
fi
done
- data_device=$(adb_sh awk '$2 == "/data" { print $1; exit }' /proc/mounts)
# KISS (we do not support sub-mounts for system partitions currently)
adb_sh grep "^overlay " /proc/mounts </dev/null |
grep -vE "^overlay.* /(apex|system|vendor)/[^ ]" |
grep " overlay ro," &&
die "expected overlay to be RW after remount"
- adb_sh grep -v noatime /proc/mounts </dev/null |
- grep -v "^${data_device}" |
- skip_administrative_mounts |
- grep -v ' ro,' &&
- die "mounts are not noatime"
- D=$(adb_sh grep " rw," /proc/mounts </dev/null |
- grep -v "^${data_device}" |
- skip_administrative_mounts |
+ D=$(echo "${RW}" |
awk '{ print $1 }' |
sed 's|/dev/root|/|' |
sort -u)
if [ -n "${D}" ]; then
adb_sh df -k ${D} </dev/null |
- sed -e 's/^Filesystem /Filesystem (rw) /'
+ sed -e 's/^Filesystem /Filesystem (rw)/'
fi >&2
for d in ${D}; do
if adb_sh tune2fs -l "${d}" </dev/null 2>&1 | grep -q "Filesystem features:.*shared_blocks" ||
@@ -1319,6 +1323,10 @@
is_overlayfs_mounted && die -t "${T}" "unexpected overlay takeover"
fi
+echo -n "${RW}" |
+ grep -v noatime &&
+ die "mounts (rw) are not noatime"
+
LOG OK "adb remount RW"
################################################################################
diff --git a/fs_mgr/tests/vts_fs_test.cpp b/fs_mgr/tests/vts_fs_test.cpp
index 8f15220..9503072 100644
--- a/fs_mgr/tests/vts_fs_test.cpp
+++ b/fs_mgr/tests/vts_fs_test.cpp
@@ -99,12 +99,7 @@
ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/super", &super_bdev));
ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/userdata", &userdata_bdev));
- std::vector<std::string> must_be_f2fs = {"/data"};
- if (vsr_level >= __ANDROID_API_U__ &&
- !DeviceSupportsFeature("android.hardware.type.automotive")) {
- must_be_f2fs.emplace_back("/metadata");
- }
-
+ std::vector<std::string> data_fs = {"/data", "/metadata"};
for (const auto& entry : fstab) {
std::string parent_bdev = entry.blk_device;
while (true) {
@@ -138,11 +133,10 @@
std::vector<std::string> allowed = {"erofs", "ext4", "f2fs"};
EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end())
<< entry.mount_point;
- } else {
- if (std::find(must_be_f2fs.begin(), must_be_f2fs.end(), entry.mount_point) !=
- must_be_f2fs.end()) {
- EXPECT_EQ(entry.fs_type, "f2fs") << entry.mount_point;
- }
+ } else if (std::find(data_fs.begin(), data_fs.end(), entry.mount_point) != data_fs.end()) {
+ std::vector<std::string> allowed = {"ext4", "f2fs"};
+ EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end())
+ << entry.mount_point << ", " << entry.fs_type;
}
}
}
diff --git a/healthd/Android.bp b/healthd/Android.bp
index 235303f..e158e07 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -76,7 +76,7 @@
defaults: ["libbatterymonitor_defaults"],
srcs: ["BatteryMonitor.cpp"],
static_libs: [
- "android.hardware.health-V2-ndk",
+ "android.hardware.health-V3-ndk",
],
whole_static_libs: [
// Need to translate HIDL to AIDL to support legacy APIs in
@@ -100,44 +100,6 @@
],
}
-cc_defaults {
- name: "android.hardware.health@2.0-service_defaults",
-
- cflags: [
- "-Wall",
- "-Werror",
- ],
-
- static_libs: [
- "android.hardware.health@2.0-impl",
- "android.hardware.health@1.0-convert",
- "libhealthservice",
- "libhealthstoragedefault",
- "libbatterymonitor",
- ],
-
- shared_libs: [
- "libbase",
- "libcutils",
- "libhidlbase",
- "liblog",
- "libutils",
- "android.hardware.health@2.0",
- ],
-}
-
-cc_binary {
- name: "android.hardware.health@2.0-service",
- defaults: ["android.hardware.health@2.0-service_defaults"],
-
- vendor: true,
- relative_install_path: "hw",
- init_rc: ["android.hardware.health@2.0-service.rc"],
- srcs: [
- "HealthServiceDefault.cpp",
- ],
-}
-
cc_library_static {
name: "libhealthd_charger_nops",
recovery_available: true,
@@ -203,12 +165,12 @@
defaults: ["libhealthd_charger_ui_defaults"],
static_libs: [
- "android.hardware.health-V2-ndk",
+ "android.hardware.health-V3-ndk",
"android.hardware.health-translate-ndk",
],
export_static_lib_headers: [
- "android.hardware.health-V2-ndk",
+ "android.hardware.health-V3-ndk",
],
}
@@ -280,7 +242,7 @@
static_libs: [
// common
"android.hardware.health@1.0-convert",
- "android.hardware.health-V2-ndk",
+ "android.hardware.health-V3-ndk",
"libbatterymonitor",
"libcharger_sysprop",
"libhealthd_charger_nops",
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 0c97632..b8bb586 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -59,6 +59,7 @@
using aidl::android::hardware::health::BatteryChargingState;
using aidl::android::hardware::health::BatteryHealth;
using aidl::android::hardware::health::BatteryHealthData;
+using aidl::android::hardware::health::BatteryPartStatus;
using aidl::android::hardware::health::BatteryStatus;
using aidl::android::hardware::health::HealthInfo;
@@ -219,6 +220,7 @@
{"Warm", BatteryHealth::GOOD},
{"Cool", BatteryHealth::GOOD},
{"Hot", BatteryHealth::OVERHEAT},
+ {"Calibration required", BatteryHealth::INCONSISTENT},
{NULL, BatteryHealth::UNKNOWN},
};
@@ -596,6 +598,9 @@
if (!mHealthdConfig->batteryStateOfHealthPath.empty())
return getIntField(mHealthdConfig->batteryStateOfHealthPath);
}
+ if (id == BATTERY_PROP_PART_STATUS) {
+ return static_cast<int>(BatteryPartStatus::UNSUPPORTED);
+ }
return 0;
}
@@ -679,6 +684,11 @@
ret = OK;
break;
+ case BATTERY_PROP_PART_STATUS:
+ val->valueInt64 = getBatteryHealthData(BATTERY_PROP_PART_STATUS);
+ ret = OK;
+ break;
+
default:
break;
}
@@ -686,6 +696,11 @@
return ret;
}
+status_t BatteryMonitor::getSerialNumber(std::optional<std::string>* out) {
+ *out = std::nullopt;
+ return OK;
+}
+
void BatteryMonitor::dumpState(int fd) {
int v;
char vs[128];
diff --git a/healthd/HealthServiceDefault.cpp b/healthd/HealthServiceDefault.cpp
deleted file mode 100644
index 89ecc2f..0000000
--- a/healthd/HealthServiceDefault.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 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 <health2/service.h>
-#include <healthd/healthd.h>
-
-void healthd_board_init(struct healthd_config*) {
- // Implementation-defined init logic goes here.
- // 1. config->periodic_chores_interval_* variables
- // 2. config->battery*Path variables
- // 3. config->energyCounter. In this implementation, energyCounter is not defined.
-
- // use defaults
-}
-
-int healthd_board_battery_update(struct android::BatteryProperties*) {
- // Implementation-defined update logic goes here. An implementation
- // can make modifications to prop before broadcasting it to all callbacks.
-
- // return 0 to log periodic polled battery status to kernel log
- return 0;
-}
-
-int main() {
- return health_service_main();
-}
diff --git a/healthd/android.hardware.health@2.0-service.rc b/healthd/android.hardware.health@2.0-service.rc
deleted file mode 100644
index 762771e..0000000
--- a/healthd/android.hardware.health@2.0-service.rc
+++ /dev/null
@@ -1,6 +0,0 @@
-service health-hal-2-0 /vendor/bin/hw/android.hardware.health@2.0-service
- class hal
- user system
- group system
- capabilities WAKE_ALARM BLOCK_SUSPEND
- file /dev/kmsg w
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index e9998ba..b30458d 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -18,6 +18,7 @@
#define HEALTHD_BATTERYMONITOR_H
#include <memory>
+#include <optional>
#include <batteryservice/BatteryService.h>
#include <utils/String8.h>
@@ -86,6 +87,8 @@
int getChargingPolicy();
int getBatteryHealthData(int id);
+ status_t getSerialNumber(std::optional<std::string>* out);
+
static void logValues(const android::hardware::health::V2_1::HealthInfo& health_info,
const struct healthd_config& healthd_config);
diff --git a/init/Android.bp b/init/Android.bp
index e5512e6..a7278d6 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -188,7 +188,6 @@
"libfs_mgr",
"libgsi",
"libhidl-gen-utils",
- "libkeyutils",
"liblog",
"liblogwrap",
"liblp",
@@ -197,6 +196,7 @@
"libselinux",
"libunwindstack",
"libutils",
+ "libvendorsupport",
],
header_libs: ["bionic_libc_platform_headers"],
bootstrap: true,
diff --git a/init/builtins.cpp b/init/builtins.cpp
index a95a4a3..606ea8c 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -592,9 +592,6 @@
} else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED ||
code == FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED ||
code == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
- if (!FscryptInstallKeyring()) {
- return Error() << "FscryptInstallKeyring() failed";
- }
SetProperty("ro.crypto.state", "encrypted");
// Although encrypted, vold has already set the device up, so we do not need to
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index e48fa15..c4d0f75 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -30,6 +30,7 @@
#include <chrono>
#include <filesystem>
#include <string>
+#include <thread>
#include <vector>
#include <android-base/chrono_utils.h>
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index d0f68a8..c0b9281 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -732,6 +732,15 @@
return true;
}
+bool IsHashtreeDisabled(const AvbHandle& vbmeta, const std::string& mount_point) {
+ if (vbmeta.status() == AvbHandleStatus::kHashtreeDisabled ||
+ vbmeta.status() == AvbHandleStatus::kVerificationDisabled) {
+ LOG(ERROR) << "Top-level vbmeta is disabled, skip Hashtree setup for " << mount_point;
+ return true; // Returns true to mount the partition directly.
+ }
+ return false;
+}
+
bool FirstStageMountVBootV2::SetUpDmVerity(FstabEntry* fstab_entry) {
AvbHashtreeResult hashtree_result;
@@ -740,34 +749,46 @@
if (!fstab_entry->avb_keys.empty()) {
if (!InitAvbHandle()) return false;
// Checks if hashtree should be disabled from the top-level /vbmeta.
- if (avb_handle_->status() == AvbHandleStatus::kHashtreeDisabled ||
- avb_handle_->status() == AvbHandleStatus::kVerificationDisabled) {
- LOG(ERROR) << "Top-level vbmeta is disabled, skip Hashtree setup for "
- << fstab_entry->mount_point;
- return true; // Returns true to mount the partition directly.
+ if (IsHashtreeDisabled(*avb_handle_, fstab_entry->mount_point)) {
+ return true;
+ }
+ auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta(
+ *fstab_entry, preload_avb_key_blobs_[fstab_entry->avb_keys]);
+ if (!avb_standalone_handle) {
+ LOG(ERROR) << "Failed to load offline vbmeta for " << fstab_entry->mount_point;
+ // Fallbacks to built-in hashtree if fs_mgr_flags.avb is set.
+ if (!fstab_entry->fs_mgr_flags.avb) return false;
+ LOG(INFO) << "Fallback to built-in hashtree for " << fstab_entry->mount_point;
+ hashtree_result =
+ avb_handle_->SetUpAvbHashtree(fstab_entry, false /* wait_for_verity_dev */);
} else {
- auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta(
- *fstab_entry, preload_avb_key_blobs_[fstab_entry->avb_keys]);
- if (!avb_standalone_handle) {
- LOG(ERROR) << "Failed to load offline vbmeta for " << fstab_entry->mount_point;
- // Fallbacks to built-in hashtree if fs_mgr_flags.avb is set.
- if (!fstab_entry->fs_mgr_flags.avb) return false;
- LOG(INFO) << "Fallback to built-in hashtree for " << fstab_entry->mount_point;
- hashtree_result =
- avb_handle_->SetUpAvbHashtree(fstab_entry, false /* wait_for_verity_dev */);
- } else {
- // Sets up hashtree via the standalone handle.
- if (IsStandaloneImageRollback(*avb_handle_, *avb_standalone_handle, *fstab_entry)) {
- return false;
- }
- hashtree_result = avb_standalone_handle->SetUpAvbHashtree(
- fstab_entry, false /* wait_for_verity_dev */);
+ // Sets up hashtree via the standalone handle.
+ if (IsStandaloneImageRollback(*avb_handle_, *avb_standalone_handle, *fstab_entry)) {
+ return false;
}
+ hashtree_result = avb_standalone_handle->SetUpAvbHashtree(
+ fstab_entry, false /* wait_for_verity_dev */);
}
} else if (fstab_entry->fs_mgr_flags.avb) {
if (!InitAvbHandle()) return false;
hashtree_result =
avb_handle_->SetUpAvbHashtree(fstab_entry, false /* wait_for_verity_dev */);
+ } else if (!fstab_entry->avb_hashtree_digest.empty()) {
+ // When fstab_entry has neither avb_keys nor avb flag, try using
+ // avb_hashtree_digest.
+ if (!InitAvbHandle()) return false;
+ // Checks if hashtree should be disabled from the top-level /vbmeta.
+ if (IsHashtreeDisabled(*avb_handle_, fstab_entry->mount_point)) {
+ return true;
+ }
+ auto avb_standalone_handle = AvbHandle::LoadAndVerifyVbmeta(*fstab_entry);
+ if (!avb_standalone_handle) {
+ LOG(ERROR) << "Failed to load vbmeta based on hashtree descriptor root digest for "
+ << fstab_entry->mount_point;
+ return false;
+ }
+ hashtree_result = avb_standalone_handle->SetUpAvbHashtree(fstab_entry,
+ false /* wait_for_verity_dev */);
} else {
return true; // No need AVB, returns true to mount the partition directly.
}
diff --git a/init/fscrypt_init_extensions.cpp b/init/fscrypt_init_extensions.cpp
index fbd8189..6a561e5 100644
--- a/init/fscrypt_init_extensions.cpp
+++ b/init/fscrypt_init_extensions.cpp
@@ -34,28 +34,12 @@
#include <cutils/properties.h>
#include <cutils/sockets.h>
#include <fscrypt/fscrypt.h>
-#include <keyutils.h>
#include <logwrap/logwrap.h>
#define TAG "fscrypt"
using namespace android::fscrypt;
-bool FscryptInstallKeyring() {
- if (keyctl_search(KEY_SPEC_SESSION_KEYRING, "keyring", "fscrypt", 0) != -1) {
- LOG(INFO) << "Keyring is already created";
- return true;
- }
- key_serial_t device_keyring = add_key("keyring", "fscrypt", 0, 0, KEY_SPEC_SESSION_KEYRING);
-
- if (device_keyring == -1) {
- PLOG(ERROR) << "Failed to create keyring";
- return false;
- }
- LOG(INFO) << "Keyring created with id " << device_keyring << " in process " << getpid();
- return true;
-}
-
// TODO(b/139378601): use a single central implementation of this.
static void delete_dir_contents(const std::string& dir) {
char* const paths[2] = {const_cast<char*>(dir.c_str()), nullptr};
diff --git a/init/fscrypt_init_extensions.h b/init/fscrypt_init_extensions.h
index d357bb2..5e0269a 100644
--- a/init/fscrypt_init_extensions.h
+++ b/init/fscrypt_init_extensions.h
@@ -25,6 +25,5 @@
kDeleteIfNecessary,
};
-bool FscryptInstallKeyring();
bool FscryptSetDirectoryPolicy(const std::string& ref_basename, FscryptAction action,
const std::string& dir);
diff --git a/init/fuzzer/Android.bp b/init/fuzzer/Android.bp
index 856ca8c..9916246 100644
--- a/init/fuzzer/Android.bp
+++ b/init/fuzzer/Android.bp
@@ -32,7 +32,6 @@
"libbase",
"libfs_mgr",
"libhidl-gen-utils",
- "libkeyutils",
"liblog",
"libprocessgroup",
"libselinux",
diff --git a/init/init.cpp b/init/init.cpp
index 83cb78b..19e909f 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -54,7 +54,6 @@
#include <android-base/thread_annotations.h>
#include <fs_avb/fs_avb.h>
#include <fs_mgr_vendor_overlay.h>
-#include <keyutils.h>
#include <libavb/libavb.h>
#include <libgsi/libgsi.h>
#include <libsnapshot/snapshot.h>
@@ -117,7 +116,6 @@
static int property_triggers_enabled = 0;
-int sigchld_fd = -1;
static int sigterm_fd = -1;
static int property_fd = -1;
@@ -717,7 +715,7 @@
static void HandleSignalFd(int signal) {
signalfd_siginfo siginfo;
- const int signal_fd = signal == SIGCHLD ? sigchld_fd : sigterm_fd;
+ const int signal_fd = signal == SIGCHLD ? Service::GetSigchldFd() : sigterm_fd;
ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
if (bytes_read != sizeof(siginfo)) {
PLOG(ERROR) << "Failed to read siginfo from signal_fd";
@@ -751,20 +749,24 @@
}
}
+static Result<void> RegisterSignalFd(Epoll* epoll, int signal, int fd) {
+ return epoll->RegisterHandler(
+ fd, [signal]() { HandleSignalFd(signal); }, EPOLLIN | EPOLLPRI);
+}
+
static Result<int> CreateAndRegisterSignalFd(Epoll* epoll, int signal) {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, signal);
- unique_fd signal_fd(signalfd(-1, &mask, SFD_CLOEXEC));
- if (signal_fd == -1) {
- return ErrnoError() << "failed to create signalfd for signal " << signal;
+ if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
+ return ErrnoError() << "failed to block signal " << signal;
}
- auto result = epoll->RegisterHandler(
- signal_fd.get(), [signal]() { HandleSignalFd(signal); }, EPOLLIN | EPOLLPRI);
- if (!result.ok()) {
- return result.error();
+ unique_fd signal_fd(signalfd(-1, &mask, SFD_CLOEXEC));
+ if (signal_fd.get() < 0) {
+ return ErrnoError() << "failed to create signalfd for signal " << signal;
}
+ OR_RETURN(RegisterSignalFd(epoll, signal, signal_fd.get()));
return signal_fd.release();
}
@@ -775,34 +777,18 @@
const struct sigaction act { .sa_flags = SA_NOCLDSTOP, .sa_handler = SIG_DFL };
sigaction(SIGCHLD, &act, nullptr);
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGCHLD);
-
- if (!IsRebootCapable()) {
- // If init does not have the CAP_SYS_BOOT capability, it is running in a container.
- // In that case, receiving SIGTERM will cause the system to shut down.
- sigaddset(&mask, SIGTERM);
- }
-
- if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
- PLOG(FATAL) << "failed to block signals";
- }
-
// Register a handler to unblock signals in the child processes.
const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);
if (result != 0) {
LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);
}
- Result<int> cs_result = CreateAndRegisterSignalFd(epoll, SIGCHLD);
+ Result<void> cs_result = RegisterSignalFd(epoll, SIGCHLD, Service::GetSigchldFd());
if (!cs_result.ok()) {
PLOG(FATAL) << cs_result.error();
}
- sigchld_fd = cs_result.value();
- Service::SetSigchldFd(sigchld_fd);
- if (sigismember(&mask, SIGTERM)) {
+ if (!IsRebootCapable()) {
Result<int> cs_result = CreateAndRegisterSignalFd(epoll, SIGTERM);
if (!cs_result.ok()) {
PLOG(FATAL) << cs_result.error();
@@ -984,11 +970,6 @@
<< " to /proc/1/oom_score_adj: " << result.error();
}
- // Set up a session keyring that all processes will have access to. It
- // will hold things like FBE encryption keys. No process should override
- // its session keyring.
- keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
-
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
diff --git a/init/init.h b/init/init.h
index b781167..9c7e918 100644
--- a/init/init.h
+++ b/init/init.h
@@ -28,8 +28,6 @@
namespace android {
namespace init {
-extern int sigchld_fd;
-
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list);
diff --git a/init/property_service.cpp b/init/property_service.cpp
index bd74358..30ad800 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -63,6 +63,7 @@
#include <selinux/android.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
+#include <vendorsupport/api_level.h>
#include "debug_ramdisk.h"
#include "epoll.h"
@@ -84,6 +85,7 @@
using android::base::ErrnoError;
using android::base::Error;
+using android::base::GetIntProperty;
using android::base::GetProperty;
using android::base::ParseInt;
using android::base::ReadFileToString;
@@ -112,7 +114,6 @@
constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
constexpr auto DIGEST_SIZE_USED = 8;
-constexpr auto API_LEVEL_CURRENT = 10000;
static bool persistent_properties_loaded = false;
@@ -1084,36 +1085,38 @@
}
}
-static int read_api_level_props(const std::vector<std::string>& api_level_props) {
- int api_level = API_LEVEL_CURRENT;
- for (const auto& api_level_prop : api_level_props) {
- api_level = android::base::GetIntProperty(api_level_prop, API_LEVEL_CURRENT);
- if (api_level != API_LEVEL_CURRENT) {
- break;
- }
- }
- return api_level;
-}
-
static void property_initialize_ro_vendor_api_level() {
// ro.vendor.api_level shows the api_level that the vendor images (vendor, odm, ...) are
// required to support.
constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
- // Api level properties of the board. The order of the properties must be kept.
- std::vector<std::string> BOARD_API_LEVEL_PROPS = {"ro.board.api_level",
- "ro.board.first_api_level"};
- // Api level properties of the device. The order of the properties must be kept.
- std::vector<std::string> DEVICE_API_LEVEL_PROPS = {"ro.product.first_api_level",
- "ro.build.version.sdk"};
+ auto vendor_api_level = GetIntProperty("ro.board.first_api_level", __ANDROID_VENDOR_API_MAX__);
+ if (vendor_api_level != __ANDROID_VENDOR_API_MAX__) {
+ // Update the vendor_api_level with "ro.board.api_level" only if both "ro.board.api_level"
+ // and "ro.board.first_api_level" are defined.
+ vendor_api_level = GetIntProperty("ro.board.api_level", vendor_api_level);
+ }
- int api_level = std::min(read_api_level_props(BOARD_API_LEVEL_PROPS),
- read_api_level_props(DEVICE_API_LEVEL_PROPS));
+ auto product_first_api_level =
+ GetIntProperty("ro.product.first_api_level", __ANDROID_API_FUTURE__);
+ if (product_first_api_level == __ANDROID_API_FUTURE__) {
+ // Fallback to "ro.build.version.sdk" if the "ro.product.first_api_level" is not defined.
+ product_first_api_level = GetIntProperty("ro.build.version.sdk", __ANDROID_API_FUTURE__);
+ }
+
+ vendor_api_level = std::min(vendor_api_level_of(product_first_api_level), vendor_api_level);
+
+ if (vendor_api_level < 0) {
+ LOG(ERROR) << "Unexpected vendor api level for " << VENDOR_API_LEVEL_PROP << ". Check "
+ << "ro.product.first_api_level and ro.build.version.sdk.";
+ vendor_api_level = __ANDROID_VENDOR_API_MAX__;
+ }
+
std::string error;
- auto res = PropertySetNoSocket(VENDOR_API_LEVEL_PROP, std::to_string(api_level), &error);
+ auto res = PropertySetNoSocket(VENDOR_API_LEVEL_PROP, std::to_string(vendor_api_level), &error);
if (res != PROP_SUCCESS) {
- LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << api_level << ": "
- << error << "(" << res << ")";
+ LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << vendor_api_level
+ << ": " << error << "(" << res << ")";
}
}
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 5757922..1a26c4d 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -563,7 +563,7 @@
}
}
if (timeout > 0ms) {
- WaitToBeReaped(sigchld_fd, pids, timeout);
+ WaitToBeReaped(Service::GetSigchldFd(), pids, timeout);
} else {
// Even if we don't to wait for services to stop, we still optimistically reap zombies.
ReapAnyOutstandingChildren();
diff --git a/init/service.cpp b/init/service.cpp
index d351a8f..eb24dd5 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -37,6 +37,7 @@
#include <cutils/sockets.h>
#include <processgroup/processgroup.h>
#include <selinux/selinux.h>
+#include <sys/signalfd.h>
#include <string>
@@ -68,6 +69,7 @@
using android::base::SetProperty;
using android::base::StartsWith;
using android::base::StringPrintf;
+using android::base::unique_fd;
using android::base::WriteStringToFile;
namespace android {
@@ -136,7 +138,6 @@
unsigned long Service::next_start_order_ = 1;
bool Service::is_exec_service_running_ = false;
-int Service::sigchld_fd_ = -1;
Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
const std::string& filename, const std::vector<std::string>& args)
@@ -792,6 +793,35 @@
mount_namespace_ = IsDefaultMountNamespaceReady() ? NS_DEFAULT : NS_BOOTSTRAP;
}
+static int ThreadCount() {
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/proc/self/task"), closedir);
+ if (!dir) {
+ return -1;
+ }
+
+ int count = 0;
+ dirent* entry;
+ while ((entry = readdir(dir.get())) != nullptr) {
+ if (entry->d_name[0] != '.') {
+ count++;
+ }
+ }
+ return count;
+}
+
+// Must be called BEFORE any threads are created. See also the sigprocmask() man page.
+unique_fd Service::CreateSigchldFd() {
+ CHECK_EQ(ThreadCount(), 1);
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ if (sigprocmask(SIG_BLOCK, &mask, nullptr) < 0) {
+ PLOG(FATAL) << "Failed to block SIGCHLD";
+ }
+
+ return unique_fd(signalfd(-1, &mask, SFD_CLOEXEC));
+}
+
void Service::SetStartedInFirstStage(pid_t pid) {
LOG(INFO) << "adding first-stage service '" << name_ << "'...";
diff --git a/init/service.h b/init/service.h
index 13c8b5f..5e9af25 100644
--- a/init/service.h
+++ b/init/service.h
@@ -156,7 +156,10 @@
const Subcontext* subcontext() const { return subcontext_; }
const std::string& filename() const { return filename_; }
void set_filename(const std::string& name) { filename_ = name; }
- static void SetSigchldFd(int sigchld_fd) { sigchld_fd_ = sigchld_fd; }
+ static int GetSigchldFd() {
+ static int sigchld_fd = CreateSigchldFd().release();
+ return sigchld_fd;
+ }
private:
void NotifyStateChange(const std::string& new_state) const;
@@ -169,10 +172,10 @@
void RunService(const std::vector<Descriptor>& descriptors, InterprocessFifo cgroups_activated,
InterprocessFifo setsid_finished);
void SetMountNamespace();
+ static ::android::base::unique_fd CreateSigchldFd();
static unsigned long next_start_order_;
static bool is_exec_service_running_;
- static int sigchld_fd_;
const std::string name_;
std::set<std::string> classnames_;
diff --git a/init/service_test.cpp b/init/service_test.cpp
index 87a2ce5..a3590b5 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -17,18 +17,45 @@
#include "service.h"
#include <algorithm>
+#include <fstream>
#include <memory>
#include <type_traits>
#include <vector>
#include <gtest/gtest.h>
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <selinux/selinux.h>
+#include <sys/signalfd.h>
#include "lmkd_service.h"
+#include "reboot.h"
+#include "service.h"
+#include "service_list.h"
+#include "service_parser.h"
#include "util.h"
+using ::android::base::ReadFileToString;
+using ::android::base::StringPrintf;
+using ::android::base::StringReplace;
+using ::android::base::unique_fd;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+
namespace android {
namespace init {
+static std::string GetSecurityContext() {
+ char* ctx;
+ if (getcon(&ctx) == -1) {
+ ADD_FAILURE() << "Failed to call getcon : " << strerror(errno);
+ }
+ std::string result{ctx};
+ freecon(ctx);
+ return result;
+}
+
TEST(service, pod_initialized) {
constexpr auto memory_size = sizeof(Service);
alignas(alignof(Service)) unsigned char old_memory[memory_size];
@@ -190,5 +217,69 @@
Test_make_temporary_oneshot_service(false, false, false, false, false);
}
+// Returns the path in the v2 cgroup hierarchy for a given process in the format /uid_%d/pid_%d.
+static std::string CgroupPath(pid_t pid) {
+ std::string cgroup_path = StringPrintf("/proc/%d/cgroup", pid);
+ std::ifstream is(cgroup_path, std::ios::in);
+ std::string line;
+ while (std::getline(is, line)) {
+ if (line.substr(0, 3) == "0::") {
+ return line.substr(3);
+ }
+ }
+ return {};
+}
+
+class ServiceStopTest : public testing::TestWithParam<bool> {};
+
+// Before November 2023, processes that were migrated to another v2 cgroup were ignored by
+// Service::Stop() if their uid_%d/pid_%d cgroup directory got removed. This test, if run with the
+// parameter set to 'true', verifies that such services are stopped.
+TEST_P(ServiceStopTest, stop) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
+
+ static constexpr std::string_view kServiceName = "ServiceA";
+ static constexpr std::string_view kScriptTemplate = R"init(
+service $name /system/bin/yes
+ user shell
+ group shell
+ seclabel $selabel
+)init";
+
+ std::string script = StringReplace(StringReplace(kScriptTemplate, "$name", kServiceName, false),
+ "$selabel", GetSecurityContext(), false);
+ ServiceList& service_list = ServiceList::GetInstance();
+ Parser parser;
+ parser.AddSectionParser("service",
+ std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+ TemporaryFile tf;
+ ASSERT_GE(tf.fd, 0);
+ ASSERT_TRUE(WriteStringToFd(script, tf.fd));
+ ASSERT_TRUE(parser.ParseConfig(tf.path));
+
+ Service* const service = ServiceList::GetInstance().FindService(kServiceName);
+ ASSERT_NE(service, nullptr);
+ ASSERT_RESULT_OK(service->Start());
+ ASSERT_TRUE(service->IsRunning());
+ if (GetParam()) {
+ const pid_t pid = service->pid();
+ const std::string cgroup_path = CgroupPath(pid);
+ EXPECT_NE(cgroup_path, "");
+ EXPECT_NE(cgroup_path, "/");
+ const std::string pid_str = std::to_string(pid);
+ EXPECT_TRUE(WriteStringToFile(pid_str, "/sys/fs/cgroup/cgroup.procs"));
+ EXPECT_EQ(CgroupPath(pid), "/");
+ EXPECT_EQ(rmdir(("/sys/fs/cgroup" + cgroup_path).c_str()), 0);
+ }
+ EXPECT_EQ(0, StopServicesAndLogViolations({service->name()}, 10s, /*terminate=*/true));
+ ServiceList::GetInstance().RemoveService(*service);
+}
+
+INSTANTIATE_TEST_SUITE_P(service, ServiceStopTest, testing::Values(false, true));
+
} // namespace init
} // namespace android
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index 9d4c7c8..8e9e713 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -118,12 +118,27 @@
return pid;
}
-void ReapAnyOutstandingChildren() {
- while (ReapOneProcess() != 0) {
+std::set<pid_t> ReapAnyOutstandingChildren() {
+ std::set<pid_t> reaped_pids;
+ for (;;) {
+ const pid_t pid = ReapOneProcess();
+ if (pid <= 0) {
+ return reaped_pids;
+ }
+ reaped_pids.emplace(pid);
}
}
-static void DiscardSiginfo(int signal_fd) {
+static void ReapAndRemove(std::vector<pid_t>& alive_pids) {
+ for (auto pid : ReapAnyOutstandingChildren()) {
+ const auto it = std::find(alive_pids.begin(), alive_pids.end(), pid);
+ if (it != alive_pids.end()) {
+ alive_pids.erase(it);
+ }
+ }
+}
+
+static void HandleSignal(int signal_fd) {
signalfd_siginfo siginfo;
ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
if (bytes_read != sizeof(siginfo)) {
@@ -136,27 +151,36 @@
std::chrono::milliseconds timeout) {
Timer t;
Epoll epoll;
- // The init process passes a valid sigchld_fd argument but unit tests do not.
if (sigchld_fd >= 0) {
- epoll.RegisterHandler(sigchld_fd, [sigchld_fd]() { DiscardSiginfo(sigchld_fd); });
+ if (auto result = epoll.Open(); result.ok()) {
+ result =
+ epoll.RegisterHandler(sigchld_fd, [sigchld_fd]() { HandleSignal(sigchld_fd); });
+ if (!result.ok()) {
+ LOG(WARNING) << __func__
+ << " RegisterHandler() failed. Falling back to sleep_for(): "
+ << result.error();
+ sigchld_fd = -1;
+ }
+ } else {
+ LOG(WARNING) << __func__ << " Epoll::Open() failed. Falling back to sleep_for(): "
+ << result.error();
+ sigchld_fd = -1;
+ }
}
- std::vector<pid_t> alive_pids(pids.begin(), pids.end());
+ std::vector<pid_t> alive_pids(pids);
+ ReapAndRemove(alive_pids);
while (!alive_pids.empty() && t.duration() < timeout) {
- pid_t pid;
- while ((pid = ReapOneProcess()) != 0) {
- auto it = std::find(alive_pids.begin(), alive_pids.end(), pid);
- if (it != alive_pids.end()) {
- alive_pids.erase(it);
+ if (sigchld_fd >= 0) {
+ auto result = epoll.Wait(std::max(timeout - t.duration(), 0ms));
+ if (result.ok()) {
+ ReapAndRemove(alive_pids);
+ continue;
+ } else {
+ LOG(WARNING) << "Epoll::Wait() failed " << result.error();
}
}
- if (alive_pids.empty()) {
- break;
- }
- if (sigchld_fd >= 0) {
- epoll.Wait(std::max(timeout - t.duration(), 0ms));
- } else {
- std::this_thread::sleep_for(50ms);
- }
+ std::this_thread::sleep_for(50ms);
+ ReapAndRemove(alive_pids);
}
LOG(INFO) << "Waiting for " << pids.size() << " pids to be reaped took " << t << " with "
<< alive_pids.size() << " of them still running";
diff --git a/init/sigchld_handler.h b/init/sigchld_handler.h
index e07a7d6..5351302 100644
--- a/init/sigchld_handler.h
+++ b/init/sigchld_handler.h
@@ -18,12 +18,13 @@
#define _INIT_SIGCHLD_HANDLER_H_
#include <chrono>
+#include <set>
#include <vector>
namespace android {
namespace init {
-void ReapAnyOutstandingChildren();
+std::set<pid_t> ReapAnyOutstandingChildren();
void WaitToBeReaped(int sigchld_fd, const std::vector<pid_t>& pids,
std::chrono::milliseconds timeout);
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
index 3a9ff5b..3a78343 100644
--- a/init/snapuserd_transition.cpp
+++ b/init/snapuserd_transition.cpp
@@ -25,6 +25,7 @@
#include <filesystem>
#include <string>
#include <string_view>
+#include <thread>
#include <android-base/file.h>
#include <android-base/logging.h>
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 586e2cf..3f0d0e9 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -297,6 +297,10 @@
}
static UeventdConfiguration GetConfiguration() {
+ if (IsMicrodroid()) {
+ return ParseConfig({"/system/etc/ueventd.rc", "/vendor/etc/ueventd.rc"});
+ }
+
auto hardware = android::base::GetProperty("ro.hardware", "");
struct LegacyPathInfo {
diff --git a/janitors/OWNERS b/janitors/OWNERS
index d871201..a28737e 100644
--- a/janitors/OWNERS
+++ b/janitors/OWNERS
@@ -3,5 +3,4 @@
cferris@google.com
dwillemsen@google.com
enh@google.com
-narayan@google.com
sadafebrahimi@google.com
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 9107838..ca6868c 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -65,9 +65,8 @@
// should be active again. E.g. Zygote specialization for child process.
void DropTaskProfilesResourceCaching();
-// Return 0 and removes the cgroup if there are no longer any processes in it.
-// Returns -1 in the case of an error occurring or if there are processes still running
-// even after retrying for up to 200ms.
+// Return 0 if all processes were killed and the cgroup was successfully removed.
+// Returns -1 in the case of an error occurring or if there are processes still running.
int killProcessGroup(uid_t uid, int initialPid, int signal);
// Returns the same as killProcessGroup(), however it does not retry, which means
@@ -76,8 +75,9 @@
// Sends the provided signal to all members of a process group, but does not wait for processes to
// exit, or for the cgroup to be removed. Callers should also ensure that killProcessGroup is called
-// later to ensure the cgroup is fully removed, otherwise system resources may leak.
-int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal);
+// later to ensure the cgroup is fully removed, otherwise system resources will leak.
+// Returns true if no errors are encountered sending signals, otherwise false.
+bool sendSignalToProcessGroup(uid_t uid, int initialPid, int signal);
int createProcessGroup(uid_t uid, int initialPid, bool memControl = false);
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index b4482d0..3209adf 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -22,6 +22,7 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
@@ -30,6 +31,7 @@
#include <unistd.h>
#include <chrono>
+#include <cstring>
#include <map>
#include <memory>
#include <mutex>
@@ -53,7 +55,9 @@
using namespace std::chrono_literals;
-#define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"
+#define PROCESSGROUP_CGROUP_PROCS_FILE "cgroup.procs"
+#define PROCESSGROUP_CGROUP_KILL_FILE "cgroup.kill"
+#define PROCESSGROUP_CGROUP_EVENTS_FILE "cgroup.events"
bool CgroupsAvailable() {
static bool cgroups_available = access("/proc/cgroups", F_OK) == 0;
@@ -74,6 +78,29 @@
return true;
}
+static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
+ return StringPrintf("%s/uid_%u", cgroup, uid);
+}
+
+static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) {
+ return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid);
+}
+
+static bool CgroupKillAvailable() {
+ static std::once_flag f;
+ static bool cgroup_kill_available = false;
+ std::call_once(f, []() {
+ std::string cg_kill;
+ CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cg_kill);
+ // cgroup.kill is not on the root cgroup, so check a non-root cgroup that should always
+ // exist
+ cg_kill = ConvertUidToPath(cg_kill.c_str(), AID_ROOT) + '/' + PROCESSGROUP_CGROUP_KILL_FILE;
+ cgroup_kill_available = access(cg_kill.c_str(), F_OK) == 0;
+ });
+
+ return cgroup_kill_available;
+}
+
static bool CgroupGetMemcgAppsPath(std::string* path) {
CgroupController controller = CgroupMap::GetInstance().FindController("memory");
@@ -205,29 +232,21 @@
false);
}
-static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
- return StringPrintf("%s/uid_%u", cgroup, uid);
-}
-
-static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) {
- return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid);
-}
-
-static int RemoveCgroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
- int ret = 0;
- auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
-
- while (retries--) {
- ret = rmdir(uid_pid_path.c_str());
- if (!ret || errno != EBUSY || !retries) break;
- std::this_thread::sleep_for(5ms);
- }
+static int RemoveCgroup(const char* cgroup, uid_t uid, int pid) {
+ auto path = ConvertUidPidToPath(cgroup, uid, pid);
+ int ret = TEMP_FAILURE_RETRY(rmdir(path.c_str()));
if (!ret && uid >= AID_ISOLATED_START && uid <= AID_ISOLATED_END) {
// Isolated UIDs are unlikely to be reused soon after removal,
// so free up the kernel resources for the UID level cgroup.
- const auto uid_path = ConvertUidToPath(cgroup, uid);
- ret = rmdir(uid_path.c_str());
+ path = ConvertUidToPath(cgroup, uid);
+ ret = TEMP_FAILURE_RETRY(rmdir(path.c_str()));
+ }
+
+ if (ret < 0 && errno == ENOENT) {
+ // This function is idempoetent, but still warn here.
+ LOG(WARNING) << "RemoveCgroup: " << path << " does not exist.";
+ ret = 0;
}
return ret;
@@ -351,35 +370,55 @@
return false;
}
-// Returns number of processes killed on success
-// Returns 0 if there are no processes in the process cgroup left to kill
-// Returns -1 on error
-static int DoKillProcessGroupOnce(const char* cgroup, uid_t uid, int initialPid, int signal) {
- // We separate all of the pids in the cgroup into those pids that are also the leaders of
- // process groups (stored in the pgids set) and those that are not (stored in the pids set).
- std::set<pid_t> pgids;
- pgids.emplace(initialPid);
- std::set<pid_t> pids;
- int processes = 0;
-
- std::unique_ptr<FILE, decltype(&fclose)> fd(nullptr, fclose);
+bool sendSignalToProcessGroup(uid_t uid, int initialPid, int signal) {
+ std::set<pid_t> pgids, pids;
if (CgroupsAvailable()) {
- auto path = ConvertUidPidToPath(cgroup, uid, initialPid) + PROCESSGROUP_CGROUP_PROCS_FILE;
- fd.reset(fopen(path.c_str(), "re"));
- if (!fd) {
- if (errno == ENOENT) {
- // This happens when process is already dead
- return 0;
+ std::string hierarchy_root_path, cgroup_v2_path;
+ CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
+ cgroup_v2_path = ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid);
+
+ if (signal == SIGKILL && CgroupKillAvailable()) {
+ LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_KILL_FILE << " to SIGKILL "
+ << cgroup_v2_path;
+
+ // We need to kill the process group in addition to the cgroup. For normal apps they
+ // should completely overlap, but system_server kills depend on process group kills to
+ // take down apps which are in their own cgroups and not individually targeted.
+ if (kill(-initialPid, signal) == -1 && errno != ESRCH) {
+ PLOG(WARNING) << "kill(" << -initialPid << ", " << signal << ") failed";
}
- PLOG(WARNING) << __func__ << " failed to open process cgroup uid " << uid << " pid "
- << initialPid;
- return -1;
+
+ const std::string killfilepath = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_KILL_FILE;
+ if (WriteStringToFile("1", killfilepath)) {
+ return true;
+ } else {
+ PLOG(ERROR) << "Failed to write 1 to " << killfilepath;
+ // Fallback to cgroup.procs below
+ }
}
+
+ // Since cgroup.kill only sends SIGKILLs, we read cgroup.procs to find each process to
+ // signal individually. This is more costly than using cgroup.kill for SIGKILLs.
+ LOG(VERBOSE) << "Using " << PROCESSGROUP_CGROUP_PROCS_FILE << " to signal (" << signal
+ << ") " << cgroup_v2_path;
+
+ // We separate all of the pids in the cgroup into those pids that are also the leaders of
+ // process groups (stored in the pgids set) and those that are not (stored in the pids set).
+ const auto procsfilepath = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_PROCS_FILE;
+ std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(procsfilepath.c_str(), "re"), fclose);
+ if (!fp) {
+ // This should only happen if the cgroup has already been removed with a successful call
+ // to killProcessGroup. Callers should only retry sendSignalToProcessGroup or
+ // killProcessGroup calls if they fail without ENOENT.
+ PLOG(ERROR) << "Failed to open " << procsfilepath;
+ kill(-initialPid, signal);
+ return false;
+ }
+
pid_t pid;
bool file_is_empty = true;
- while (fscanf(fd.get(), "%d\n", &pid) == 1 && pid >= 0) {
- processes++;
+ while (fscanf(fp.get(), "%d\n", &pid) == 1 && pid >= 0) {
file_is_empty = false;
if (pid == 0) {
// Should never happen... but if it does, trying to kill this
@@ -409,6 +448,8 @@
}
}
+ pgids.emplace(initialPid);
+
// Kill all process groups.
for (const auto pgid : pgids) {
LOG(VERBOSE) << "Killing process group " << -pgid << " in uid " << uid
@@ -429,101 +470,174 @@
}
}
- return (!fd || feof(fd.get())) ? processes : -1;
+ return true;
}
-static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) {
+template <typename T>
+static std::chrono::milliseconds toMillisec(T&& duration) {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(duration);
+}
+
+enum class populated_status
+{
+ populated,
+ not_populated,
+ error
+};
+
+static populated_status cgroupIsPopulated(int events_fd) {
+ const std::string POPULATED_KEY("populated ");
+ const std::string::size_type MAX_EVENTS_FILE_SIZE = 32;
+
+ std::string buf;
+ buf.resize(MAX_EVENTS_FILE_SIZE);
+ ssize_t len = TEMP_FAILURE_RETRY(pread(events_fd, buf.data(), buf.size(), 0));
+ if (len == -1) {
+ PLOG(ERROR) << "Could not read cgroup.events: ";
+ // Potentially ENODEV if the cgroup has been removed since we opened this file, but that
+ // shouldn't have happened yet.
+ return populated_status::error;
+ }
+
+ if (len == 0) {
+ LOG(ERROR) << "cgroup.events EOF";
+ return populated_status::error;
+ }
+
+ buf.resize(len);
+
+ const std::string::size_type pos = buf.find(POPULATED_KEY);
+ if (pos == std::string::npos) {
+ LOG(ERROR) << "Could not find populated key in cgroup.events";
+ return populated_status::error;
+ }
+
+ if (pos + POPULATED_KEY.size() + 1 > len) {
+ LOG(ERROR) << "Partial read of cgroup.events";
+ return populated_status::error;
+ }
+
+ return buf[pos + POPULATED_KEY.size()] == '1' ?
+ populated_status::populated : populated_status::not_populated;
+}
+
+// The default timeout of 2200ms comes from the default number of retries in a previous
+// implementation of this function. The default retry value was 40 for killing and 400 for cgroup
+// removal with 5ms sleeps between each retry.
+static int KillProcessGroup(
+ uid_t uid, int initialPid, int signal, bool once = false,
+ std::chrono::steady_clock::time_point until = std::chrono::steady_clock::now() + 2200ms) {
CHECK_GE(uid, 0);
CHECK_GT(initialPid, 0);
+ // Always attempt to send a kill signal to at least the initialPid, at least once, regardless of
+ // whether its cgroup exists or not. This should only be necessary if a bug results in the
+ // migration of the targeted process out of its cgroup, which we will also attempt to kill.
+ const bool signal_ret = sendSignalToProcessGroup(uid, initialPid, signal);
+
+ if (!CgroupsAvailable() || !signal_ret) return signal_ret ? 0 : -1;
+
std::string hierarchy_root_path;
- if (CgroupsAvailable()) {
- CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
- }
- const char* cgroup = hierarchy_root_path.c_str();
+ CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
- std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
+ const std::string cgroup_v2_path =
+ ConvertUidPidToPath(hierarchy_root_path.c_str(), uid, initialPid);
- int retry = retries;
- int processes;
- while ((processes = DoKillProcessGroupOnce(cgroup, uid, initialPid, signal)) > 0) {
- LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid;
- if (!CgroupsAvailable()) {
- // makes no sense to retry, because there are no cgroup_procs file
- processes = 0; // no remaining processes
- break;
- }
- if (retry > 0) {
- std::this_thread::sleep_for(5ms);
- --retry;
- } else {
- break;
- }
- }
-
- if (processes < 0) {
- PLOG(ERROR) << "Error encountered killing process cgroup uid " << uid << " pid "
- << initialPid;
+ const std::string eventsfile = cgroup_v2_path + '/' + PROCESSGROUP_CGROUP_EVENTS_FILE;
+ android::base::unique_fd events_fd(open(eventsfile.c_str(), O_RDONLY));
+ if (events_fd.get() == -1) {
+ PLOG(WARNING) << "Error opening " << eventsfile << " for KillProcessGroup";
return -1;
}
- std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
- auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
+ struct pollfd fds = {
+ .fd = events_fd,
+ .events = POLLPRI,
+ };
- // We only calculate the number of 'processes' when killing the processes.
- // In the retries == 0 case, we only kill the processes once and therefore
- // will not have waited then recalculated how many processes are remaining
- // after the first signals have been sent.
- // Logging anything regarding the number of 'processes' here does not make sense.
+ const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
- if (processes == 0) {
- if (retries > 0) {
- LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid
- << " in " << static_cast<int>(ms) << "ms";
+ // The primary reason to loop here is to capture any new forks or migrations that could occur
+ // after we send signals to the original set of processes, but before all of those processes
+ // exit and the cgroup becomes unpopulated, or before we remove the cgroup. We try hard to
+ // ensure this completes successfully to avoid permanent memory leaks, but we still place a
+ // large default upper bound on the amount of time we spend in this loop. The amount of CPU
+ // contention, and the amount of work that needs to be done in do_exit for each process
+ // determines how long this will take.
+ int ret;
+ do {
+ populated_status populated;
+ while ((populated = cgroupIsPopulated(events_fd.get())) == populated_status::populated &&
+ std::chrono::steady_clock::now() < until) {
+
+ sendSignalToProcessGroup(uid, initialPid, signal);
+ if (once) {
+ populated = cgroupIsPopulated(events_fd.get());
+ break;
+ }
+
+ const std::chrono::steady_clock::time_point poll_start =
+ std::chrono::steady_clock::now();
+
+ if (poll_start < until)
+ ret = TEMP_FAILURE_RETRY(poll(&fds, 1, toMillisec(until - poll_start).count()));
+
+ if (ret == -1) {
+ // Fallback to 5ms sleeps if poll fails
+ PLOG(ERROR) << "Poll on " << eventsfile << "failed";
+ const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
+ if (now < until)
+ std::this_thread::sleep_for(std::min(5ms, toMillisec(until - now)));
+ }
+
+ LOG(VERBOSE) << "Waited "
+ << toMillisec(std::chrono::steady_clock::now() - poll_start).count()
+ << " ms for " << eventsfile << " poll";
}
- if (!CgroupsAvailable()) {
- // nothing to do here, if cgroups isn't available
- return 0;
+ const std::chrono::milliseconds kill_duration =
+ toMillisec(std::chrono::steady_clock::now() - start);
+
+ if (populated == populated_status::populated) {
+ LOG(WARNING) << "Still waiting on process(es) to exit for cgroup " << cgroup_v2_path
+ << " after " << kill_duration.count() << " ms";
+ // We'll still try the cgroup removal below which we expect to log an error.
+ } else if (populated == populated_status::not_populated) {
+ LOG(VERBOSE) << "Killed all processes under cgroup " << cgroup_v2_path
+ << " after " << kill_duration.count() << " ms";
}
- // 400 retries correspond to 2 secs max timeout
- int err = RemoveCgroup(cgroup, uid, initialPid, 400);
+ ret = RemoveCgroup(hierarchy_root_path.c_str(), uid, initialPid);
+ if (ret)
+ PLOG(ERROR) << "Unable to remove cgroup " << cgroup_v2_path;
+ else
+ LOG(INFO) << "Removed cgroup " << cgroup_v2_path;
if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
+ // This per-application memcg v1 case should eventually be removed after migration to
+ // memcg v2.
std::string memcg_apps_path;
if (CgroupGetMemcgAppsPath(&memcg_apps_path) &&
- RemoveCgroup(memcg_apps_path.c_str(), uid, initialPid, 400) < 0) {
- return -1;
+ (ret = RemoveCgroup(memcg_apps_path.c_str(), uid, initialPid)) < 0) {
+ const auto memcg_v1_cgroup_path =
+ ConvertUidPidToPath(memcg_apps_path.c_str(), uid, initialPid);
+ PLOG(ERROR) << "Unable to remove memcg v1 cgroup " << memcg_v1_cgroup_path;
}
}
- return err;
- } else {
- if (retries > 0) {
- LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid
- << " in " << static_cast<int>(ms) << "ms, " << processes
- << " processes remain";
- }
- return -1;
- }
+ if (once) break;
+ if (std::chrono::steady_clock::now() >= until) break;
+ } while (ret && errno == EBUSY);
+
+ return ret;
}
int killProcessGroup(uid_t uid, int initialPid, int signal) {
- return KillProcessGroup(uid, initialPid, signal, 40 /*retries*/);
+ return KillProcessGroup(uid, initialPid, signal);
}
int killProcessGroupOnce(uid_t uid, int initialPid, int signal) {
- return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/);
-}
-
-int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal) {
- std::string hierarchy_root_path;
- if (CgroupsAvailable()) {
- CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
- }
- const char* cgroup = hierarchy_root_path.c_str();
- return DoKillProcessGroupOnce(cgroup, uid, initialPid, signal);
+ return KillProcessGroup(uid, initialPid, signal, true);
}
static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup,
@@ -563,7 +677,7 @@
return -errno;
}
- auto uid_pid_procs_file = uid_pid_path + PROCESSGROUP_CGROUP_PROCS_FILE;
+ auto uid_pid_procs_file = uid_pid_path + '/' + PROCESSGROUP_CGROUP_PROCS_FILE;
if (!WriteStringToFile(std::to_string(initialPid), uid_pid_procs_file)) {
ret = -errno;
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 2c08b0b..f2ef316 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -91,6 +91,11 @@
"Name": "CfqWeight",
"Controller": "io",
"File": "io.weight"
+ },
+ {
+ "Name": "IoPrioClass",
+ "Controller": "io",
+ "File": "io.prio.class"
}
],
@@ -479,6 +484,15 @@
"Value": "200",
"Optional": "true"
}
+ },
+ {
+ "Name": "SetAttribute",
+ "Params":
+ {
+ "Name": "IoPrioClass",
+ "Value": "restrict-to-be",
+ "Optional": "true"
+ }
}
]
},
@@ -511,6 +525,15 @@
"Value": "1000",
"Optional": "true"
}
+ },
+ {
+ "Name": "SetAttribute",
+ "Params":
+ {
+ "Name": "IoPrioClass",
+ "Value": "restrict-to-be",
+ "Optional": "true"
+ }
}
]
},
@@ -543,6 +566,15 @@
"Value": "1000",
"Optional": "true"
}
+ },
+ {
+ "Name": "SetAttribute",
+ "Params":
+ {
+ "Name": "IoPrioClass",
+ "Value": "promote-to-rt",
+ "Optional": "true"
+ }
}
]
},
@@ -575,6 +607,15 @@
"Value": "1000",
"Optional": "true"
}
+ },
+ {
+ "Name": "SetAttribute",
+ "Params":
+ {
+ "Name": "IoPrioClass",
+ "Value": "promote-to-rt",
+ "Optional": "true"
+ }
}
]
},
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 4d4294b..ad5b752 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -56,7 +56,7 @@
}
cc_defaults {
- name: "libutils_defaults",
+ name: "libutils_defaults_nodeps",
vendor_available: true,
product_available: true,
recovery_available: true,
@@ -69,10 +69,6 @@
"-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
- shared_libs: [
- "libcutils",
- "liblog",
- ],
sanitize: {
misc_undefined: ["integer"],
},
@@ -118,6 +114,18 @@
}
cc_defaults {
+ name: "libutils_defaults",
+ defaults: [
+ "libutils_defaults_nodeps",
+ ],
+
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ ],
+}
+
+cc_defaults {
name: "libutils_impl_defaults",
defaults: [
"libutils_defaults",
@@ -183,6 +191,7 @@
target: {
product: {
header_abi_checker: {
+ enabled: true,
// AFDO affects weak symbols.
diff_flags: ["-allow-adding-removing-weak-symbols"],
ref_dump_dirs: ["abi-dumps"],
@@ -190,6 +199,7 @@
},
vendor: {
header_abi_checker: {
+ enabled: true,
// AFDO affects weak symbols.
diff_flags: ["-allow-adding-removing-weak-symbols"],
ref_dump_dirs: ["abi-dumps"],
@@ -203,6 +213,7 @@
defaults: ["libutils_impl_defaults"],
cflags: [
+ "-DDEBUG_CALLBACKS=1",
"-DDEBUG_POLL_AND_WAKE=1",
"-DDEBUG_REFS=1",
"-DDEBUG_TOKENIZER=1",
diff --git a/libutils/Looper.cpp b/libutils/Looper.cpp
index 402e43c..576c61d 100644
--- a/libutils/Looper.cpp
+++ b/libutils/Looper.cpp
@@ -534,7 +534,7 @@
int Looper::removeSequenceNumberLocked(SequenceNumber seq) {
#if DEBUG_CALLBACKS
- ALOGD("%p ~ removeFd - fd=%d, seq=%u", this, fd, seq);
+ ALOGD("%p ~ removeFd - seq=%" PRIu64, this, seq);
#endif
const auto& request_it = mRequests.find(seq);
diff --git a/libutils/binder/Android.bp b/libutils/binder/Android.bp
index a049f3d..60b0cb6 100644
--- a/libutils/binder/Android.bp
+++ b/libutils/binder/Android.bp
@@ -3,9 +3,9 @@
}
cc_defaults {
- name: "libutils_binder_impl_defaults",
+ name: "libutils_binder_impl_defaults_nodeps",
defaults: [
- "libutils_defaults",
+ "libutils_defaults_nodeps",
"apex-lowest-min-sdk-version",
],
native_bridge_supported: true,
@@ -30,11 +30,33 @@
afdo: true,
}
+cc_defaults {
+ name: "libutils_binder_impl_defaults",
+ defaults: [
+ "libutils_defaults",
+ "libutils_binder_impl_defaults_nodeps",
+ ],
+}
+
cc_library {
name: "libutils_binder",
defaults: ["libutils_binder_impl_defaults"],
}
+cc_library_shared {
+ name: "libutils_binder_sdk",
+ defaults: ["libutils_binder_impl_defaults_nodeps"],
+
+ header_libs: [
+ "liblog_stub",
+ ],
+
+ cflags: [
+ "-DANDROID_LOG_STUB_WEAK_PRINT",
+ "-DANDROID_UTILS_CALLSTACK_ENABLED=0",
+ ],
+}
+
cc_library {
name: "libutils_binder_test_compile",
defaults: ["libutils_binder_impl_defaults"],
diff --git a/libutils/binder/StrongPointer_test.cpp b/libutils/binder/StrongPointer_test.cpp
index f27c1f1..aa993c3 100644
--- a/libutils/binder/StrongPointer_test.cpp
+++ b/libutils/binder/StrongPointer_test.cpp
@@ -106,3 +106,17 @@
EXPECT_DEATH(sp<TypeParam>::fromExisting(foo), "");
delete foo;
}
+
+TYPED_TEST(StrongPointer, release) {
+ bool isDeleted = false;
+ TypeParam* foo = nullptr;
+ {
+ sp<TypeParam> sp1 = sp<TypeParam>::make(&isDeleted);
+ ASSERT_EQ(1, sp1->getStrongCount());
+ foo = sp1.release();
+ }
+ ASSERT_FALSE(isDeleted) << "release failed, deleted anyway when sp left scope";
+ ASSERT_EQ(1, foo->getStrongCount()) << "release mismanaged refcount";
+ foo->decStrong(nullptr);
+ ASSERT_TRUE(isDeleted) << "foo was leaked!";
+}
diff --git a/libutils/binder/include/utils/StrongPointer.h b/libutils/binder/include/utils/StrongPointer.h
index 54aa691..43c00c9 100644
--- a/libutils/binder/include/utils/StrongPointer.h
+++ b/libutils/binder/include/utils/StrongPointer.h
@@ -98,6 +98,15 @@
void clear();
+ // Releases the ownership of the object managed by this instance of sp, if any.
+ // The caller is now responsible for managing it. That is, the caller must ensure
+ // decStrong() is called when the pointer is no longer used.
+ [[nodiscard]] inline T* release() noexcept {
+ auto ret = m_ptr;
+ m_ptr = nullptr;
+ return ret;
+ }
+
// Accessors
inline T& operator* () const { return *m_ptr; }
diff --git a/libvendorsupport/Android.bp b/libvendorsupport/Android.bp
new file mode 100644
index 0000000..b4457b1
--- /dev/null
+++ b/libvendorsupport/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "libvendorsupport",
+ native_bridge_supported: true,
+ recovery_available: true,
+ llndk: {
+ symbol_file: "libvendorsupport.map.txt",
+ },
+ srcs: ["version_props.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ local_include_dirs: ["include/vendorsupport"],
+ export_include_dirs: ["include"],
+ shared_libs: [
+ "liblog",
+ ],
+}
diff --git a/libvendorsupport/OWNERS b/libvendorsupport/OWNERS
new file mode 100644
index 0000000..2ab18eb
--- /dev/null
+++ b/libvendorsupport/OWNERS
@@ -0,0 +1,2 @@
+jiyong@google.com
+justinyun@google.com
diff --git a/libvendorsupport/TEST_MAPPING b/libvendorsupport/TEST_MAPPING
new file mode 100644
index 0000000..5bd09ba
--- /dev/null
+++ b/libvendorsupport/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "libvendorsupport-tests"
+ }
+ ]
+}
diff --git a/libvendorsupport/include/vendorsupport/api_level.h b/libvendorsupport/include/vendorsupport/api_level.h
new file mode 100644
index 0000000..ba1a6b8
--- /dev/null
+++ b/libvendorsupport/include/vendorsupport/api_level.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <android/api-level.h>
+
+#define __ANDROID_VENDOR_API_MAX__ 1000000
+#define __INVALID_API_LEVEL -1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Find corresponding vendor API level from an SDK API version.
+ *
+ * @details
+ * SDK API versions and vendor API levels are not compatible and not
+ * convertible. However, this function can be used to compare the two versions
+ * to know which one is newer than the other.
+ *
+ * @param sdk_api_level The SDK version int. This must be less than 10000.
+ * @return The corresponding vendor API level of the SDK version. -1 if the SDK
+ * version is invalid or 10000.
+ */
+int vendor_api_level_of(int sdk_api_level);
+
+/**
+ * @brief Find corresponding SDK API version from a vendor API level.
+ *
+ * @param vendor_api_level The vendor API level int.
+ * @return The corresponding SDK API version of the vendor API level. -1 if the
+ * vendor API level is invalid.
+ */
+int sdk_api_level_of(int vendor_api_level);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libvendorsupport/libvendorsupport.map.txt b/libvendorsupport/libvendorsupport.map.txt
new file mode 100644
index 0000000..9a23b94
--- /dev/null
+++ b/libvendorsupport/libvendorsupport.map.txt
@@ -0,0 +1,7 @@
+LIBVENDORSUPPORT {
+ global:
+ vendor_api_level_of; # llndk systemapi
+ sdk_api_level_of; # llndk systemapi
+ local:
+ *;
+};
diff --git a/libvendorsupport/tests/Android.bp b/libvendorsupport/tests/Android.bp
new file mode 100644
index 0000000..42e3371
--- /dev/null
+++ b/libvendorsupport/tests/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "libvendorsupport-tests",
+ srcs: [
+ "version_props_test.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ shared_libs: [
+ "libvendorsupport",
+ ],
+ test_suites: ["general-tests"],
+}
+
diff --git a/libvendorsupport/tests/version_props_test.cpp b/libvendorsupport/tests/version_props_test.cpp
new file mode 100644
index 0000000..538a2e2
--- /dev/null
+++ b/libvendorsupport/tests/version_props_test.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <gtest/gtest.h>
+
+#include <vendorsupport/api_level.h>
+
+using namespace std;
+
+namespace {
+
+TEST(vendorsupport, get_corresponding_vendor_api_level) {
+ ASSERT_EQ(__ANDROID_API_U__, vendor_api_level_of(__ANDROID_API_U__));
+ ASSERT_EQ(202404, vendor_api_level_of(__ANDROID_API_V__));
+ ASSERT_EQ(__INVALID_API_LEVEL, vendor_api_level_of(__ANDROID_API_FUTURE__));
+}
+
+TEST(vendorsupport, get_corresponding_sdk_api_level) {
+ ASSERT_EQ(__ANDROID_API_U__, sdk_api_level_of(__ANDROID_API_U__));
+ ASSERT_EQ(__ANDROID_API_V__, sdk_api_level_of(202404));
+ ASSERT_EQ(__INVALID_API_LEVEL, sdk_api_level_of(__ANDROID_VENDOR_API_MAX__));
+ ASSERT_EQ(__INVALID_API_LEVEL, sdk_api_level_of(35));
+}
+
+} // namespace
\ No newline at end of file
diff --git a/libvendorsupport/version_props.c b/libvendorsupport/version_props.c
new file mode 100644
index 0000000..4d0e45e
--- /dev/null
+++ b/libvendorsupport/version_props.c
@@ -0,0 +1,41 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "api_level.h"
+
+#include <log/log.h>
+
+int vendor_api_level_of(int sdk_api_level) {
+ if (sdk_api_level < __ANDROID_API_V__) {
+ return sdk_api_level;
+ }
+ // In Android V, vendor API level started with version 202404.
+ // The calculation assumes that the SDK api level bumps once a year.
+ if (sdk_api_level < __ANDROID_API_FUTURE__) {
+ return 202404 + ((sdk_api_level - __ANDROID_API_V__) * 100);
+ }
+ ALOGE("The SDK version must be less than 10000: %d", sdk_api_level);
+ return __INVALID_API_LEVEL;
+}
+
+int sdk_api_level_of(int vendor_api_level) {
+ if (vendor_api_level < __ANDROID_API_V__) {
+ return vendor_api_level;
+ }
+ if (vendor_api_level >= 202404 && vendor_api_level < __ANDROID_VENDOR_API_MAX__) {
+ return (vendor_api_level - 202404) / 100 + __ANDROID_API_V__;
+ }
+ ALOGE("Unexpected vendor api level: %d", vendor_api_level);
+ return __INVALID_API_LEVEL;
+}
diff --git a/libvndksupport/include/vndksupport/linker.h b/libvndksupport/include/vndksupport/linker.h
index 5f48c39..6845135 100644
--- a/libvndksupport/include/vndksupport/linker.h
+++ b/libvndksupport/include/vndksupport/linker.h
@@ -20,15 +20,8 @@
extern "C" {
#endif
-/*
- * Returns whether the current process is a vendor process.
- *
- * Note that this is only checking what process is running and has nothing to
- * do with what namespace the caller is loaded at. For example, a VNDK-SP
- * library loaded by SP-HAL calling this function may still get a 'false',
- * because it is running in a system process.
- */
-int android_is_in_vendor_process();
+int android_is_in_vendor_process() __attribute__((
+ deprecated("This function would not give exact result if VNDK is deprecated.")));
void* android_load_sphal_library(const char* name, int flag);
diff --git a/libvndksupport/libvndksupport.map.txt b/libvndksupport/libvndksupport.map.txt
index 1d94b9d..325505d 100644
--- a/libvndksupport/libvndksupport.map.txt
+++ b/libvndksupport/libvndksupport.map.txt
@@ -1,6 +1,6 @@
LIBVNDKSUPPORT {
global:
- android_is_in_vendor_process; # llndk systemapi
+ android_is_in_vendor_process; # llndk-deprecated=35 systemapi
android_load_sphal_library; # llndk systemapi
android_unload_sphal_library; # llndk systemapi
local:
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 87646f9..b4a16d3 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -25,4 +25,8 @@
},
},
export_include_dirs: ["include"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.runtime",
+ ],
}
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index cc6b64a..7444f96 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -72,6 +72,11 @@
endif
endif
+EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE :=
+ifneq ($(PRODUCT_SCUDO_ALLOCATION_RING_BUFFER_SIZE),)
+ EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE := export SCUDO_ALLOCATION_RING_BUFFER_SIZE $(PRODUCT_SCUDO_ALLOCATION_RING_BUFFER_SIZE)
+endif
+
EXPORT_GLOBAL_GCOV_OPTIONS :=
ifeq ($(NATIVE_COVERAGE),true)
EXPORT_GLOBAL_GCOV_OPTIONS := export GCOV_PREFIX /data/misc/trace
@@ -92,7 +97,7 @@
# create some directories (some are mount points) and symlinks
LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
dev proc sys system data data_mirror odm oem acct config storage mnt apex bootstrap-apex debug_ramdisk \
- linkerconfig second_stage_resources postinstall $(BOARD_ROOT_EXTRA_FOLDERS)); \
+ linkerconfig second_stage_resources postinstall tmp $(BOARD_ROOT_EXTRA_FOLDERS)); \
ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
ln -sf /data/user_de/0/com.android.shell/files/bugreports $(TARGET_ROOT_OUT)/bugreports; \
@@ -216,6 +221,7 @@
$(hide) sed -i -e 's?%EXPORT_GLOBAL_GCOV_OPTIONS%?$(EXPORT_GLOBAL_GCOV_OPTIONS)?g' $@
$(hide) sed -i -e 's?%EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%?$(EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS)?g' $@
$(hide) sed -i -e 's?%EXPORT_GLOBAL_HWASAN_OPTIONS%?$(EXPORT_GLOBAL_HWASAN_OPTIONS)?g' $@
+ $(hide) sed -i -e 's?%EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE%?$(EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE)?g' $@
# Append PLATFORM_VNDK_VERSION to base name.
define append_vndk_version
diff --git a/rootdir/init.environ.rc.in b/rootdir/init.environ.rc.in
index bf6e986..7ba1f46 100644
--- a/rootdir/init.environ.rc.in
+++ b/rootdir/init.environ.rc.in
@@ -14,3 +14,4 @@
%EXPORT_GLOBAL_GCOV_OPTIONS%
%EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%
%EXPORT_GLOBAL_HWASAN_OPTIONS%
+ %EXPORT_GLOBAL_SCUDO_ALLOCATION_RING_BUFFER_SIZE%
diff --git a/rootdir/init.rc b/rootdir/init.rc
index b455058..6b783fa 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -92,6 +92,12 @@
# checker programs.
mkdir /dev/fscklogs 0770 root system
+ # Create tmpfs for use by the shell user.
+ mount tmpfs tmpfs /tmp
+ restorecon /tmp
+ chown shell shell /tmp
+ chmod 0771 /tmp
+
on init
sysclktz 0
@@ -776,7 +782,6 @@
mkdir /data/misc/vpn 0770 system vpn
mkdir /data/misc/shared_relro 0771 shared_relro shared_relro
mkdir /data/misc/systemkeys 0700 system system
- mkdir /data/misc/threadnetwork 0770 thread_network thread_network
mkdir /data/misc/wifi 0770 wifi wifi
mkdir /data/misc/wifi/sockets 0770 wifi wifi
mkdir /data/misc/wifi/wpa_supplicant 0770 wifi wifi
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 60dcc2a..3927501 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -71,6 +71,7 @@
/dev/mtp_usb 0660 root mtp
/dev/usb_accessory 0660 root usb
/dev/tun 0660 system vpn
+/dev/hidraw* 0660 system system
# CDMA radio interface MUX
/dev/ppp 0660 radio vpn
diff --git a/storaged/Android.bp b/storaged/Android.bp
index fe8c1f3..357c0e6 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -24,7 +24,7 @@
shared_libs: [
"android.hardware.health@1.0",
"android.hardware.health@2.0",
- "android.hardware.health-V2-ndk",
+ "android.hardware.health-V3-ndk",
"libbase",
"libbinder",
"libbinder_ndk",
diff --git a/trusty/OWNERS b/trusty/OWNERS
index bf16912..4016792 100644
--- a/trusty/OWNERS
+++ b/trusty/OWNERS
@@ -2,7 +2,6 @@
arve@android.com
danielangell@google.com
gmar@google.com
-marcone@google.com
mikemcternan@google.com
mmaurer@google.com
ncbray@google.com
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index f782d2a..0915eab 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -107,7 +107,11 @@
return {};
}
- assert(st.st_size >= 0);
+ if (st.st_size == 0) {
+ LOG(ERROR) << "Zero length file '" << file_name << "'";
+ return {};
+ }
+
file_size = st.st_size;
/* The dmabuf size needs to be a multiple of the page size */
@@ -123,7 +127,8 @@
BufferAllocator alloc;
unique_fd dmabuf_fd(alloc.Alloc(kDmabufSystemHeapName, file_page_size));
if (!dmabuf_fd.ok()) {
- LOG(ERROR) << "Error creating dmabuf: " << dmabuf_fd.get();
+ LOG(ERROR) << "Error creating dmabuf for " << file_page_size
+ << " bytes: " << dmabuf_fd.get();
return dmabuf_fd;
}
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index 0f50787..dabe118 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -22,8 +22,10 @@
#include <unistd.h>
#include <getopt.h>
#define __USE_GNU
+#include <inttypes.h>
#include <sys/mman.h>
#include <sys/uio.h>
+#include <time.h>
#include <BufferAllocator/BufferAllocatorWrapper.h>
@@ -31,8 +33,27 @@
#define TIPC_DEFAULT_DEVNAME "/dev/trusty-ipc-dev0"
-static const char *dev_name = NULL;
-static const char *test_name = NULL;
+/* clang-format off */
+#define BENCH_RESULT_TPL \
+"{" \
+" \"schema_version\": 3," \
+" \"suite_name\": \"crypto\"," \
+" \"bench_name\": \"%s\"," \
+" \"results\": [" \
+" {" \
+" \"metric_name\": \"time_micro_sec\"," \
+" \"min\": \"%" PRId64 "\"," \
+" \"max\": \"%" PRId64 "\"," \
+" \"avg\": \"%" PRId64 "\"," \
+" \"cold\": \"%" PRId64 "\"," \
+" \"raw_min\": %" PRId64 "," \
+" \"raw_max\": %" PRId64 "," \
+" \"raw_avg\": %" PRId64 "," \
+" \"raw_cold\": %" PRId64 "" \
+" }," \
+" ]" \
+"}"
+/* clang-format on */
static const char *uuid_name = "com.android.ipc-unittest.srv.uuid";
static const char *echo_name = "com.android.ipc-unittest.srv.echo";
@@ -46,7 +67,7 @@
static const char* receiver_name = "com.android.trusty.memref.receiver";
static const size_t memref_chunk_size = 4096;
-static const char* _sopts = "hsvDS:t:r:m:b:";
+static const char* _sopts = "hsvDS:t:r:m:b:B:";
/* clang-format off */
static const struct option _lopts[] = {
{"help", no_argument, 0, 'h'},
@@ -57,6 +78,8 @@
{"repeat", required_argument, 0, 'r'},
{"burst", required_argument, 0, 'b'},
{"msgsize", required_argument, 0, 'm'},
+ {"test", required_argument, 0, 't'},
+ {"bench", required_argument, 0, 'B'},
{0, 0, 0, 0}
};
/* clang-format on */
@@ -74,6 +97,7 @@
" -m, --msgsize size max message size\n"
" -v, --variable variable message size\n"
" -s, --silent silent\n"
+ " -B, --bench Run as Benchmark N times\n"
"\n";
static const char* usage_long =
@@ -96,12 +120,34 @@
" send-fd - transmit dma_buf to trusty, use as shm\n"
"\n";
-static uint opt_repeat = 1;
-static uint opt_msgsize = 32;
-static uint opt_msgburst = 32;
-static bool opt_variable = false;
-static bool opt_silent = false;
-static char* srv_name = NULL;
+struct tipc_test_params {
+ uint repeat;
+ uint msgsize;
+ uint msgburst;
+ bool variable;
+ bool silent;
+ uint bench;
+ char* srv_name;
+ char* dev_name;
+ char* test_name;
+};
+typedef int (*tipc_test_func_t)(const struct tipc_test_params*);
+
+struct tipc_test_def {
+ char* test_name;
+ tipc_test_func_t func;
+};
+
+static void init_params(struct tipc_test_params* params) {
+ params->repeat = 1;
+ params->msgsize = 32;
+ params->msgburst = 32;
+ params->variable = false;
+ params->silent = false;
+ params->bench = 0;
+ params->srv_name = NULL;
+ params->test_name = NULL;
+}
static void print_usage_and_exit(const char *prog, int code, bool verbose)
{
@@ -110,8 +156,7 @@
exit(code);
}
-static void parse_options(int argc, char **argv)
-{
+static void parse_options(int argc, char** argv, struct tipc_test_params* params) {
int c;
int oidx = 0;
@@ -121,35 +166,39 @@
switch (c) {
case 'D':
- dev_name = strdup(optarg);
+ params->dev_name = strdup(optarg);
break;
case 'S':
- srv_name = strdup(optarg);
+ params->srv_name = strdup(optarg);
break;
case 't':
- test_name = strdup(optarg);
+ params->test_name = strdup(optarg);
break;
case 'v':
- opt_variable = true;
+ params->variable = true;
break;
case 'r':
- opt_repeat = atoi(optarg);
+ params->repeat = atoi(optarg);
break;
case 'm':
- opt_msgsize = atoi(optarg);
+ params->msgsize = atoi(optarg);
break;
case 'b':
- opt_msgburst = atoi(optarg);
+ params->msgburst = atoi(optarg);
break;
case 's':
- opt_silent = true;
+ params->silent = true;
+ break;
+
+ case 'B':
+ params->bench = atoi(optarg);
break;
case 'h':
@@ -162,32 +211,31 @@
}
}
-static int connect_test(uint repeat)
-{
+static int connect_test(const struct tipc_test_params* params) {
uint i;
int echo_fd;
int dsink_fd;
int custom_fd;
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
+ if (!params->silent) {
+ printf("%s: repeat = %u\n", __func__, params->repeat);
}
- for (i = 0; i < repeat; i++) {
- if (srv_name) {
- custom_fd = tipc_connect(dev_name, srv_name);
+ for (i = 0; i < params->repeat; i++) {
+ if (params->srv_name) {
+ custom_fd = tipc_connect(params->dev_name, params->srv_name);
if (custom_fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n", srv_name);
+ fprintf(stderr, "Failed to connect to '%s' service\n", params->srv_name);
}
if (custom_fd >= 0) {
tipc_close(custom_fd);
}
} else {
- echo_fd = tipc_connect(dev_name, echo_name);
+ echo_fd = tipc_connect(params->dev_name, echo_name);
if (echo_fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
}
- dsink_fd = tipc_connect(dev_name, datasink_name);
+ dsink_fd = tipc_connect(params->dev_name, datasink_name);
if (dsink_fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "datasink");
}
@@ -201,79 +249,75 @@
}
}
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-static int connect_foo(uint repeat)
-{
+static int connect_foo(const struct tipc_test_params* params) {
uint i;
int fd;
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
+ if (!params->silent) {
+ printf("%s: repeat = %u\n", __func__, params->repeat);
}
- for (i = 0; i < repeat; i++) {
- fd = tipc_connect(dev_name, "foo");
+ for (i = 0; i < params->repeat; i++) {
+ fd = tipc_connect(params->dev_name, "foo");
if (fd >= 0) {
fprintf(stderr, "succeeded to connect to '%s' service\n", "foo");
tipc_close(fd);
}
}
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-
-static int closer1_test(uint repeat)
-{
+static int closer1_test(const struct tipc_test_params* params) {
uint i;
int fd;
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
+ if (!params->silent) {
+ printf("%s: repeat = %u\n", __func__, params->repeat);
}
- for (i = 0; i < repeat; i++) {
- fd = tipc_connect(dev_name, closer1_name);
+ for (i = 0; i < params->repeat; i++) {
+ fd = tipc_connect(params->dev_name, closer1_name);
if (fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "closer1");
continue;
}
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: connected\n", __func__);
}
tipc_close(fd);
}
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-static int closer2_test(uint repeat)
-{
+static int closer2_test(const struct tipc_test_params* params) {
uint i;
int fd;
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
+ if (!params->silent) {
+ printf("%s: repeat = %u\n", __func__, params->repeat);
}
- for (i = 0; i < repeat; i++) {
- fd = tipc_connect(dev_name, closer2_name);
+ for (i = 0; i < params->repeat; i++) {
+ fd = tipc_connect(params->dev_name, closer2_name);
if (fd < 0) {
- if (!opt_silent) {
+ if (!params->silent) {
printf("failed to connect to '%s' service\n", "closer2");
}
} else {
@@ -283,38 +327,37 @@
}
}
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-static int closer3_test(uint repeat)
-{
+static int closer3_test(const struct tipc_test_params* params) {
uint i, j;
ssize_t rc;
int fd[4];
char buf[64];
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
+ if (!params->silent) {
+ printf("%s: repeat = %u\n", __func__, params->repeat);
}
- for (i = 0; i < repeat; i++) {
+ for (i = 0; i < params->repeat; i++) {
/* open 4 connections to closer3 service */
for (j = 0; j < 4; j++) {
- fd[j] = tipc_connect(dev_name, closer3_name);
+ fd[j] = tipc_connect(params->dev_name, closer3_name);
if (fd[j] < 0) {
fprintf(stderr, "fd[%d]: failed to connect to '%s' service\n", j, "closer3");
} else {
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: fd[%d]=%d: connected\n", __func__, j, fd[j]);
}
memset(buf, i + j, sizeof(buf));
rc = write(fd[j], buf, sizeof(buf));
if (rc != sizeof(buf)) {
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: fd[%d]=%d: write returned = %zd\n", __func__, j, fd[j], rc);
}
perror("closer3_test: write");
@@ -330,7 +373,7 @@
if (fd[j] < 0) continue;
rc = write(fd[j], buf, sizeof(buf));
if (rc != sizeof(buf)) {
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: fd[%d]=%d: write returned = %zd\n", __func__, j, fd[j], rc);
}
perror("closer3_test: write");
@@ -345,38 +388,36 @@
}
}
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-
-static int echo_test(uint repeat, uint msgsz, bool var)
-{
+static int echo_test(const struct tipc_test_params* params) {
uint i;
ssize_t rc;
size_t msg_len;
int echo_fd = -1;
- char tx_buf[msgsz];
- char rx_buf[msgsz];
+ char tx_buf[params->msgsize];
+ char rx_buf[params->msgsize];
- if (!opt_silent) {
- printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
- var ? "true" : "false");
+ if (!params->silent) {
+ printf("%s: repeat %u: params->msgsize %u: variable %s\n", __func__, params->repeat,
+ params->msgsize, params->variable ? "true" : "false");
}
- echo_fd = tipc_connect(dev_name, echo_name);
+ echo_fd = tipc_connect(params->dev_name, echo_name);
if (echo_fd < 0) {
fprintf(stderr, "Failed to connect to service\n");
return echo_fd;
}
- for (i = 0; i < repeat; i++) {
- msg_len = msgsz;
- if (opt_variable && msgsz) {
- msg_len = rand() % msgsz;
+ for (i = 0; i < params->repeat; i++) {
+ msg_len = params->msgsize;
+ if (params->variable && params->msgsize) {
+ msg_len = rand() % params->msgsize;
}
memset(tx_buf, i + 1, msg_len);
@@ -406,37 +447,37 @@
tipc_close(echo_fd);
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-static int burst_write_test(uint repeat, uint msgburst, uint msgsz, bool var)
-{
+static int burst_write_test(const struct tipc_test_params* params) {
int fd;
uint i, j;
ssize_t rc;
size_t msg_len;
- char tx_buf[msgsz];
+ char tx_buf[params->msgsize];
- if (!opt_silent) {
- printf("%s: repeat %u: burst %u: msgsz %u: variable %s\n", __func__, repeat, msgburst,
- msgsz, var ? "true" : "false");
+ if (!params->silent) {
+ printf("%s: repeat %u: burst %u: params->msgsize %u: variable %s\n", __func__,
+ params->repeat, params->msgburst, params->msgsize,
+ params->variable ? "true" : "false");
}
- for (i = 0; i < repeat; i++) {
- fd = tipc_connect(dev_name, datasink_name);
+ for (i = 0; i < params->repeat; i++) {
+ fd = tipc_connect(params->dev_name, datasink_name);
if (fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "datasink");
break;
}
- for (j = 0; j < msgburst; j++) {
- msg_len = msgsz;
- if (var && msgsz) {
- msg_len = rand() % msgsz;
+ for (j = 0; j < params->msgburst; j++) {
+ msg_len = params->msgsize;
+ if (params->variable && params->msgsize) {
+ msg_len = rand() % params->msgsize;
}
memset(tx_buf, i + 1, msg_len);
@@ -450,23 +491,21 @@
tipc_close(fd);
}
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-
-static int _wait_for_msg(int fd, uint msgsz, int timeout)
-{
+static int _wait_for_msg(int fd, int timeout, const struct tipc_test_params* params) {
int rc;
fd_set rfds;
uint msgcnt = 0;
- char rx_buf[msgsz];
+ char rx_buf[params->msgsize];
struct timeval tv;
- if (!opt_silent) {
+ if (!params->silent) {
printf("waiting (%d) for msg\n", timeout);
}
@@ -480,7 +519,7 @@
rc = select(fd + 1, &rfds, NULL, NULL, &tv);
if (rc == 0) {
- if (!opt_silent) {
+ if (!params->silent) {
printf("select timedout\n");
}
break;
@@ -502,42 +541,40 @@
}
}
- if (!opt_silent) {
+ if (!params->silent) {
printf("got %u messages\n", msgcnt);
}
return 0;
}
-
-static int select_test(uint repeat, uint msgburst, uint msgsz)
-{
+static int select_test(const struct tipc_test_params* params) {
int fd;
uint i, j;
ssize_t rc;
- char tx_buf[msgsz];
+ char tx_buf[params->msgsize];
- if (!opt_silent) {
- printf("%s: repeat %u\n", __func__, repeat);
+ if (!params->silent) {
+ printf("%s: repeat %u\n", __func__, params->repeat);
}
- fd = tipc_connect(dev_name, echo_name);
+ fd = tipc_connect(params->dev_name, echo_name);
if (fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
return fd;
}
- for (i = 0; i < repeat; i++) {
- _wait_for_msg(fd, msgsz, 1);
+ for (i = 0; i < params->repeat; i++) {
+ _wait_for_msg(fd, 1, params);
- if (!opt_silent) {
- printf("sending burst: %u msg\n", msgburst);
+ if (!params->silent) {
+ printf("sending burst: %u msg\n", params->msgburst);
}
- for (j = 0; j < msgburst; j++) {
- memset(tx_buf, i + j, msgsz);
- rc = write(fd, tx_buf, msgsz);
- if ((size_t)rc != msgsz) {
+ for (j = 0; j < params->msgburst; j++) {
+ memset(tx_buf, i + j, params->msgsize);
+ rc = write(fd, tx_buf, params->msgsize);
+ if ((size_t)rc != params->msgsize) {
perror("burst_test: write");
break;
}
@@ -546,37 +583,36 @@
tipc_close(fd);
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-static int blocked_read_test(uint repeat)
-{
+static int blocked_read_test(const struct tipc_test_params* params) {
int fd;
uint i;
ssize_t rc;
char rx_buf[512];
- if (!opt_silent) {
- printf("%s: repeat %u\n", __func__, repeat);
+ if (!params->silent) {
+ printf("%s: repeat %u\n", __func__, params->repeat);
}
- fd = tipc_connect(dev_name, echo_name);
+ fd = tipc_connect(params->dev_name, echo_name);
if (fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
return fd;
}
- for (i = 0; i < repeat; i++) {
+ for (i = 0; i < params->repeat; i++) {
rc = read(fd, rx_buf, sizeof(rx_buf));
if (rc < 0) {
perror("select_test: read");
break;
} else {
- if (!opt_silent) {
+ if (!params->silent) {
printf("got %zd bytes\n", rc);
}
}
@@ -584,15 +620,14 @@
tipc_close(fd);
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-static int ta2ta_ipc_test(void)
-{
+static int ta2ta_ipc_test(const struct tipc_test_params* params) {
enum test_message_header {
TEST_PASSED = 0,
TEST_FAILED = 1,
@@ -604,11 +639,11 @@
int ret;
unsigned char rx_buf[256];
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s:\n", __func__);
}
- fd = tipc_connect(dev_name, main_ctrl_name);
+ fd = tipc_connect(params->dev_name, main_ctrl_name);
if (fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "main_ctrl");
return fd;
@@ -658,13 +693,12 @@
uuid->clock_seq_and_node[7]);
}
-static int dev_uuid_test(void)
-{
+static int dev_uuid_test(const struct tipc_test_params* params) {
int fd;
ssize_t rc;
uuid_t uuid;
- fd = tipc_connect(dev_name, uuid_name);
+ fd = tipc_connect(params->dev_name, uuid_name);
if (fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "uuid");
return fd;
@@ -677,7 +711,7 @@
} else if (rc != sizeof(uuid)) {
fprintf(stderr, "unexpected uuid size (%d vs. %d)\n", (int)rc, (int)sizeof(uuid));
} else {
- print_uuid(dev_name, &uuid);
+ print_uuid(params->dev_name, &uuid);
}
tipc_close(fd);
@@ -685,61 +719,58 @@
return 0;
}
-static int ta_access_test(void)
-{
+static int ta_access_test(const struct tipc_test_params* params) {
int fd;
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s:\n", __func__);
}
- fd = tipc_connect(dev_name, ta_only_name);
+ fd = tipc_connect(params->dev_name, ta_only_name);
if (fd >= 0) {
fprintf(stderr, "Succeed to connect to '%s' service\n", "ta_only");
tipc_close(fd);
}
- fd = tipc_connect(dev_name, ns_only_name);
+ fd = tipc_connect(params->dev_name, ns_only_name);
if (fd < 0) {
fprintf(stderr, "Failed to connect to '%s' service\n", "ns_only");
return fd;
}
tipc_close(fd);
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-
-static int writev_test(uint repeat, uint msgsz, bool var)
-{
+static int writev_test(const struct tipc_test_params* params) {
uint i;
ssize_t rc;
size_t msg_len;
int echo_fd = -1;
- char tx0_buf[msgsz];
- char tx1_buf[msgsz];
- char rx_buf[msgsz];
+ char tx0_buf[params->msgsize];
+ char tx1_buf[params->msgsize];
+ char rx_buf[params->msgsize];
struct iovec iovs[2] = {{tx0_buf, 0}, {tx1_buf, 0}};
- if (!opt_silent) {
- printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
- var ? "true" : "false");
+ if (!params->silent) {
+ printf("%s: repeat %u: params->msgsize %u: variable %s\n", __func__, params->repeat,
+ params->msgsize, params->variable ? "true" : "false");
}
- echo_fd = tipc_connect(dev_name, echo_name);
+ echo_fd = tipc_connect(params->dev_name, echo_name);
if (echo_fd < 0) {
fprintf(stderr, "Failed to connect to service\n");
return echo_fd;
}
- for (i = 0; i < repeat; i++) {
- msg_len = msgsz;
- if (opt_variable && msgsz) {
- msg_len = rand() % msgsz;
+ for (i = 0; i < params->repeat; i++) {
+ msg_len = params->msgsize;
+ if (params->variable && params->msgsize) {
+ msg_len = rand() % params->msgsize;
}
iovs[0].iov_len = msg_len / 3;
@@ -786,39 +817,38 @@
tipc_close(echo_fd);
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-static int readv_test(uint repeat, uint msgsz, bool var)
-{
+static int readv_test(const struct tipc_test_params* params) {
uint i;
ssize_t rc;
size_t msg_len;
int echo_fd = -1;
- char tx_buf[msgsz];
- char rx0_buf[msgsz];
- char rx1_buf[msgsz];
+ char tx_buf[params->msgsize];
+ char rx0_buf[params->msgsize];
+ char rx1_buf[params->msgsize];
struct iovec iovs[2] = {{rx0_buf, 0}, {rx1_buf, 0}};
- if (!opt_silent) {
- printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
- var ? "true" : "false");
+ if (!params->silent) {
+ printf("%s: repeat %u: params->msgsize %u: variable %s\n", __func__, params->repeat,
+ params->msgsize, params->variable ? "true" : "false");
}
- echo_fd = tipc_connect(dev_name, echo_name);
+ echo_fd = tipc_connect(params->dev_name, echo_name);
if (echo_fd < 0) {
fprintf(stderr, "Failed to connect to service\n");
return echo_fd;
}
- for (i = 0; i < repeat; i++) {
- msg_len = msgsz;
- if (opt_variable && msgsz) {
- msg_len = rand() % msgsz;
+ for (i = 0; i < params->repeat; i++) {
+ msg_len = params->msgsize;
+ if (params->variable && params->msgsize) {
+ msg_len = rand() % params->msgsize;
}
iovs[0].iov_len = msg_len / 3;
@@ -865,14 +895,14 @@
tipc_close(echo_fd);
- if (!opt_silent) {
+ if (!params->silent) {
printf("%s: done\n", __func__);
}
return 0;
}
-static int send_fd_test(void) {
+static int send_fd_test(const struct tipc_test_params* params) {
int ret;
int dma_buf = -1;
int fd = -1;
@@ -881,7 +911,7 @@
const size_t num_chunks = 10;
- fd = tipc_connect(dev_name, receiver_name);
+ fd = tipc_connect(params->dev_name, receiver_name);
if (fd < 0) {
fprintf(stderr, "Failed to connect to test support TA - is it missing?\n");
ret = -1;
@@ -948,6 +978,73 @@
return ret;
}
+uint64_t get_time_us(void) {
+ struct timespec spec;
+
+ clock_gettime(CLOCK_MONOTONIC, &spec);
+ return spec.tv_sec * 1000000 + spec.tv_nsec / 1000;
+}
+
+static const struct tipc_test_def tipc_tests[] = {
+ {"connect", connect_test},
+ {"connect_foo", connect_foo},
+ {"burst_write", burst_write_test},
+ {"select", select_test},
+ {"blocked_read", blocked_read_test},
+ {"closer1", closer1_test},
+ {"closer2", closer2_test},
+ {"closer3", closer3_test},
+ {"echo", echo_test},
+ {"ta2ta-ipc", ta2ta_ipc_test},
+ {"dev-uuid", dev_uuid_test},
+ {"ta-access", ta_access_test},
+ {"writev", writev_test},
+ {"readv", readv_test},
+ {"send-fd", send_fd_test},
+};
+
+tipc_test_func_t get_test_function(const struct tipc_test_params* params) {
+ for (size_t i = 0; i < sizeof(tipc_tests) / sizeof(tipc_tests[0]); i++) {
+ if (strcmp(params->test_name, tipc_tests[i].test_name) == 0) {
+ return tipc_tests[i].func;
+ }
+ }
+ fprintf(stderr, "Unrecognized test name '%s'\n", params->test_name);
+ exit(1);
+}
+
+static int run_as_bench(const struct tipc_test_params* params) {
+ int rc = 0;
+ int64_t min = INT64_MAX;
+ int64_t max = 0;
+ int64_t avg = 0;
+ int64_t cold = 0;
+
+ uint64_t start;
+ uint64_t end;
+
+ tipc_test_func_t test = get_test_function(params);
+
+ for (size_t i = 0; (i < params->bench + 1) && rc == 0; ++i) {
+ start = get_time_us();
+ rc |= test(params);
+ end = get_time_us();
+ int64_t t = end - start;
+
+ if (i == 0) {
+ cold = t;
+ } else {
+ min = (t < min) ? t : min;
+ max = (t > max) ? t : max;
+ avg += t;
+ }
+ }
+ avg /= params->bench;
+
+ fprintf(stderr, BENCH_RESULT_TPL, params->test_name, min, max, avg, cold, min, max, avg, cold);
+ return rc;
+}
+
int main(int argc, char **argv)
{
int rc = 0;
@@ -955,52 +1052,25 @@
if (argc <= 1) {
print_usage_and_exit(argv[0], EXIT_FAILURE, false);
}
+ struct tipc_test_params params;
+ init_params(¶ms);
+ parse_options(argc, argv, ¶ms);
- parse_options(argc, argv);
-
- if (!dev_name) {
- dev_name = TIPC_DEFAULT_DEVNAME;
+ if (!params.dev_name) {
+ params.dev_name = TIPC_DEFAULT_DEVNAME;
}
- if (!test_name) {
+ if (!params.test_name) {
fprintf(stderr, "need a Test to run\n");
print_usage_and_exit(argv[0], EXIT_FAILURE, true);
}
- if (strcmp(test_name, "connect") == 0) {
- rc = connect_test(opt_repeat);
- } else if (strcmp(test_name, "connect_foo") == 0) {
- rc = connect_foo(opt_repeat);
- } else if (strcmp(test_name, "burst_write") == 0) {
- rc = burst_write_test(opt_repeat, opt_msgburst, opt_msgsize, opt_variable);
- } else if (strcmp(test_name, "select") == 0) {
- rc = select_test(opt_repeat, opt_msgburst, opt_msgsize);
- } else if (strcmp(test_name, "blocked_read") == 0) {
- rc = blocked_read_test(opt_repeat);
- } else if (strcmp(test_name, "closer1") == 0) {
- rc = closer1_test(opt_repeat);
- } else if (strcmp(test_name, "closer2") == 0) {
- rc = closer2_test(opt_repeat);
- } else if (strcmp(test_name, "closer3") == 0) {
- rc = closer3_test(opt_repeat);
- } else if (strcmp(test_name, "echo") == 0) {
- rc = echo_test(opt_repeat, opt_msgsize, opt_variable);
- } else if (strcmp(test_name, "ta2ta-ipc") == 0) {
- rc = ta2ta_ipc_test();
- } else if (strcmp(test_name, "dev-uuid") == 0) {
- rc = dev_uuid_test();
- } else if (strcmp(test_name, "ta-access") == 0) {
- rc = ta_access_test();
- } else if (strcmp(test_name, "writev") == 0) {
- rc = writev_test(opt_repeat, opt_msgsize, opt_variable);
- } else if (strcmp(test_name, "readv") == 0) {
- rc = readv_test(opt_repeat, opt_msgsize, opt_variable);
- } else if (strcmp(test_name, "send-fd") == 0) {
- rc = send_fd_test();
+ if (params.bench > 0) {
+ rc = run_as_bench(¶ms);
+ params.bench = 0;
} else {
- fprintf(stderr, "Unrecognized test name '%s'\n", test_name);
- print_usage_and_exit(argv[0], EXIT_FAILURE, true);
+ tipc_test_func_t test = get_test_function(¶ms);
+ rc = test(¶ms);
}
-
return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/trusty/secretkeeper/Android.bp b/trusty/secretkeeper/Android.bp
new file mode 100644
index 0000000..f6b740a
--- /dev/null
+++ b/trusty/secretkeeper/Android.bp
@@ -0,0 +1,96 @@
+//
+// Copyright (C) 2022 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+ name: "android.hardware.security.secretkeeper.trusty",
+ relative_install_path: "hw",
+ vendor: true,
+ init_rc: ["android.hardware.security.secretkeeper.trusty.rc"],
+ vintf_fragments: ["android.hardware.security.secretkeeper.trusty.xml"],
+ srcs: [
+ "src/hal_main.rs",
+ ],
+ rustlibs: [
+ "libandroid_logger",
+ "libbinder_rs",
+ "libauthgraph_hal",
+ "libtrusty-rs",
+ "liblibc",
+ "liblog_rust",
+ "libsecretkeeper_hal",
+ ],
+ defaults: [
+ "secretkeeper_use_latest_hal_aidl_rust",
+ ],
+ prefer_rlib: true,
+}
+
+cc_defaults {
+ name: "trusty_secretkeeper_fuzz_defaults",
+ srcs: [":trusty_tipc_fuzzer"],
+ fuzz_config: {
+ cc: [
+ "alanstokes@google.com",
+ "drysdale@google.com",
+ "shikhapanwar@google.com",
+ ],
+ componentid: 867125,
+ // TODO: add Secretkeeper hotlist
+ // hotlists: [""],
+ },
+}
+
+cc_fuzz {
+ name: "trusty_secretkeeper_sk_fuzzer",
+ defaults: [
+ "trusty_fuzzer_defaults",
+ "trusty_secretkeeper_fuzz_defaults",
+ ],
+ cflags: [
+ "-DTRUSTY_APP_PORT=\"com.android.trusty.secretkeeper\"",
+ "-DTRUSTY_APP_UUID=\"4582bf12-1f7d-4830-9be5-36e6bd91c2c6\"",
+ "-DTRUSTY_APP_FILENAME=\"secretkeeper_app.syms.elf\"",
+ ],
+}
+
+cc_fuzz {
+ name: "trusty_secretkeeper_ag_fuzzer",
+ defaults: [
+ "trusty_fuzzer_defaults",
+ "trusty_secretkeeper_fuzz_defaults",
+ ],
+ cflags: [
+ "-DTRUSTY_APP_PORT=\"com.android.trusty.secretkeeper.authgraph\"",
+ "-DTRUSTY_APP_UUID=\"4582bf12-1f7d-4830-9be5-36e6bd91c2c6\"",
+ "-DTRUSTY_APP_FILENAME=\"secretkeeper_app.syms.elf\"",
+ ],
+}
+
+cc_fuzz {
+ name: "trusty_secretkeeper_bl_fuzzer",
+ defaults: [
+ "trusty_fuzzer_defaults",
+ "trusty_secretkeeper_fuzz_defaults",
+ ],
+ cflags: [
+ "-DTRUSTY_APP_PORT=\"com.android.trusty.secretkeeper.bootloader\"",
+ "-DTRUSTY_APP_UUID=\"4582bf12-1f7d-4830-9be5-36e6bd91c2c6\"",
+ "-DTRUSTY_APP_FILENAME=\"secretkeeper_app.syms.elf\"",
+ ],
+}
diff --git a/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.rc b/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.rc
new file mode 100644
index 0000000..3be03ad
--- /dev/null
+++ b/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.rc
@@ -0,0 +1,4 @@
+service vendor.secretkeeper.trusty /vendor/bin/hw/android.hardware.security.secretkeeper.trusty
+ class hal
+ user nobody
+ group drmrpc
\ No newline at end of file
diff --git a/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.xml b/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.xml
new file mode 100644
index 0000000..2ac152b
--- /dev/null
+++ b/trusty/secretkeeper/android.hardware.security.secretkeeper.trusty.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.security.secretkeeper</name>
+ <version>1</version>
+ <fqname>ISecretkeeper/default</fqname>
+ </hal>
+</manifest>
diff --git a/trusty/secretkeeper/src/hal_main.rs b/trusty/secretkeeper/src/hal_main.rs
new file mode 100644
index 0000000..9439c36
--- /dev/null
+++ b/trusty/secretkeeper/src/hal_main.rs
@@ -0,0 +1,141 @@
+//
+// 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.
+
+//! This module implements the HAL service for Secretkeeper in Trusty.
+use authgraph_hal::{channel::SerializedChannel};
+use secretkeeper_hal::SecretkeeperService;
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{
+ ISecretkeeper, BpSecretkeeper,
+};
+use log::{error, info};
+use std::{
+ ffi::CString,
+ panic,
+ sync::{Arc, Mutex},
+};
+use trusty::DEFAULT_DEVICE;
+
+const SK_TIPC_SERVICE_PORT: &str = "com.android.trusty.secretkeeper";
+const AG_TIPC_SERVICE_PORT: &str = "com.android.trusty.secretkeeper.authgraph";
+
+static SERVICE_INSTANCE: &str = "default";
+
+/// Local error type for failures in the HAL service.
+#[derive(Debug, Clone)]
+struct HalServiceError(String);
+
+#[derive(Debug)]
+struct TipcChannel {
+ channel: Arc<Mutex<trusty::TipcChannel>>,
+}
+
+impl TipcChannel {
+ fn new(channel: trusty::TipcChannel) -> Self {
+ Self { channel: Arc::new(Mutex::new(channel)) }
+ }
+}
+
+impl SerializedChannel for TipcChannel {
+ const MAX_SIZE: usize = 4000;
+ fn execute(&self, req_data: &[u8]) -> binder::Result<Vec<u8>> {
+ // Hold lock across both request and response.
+ let mut channel = self.channel.lock().unwrap();
+ channel.send(req_data).map_err(|e| {
+ binder::Status::new_exception(
+ binder::ExceptionCode::TRANSACTION_FAILED,
+ Some(
+ &CString::new(format!(
+ "Failed to send the request via tipc channel because of {:?}",
+ e
+ ))
+ .unwrap(),
+ ),
+ )
+ })?;
+ // TODO: cope with fragmentation and reassembly
+ let mut rsp_data = Vec::new();
+ channel.recv(&mut rsp_data).map_err(|e| {
+ binder::Status::new_exception(
+ binder::ExceptionCode::TRANSACTION_FAILED,
+ Some(
+ &CString::new(format!(
+ "Failed to receive the response via tipc channel because of {:?}",
+ e
+ ))
+ .unwrap(),
+ ),
+ )
+ })?;
+ Ok(rsp_data)
+ }
+}
+
+fn main() {
+ if let Err(e) = inner_main() {
+ panic!("HAL service failed: {:?}", e);
+ }
+}
+
+fn inner_main() -> Result<(), HalServiceError> {
+ // Initialize Android logging.
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("secretkeeper-hal-trusty")
+ .with_min_level(log::Level::Info)
+ .with_log_id(android_logger::LogId::System),
+ );
+ // Redirect panic messages to logcat.
+ panic::set_hook(Box::new(|panic_info| {
+ error!("{}", panic_info);
+ }));
+
+ info!("Trusty Secretkeeper HAL service is starting.");
+
+ info!("Starting thread pool now.");
+ binder::ProcessState::start_thread_pool();
+
+ // Create connections to the TA.
+ let ag_connection = trusty::TipcChannel::connect(DEFAULT_DEVICE, AG_TIPC_SERVICE_PORT)
+ .map_err(|e| {
+ HalServiceError(format!(
+ "Failed to connect to Trusty port {AG_TIPC_SERVICE_PORT} because of {:?}.",
+ e
+ ))
+ })?;
+ let ag_tipc_channel = TipcChannel::new(ag_connection);
+
+ let sk_connection = trusty::TipcChannel::connect(DEFAULT_DEVICE, SK_TIPC_SERVICE_PORT)
+ .map_err(|e| {
+ HalServiceError(format!(
+ "Failed to connect to Trusty port {SK_TIPC_SERVICE_PORT} because of {:?}.",
+ e
+ ))
+ })?;
+ let sk_tipc_channel = TipcChannel::new(sk_connection);
+
+ // Register the AIDL service
+ let service = SecretkeeperService::new_as_binder(sk_tipc_channel, ag_tipc_channel);
+ let service_name =
+ format!("{}/{}", <BpSecretkeeper as ISecretkeeper>::get_descriptor(), SERVICE_INSTANCE);
+ binder::add_service(&service_name, service.as_binder()).map_err(|e| {
+ HalServiceError(format!("Failed to register service {} because of {:?}.", service_name, e))
+ })?;
+
+ info!("Successfully registered Secretkeeper HAL service.");
+ info!("Joining thread pool now.");
+ binder::ProcessState::join_thread_pool();
+ info!("Secretkeeper HAL service is terminating."); // should not reach here
+ Ok(())
+}
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index 2299481..8c8edb7 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -353,7 +353,6 @@
if (open_flags & O_CREAT) {
sync_parent(path, watcher);
}
- free(path);
/* at this point rc contains storage file fd */
msg->result = STORAGE_NO_ERROR;
@@ -361,6 +360,9 @@
ALOGV("%s: \"%s\": fd = %u: handle = %d\n",
__func__, path, rc, resp.handle);
+ free(path);
+ path = NULL;
+
/* a backing file has been opened, notify any waiting init steps */
if (!fs_ready_initialized) {
rc = property_set(FS_READY_PROPERTY, "1");
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index 1986c73..d645c3e 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -35,8 +35,16 @@
LOCAL_KEYMINT_PRODUCT_PACKAGE := android.hardware.security.keymint-service.trusty
endif
+# TODO(b/306364873): move this to be flag-controlled?
+ifeq ($(SECRETKEEPER_ENABLED),)
+ LOCAL_SECRETKEEPER_PRODUCT_PACKAGE :=
+else
+ LOCAL_SECRETKEEPER_PRODUCT_PACKAGE := android.hardware.security.secretkeeper.trusty
+endif
+
PRODUCT_PACKAGES += \
$(LOCAL_KEYMINT_PRODUCT_PACKAGE) \
+ $(LOCAL_SECRETKEEPER_PRODUCT_PACKAGE) \
android.hardware.gatekeeper-service.trusty \
trusty_apploader \