DO NOT MERGE - Merge ab/7272582
Bug: 190855093
Change-Id: I20031b6d8af2ee6fe1ef559c5983cee480a3acb2
diff --git a/bootstat/Android.bp b/bootstat/Android.bp
index 545a06f..ca59ef3 100644
--- a/bootstat/Android.bp
+++ b/bootstat/Android.bp
@@ -35,7 +35,7 @@
"libcutils",
"liblog",
],
- static_libs: ["libgtest_prod"],
+ header_libs: ["libgtest_prod_headers"],
}
// bootstat static library
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 7b6f6c0..198e4de 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -276,6 +276,12 @@
],
}
+cc_test_library {
+ name: "libcrash_test",
+ defaults: ["debuggerd_defaults"],
+ srcs: ["crash_test.cpp"],
+}
+
cc_test {
name: "debuggerd_test",
defaults: ["debuggerd_defaults"],
@@ -341,6 +347,10 @@
},
},
+ data: [
+ ":libcrash_test",
+ ],
+
test_suites: ["device-tests"],
}
diff --git a/debuggerd/crash_test.cpp b/debuggerd/crash_test.cpp
new file mode 100644
index 0000000..c15145f
--- /dev/null
+++ b/debuggerd/crash_test.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright 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 <stdint.h>
+
+extern "C" void crash() {
+ *reinterpret_cast<volatile char*>(0xdead) = '1';
+}
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 144faee..24804d0 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -15,6 +15,7 @@
*/
#include <dirent.h>
+#include <dlfcn.h>
#include <err.h>
#include <fcntl.h>
#include <malloc.h>
@@ -30,6 +31,7 @@
#include <chrono>
#include <regex>
+#include <string>
#include <thread>
#include <android/fdsan.h>
@@ -49,6 +51,7 @@
#include <android-base/test_utils.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libminijail.h>
@@ -63,6 +66,7 @@
using android::base::SendFileDescriptors;
using android::base::unique_fd;
+using ::testing::HasSubstr;
#if defined(__LP64__)
#define ARCH_SUFFIX "64"
@@ -274,7 +278,7 @@
}
if (signo == 0) {
- ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_TRUE(WIFEXITED(status)) << "Terminated due to unexpected signal " << WTERMSIG(status);
ASSERT_EQ(0, WEXITSTATUS(signo));
} else {
ASSERT_FALSE(WIFEXITED(status));
@@ -305,6 +309,19 @@
*output = std::move(result);
}
+class LogcatCollector {
+ public:
+ LogcatCollector() { system("logcat -c"); }
+
+ void Collect(std::string* output) {
+ FILE* cmd_stdout = popen("logcat -d '*:S DEBUG'", "r");
+ ASSERT_NE(cmd_stdout, nullptr);
+ unique_fd tmp_fd(TEMP_FAILURE_RETRY(dup(fileno(cmd_stdout))));
+ ConsumeFd(std::move(tmp_fd), output);
+ pclose(cmd_stdout);
+ }
+};
+
TEST_F(CrasherTest, smoke) {
int intercept_result;
unique_fd output_fd;
@@ -439,6 +456,7 @@
}
GwpAsanTestParameters params = GetParam();
+ LogcatCollector logcat_collector;
int intercept_result;
unique_fd output_fd;
@@ -458,22 +476,23 @@
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
- std::string result;
- ConsumeFd(std::move(output_fd), &result);
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
- ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
- if (params.free_before_access) {
- ASSERT_MATCH(result, R"(deallocated by thread .*
- #00 pc)");
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
+ ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
+ if (params.free_before_access) {
+ ASSERT_MATCH(result, R"(deallocated by thread .*\n.*#00 pc)");
+ }
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*\n.*#00 pc)");
}
- ASSERT_MATCH(result, R"(allocated by thread .*
- #00 pc)");
}
struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
-INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(16, 131072));
+INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(0, 16, 131072));
TEST_P(SizeParamCrasherTest, mte_uaf) {
#if defined(__aarch64__)
@@ -481,6 +500,13 @@
GTEST_SKIP() << "Requires MTE";
}
+ // Any UAF on a zero-sized allocation will be out-of-bounds so it won't be reported.
+ if (GetParam() == 0) {
+ return;
+ }
+
+ LogcatCollector logcat_collector;
+
int intercept_result;
unique_fd output_fd;
StartProcess([&]() {
@@ -497,16 +523,49 @@
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
+
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
+ std::to_string(GetParam()) + R"(-byte allocation)");
+ ASSERT_MATCH(result, R"(deallocated by thread .*?\n.*#00 pc)");
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*?\n.*#00 pc)");
+ }
+#else
+ GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_P(SizeParamCrasherTest, mte_oob_uaf) {
+#if defined(__aarch64__)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&]() {
+ SetTagCheckingLevelSync();
+ volatile int* p = (volatile int*)malloc(GetParam());
+ free((void *)p);
+ p[-1] = 42;
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
std::string result;
ConsumeFd(std::move(output_fd), &result);
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
- std::to_string(GetParam()) + R"(-byte allocation)");
- ASSERT_MATCH(result, R"(deallocated by thread .*
- #00 pc)");
- ASSERT_MATCH(result, R"(allocated by thread .*
- #00 pc)");
+ ASSERT_NOT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 4 bytes left)");
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@@ -518,6 +577,7 @@
GTEST_SKIP() << "Requires MTE";
}
+ LogcatCollector logcat_collector;
int intercept_result;
unique_fd output_fd;
StartProcess([&]() {
@@ -533,14 +593,16 @@
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
- std::string result;
- ConsumeFd(std::move(output_fd), &result);
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
- std::to_string(GetParam()) + R"(-byte allocation)");
- ASSERT_MATCH(result, R"(allocated by thread .*
- #00 pc)");
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
+ std::to_string(GetParam()) + R"(-byte allocation)");
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*?\n.*#00 pc)");
+ }
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@@ -573,7 +635,7 @@
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
std::to_string(GetParam()) + R"(-byte allocation)");
- ASSERT_MATCH(result, R"(allocated by thread .*
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*
#00 pc)");
#else
GTEST_SKIP() << "Requires aarch64";
@@ -586,6 +648,8 @@
GTEST_SKIP() << "Requires MTE";
}
+ LogcatCollector logcat_collector;
+
int intercept_result;
unique_fd output_fd;
StartProcess([]() {
@@ -618,17 +682,23 @@
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
- std::string result;
- ConsumeFd(std::move(output_fd), &result);
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
- ASSERT_MATCH(
- result,
- R"(Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.)");
-
- // Adjacent untracked allocations may cause us to see the wrong underflow here (or only
- // overflows), so we can't match explicitly for an underflow message.
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+ ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, "
+ "listing them in decreasing order of probability."));
+ // Adjacent untracked allocations may cause us to see the wrong underflow here (or only
+ // overflows), so we can't match explicitly for an underflow message.
+ ASSERT_MATCH(result,
+ R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
+ // Ensure there's at least two allocation traces (one for each cause).
+ ASSERT_MATCH(
+ result,
+ R"((^|\s)allocated by thread .*?\n.*#00 pc(.|\n)*?(^|\s)allocated by thread .*?\n.*#00 pc)");
+ }
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@@ -1510,6 +1580,60 @@
ASSERT_MATCH(result, R"(Cause: stack pointer[^\n]*stack overflow.\n)");
}
+static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) {
+ std::string test_lib(testing::internal::GetArgvs()[0]);
+ auto const value = test_lib.find_last_of('/');
+ if (value == std::string::npos) {
+ test_lib = "./";
+ } else {
+ test_lib = test_lib.substr(0, value + 1) + "./";
+ }
+ test_lib += "libcrash_test.so";
+
+ *tmp_so_name = std::string(tmp_dir) + "/libcrash_test.so";
+ std::string cp_cmd = android::base::StringPrintf("cp %s %s", test_lib.c_str(), tmp_dir);
+
+ // Copy the shared so to a tempory directory.
+ return system(cp_cmd.c_str()) == 0;
+}
+
+TEST_F(CrasherTest, unreadable_elf) {
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([]() {
+ TemporaryDir td;
+ std::string tmp_so_name;
+ if (!CopySharedLibrary(td.path, &tmp_so_name)) {
+ _exit(1);
+ }
+ void* handle = dlopen(tmp_so_name.c_str(), RTLD_NOW);
+ if (handle == nullptr) {
+ _exit(1);
+ }
+ // Delete the original shared library so that we get the warning
+ // about unreadable elf files.
+ if (unlink(tmp_so_name.c_str()) == -1) {
+ _exit(1);
+ }
+ void (*crash_func)() = reinterpret_cast<void (*)()>(dlsym(handle, "crash"));
+ if (crash_func == nullptr) {
+ _exit(1);
+ }
+ crash_func();
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+ ASSERT_MATCH(result, R"(NOTE: Function names and BuildId information is missing )");
+}
+
TEST(tombstoned, proto) {
const pid_t self = getpid();
unique_fd tombstoned_socket, text_fd, proto_fd;
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index e0cc662..ad903ce 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -106,9 +106,9 @@
unwindstack::MapInfo* map_info = maps->Find(sp);
if (map_info == nullptr) {
return "stack pointer is in a non-existent map; likely due to stack overflow.";
- } else if ((map_info->flags & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
+ } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
return "stack pointer is not in a rw map; likely due to stack overflow.";
- } else if ((sp - map_info->start) <= kMaxDifferenceBytes) {
+ } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) {
return "stack pointer is close to top of stack; likely stack overflow.";
}
}
@@ -137,7 +137,7 @@
} else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
unwindstack::MapInfo* map_info = maps->Find(fault_addr);
- if (map_info != nullptr && map_info->flags == PROT_EXEC) {
+ if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
cause = "execute-only (no-read) memory access error; likely due to data in .text.";
} else {
cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps);
@@ -244,7 +244,7 @@
"memory map (%zu entr%s):",
maps->Total(), maps->Total() == 1 ? "y" : "ies");
if (print_fault_address_marker) {
- if (maps->Total() != 0 && addr < maps->Get(0)->start) {
+ if (maps->Total() != 0 && addr < maps->Get(0)->start()) {
_LOG(log, logtype::MAPS, "\n--->Fault address falls at %s before any mapped regions\n",
get_addr_string(addr).c_str());
print_fault_address_marker = false;
@@ -261,37 +261,37 @@
for (auto const& map_info : *maps) {
line = " ";
if (print_fault_address_marker) {
- if (addr < map_info->start) {
+ if (addr < map_info->start()) {
_LOG(log, logtype::MAPS, "--->Fault address falls at %s between mapped regions\n",
get_addr_string(addr).c_str());
print_fault_address_marker = false;
- } else if (addr >= map_info->start && addr < map_info->end) {
+ } else if (addr >= map_info->start() && addr < map_info->end()) {
line = "--->";
print_fault_address_marker = false;
}
}
- line += get_addr_string(map_info->start) + '-' + get_addr_string(map_info->end - 1) + ' ';
- if (map_info->flags & PROT_READ) {
+ line += get_addr_string(map_info->start()) + '-' + get_addr_string(map_info->end() - 1) + ' ';
+ if (map_info->flags() & PROT_READ) {
line += 'r';
} else {
line += '-';
}
- if (map_info->flags & PROT_WRITE) {
+ if (map_info->flags() & PROT_WRITE) {
line += 'w';
} else {
line += '-';
}
- if (map_info->flags & PROT_EXEC) {
+ if (map_info->flags() & PROT_EXEC) {
line += 'x';
} else {
line += '-';
}
- line += StringPrintf(" %8" PRIx64 " %8" PRIx64, map_info->offset,
- map_info->end - map_info->start);
+ line += StringPrintf(" %8" PRIx64 " %8" PRIx64, map_info->offset(),
+ map_info->end() - map_info->start());
bool space_needed = true;
- if (!map_info->name.empty()) {
+ if (!map_info->name().empty()) {
space_needed = false;
- line += " " + map_info->name;
+ line += " " + map_info->name();
std::string build_id = map_info->GetPrintableBuildID();
if (!build_id.empty()) {
line += " (BuildId: " + build_id + ")";
@@ -369,8 +369,8 @@
std::string label{"memory near "s + reg_name};
if (maps) {
unwindstack::MapInfo* map_info = maps->Find(untag_address(reg_value));
- if (map_info != nullptr && !map_info->name.empty()) {
- label += " (" + map_info->name + ")";
+ if (map_info != nullptr && !map_info->name().empty()) {
+ label += " (" + map_info->name() + ")";
}
}
dump_memory(log, memory, reg_value, label);
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 7657001..abd1f12 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -102,9 +102,9 @@
unwindstack::MapInfo* map_info = maps->Find(sp);
if (map_info == nullptr) {
return "stack pointer is in a non-existent map; likely due to stack overflow.";
- } else if ((map_info->flags & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
+ } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
return "stack pointer is not in a rw map; likely due to stack overflow.";
- } else if ((sp - map_info->start) <= kMaxDifferenceBytes) {
+ } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) {
return "stack pointer is close to top of stack; likely stack overflow.";
}
}
@@ -221,7 +221,7 @@
}
} else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
unwindstack::MapInfo* map_info = maps->Find(fault_addr);
- if (map_info != nullptr && map_info->flags == PROT_EXEC) {
+ if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
cause = "execute-only (no-read) memory access error; likely due to data in .text.";
} else {
cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
@@ -359,7 +359,7 @@
dump.set_register_name(name);
unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
if (map_info) {
- dump.set_mapping_name(map_info->name);
+ dump.set_mapping_name(map_info->name());
}
char buf[256];
@@ -395,6 +395,15 @@
unwinder->LastErrorAddress());
}
} else {
+ if (unwinder->elf_from_memory_not_file()) {
+ auto backtrace_note = thread.mutable_backtrace_note();
+ *backtrace_note->Add() =
+ "Function names and BuildId information is missing for some frames due";
+ *backtrace_note->Add() =
+ "to unreadable libraries. For unwinds of apps, only shared libraries";
+ *backtrace_note->Add() = "found under the lib/ directory are readable.";
+ *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable.";
+ }
unwinder->SetDisplayBuildID(true);
for (const auto& frame : unwinder->frames()) {
BacktraceFrame* f = thread.add_current_backtrace();
@@ -417,21 +426,21 @@
for (const auto& map_info : *maps) {
auto* map = tombstone->add_memory_mappings();
- map->set_begin_address(map_info->start);
- map->set_end_address(map_info->end);
- map->set_offset(map_info->offset);
+ map->set_begin_address(map_info->start());
+ map->set_end_address(map_info->end());
+ map->set_offset(map_info->offset());
- if (map_info->flags & PROT_READ) {
+ if (map_info->flags() & PROT_READ) {
map->set_read(true);
}
- if (map_info->flags & PROT_WRITE) {
+ if (map_info->flags() & PROT_WRITE) {
map->set_write(true);
}
- if (map_info->flags & PROT_EXEC) {
+ if (map_info->flags() & PROT_EXEC) {
map->set_execute(true);
}
- map->set_mapping_name(map_info->name);
+ map->set_mapping_name(map_info->name());
std::string build_id = map_info->GetPrintableBuildID();
if (!build_id.empty()) {
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 020b0a5..a932d48 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -171,6 +171,10 @@
const Thread& thread, bool should_log) {
CBS("");
CB(should_log, "backtrace:");
+ if (!thread.backtrace_note().empty()) {
+ CB(should_log, " NOTE: %s",
+ android::base::Join(thread.backtrace_note(), "\n NOTE: ").c_str());
+ }
print_backtrace(callback, tombstone, thread.current_backtrace(), should_log);
}
@@ -268,14 +272,14 @@
if (tombstone.causes_size() > 1) {
CBS("");
- CBS("Note: multiple potential causes for this crash were detected, listing them in decreasing "
+ CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing "
"order of probability.");
}
for (const Cause& cause : tombstone.causes()) {
if (tombstone.causes_size() > 1) {
CBS("");
- CBS("Cause: %s", cause.human_readable().c_str());
+ CBL("Cause: %s", cause.human_readable().c_str());
}
if (cause.has_memory_error() && cause.memory_error().has_heap()) {
@@ -283,14 +287,14 @@
if (heap_object.deallocation_backtrace_size() != 0) {
CBS("");
- CBS("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
- print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), false);
+ CBL("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
+ print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), true);
}
if (heap_object.allocation_backtrace_size() != 0) {
CBS("");
- CBS("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
- print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), false);
+ CBL("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
+ print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), true);
}
}
}
diff --git a/debuggerd/proto/Android.bp b/debuggerd/proto/Android.bp
index b78224b..73cf573 100644
--- a/debuggerd/proto/Android.bp
+++ b/debuggerd/proto/Android.bp
@@ -31,6 +31,7 @@
stl: "libc++_static",
apex_available: [
+ "//apex_available:platform",
"com.android.runtime",
],
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index 294e4e5..22fc30e 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -119,11 +119,12 @@
int32 id = 1;
string name = 2;
repeated Register registers = 3;
+ repeated string backtrace_note = 7;
repeated BacktraceFrame current_backtrace = 4;
repeated MemoryDump memory_dump = 5;
int64 tagged_addr_ctrl = 6;
- reserved 7 to 999;
+ reserved 8 to 999;
}
message BacktraceFrame {
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index 1585cc6..21887ab 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -24,7 +24,7 @@
rt_sigprocmask: 1
rt_sigaction: 1
rt_tgsigqueueinfo: 1
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS
madvise: 1
mprotect: arg2 in 0x1|0x2
munmap: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index cd5aad4..90843fc 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -34,7 +34,12 @@
rt_tgsigqueueinfo: 1
#define PR_SET_VMA 0x53564d41
+#if defined(__aarch64__)
+// PR_PAC_RESET_KEYS happens on aarch64 in pthread_create path.
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS
+#else
prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA
+#endif
#if 0
libminijail on vendor partitions older than P does not have constants from <sys/mman.h>.
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index 0b87b7a..05d8050 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -165,8 +165,7 @@
switch (dump_type) {
case kDebuggerdNativeBacktrace:
- case kDebuggerdJavaBacktrace:
- // Don't generate tombstones for backtrace requests.
+ // Don't generate tombstones for native backtrace requests.
return {};
case kDebuggerdTombstoneProto:
@@ -178,6 +177,7 @@
result.text = create_temporary_file();
break;
+ case kDebuggerdJavaBacktrace:
case kDebuggerdTombstone:
result.text = create_temporary_file();
break;
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 43b2ddd..2c70778 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -183,7 +183,7 @@
],
static_libs: [
- "libgtest_prod",
+ "libc++fs",
"libhealthhalutils",
"libsnapshot_cow",
"libsnapshot_nobinder",
@@ -192,6 +192,7 @@
header_libs: [
"avb_headers",
+ "libgtest_prod_headers",
"libsnapshot_headers",
"libstorage_literals_headers",
],
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 333ca50..ee0aa58 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -27,6 +27,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr_overlayfs.h>
@@ -162,7 +163,9 @@
partition_name == "boot_b")) {
CopyAVBFooter(&data, block_device_size);
}
- WipeOverlayfsForPartition(device, partition_name);
+ if (android::base::GetProperty("ro.system.build.type", "") != "user") {
+ WipeOverlayfsForPartition(device, partition_name);
+ }
int result = FlashBlockDevice(handle.fd(), data);
sync();
return result;
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index dfed77e..6a49fdf 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -166,6 +166,11 @@
"vbmeta_system.sig",
"vbmeta_system",
true, ImageType::BootCritical },
+ { "vbmeta_vendor",
+ "vbmeta_vendor.img",
+ "vbmeta_vendor.sig",
+ "vbmeta_vendor",
+ true, ImageType::BootCritical },
{ "vendor", "vendor.img", "vendor.sig", "vendor", true, ImageType::Normal },
{ "vendor_boot",
"vendor_boot.img", "vendor_boot.sig",
@@ -411,6 +416,13 @@
" gsi wipe|disable Wipe or disable a GSI installation (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"
+ " Create a logical partition with the given name and\n"
+ " size, in the super partition.\n"
+ " delete-logical-partition NAME\n"
+ " Delete a logical partition with the given name.\n"
+ " resize-logical-partition NAME SIZE\n"
+ " Change the size of the named logical partition.\n"
" snapshot-update cancel On devices that support snapshot-based updates, cancel\n"
" an in-progress update. This may make the device\n"
" unbootable until it is reflashed.\n"
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index a349408..84709b6 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -17,6 +17,9 @@
},
{
"name": "libsnapshot_fuzzer_test"
+ },
+ {
+ "name": "cow_api_test"
}
]
}
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index bbbb7e8..af71fe6 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -264,12 +264,12 @@
F2FS_FSCK_BIN, "-f", "-c", "10000", "--debug-cache", blk_device.c_str()};
if (should_force_check(*fs_stat)) {
- LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache"
+ LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache "
<< realpath(blk_device);
ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_forced_argv), f2fs_fsck_forced_argv,
&status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
} else {
- LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache"
+ LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache "
<< realpath(blk_device);
ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv, &status, false,
LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
@@ -2265,3 +2265,81 @@
}
return LP_METADATA_DEFAULT_PARTITION_NAME;
}
+
+bool fs_mgr_create_canonical_mount_point(const std::string& mount_point) {
+ auto saved_errno = errno;
+ auto ok = true;
+ auto created_mount_point = !mkdir(mount_point.c_str(), 0755);
+ std::string real_mount_point;
+ if (!Realpath(mount_point, &real_mount_point)) {
+ ok = false;
+ PERROR << "failed to realpath(" << mount_point << ")";
+ } else if (mount_point != real_mount_point) {
+ ok = false;
+ LERROR << "mount point is not canonical: realpath(" << mount_point << ") -> "
+ << real_mount_point;
+ }
+ if (!ok && created_mount_point) {
+ rmdir(mount_point.c_str());
+ }
+ errno = saved_errno;
+ return ok;
+}
+
+bool fs_mgr_mount_overlayfs_fstab_entry(const FstabEntry& entry) {
+ auto overlayfs_valid_result = fs_mgr_overlayfs_valid();
+ if (overlayfs_valid_result == OverlayfsValidResult::kNotSupported) {
+ LERROR << __FUNCTION__ << "(): kernel does not support overlayfs";
+ return false;
+ }
+
+#if ALLOW_ADBD_DISABLE_VERITY == 0
+ // Allowlist the mount point if user build.
+ static const std::vector<const std::string> kAllowedPaths = {
+ "/odm", "/odm_dlkm", "/oem", "/product", "/system_ext", "/vendor", "/vendor_dlkm",
+ };
+ static const std::vector<const std::string> kAllowedPrefixes = {
+ "/mnt/product/",
+ "/mnt/vendor/",
+ };
+ if (std::none_of(kAllowedPaths.begin(), kAllowedPaths.end(),
+ [&entry](const auto& path) -> bool {
+ return entry.mount_point == path ||
+ StartsWith(entry.mount_point, path + "/");
+ }) &&
+ std::none_of(kAllowedPrefixes.begin(), kAllowedPrefixes.end(),
+ [&entry](const auto& prefix) -> bool {
+ return entry.mount_point != prefix &&
+ StartsWith(entry.mount_point, prefix);
+ })) {
+ LERROR << __FUNCTION__
+ << "(): mount point is forbidden on user build: " << entry.mount_point;
+ return false;
+ }
+#endif // ALLOW_ADBD_DISABLE_VERITY == 0
+
+ if (!fs_mgr_create_canonical_mount_point(entry.mount_point)) {
+ return false;
+ }
+
+ auto options = "lowerdir=" + entry.lowerdir;
+ if (overlayfs_valid_result == OverlayfsValidResult::kOverrideCredsRequired) {
+ options += ",override_creds=off";
+ }
+
+ // Use "overlay-" + entry.blk_device as the mount() source, so that adb-remout-test don't
+ // confuse this with adb remount overlay, whose device name is "overlay".
+ // Overlayfs is a pseudo filesystem, so the source device is a symbolic value and isn't used to
+ // back the filesystem. However the device name would be shown in /proc/mounts.
+ auto source = "overlay-" + entry.blk_device;
+ auto report = "__mount(source=" + source + ",target=" + entry.mount_point + ",type=overlay," +
+ options + ")=";
+ auto ret = mount(source.c_str(), entry.mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
+ options.c_str());
+ if (ret) {
+ PERROR << report << ret;
+ return false;
+ }
+ LINFO << report << ret;
+ return true;
+}
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 42bf356..d0c89b9 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -127,15 +127,16 @@
}
fs_options.append(flag);
- if (entry->fs_type == "f2fs" && StartsWith(flag, "reserve_root=")) {
- std::string arg;
- if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) {
- arg = flag.substr(equal_sign + 1);
- }
- if (!ParseInt(arg, &entry->reserved_size)) {
- LWARNING << "Warning: reserve_root= flag malformed: " << arg;
- } else {
- entry->reserved_size <<= 12;
+ if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) {
+ const auto arg = flag.substr(equal_sign + 1);
+ if (entry->fs_type == "f2fs" && StartsWith(flag, "reserve_root=")) {
+ if (!ParseInt(arg, &entry->reserved_size)) {
+ LWARNING << "Warning: reserve_root= flag malformed: " << arg;
+ } else {
+ entry->reserved_size <<= 12;
+ }
+ } else if (StartsWith(flag, "lowerdir=")) {
+ entry->lowerdir = std::move(arg);
}
}
}
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 1134f14..4d32bda 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -92,10 +92,6 @@
return false;
}
-std::vector<std::string> fs_mgr_overlayfs_required_devices(Fstab*) {
- return {};
-}
-
bool fs_mgr_overlayfs_setup(const char*, const char*, bool* change, bool) {
if (change) *change = false;
return false;
@@ -116,6 +112,8 @@
void MapScratchPartitionIfNeeded(Fstab*, const std::function<bool(const std::set<std::string>&)>&) {
}
+void CleanupOldScratchFiles() {}
+
void TeardownAllOverlayForMountPoint(const std::string&) {}
} // namespace fs_mgr
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index e685070..5411aca 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -420,7 +420,8 @@
break;
}
// Find overlayfs mount point?
- if ((mount_point == "/") && (rentry.mount_point == "/system")) {
+ if ((mount_point == "/" && rentry.mount_point == "/system") ||
+ (mount_point == "/system" && rentry.mount_point == "/")) {
blk_device = rentry.blk_device;
mount_point = "/system";
found = true;
diff --git a/fs_mgr/fs_mgr_vendor_overlay.cpp b/fs_mgr/fs_mgr_vendor_overlay.cpp
index 830f0dd..1372511 100644
--- a/fs_mgr/fs_mgr_vendor_overlay.cpp
+++ b/fs_mgr/fs_mgr_vendor_overlay.cpp
@@ -92,7 +92,7 @@
}
auto report = "__mount(source=overlay,target="s + vendor_mount_point + ",type=overlay," +
options + ")=";
- auto ret = mount("overlay", vendor_mount_point.c_str(), "overlay", MS_RDONLY | MS_RELATIME,
+ auto ret = mount("overlay", vendor_mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
options.c_str());
if (ret) {
PERROR << report << ret;
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 22c02cc..4d3ecc9 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -131,3 +131,12 @@
// Finds the dm_bow device on which this block device is stacked, or returns
// empty string
std::string fs_mgr_find_bow_device(const std::string& block_device);
+
+// Creates mount point if not already existed, and checks that mount point is a
+// canonical path that doesn't contain any symbolic link or /../.
+bool fs_mgr_create_canonical_mount_point(const std::string& mount_point);
+
+// Like fs_mgr_do_mount_one() but for overlayfs fstab entries.
+// Unlike fs_mgr_overlayfs, mount overlayfs without upperdir and workdir, so the
+// filesystem cannot be remount read-write.
+bool fs_mgr_mount_overlayfs_fstab_entry(const android::fs_mgr::FstabEntry& entry);
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index d45e2de..6caab1f 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -27,7 +27,6 @@
android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab);
bool fs_mgr_overlayfs_mount_all(android::fs_mgr::Fstab* fstab);
-std::vector<std::string> fs_mgr_overlayfs_required_devices(android::fs_mgr::Fstab* fstab);
bool fs_mgr_overlayfs_setup(const char* backing = nullptr, const char* mount_point = nullptr,
bool* change = nullptr, bool force = true);
bool fs_mgr_overlayfs_teardown(const char* mount_point = nullptr, bool* change = nullptr);
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 2704e47..f33768b 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -55,6 +55,7 @@
std::string vbmeta_partition;
uint64_t zram_backingdev_size = 0;
std::string avb_keys;
+ std::string lowerdir;
struct FsMgrFlags {
bool wait : 1;
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index d5b8a61..c4874b8 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -35,6 +35,10 @@
#include "utility.h"
+#ifndef DM_DEFERRED_REMOVE
+#define DM_DEFERRED_REMOVE (1 << 17)
+#endif
+
namespace android {
namespace dm {
@@ -133,6 +137,25 @@
return DeleteDevice(name, 0ms);
}
+bool DeviceMapper::DeleteDeviceDeferred(const std::string& name) {
+ struct dm_ioctl io;
+ InitIo(&io, name);
+
+ io.flags |= DM_DEFERRED_REMOVE;
+ if (ioctl(fd_, DM_DEV_REMOVE, &io)) {
+ PLOG(ERROR) << "DM_DEV_REMOVE with DM_DEFERRED_REMOVE failed for [" << name << "]";
+ return false;
+ }
+ return true;
+}
+
+bool DeviceMapper::DeleteDeviceIfExistsDeferred(const std::string& name) {
+ if (GetState(name) == DmDeviceState::INVALID) {
+ return true;
+ }
+ return DeleteDeviceDeferred(name);
+}
+
static std::string GenerateUuid() {
uuid_t uuid_bytes;
uuid_generate(uuid_bytes);
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index ef46eb9..b0639e6 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -95,7 +95,9 @@
}
void DmTargetVerity::SetVerityMode(const std::string& mode) {
- if (mode != "restart_on_corruption" && mode != "ignore_corruption") {
+ if (mode != "panic_on_corruption" &&
+ mode != "restart_on_corruption" &&
+ mode != "ignore_corruption") {
LOG(ERROR) << "Unknown verity mode: " << mode;
valid_ = false;
return;
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index 41d3145..8006db2 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -35,6 +35,7 @@
#include <libdm/dm.h>
#include <libdm/loop_control.h>
#include "test_util.h"
+#include "utility.h"
using namespace std;
using namespace std::chrono_literals;
@@ -617,3 +618,64 @@
auto sub_block_device = dm.GetParentBlockDeviceByPath(dev.path());
ASSERT_EQ(loop.device(), *sub_block_device);
}
+
+TEST(libdm, DeleteDeviceDeferredNoReferences) {
+ unique_fd tmp(CreateTempFile("file_1", 4096));
+ ASSERT_GE(tmp, 0);
+ LoopDevice loop(tmp, 10s);
+ ASSERT_TRUE(loop.valid());
+
+ DmTable table;
+ ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop.device(), 0));
+ ASSERT_TRUE(table.valid());
+ TempDevice dev("libdm-test-dm-linear", table);
+ ASSERT_TRUE(dev.valid());
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+
+ std::string path;
+ ASSERT_TRUE(dm.GetDmDevicePathByName("libdm-test-dm-linear", &path));
+ ASSERT_EQ(0, access(path.c_str(), F_OK));
+
+ ASSERT_TRUE(dm.DeleteDeviceDeferred("libdm-test-dm-linear"));
+
+ ASSERT_TRUE(WaitForFileDeleted(path, 5s));
+ ASSERT_EQ(DmDeviceState::INVALID, dm.GetState("libdm-test-dm-linear"));
+ ASSERT_NE(0, access(path.c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(libdm, DeleteDeviceDeferredWaitsForLastReference) {
+ unique_fd tmp(CreateTempFile("file_1", 4096));
+ ASSERT_GE(tmp, 0);
+ LoopDevice loop(tmp, 10s);
+ ASSERT_TRUE(loop.valid());
+
+ DmTable table;
+ ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop.device(), 0));
+ ASSERT_TRUE(table.valid());
+ TempDevice dev("libdm-test-dm-linear", table);
+ ASSERT_TRUE(dev.valid());
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+
+ std::string path;
+ ASSERT_TRUE(dm.GetDmDevicePathByName("libdm-test-dm-linear", &path));
+ ASSERT_EQ(0, access(path.c_str(), F_OK));
+
+ {
+ // Open a reference to block device.
+ unique_fd fd(TEMP_FAILURE_RETRY(open(dev.path().c_str(), O_RDONLY | O_CLOEXEC)));
+ ASSERT_GE(fd.get(), 0);
+
+ ASSERT_TRUE(dm.DeleteDeviceDeferred("libdm-test-dm-linear"));
+
+ ASSERT_EQ(0, access(path.c_str(), F_OK));
+ }
+
+ // After release device will be removed.
+ ASSERT_TRUE(WaitForFileDeleted(path, 5s));
+ ASSERT_EQ(DmDeviceState::INVALID, dm.GetState("libdm-test-dm-linear"));
+ ASSERT_NE(0, access(path.c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+}
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 5d6db46..70b14fa 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -95,6 +95,12 @@
bool DeleteDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms);
bool DeleteDeviceIfExists(const std::string& name, const std::chrono::milliseconds& timeout_ms);
+ // Enqueues a deletion of device mapper device with the given name once last reference is
+ // closed.
+ // Returns 'true' on success, false otherwise.
+ bool DeleteDeviceDeferred(const std::string& name);
+ bool DeleteDeviceIfExistsDeferred(const std::string& name);
+
// Fetches and returns the complete state of the underlying device mapper
// device with given name.
std::optional<Info> GetDetailedInfo(const std::string& name) const;
diff --git a/fs_mgr/libfiemap/binder.cpp b/fs_mgr/libfiemap/binder.cpp
index c8516ab..31a57a8 100644
--- a/fs_mgr/libfiemap/binder.cpp
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -224,8 +224,9 @@
return false;
}
-std::unique_ptr<IImageManager> IImageManager::Open(
- const std::string& dir, const std::chrono::milliseconds& /*timeout_ms*/) {
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir,
+ const std::chrono::milliseconds& /*timeout_ms*/,
+ const DeviceInfo&) {
android::sp<IGsiService> service = android::gsi::GetGsiService();
android::sp<IImageService> manager;
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index 44f659b..dcbbc54 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -55,7 +55,8 @@
static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test";
static constexpr char kOtaTestImageMetadataDir[] = "/metadata/gsi/ota/test";
-std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix) {
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix,
+ const DeviceInfo& device_info) {
auto metadata_dir = "/metadata/gsi/" + dir_prefix;
auto data_dir = "/data/gsi/" + dir_prefix;
auto install_dir_file = gsi::DsuInstallDirFile(gsi::GetDsuSlot(dir_prefix));
@@ -63,17 +64,28 @@
if (ReadFileToString(install_dir_file, &path)) {
data_dir = path;
}
- return Open(metadata_dir, data_dir);
+ return Open(metadata_dir, data_dir, device_info);
}
std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir,
- const std::string& data_dir) {
- return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir));
+ const std::string& data_dir,
+ const DeviceInfo& device_info) {
+ return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir, device_info));
}
-ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir)
- : metadata_dir_(metadata_dir), data_dir_(data_dir) {
+ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir,
+ const DeviceInfo& device_info)
+ : metadata_dir_(metadata_dir), data_dir_(data_dir), device_info_(device_info) {
partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
+
+ // Allow overriding whether ImageManager thinks it's in recovery, for testing.
+#ifdef __ANDROID_RECOVERY__
+ device_info_.is_recovery = {true};
+#else
+ if (!device_info_.is_recovery.has_value()) {
+ device_info_.is_recovery = {false};
+ }
+#endif
}
std::string ImageManager::GetImageHeaderPath(const std::string& name) {
@@ -261,10 +273,11 @@
return false;
}
-#if defined __ANDROID_RECOVERY__
- LOG(ERROR) << "Cannot remove images backed by /data in recovery";
- return false;
-#else
+ if (device_info_.is_recovery.value()) {
+ LOG(ERROR) << "Cannot remove images backed by /data in recovery";
+ return false;
+ }
+
std::string message;
auto header_file = GetImageHeaderPath(name);
if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) {
@@ -278,7 +291,6 @@
LOG(ERROR) << "Error removing " << status_file << ": " << message;
}
return RemoveImageMetadata(metadata_dir_, name);
-#endif
}
// Create a block device for an image file, using its extents in its
@@ -521,6 +533,9 @@
// filesystem. This should only happen on devices with no encryption, or
// devices with FBE and no metadata encryption. For these cases it suffices
// to perform normal file writes to /data/gsi (which is unencrypted).
+ //
+ // Note: this is not gated on DeviceInfo, because the recovery-specific path
+ // must only be used in actual recovery.
std::string block_device;
bool can_use_devicemapper;
if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) {
diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
index 7e30509..3c87000 100644
--- a/fs_mgr/libfiemap/include/libfiemap/image_manager.h
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -21,6 +21,7 @@
#include <chrono>
#include <functional>
#include <memory>
+#include <optional>
#include <set>
#include <string>
@@ -37,11 +38,17 @@
virtual ~IImageManager() {}
+ // Helper for dependency injection.
+ struct DeviceInfo {
+ std::optional<bool> is_recovery;
+ };
+
// When linking to libfiemap_binder, the Open() call will use binder.
// Otherwise, the Open() call will use the ImageManager implementation
- // below.
+ // below. In binder mode, device_info is ignored.
static std::unique_ptr<IImageManager> Open(const std::string& dir_prefix,
- const std::chrono::milliseconds& timeout_ms);
+ const std::chrono::milliseconds& timeout_ms,
+ const DeviceInfo& device_info = {});
// Flags for CreateBackingImage().
static constexpr int CREATE_IMAGE_DEFAULT = 0x0;
@@ -131,11 +138,13 @@
// Return an ImageManager for the given metadata and data directories. Both
// directories must already exist.
static std::unique_ptr<ImageManager> Open(const std::string& metadata_dir,
- const std::string& data_dir);
+ const std::string& data_dir,
+ const DeviceInfo& device_info = {});
// Helper function that derives the metadata and data dirs given a single
// prefix.
- static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix);
+ static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix,
+ const DeviceInfo& device_info = {});
// Methods that must be implemented from IImageManager.
FiemapStatus CreateBackingImage(const std::string& name, uint64_t size, int flags,
@@ -166,7 +175,8 @@
FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes);
private:
- ImageManager(const std::string& metadata_dir, const std::string& data_dir);
+ ImageManager(const std::string& metadata_dir, const std::string& data_dir,
+ const DeviceInfo& device_info);
std::string GetImageHeaderPath(const std::string& name);
std::string GetStatusFilePath(const std::string& image_name);
bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
@@ -187,6 +197,7 @@
std::string metadata_dir_;
std::string data_dir_;
std::unique_ptr<IPartitionOpener> partition_opener_;
+ DeviceInfo device_info_;
};
// RAII helper class for mapping and opening devices with an ImageManager.
diff --git a/fs_mgr/libfiemap/passthrough.cpp b/fs_mgr/libfiemap/passthrough.cpp
index 1ccd9a0..d521804 100644
--- a/fs_mgr/libfiemap/passthrough.cpp
+++ b/fs_mgr/libfiemap/passthrough.cpp
@@ -20,9 +20,10 @@
namespace fiemap {
std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir_prefix,
- const std::chrono::milliseconds& timeout_ms) {
+ const std::chrono::milliseconds& timeout_ms,
+ const DeviceInfo& device_info) {
(void)timeout_ms;
- return ImageManager::Open(dir_prefix);
+ return ImageManager::Open(dir_prefix, device_info);
}
} // namespace fiemap
diff --git a/fs_mgr/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index c1c181a..6892025 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -78,6 +78,7 @@
shared_libs: [
"libbase",
"libchrome",
+ "libcrypto",
],
target: {
darwin: {
@@ -107,9 +108,6 @@
static_libs: [
"libfs_avb_test_util",
],
- shared_libs: [
- "libcrypto",
- ],
compile_multilib: "first",
data: [
":avbtool",
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index 2288674..31494c1 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -61,7 +61,9 @@
// Converts veritymode to the format used in kernel.
std::string dm_verity_mode;
- if (verity_mode == "enforcing") {
+ if (verity_mode == "panicking") {
+ dm_verity_mode = "panic_on_corruption";
+ } else if (verity_mode == "enforcing") {
dm_verity_mode = "restart_on_corruption";
} else if (verity_mode == "logging") {
dm_verity_mode = "ignore_corruption";
diff --git a/fs_mgr/libfs_avb/tests/avb_util_test.cpp b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
index 1827566..0288d85 100644
--- a/fs_mgr/libfs_avb/tests/avb_util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
@@ -755,6 +755,7 @@
std::string out_public_key_data;
std::unique_ptr<VBMetaData> vbmeta = VerifyVBMetaData(
fd, "system", "" /*expected_public_key_blob */, &out_public_key_data, &verify_result);
+ ASSERT_EQ(0, close(fd.release()));
EXPECT_NE(nullptr, vbmeta);
EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result);
@@ -776,6 +777,7 @@
// Should return ErrorVerification.
vbmeta = VerifyVBMetaData(hash_modified_fd, "system", "" /*expected_public_key_blob */,
nullptr /* out_public_key_data */, &verify_result);
+ ASSERT_EQ(0, close(hash_modified_fd.release()));
EXPECT_NE(nullptr, vbmeta);
EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result);
@@ -791,6 +793,7 @@
// Should return ErrorVerification.
vbmeta = VerifyVBMetaData(aux_modified_fd, "system", "" /*expected_public_key_blob */,
nullptr /* out_public_key_data */, &verify_result);
+ ASSERT_EQ(0, close(aux_modified_fd.release()));
EXPECT_NE(nullptr, vbmeta);
EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result);
@@ -802,6 +805,7 @@
// Should return ResultOK..
vbmeta = VerifyVBMetaData(ok_fd, "system", "" /*expected_public_key_blob */,
nullptr /* out_public_key_data */, &verify_result);
+ ASSERT_EQ(0, close(ok_fd.release()));
EXPECT_NE(nullptr, vbmeta);
EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result);
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
index 17f4c4e..1c95cf0 100644
--- a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
+++ b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
@@ -122,6 +122,7 @@
const size_t padding_size) {
VBMetaImage vbmeta_image;
vbmeta_image.path = test_dir_.Append(output_file_name);
+ GTEST_LOG_(INFO) << "ExtractVBMetaImage: " << image_path << " to " << output_file_name;
EXPECT_COMMAND(0,
"avbtool extract_vbmeta_image"
" --image %s"
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index ea92d25..6a764e4 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -251,6 +251,7 @@
"snapshot_metadata_updater_test.cpp",
"snapshot_reader_test.cpp",
"snapshot_test.cpp",
+ "snapshot_writer_test.cpp",
],
shared_libs: [
"libbinder",
@@ -264,7 +265,8 @@
"android.hardware.boot@1.0",
"android.hardware.boot@1.1",
"libbrotli",
- "libfs_mgr",
+ "libc++fs",
+ "libfs_mgr_binder",
"libgsi",
"libgmock",
"liblp",
@@ -297,6 +299,7 @@
],
static_libs: [
"libbrotli",
+ "libc++fs",
"libfstab",
"libsnapshot",
"libsnapshot_cow",
@@ -326,6 +329,7 @@
"power_test.cpp",
],
static_libs: [
+ "libc++fs",
"libsnapshot",
"update_metadata-protos",
],
@@ -355,6 +359,7 @@
static_libs: [
"libbase",
"libbrotli",
+ "libc++fs",
"libchrome",
"libcrypto_static",
"libcutils",
@@ -416,7 +421,8 @@
"snapuserd_server.cpp",
"snapuserd.cpp",
"snapuserd_daemon.cpp",
- "snapuserd_worker.cpp",
+ "snapuserd_worker.cpp",
+ "snapuserd_readahead.cpp",
],
cflags: [
@@ -473,6 +479,9 @@
"libgtest",
"libsnapshot_cow",
],
+ test_suites: [
+ "device-tests"
+ ],
test_min_api_level: 30,
auto_gen_config: true,
require_root: false,
@@ -555,7 +564,7 @@
srcs: [
"cow_snapuserd_test.cpp",
"snapuserd.cpp",
- "snapuserd_worker.cpp",
+ "snapuserd_worker.cpp",
],
cflags: [
"-Wall",
@@ -572,7 +581,7 @@
"libsnapshot_snapuserd",
"libcutils_sockets",
"libz",
- "libfs_mgr",
+ "libfs_mgr",
"libdm",
],
header_libs: [
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 1ebc29f..77ed92c 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -139,7 +139,28 @@
Cancelled = 7;
};
-// Next: 7
+// Next 14:
+//
+// To understand the source of each failure, read snapshot.cpp. To handle new
+// sources of failure, avoid reusing an existing code; add a new code instead.
+enum MergeFailureCode {
+ Ok = 0;
+ ReadStatus = 1;
+ GetTableInfo = 2;
+ UnknownTable = 3;
+ GetTableParams = 4;
+ ActivateNewTable = 5;
+ AcquireLock = 6;
+ ListSnapshots = 7;
+ WriteStatus = 8;
+ UnknownTargetType = 9;
+ QuerySnapshotStatus = 10;
+ ExpectedMergeTarget = 11;
+ UnmergedSectorsAfterCompletion = 12;
+ UnexpectedMergeState = 13;
+};
+
+// Next: 8
message SnapshotUpdateStatus {
UpdateState state = 1;
@@ -160,9 +181,12 @@
// Merge phase (if state == MERGING).
MergePhase merge_phase = 6;
+
+ // Merge failure code, filled if state == MergeFailed.
+ MergeFailureCode merge_failure_code = 7;
}
-// Next: 9
+// Next: 10
message SnapshotMergeReport {
// Status of the update after the merge attempts.
UpdateState state = 1;
@@ -188,4 +212,8 @@
// Time from sys.boot_completed to merge start, in milliseconds.
uint32 boot_complete_to_merge_start_time_ms = 8;
+
+ // Merge failure code, filled if the merge failed at any time (regardless
+ // of whether it succeeded at a later time).
+ MergeFailureCode merge_failure_code = 9;
}
diff --git a/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt b/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt
new file mode 100644
index 0000000..c474f4c
--- /dev/null
+++ b/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt
@@ -0,0 +1,41 @@
+device_info_data {
+ allow_set_slot_as_unbootable: true
+ is_recovery: true
+}
+is_super_metadata_valid: true
+super_data {
+ partitions {
+ partition_name: "sys_a"
+ new_partition_info {
+ size: 3145728
+ }
+ }
+ partitions {
+ partition_name: "vnnd_"
+ new_partition_info {
+ size: 3145728
+ }
+ }
+ partitions {
+ partition_name: "prd_a"
+ new_partition_info {
+ }
+ }
+ dynamic_partition_metadata {
+ groups {
+ name: "group_google_dp_a"
+ size: 34375467008
+ partition_names: "sys_a"
+ partition_names: "vnd_a"
+ partition_names: "prd_a"
+ }
+ }
+}
+has_metadata_snapshots_dir: true
+actions {
+ handle_imminent_data_wipe: true
+}
+actions {
+ begin_update {
+ }
+}
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 5d63220..b75b154 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -25,6 +25,10 @@
#include <libsnapshot/cow_reader.h>
#include <libsnapshot/cow_writer.h>
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+
namespace android {
namespace snapshot {
@@ -781,6 +785,202 @@
ASSERT_TRUE(reader.Parse(cow_->fd));
}
+AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) {
+ data.resize(writer->options().block_size, '\0');
+ if (!writer->AddRawBlocks(new_block, data.data(), data.size())) {
+ return AssertionFailure() << "Failed to add raw block";
+ }
+ return AssertionSuccess();
+}
+
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+ const std::string& data) {
+ CowHeader header;
+ reader->GetHeader(&header);
+
+ std::string cmp = data;
+ cmp.resize(header.block_size, '\0');
+
+ StringSink sink;
+ if (!reader->ReadData(op, &sink)) {
+ return AssertionFailure() << "Failed to read data block";
+ }
+ if (cmp != sink.stream()) {
+ return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got "
+ << sink.stream();
+ }
+
+ return AssertionSuccess();
+}
+
+TEST_F(CowTest, ResumeMidCluster) {
+ CowOptions options;
+ options.cluster_ops = 7;
+ auto writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+ ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+ ASSERT_TRUE(writer->AddLabel(2));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+
+ auto iter = reader.GetOpIter();
+ size_t num_replace = 0;
+ size_t max_in_cluster = 0;
+ size_t num_in_cluster = 0;
+ size_t num_clusters = 0;
+ while (!iter->Done()) {
+ const auto& op = iter->Get();
+
+ num_in_cluster++;
+ max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+ 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) {
+ num_in_cluster = 0;
+ num_clusters++;
+ }
+
+ iter->Next();
+ }
+ ASSERT_EQ(num_replace, 8);
+ ASSERT_EQ(max_in_cluster, 7);
+ ASSERT_EQ(num_clusters, 2);
+}
+
+TEST_F(CowTest, ResumeEndCluster) {
+ CowOptions options;
+ int cluster_ops = 5;
+ options.cluster_ops = cluster_ops;
+ auto writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+ ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+ ASSERT_TRUE(writer->AddLabel(2));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+
+ auto iter = reader.GetOpIter();
+ size_t num_replace = 0;
+ size_t max_in_cluster = 0;
+ size_t num_in_cluster = 0;
+ size_t num_clusters = 0;
+ while (!iter->Done()) {
+ const auto& op = iter->Get();
+
+ num_in_cluster++;
+ max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+ 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) {
+ num_in_cluster = 0;
+ num_clusters++;
+ }
+
+ iter->Next();
+ }
+ ASSERT_EQ(num_replace, 8);
+ ASSERT_EQ(max_in_cluster, cluster_ops);
+ ASSERT_EQ(num_clusters, 3);
+}
+
+TEST_F(CowTest, DeleteMidCluster) {
+ CowOptions options;
+ options.cluster_ops = 7;
+ auto writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+ ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+
+ auto iter = reader.GetOpIter();
+ size_t num_replace = 0;
+ size_t max_in_cluster = 0;
+ size_t num_in_cluster = 0;
+ size_t num_clusters = 0;
+ while (!iter->Done()) {
+ const auto& op = iter->Get();
+
+ num_in_cluster++;
+ max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+ 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) {
+ num_in_cluster = 0;
+ num_clusters++;
+ }
+
+ iter->Next();
+ }
+ ASSERT_EQ(num_replace, 3);
+ ASSERT_EQ(max_in_cluster, 5); // 3 data, 1 label, 1 cluster op
+ ASSERT_EQ(num_clusters, 1);
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 7199b38..2349e4a 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -94,11 +94,6 @@
<< "Expected: " << kCowMagicNumber;
return false;
}
- if (header_.header_size != sizeof(CowHeader)) {
- LOG(ERROR) << "Header size unknown, read " << header_.header_size << ", expected "
- << sizeof(CowHeader);
- return false;
- }
if (header_.footer_size != sizeof(CowFooter)) {
LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
<< sizeof(CowFooter);
@@ -123,8 +118,7 @@
return false;
}
- if ((header_.major_version != kCowVersionMajor) ||
- (header_.minor_version != kCowVersionMinor)) {
+ if ((header_.major_version > kCowVersionMajor) || (header_.minor_version != kCowVersionMinor)) {
LOG(ERROR) << "Header version mismatch";
LOG(ERROR) << "Major version: " << header_.major_version
<< "Expected: " << kCowVersionMajor;
@@ -137,10 +131,25 @@
}
bool CowReader::ParseOps(std::optional<uint64_t> label) {
- uint64_t pos = lseek(fd_.get(), sizeof(header_), SEEK_SET);
- if (pos != sizeof(header_)) {
- PLOG(ERROR) << "lseek ops failed";
- return false;
+ uint64_t pos;
+
+ // Skip the scratch space
+ if (header_.major_version >= 2 && (header_.buffer_size > 0)) {
+ LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
+ size_t init_offset = header_.header_size + header_.buffer_size;
+ pos = lseek(fd_.get(), init_offset, SEEK_SET);
+ if (pos != init_offset) {
+ PLOG(ERROR) << "lseek ops failed";
+ return false;
+ }
+ } else {
+ pos = lseek(fd_.get(), header_.header_size, SEEK_SET);
+ if (pos != header_.header_size) {
+ PLOG(ERROR) << "lseek ops failed";
+ return false;
+ }
+ // Reading a v1 version of COW which doesn't have buffer_size.
+ header_.buffer_size = 0;
}
auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
@@ -360,6 +369,24 @@
// Replace-op-4, Zero-op-9, Replace-op-5 }
//==============================================================
+ num_copy_ops = FindNumCopyops();
+
+ std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
+ [](CowOperation& op1, CowOperation& op2) -> bool {
+ return op1.new_block > op2.new_block;
+ });
+
+ if (header_.num_merge_ops > 0) {
+ ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
+ }
+
+ num_copy_ops = FindNumCopyops();
+ set_copy_ops(num_copy_ops);
+}
+
+uint64_t CowReader::FindNumCopyops() {
+ uint64_t num_copy_ops = 0;
+
for (uint64_t i = 0; i < ops_->size(); i++) {
auto& current_op = ops_->data()[i];
if (current_op.type != kCowCopyOp) {
@@ -368,15 +395,7 @@
num_copy_ops += 1;
}
- std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
- [](CowOperation& op1, CowOperation& op2) -> bool {
- return op1.new_block > op2.new_block;
- });
-
- if (header_.num_merge_ops > 0) {
- CHECK(ops_->size() >= header_.num_merge_ops);
- ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
- }
+ return num_copy_ops;
}
bool CowReader::GetHeader(CowHeader* header) {
@@ -470,7 +489,7 @@
bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
// Validate the offset, taking care to acknowledge possible overflow of offset+len.
- if (offset < sizeof(header_) || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
+ if (offset < header_.header_size || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
offset + len > fd_size_ - sizeof(CowFooter)) {
LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
return false;
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 045d9db..767cd04 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -96,11 +96,14 @@
class CowSnapuserdTest final {
public:
bool Setup();
+ bool SetupCopyOverlap_1();
+ bool SetupCopyOverlap_2();
bool Merge();
void ValidateMerge();
void ReadSnapshotDeviceAndValidate();
void Shutdown();
void MergeInterrupt();
+ void ReadDmUserBlockWithoutDaemon();
std::string snapshot_dev() const { return snapshot_dev_->path(); }
@@ -114,6 +117,9 @@
void StartMerge();
void CreateCowDevice();
+ void CreateCowDeviceWithCopyOverlap_1();
+ void CreateCowDeviceWithCopyOverlap_2();
+ bool SetupDaemon();
void CreateBaseDevice();
void InitCowDevice();
void SetDeviceControlName();
@@ -191,6 +197,33 @@
return setup_ok_;
}
+bool CowSnapuserdTest::SetupCopyOverlap_1() {
+ CreateBaseDevice();
+ CreateCowDeviceWithCopyOverlap_1();
+ return SetupDaemon();
+}
+
+bool CowSnapuserdTest::SetupCopyOverlap_2() {
+ CreateBaseDevice();
+ CreateCowDeviceWithCopyOverlap_2();
+ return SetupDaemon();
+}
+
+bool CowSnapuserdTest::SetupDaemon() {
+ SetDeviceControlName();
+
+ StartSnapuserdDaemon();
+ InitCowDevice();
+
+ CreateDmUserDevice();
+ InitDaemon();
+
+ CreateSnapshotDevice();
+ setup_ok_ = true;
+
+ return setup_ok_;
+}
+
void CowSnapuserdTest::StartSnapuserdDaemon() {
pid_t pid = fork();
ASSERT_GE(pid, 0);
@@ -255,6 +288,101 @@
ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
}
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
+ std::string path = android::base::GetExecutableDirectory();
+ cow_system_ = std::make_unique<TemporaryFile>(path);
+
+ CowOptions options;
+ options.compression = "gz";
+ CowWriter writer(options);
+
+ ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+ size_t num_blocks = size_ / options.block_size;
+ size_t x = num_blocks;
+ size_t blk_src_copy = 0;
+
+ // Create overlapping copy operations
+ while (1) {
+ ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
+ x -= 1;
+ if (x == 1) {
+ break;
+ }
+ blk_src_copy += 1;
+ }
+
+ // Flush operations
+ ASSERT_TRUE(writer.Finalize());
+
+ // Construct the buffer required for validation
+ orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+ // Read the entire base device
+ ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+ true);
+
+ // Merged operations required for validation
+ int block_size = 4096;
+ x = num_blocks;
+ loff_t src_offset = block_size;
+ loff_t dest_offset = 0;
+
+ while (1) {
+ memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset,
+ block_size);
+ x -= 1;
+ if (x == 1) {
+ break;
+ }
+ src_offset += block_size;
+ dest_offset += block_size;
+ }
+}
+
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
+ std::string path = android::base::GetExecutableDirectory();
+ cow_system_ = std::make_unique<TemporaryFile>(path);
+
+ CowOptions options;
+ options.compression = "gz";
+ CowWriter writer(options);
+
+ ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+ size_t num_blocks = size_ / options.block_size;
+ size_t x = num_blocks;
+ size_t blk_src_copy = num_blocks - 1;
+
+ // Create overlapping copy operations
+ while (1) {
+ ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
+ x -= 1;
+ if (x == 0) {
+ ASSERT_EQ(blk_src_copy, 0);
+ break;
+ }
+ blk_src_copy -= 1;
+ }
+
+ // Flush operations
+ ASSERT_TRUE(writer.Finalize());
+
+ // Construct the buffer required for validation
+ orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+ // Read the entire base device
+ ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+ true);
+
+ // Merged operations
+ ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
+ true);
+ ASSERT_EQ(android::base::ReadFullyAtOffset(
+ base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
+ true);
+}
+
void CowSnapuserdTest::CreateCowDevice() {
unique_fd rnd_fd;
loff_t offset = 0;
@@ -354,6 +482,36 @@
ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
}
+void CowSnapuserdTest::ReadDmUserBlockWithoutDaemon() {
+ DmTable dmuser_table;
+ std::string dm_user_name = "dm-test-device";
+ unique_fd fd;
+
+ // Create a dm-user block device
+ ASSERT_TRUE(dmuser_table.AddTarget(std::make_unique<DmTargetUser>(0, 123456, dm_user_name)));
+ ASSERT_TRUE(dmuser_table.valid());
+
+ dmuser_dev_ = std::make_unique<TempDevice>(dm_user_name, dmuser_table);
+ ASSERT_TRUE(dmuser_dev_->valid());
+ ASSERT_FALSE(dmuser_dev_->path().empty());
+
+ fd.reset(open(dmuser_dev_->path().c_str(), O_RDONLY));
+ ASSERT_GE(fd, 0);
+
+ std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+ loff_t offset = 0;
+ // Every IO should fail as there is no daemon to process the IO
+ for (size_t j = 0; j < 10; j++) {
+ ASSERT_EQ(ReadFullyAtOffset(fd, (char*)buffer.get() + offset, BLOCK_SZ, offset), false);
+
+ offset += BLOCK_SZ;
+ }
+
+ fd = {};
+ ASSERT_TRUE(dmuser_dev_->Destroy());
+}
+
void CowSnapuserdTest::InitDaemon() {
bool ok = client_->AttachDmUser(system_device_ctrl_name_);
ASSERT_TRUE(ok);
@@ -757,6 +915,36 @@
harness.ValidateMerge();
harness.Shutdown();
}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
+ CowSnapuserdTest harness;
+ ASSERT_TRUE(harness.SetupCopyOverlap_1());
+ ASSERT_TRUE(harness.Merge());
+ harness.ValidateMerge();
+ harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
+ CowSnapuserdTest harness;
+ ASSERT_TRUE(harness.SetupCopyOverlap_2());
+ ASSERT_TRUE(harness.Merge());
+ harness.ValidateMerge();
+ harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+ CowSnapuserdTest harness;
+ ASSERT_TRUE(harness.SetupCopyOverlap_1());
+ harness.MergeInterrupt();
+ harness.ValidateMerge();
+ harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, ReadDmUserBlockWithoutDaemon) {
+ CowSnapuserdTest harness;
+ harness.ReadDmUserBlockWithoutDaemon();
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 59f6d6f..51c00a9 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -94,6 +94,7 @@
header_.block_size = options_.block_size;
header_.num_merge_ops = 0;
header_.cluster_ops = options_.cluster_ops;
+ header_.buffer_size = 0;
footer_ = {};
footer_.op.data_length = 64;
footer_.op.type = kCowFooterOp;
@@ -139,12 +140,6 @@
return true;
}
-void CowWriter::InitializeMerge(borrowed_fd fd, CowHeader* header) {
- fd_ = fd;
- memcpy(&header_, header, sizeof(CowHeader));
- merge_in_progress_ = true;
-}
-
bool CowWriter::Initialize(unique_fd&& fd) {
owned_fd_ = std::move(fd);
return Initialize(borrowed_fd{owned_fd_});
@@ -172,7 +167,7 @@
}
void CowWriter::InitPos() {
- next_op_pos_ = sizeof(header_);
+ next_op_pos_ = sizeof(header_) + header_.buffer_size;
cluster_size_ = header_.cluster_ops * sizeof(CowOperation);
if (header_.cluster_ops) {
next_data_pos_ = next_op_pos_ + cluster_size_;
@@ -196,6 +191,10 @@
return false;
}
+ if (options_.scratch_space) {
+ header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+ }
+
// Headers are not complete, but this ensures the file is at the right
// position.
if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
@@ -203,7 +202,27 @@
return false;
}
+ if (options_.scratch_space) {
+ // Initialize the scratch space
+ std::string data(header_.buffer_size, 0);
+ if (!android::base::WriteFully(fd_, data.data(), header_.buffer_size)) {
+ PLOG(ERROR) << "writing scratch space failed";
+ return false;
+ }
+ }
+
+ if (!Sync()) {
+ LOG(ERROR) << "Header sync failed";
+ return false;
+ }
+
+ if (lseek(fd_.get(), sizeof(header_) + header_.buffer_size, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek failed";
+ return false;
+ }
+
InitPos();
+
return true;
}
@@ -232,15 +251,11 @@
// Free reader so we own the descriptor position again.
reader = nullptr;
- // Remove excess data
- if (!Truncate(next_op_pos_)) {
- return false;
- }
if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
PLOG(ERROR) << "lseek failed";
return false;
}
- return true;
+ return EmitClusterIfNeeded();
}
bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
@@ -319,6 +334,14 @@
return WriteOperation(op);
}
+bool CowWriter::EmitClusterIfNeeded() {
+ // If there isn't room for another op and the cluster end op, end the current cluster
+ if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) {
+ if (!EmitCluster()) return false;
+ }
+ return true;
+}
+
std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
switch (compression_) {
case kCowCompressGz: {
@@ -379,6 +402,21 @@
auto continue_num_ops = footer_.op.num_ops;
bool extra_cluster = false;
+ // Blank out extra ops, in case we're in append mode and dropped ops.
+ if (cluster_size_) {
+ auto unused_cluster_space = cluster_size_ - current_cluster_size_;
+ std::string clr;
+ clr.resize(unused_cluster_space, '\0');
+ if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+ PLOG(ERROR) << "Failed to seek to footer position.";
+ return false;
+ }
+ if (!android::base::WriteFully(fd_, clr.data(), clr.size())) {
+ PLOG(ERROR) << "clearing unused cluster area failed";
+ return false;
+ }
+ }
+
// Footer should be at the end of a file, so if there is data after the current block, end it
// and start a new cluster.
if (cluster_size_ && current_data_size_ > 0) {
@@ -403,6 +441,17 @@
return false;
}
+ // Remove excess data, if we're in append mode and threw away more data
+ // than we wrote before.
+ off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
+ if (offs < 0) {
+ PLOG(ERROR) << "Failed to lseek to find current position";
+ return false;
+ }
+ if (!Truncate(offs)) {
+ return false;
+ }
+
// Reposition for additional Writing
if (extra_cluster) {
current_cluster_size_ = continue_cluster_size;
@@ -445,12 +494,7 @@
if (!WriteRawData(data, size)) return false;
}
AddOperation(op);
- // If there isn't room for another op and the cluster end op, end the current cluster
- if (cluster_size_ && op.type != kCowClusterOp &&
- cluster_size_ < current_cluster_size_ + 2 * sizeof(op)) {
- if (!EmitCluster()) return false;
- }
- return true;
+ return EmitClusterIfNeeded();
}
void CowWriter::AddOperation(const CowOperation& op) {
@@ -492,24 +536,6 @@
return true;
}
-bool CowWriter::CommitMerge(int merged_ops) {
- CHECK(merge_in_progress_);
- header_.num_merge_ops += merged_ops;
-
- if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
- PLOG(ERROR) << "lseek failed";
- return false;
- }
-
- if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&header_),
- sizeof(header_))) {
- PLOG(ERROR) << "WriteFully failed";
- return false;
- }
-
- return Sync();
-}
-
bool CowWriter::Truncate(off_t length) {
if (is_dev_null_ || is_block_device_) {
return true;
diff --git a/fs_mgr/libsnapshot/device_info.cpp b/fs_mgr/libsnapshot/device_info.cpp
index 0e90100..14ce0ee 100644
--- a/fs_mgr/libsnapshot/device_info.cpp
+++ b/fs_mgr/libsnapshot/device_info.cpp
@@ -17,6 +17,7 @@
#include <android-base/logging.h>
#include <fs_mgr.h>
#include <fs_mgr_overlayfs.h>
+#include <libfiemap/image_manager.h>
namespace android {
namespace snapshot {
@@ -26,6 +27,7 @@
using android::hardware::boot::V1_0::CommandResult;
#endif
+using namespace std::chrono_literals;
using namespace std::string_literals;
#ifdef __ANDROID_RECOVERY__
@@ -34,10 +36,6 @@
constexpr bool kIsRecovery = false;
#endif
-std::string DeviceInfo::GetGsidDir() const {
- return "ota"s;
-}
-
std::string DeviceInfo::GetMetadataDir() const {
return "/metadata/ota"s;
}
@@ -100,6 +98,10 @@
return kIsRecovery;
}
+bool DeviceInfo::IsFirstStageInit() const {
+ return first_stage_init_;
+}
+
bool DeviceInfo::SetSlotAsUnbootable([[maybe_unused]] unsigned int slot) {
#ifdef LIBSNAPSHOT_USE_HAL
if (!EnsureBootHal()) {
@@ -120,5 +122,22 @@
#endif
}
+std::unique_ptr<android::fiemap::IImageManager> DeviceInfo::OpenImageManager() const {
+ return IDeviceInfo::OpenImageManager("ota");
+}
+
+std::unique_ptr<android::fiemap::IImageManager> ISnapshotManager::IDeviceInfo::OpenImageManager(
+ const std::string& gsid_dir) const {
+ if (IsRecovery() || IsFirstStageInit()) {
+ android::fiemap::ImageManager::DeviceInfo device_info = {
+ .is_recovery = {IsRecovery()},
+ };
+ return android::fiemap::ImageManager::Open(gsid_dir, device_info);
+ } else {
+ // For now, use a preset timeout.
+ return android::fiemap::IImageManager::Open(gsid_dir, 15000ms);
+ }
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/device_info.h b/fs_mgr/libsnapshot/device_info.h
index d8d3d91..7999c99 100644
--- a/fs_mgr/libsnapshot/device_info.h
+++ b/fs_mgr/libsnapshot/device_info.h
@@ -29,7 +29,6 @@
using MergeStatus = android::hardware::boot::V1_1::MergeStatus;
public:
- std::string GetGsidDir() const override;
std::string GetMetadataDir() const override;
std::string GetSlotSuffix() const override;
std::string GetOtherSlotSuffix() const override;
@@ -39,11 +38,16 @@
bool SetBootControlMergeStatus(MergeStatus status) override;
bool SetSlotAsUnbootable(unsigned int slot) override;
bool IsRecovery() const override;
+ std::unique_ptr<IImageManager> OpenImageManager() const override;
+ bool IsFirstStageInit() const override;
+
+ void set_first_stage_init(bool value) { first_stage_init_ = value; }
private:
bool EnsureBootHal();
android::fs_mgr::PartitionOpener opener_;
+ bool first_stage_init_ = false;
#ifdef LIBSNAPSHOT_USE_HAL
android::sp<android::hardware::boot::V1_1::IBootControl> boot_control_;
#endif
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 797b8ef..000e5e1 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -21,15 +21,22 @@
namespace snapshot {
static constexpr uint64_t kCowMagicNumber = 0x436f77634f572121ULL;
-static constexpr uint32_t kCowVersionMajor = 1;
+static constexpr uint32_t kCowVersionMajor = 2;
static constexpr uint32_t kCowVersionMinor = 0;
+static constexpr uint32_t kCowVersionManifest = 2;
+
+static constexpr uint32_t BLOCK_SZ = 4096;
+static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+
// This header appears as the first sequence of bytes in the COW. All fields
// in the layout are little-endian encoded. The on-disk layout is:
//
// +-----------------------+
// | Header (fixed) |
// +-----------------------+
+// | Scratch space |
+// +-----------------------+
// | Operation (variable) |
// | Data (variable) |
// +-----------------------+
@@ -68,6 +75,9 @@
// Tracks merge operations completed
uint64_t num_merge_ops;
+
+ // Scratch space used during merge
+ uint32_t buffer_size;
} __attribute__((packed));
// This structure is the same size of a normal Operation, but is repurposed for the footer.
@@ -144,11 +154,31 @@
static constexpr uint8_t kCowCompressGz = 1;
static constexpr uint8_t kCowCompressBrotli = 2;
+static constexpr uint8_t kCowReadAheadNotStarted = 0;
+static constexpr uint8_t kCowReadAheadInProgress = 1;
+static constexpr uint8_t kCowReadAheadDone = 2;
+
struct CowFooter {
CowFooterOperation op;
CowFooterData data;
} __attribute__((packed));
+struct ScratchMetadata {
+ // Block of data in the image that operation modifies
+ // and read-ahead thread stores the modified data
+ // in the scratch space
+ uint64_t new_block;
+ // Offset within the file to read the data
+ uint64_t file_offset;
+} __attribute__((packed));
+
+struct BufferState {
+ uint8_t read_ahead_state;
+} __attribute__((packed));
+
+// 2MB Scratch space used for read-ahead
+static constexpr uint64_t BUFFER_REGION_DEFAULT_SIZE = (1ULL << 21);
+
std::ostream& operator<<(std::ostream& os, CowOperation const& arg);
int64_t GetNextOpOffset(const CowOperation& 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 552fd96..9ebcfd9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -141,18 +141,21 @@
bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
- void UpdateMergeProgress(uint64_t merge_ops) { header_.num_merge_ops += merge_ops; }
-
void InitializeMerge();
void set_total_data_ops(uint64_t size) { total_data_ops_ = size; }
uint64_t total_data_ops() { return total_data_ops_; }
+ void set_copy_ops(uint64_t size) { copy_ops_ = size; }
+
+ uint64_t total_copy_ops() { return copy_ops_; }
+
void CloseCowFd() { owned_fd_ = {}; }
private:
bool ParseOps(std::optional<uint64_t> label);
+ uint64_t FindNumCopyops();
android::base::unique_fd owned_fd_;
android::base::borrowed_fd fd_;
@@ -162,6 +165,7 @@
std::optional<uint64_t> last_label_;
std::shared_ptr<std::vector<CowOperation>> ops_;
uint64_t total_data_ops_;
+ uint64_t copy_ops_;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 6ffd5d8..f43ea68 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -36,6 +36,8 @@
// Number of CowOperations in a cluster. 0 for no clustering. Cannot be 1.
uint32_t cluster_ops = 200;
+
+ bool scratch_space = true;
};
// Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -100,13 +102,12 @@
bool InitializeAppend(android::base::unique_fd&&, uint64_t label);
bool InitializeAppend(android::base::borrowed_fd fd, uint64_t label);
- void InitializeMerge(android::base::borrowed_fd fd, CowHeader* header);
- bool CommitMerge(int merged_ops);
-
bool Finalize() override;
uint64_t GetCowSize() override;
+ uint32_t GetCowVersion() { return header_.major_version; }
+
protected:
virtual bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
@@ -115,6 +116,7 @@
private:
bool EmitCluster();
+ bool EmitClusterIfNeeded();
void SetupHeaders();
bool ParseOptions();
bool OpenForWrite();
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
index ef9d648..573a85b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
@@ -22,7 +22,6 @@
class MockDeviceInfo : public SnapshotManager::IDeviceInfo {
public:
- MOCK_METHOD(std::string, GetGsidDir, (), (const, override));
MOCK_METHOD(std::string, GetMetadataDir, (), (const, override));
MOCK_METHOD(std::string, GetSlotSuffix, (), (const, override));
MOCK_METHOD(std::string, GetOtherSlotSuffix, (), (const, override));
@@ -32,6 +31,9 @@
MOCK_METHOD(bool, SetBootControlMergeStatus, (MergeStatus status), (override));
MOCK_METHOD(bool, SetSlotAsUnbootable, (unsigned int slot), (override));
MOCK_METHOD(bool, IsRecovery, (), (const, override));
+ MOCK_METHOD(bool, IsFirstStageInit, (), (const, override));
+ MOCK_METHOD(std::unique_ptr<android::fiemap::IImageManager>, OpenImageManager, (),
+ (const, override));
};
} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 1cb966b..94d5055 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -27,6 +27,7 @@
MOCK_METHOD(bool, CancelUpdate, (), (override));
MOCK_METHOD(bool, FinishedSnapshotWrites, (bool wipe), (override));
MOCK_METHOD(void, UpdateCowStats, (ISnapshotMergeStats * stats), (override));
+ MOCK_METHOD(MergeFailureCode, ReadMergeFailureCode, (), (override));
MOCK_METHOD(bool, InitiateMerge, (), (override));
MOCK_METHOD(UpdateState, ProcessUpdateState,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
index ac2c787..067f99c 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
@@ -34,11 +34,13 @@
MOCK_METHOD(void, set_estimated_cow_size_bytes, (uint64_t), (override));
MOCK_METHOD(void, set_boot_complete_time_ms, (uint32_t), (override));
MOCK_METHOD(void, set_boot_complete_to_merge_start_time_ms, (uint32_t), (override));
+ MOCK_METHOD(void, set_merge_failure_code, (MergeFailureCode), (override));
MOCK_METHOD(uint64_t, cow_file_size, (), (override));
MOCK_METHOD(uint64_t, total_cow_size_bytes, (), (override));
MOCK_METHOD(uint64_t, estimated_cow_size_bytes, (), (override));
MOCK_METHOD(uint32_t, boot_complete_time_ms, (), (override));
MOCK_METHOD(uint32_t, boot_complete_to_merge_start_time_ms, (), (override));
+ MOCK_METHOD(MergeFailureCode, merge_failure_code, (), (override));
MOCK_METHOD(std::unique_ptr<Result>, Finish, (), (override));
using ISnapshotMergeStats::Result;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 7e74fac..603e896 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -94,8 +94,9 @@
// Dependency injection for testing.
class IDeviceInfo {
public:
+ using IImageManager = android::fiemap::IImageManager;
+
virtual ~IDeviceInfo() {}
- virtual std::string GetGsidDir() const = 0;
virtual std::string GetMetadataDir() const = 0;
virtual std::string GetSlotSuffix() const = 0;
virtual std::string GetOtherSlotSuffix() const = 0;
@@ -107,6 +108,11 @@
virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
virtual bool IsRecovery() const = 0;
virtual bool IsTestDevice() const { return false; }
+ virtual bool IsFirstStageInit() const = 0;
+ virtual std::unique_ptr<IImageManager> OpenImageManager() const = 0;
+
+ // Helper method for implementing OpenImageManager.
+ std::unique_ptr<IImageManager> OpenImageManager(const std::string& gsid_dir) const;
};
virtual ~ISnapshotManager() = default;
@@ -167,6 +173,10 @@
virtual UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
const std::function<bool()>& before_cancel = {}) = 0;
+ // If ProcessUpdateState() returned MergeFailed, this returns the appropriate
+ // code. Otherwise, MergeFailureCode::Ok is returned.
+ virtual MergeFailureCode ReadMergeFailureCode() = 0;
+
// Find the status of the current update, if any.
//
// |progress| depends on the returned status:
@@ -332,6 +342,7 @@
bool CancelUpdate() override;
bool FinishedSnapshotWrites(bool wipe) override;
void UpdateCowStats(ISnapshotMergeStats* stats) override;
+ MergeFailureCode ReadMergeFailureCode() override;
bool InitiateMerge() override;
UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
const std::function<bool()>& before_cancel = {}) override;
@@ -381,11 +392,13 @@
FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
FRIEND_TEST(SnapshotTest, MapSnapshot);
FRIEND_TEST(SnapshotTest, Merge);
+ FRIEND_TEST(SnapshotTest, MergeFailureCode);
FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
+ FRIEND_TEST(SnapshotUpdateTest, DataWipeWithStaleSnapshots);
FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
@@ -413,7 +426,6 @@
bool EnsureSnapuserdConnected();
// Helpers for first-stage init.
- bool ForceLocalImageManager();
const std::unique_ptr<IDeviceInfo>& device() const { return device_; }
// Helper functions for tests.
@@ -493,6 +505,9 @@
// Unmap a COW image device previously mapped with MapCowImage().
bool UnmapCowImage(const std::string& name);
+ // Unmap a COW and remove it from a MetadataBuilder.
+ void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata);
+
// Unmap and remove all known snapshots.
bool RemoveAllSnapshots(LockedFile* lock);
@@ -529,7 +544,8 @@
// Interact with /metadata/ota/state.
UpdateState ReadUpdateState(LockedFile* file);
SnapshotUpdateStatus ReadSnapshotUpdateStatus(LockedFile* file);
- bool WriteUpdateState(LockedFile* file, UpdateState state);
+ bool WriteUpdateState(LockedFile* file, UpdateState state,
+ MergeFailureCode failure_code = MergeFailureCode::Ok);
bool WriteSnapshotUpdateStatus(LockedFile* file, const SnapshotUpdateStatus& status);
std::string GetStateFilePath() const;
@@ -538,12 +554,12 @@
std::string GetMergeStateFilePath() const;
// Helpers for merging.
- bool MergeSecondPhaseSnapshots(LockedFile* lock);
- bool SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
- bool RewriteSnapshotDeviceTable(const std::string& dm_name);
+ MergeFailureCode MergeSecondPhaseSnapshots(LockedFile* lock);
+ MergeFailureCode SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
+ MergeFailureCode RewriteSnapshotDeviceTable(const std::string& dm_name);
bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name);
void AcknowledgeMergeSuccess(LockedFile* lock);
- void AcknowledgeMergeFailure();
+ void AcknowledgeMergeFailure(MergeFailureCode failure_code);
MergePhase DecideMergePhase(const SnapshotStatus& status);
std::unique_ptr<LpMetadata> ReadCurrentMetadata();
@@ -570,14 +586,22 @@
const SnapshotStatus& status);
bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
+ struct MergeResult {
+ explicit MergeResult(UpdateState state,
+ MergeFailureCode failure_code = MergeFailureCode::Ok)
+ : state(state), failure_code(failure_code) {}
+ UpdateState state;
+ MergeFailureCode failure_code;
+ };
+
// Only the following UpdateStates are used here:
// UpdateState::Merging
// UpdateState::MergeCompleted
// UpdateState::MergeFailed
// UpdateState::MergeNeedsReboot
- UpdateState CheckMergeState(const std::function<bool()>& before_cancel);
- UpdateState CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel);
- UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name,
+ MergeResult CheckMergeState(const std::function<bool()>& before_cancel);
+ MergeResult CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel);
+ MergeResult CheckTargetMergeState(LockedFile* lock, const std::string& name,
const SnapshotUpdateStatus& update_status);
// Interact with status files under /metadata/ota/snapshots.
@@ -738,11 +762,14 @@
// Helper of UpdateUsesCompression
bool UpdateUsesCompression(LockedFile* lock);
+ // Wrapper around libdm, with diagnostics.
+ bool DeleteDeviceIfExists(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms = {});
+
std::string gsid_dir_;
std::string metadata_dir_;
std::unique_ptr<IDeviceInfo> device_;
std::unique_ptr<IImageManager> images_;
- bool has_local_image_manager_ = false;
bool use_first_stage_snapuserd_ = false;
bool in_factory_data_reset_ = false;
std::function<bool(const std::string&)> uevent_regen_callback_;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index e617d7a..4ce5077 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -34,11 +34,13 @@
virtual void set_estimated_cow_size_bytes(uint64_t bytes) = 0;
virtual void set_boot_complete_time_ms(uint32_t ms) = 0;
virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0;
+ virtual void set_merge_failure_code(MergeFailureCode code) = 0;
virtual uint64_t cow_file_size() = 0;
virtual uint64_t total_cow_size_bytes() = 0;
virtual uint64_t estimated_cow_size_bytes() = 0;
virtual uint32_t boot_complete_time_ms() = 0;
virtual uint32_t boot_complete_to_merge_start_time_ms() = 0;
+ virtual MergeFailureCode merge_failure_code() = 0;
// Called when merge ends. Properly clean up permanent storage.
class Result {
@@ -70,6 +72,8 @@
uint32_t boot_complete_time_ms() override;
void set_boot_complete_to_merge_start_time_ms(uint32_t ms) override;
uint32_t boot_complete_to_merge_start_time_ms() override;
+ void set_merge_failure_code(MergeFailureCode code) override;
+ MergeFailureCode merge_failure_code() override;
std::unique_ptr<Result> Finish() override;
private:
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index cc75db8..a7cd939 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -29,6 +29,7 @@
bool CancelUpdate() override;
bool FinishedSnapshotWrites(bool wipe) override;
void UpdateCowStats(ISnapshotMergeStats* stats) override;
+ MergeFailureCode ReadMergeFailureCode() override;
bool InitiateMerge() override;
UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
const std::function<bool()>& before_cancel = {}) override;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
index 2b6c8ef..6bb7a39 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
@@ -47,9 +47,6 @@
static constexpr uint32_t CHUNK_SIZE = 8;
static constexpr uint32_t CHUNK_SHIFT = (__builtin_ffs(CHUNK_SIZE) - 1);
-static constexpr uint32_t BLOCK_SZ = 4096;
-static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
-
#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
// This structure represents the kernel COW header.
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index b038527..4e7ccf1 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -77,7 +77,6 @@
: TestDeviceInfo(fake_super) {
set_slot_suffix(slot_suffix);
}
- std::string GetGsidDir() const override { return "ota/test"s; }
std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
std::string GetSlotSuffix() const override { return slot_suffix_; }
std::string GetOtherSlotSuffix() const override { return slot_suffix_ == "_a" ? "_b" : "_a"; }
@@ -96,6 +95,10 @@
return true;
}
bool IsTestDevice() const override { return true; }
+ bool IsFirstStageInit() const override { return first_stage_init_; }
+ std::unique_ptr<IImageManager> OpenImageManager() const override {
+ return IDeviceInfo::OpenImageManager("ota/test");
+ }
bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; }
@@ -104,6 +107,7 @@
opener_ = std::make_unique<TestPartitionOpener>(path);
}
void set_recovery(bool value) { recovery_ = value; }
+ void set_first_stage_init(bool value) { first_stage_init_ = value; }
MergeStatus merge_status() const { return merge_status_; }
private:
@@ -111,6 +115,7 @@
std::unique_ptr<TestPartitionOpener> opener_;
MergeStatus merge_status_;
bool recovery_ = false;
+ bool first_stage_init_ = false;
std::unordered_set<uint32_t> unbootable_slots_;
};
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index 453b5c6..4a84fba 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -38,7 +38,8 @@
static void usage(void) {
LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
LOG(ERROR) << "\t -s Run Silent";
- LOG(ERROR) << "\t -d Attempt to decompress\n";
+ LOG(ERROR) << "\t -d Attempt to decompress";
+ LOG(ERROR) << "\t -b Show data for failed decompress\n";
}
// Sink that always appends to the end of a string.
@@ -59,7 +60,25 @@
std::string stream_;
};
-static bool Inspect(const std::string& path, bool silent, bool decompress) {
+static void ShowBad(CowReader& reader, const struct CowOperation& op) {
+ size_t count;
+ auto buffer = std::make_unique<uint8_t[]>(op.data_length);
+
+ if (!reader.GetRawBytes(op.source, buffer.get(), op.data_length, &count)) {
+ std::cerr << "Failed to read at all!\n";
+ } else {
+ std::cout << "The Block data is:\n";
+ for (int i = 0; i < op.data_length; i++) {
+ std::cout << std::hex << (int)buffer[i];
+ }
+ std::cout << std::dec << "\n\n";
+ if (op.data_length >= sizeof(CowOperation)) {
+ std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n";
+ }
+ }
+}
+
+static bool Inspect(const std::string& path, bool silent, bool decompress, bool show_bad) {
android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
if (fd < 0) {
PLOG(ERROR) << "open failed: " << path;
@@ -107,6 +126,7 @@
if (!reader.ReadData(op, &sink)) {
std::cerr << "Failed to decompress for :" << op << "\n";
success = false;
+ if (show_bad) ShowBad(reader, op);
}
sink.Reset();
}
@@ -124,7 +144,8 @@
int ch;
bool silent = false;
bool decompress = false;
- while ((ch = getopt(argc, argv, "sd")) != -1) {
+ bool show_bad = false;
+ while ((ch = getopt(argc, argv, "sdb")) != -1) {
switch (ch) {
case 's':
silent = true;
@@ -132,6 +153,9 @@
case 'd':
decompress = true;
break;
+ case 'b':
+ show_bad = true;
+ break;
default:
android::snapshot::usage();
}
@@ -143,7 +167,7 @@
return 1;
}
- if (!android::snapshot::Inspect(argv[optind], silent, decompress)) {
+ if (!android::snapshot::Inspect(argv[optind], silent, decompress, show_bad)) {
return 1;
}
return 0;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index a0a1e4f..e2c03ae 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -21,6 +21,7 @@
#include <sys/types.h>
#include <sys/unistd.h>
+#include <filesystem>
#include <optional>
#include <thread>
#include <unordered_set>
@@ -94,18 +95,16 @@
if (!info) {
info = new DeviceInfo();
}
- auto sm = std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
- if (info->IsRecovery()) {
- sm->ForceLocalImageManager();
- }
- return sm;
+ return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
}
std::unique_ptr<SnapshotManager> SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) {
- auto sm = New(info);
- if (!sm || !sm->ForceLocalImageManager()) {
- return nullptr;
+ if (!info) {
+ DeviceInfo* impl = new DeviceInfo();
+ impl->set_first_stage_init(true);
+ info = impl;
}
+ auto sm = New(info);
// The first-stage version of snapuserd is explicitly started by init. Do
// not attempt to using it during tests (which run in normal AOSP).
@@ -116,7 +115,6 @@
}
SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
- gsid_dir_ = device_->GetGsidDir();
metadata_dir_ = device_->GetMetadataDir();
}
@@ -537,9 +535,7 @@
bool ok;
std::string cow_dev;
- if (has_local_image_manager_) {
- // If we forced a local image manager, it means we don't have binder,
- // which means first-stage init. We must use device-mapper.
+ if (device_->IsRecovery() || device_->IsFirstStageInit()) {
const auto& opener = device_->GetPartitionOpener();
ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev);
} else {
@@ -587,8 +583,7 @@
bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
CHECK(lock);
- auto& dm = DeviceMapper::Instance();
- if (!dm.DeleteDeviceIfExists(name)) {
+ if (!DeleteDeviceIfExists(name)) {
LOG(ERROR) << "Could not delete snapshot device: " << name;
return false;
}
@@ -746,32 +741,35 @@
return false;
}
- bool rewrote_all = true;
+ auto reported_code = MergeFailureCode::Ok;
for (const auto& snapshot : *merge_group) {
// If this fails, we have no choice but to continue. Everything must
// be merged. This is not an ideal state to be in, but it is safe,
// because we the next boot will try again.
- if (!SwitchSnapshotToMerge(lock.get(), snapshot)) {
+ auto code = SwitchSnapshotToMerge(lock.get(), snapshot);
+ if (code != MergeFailureCode::Ok) {
LOG(ERROR) << "Failed to switch snapshot to a merge target: " << snapshot;
- rewrote_all = false;
+ if (reported_code == MergeFailureCode::Ok) {
+ reported_code = code;
+ }
}
}
// If we couldn't switch everything to a merge target, pre-emptively mark
// this merge as failed. It will get acknowledged when WaitForMerge() is
// called.
- if (!rewrote_all) {
- WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+ if (reported_code != MergeFailureCode::Ok) {
+ WriteUpdateState(lock.get(), UpdateState::MergeFailed, reported_code);
}
// Return true no matter what, because a merge was initiated.
return true;
}
-bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
+MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, name, &status)) {
- return false;
+ return MergeFailureCode::ReadStatus;
}
if (status.state() != SnapshotState::CREATED) {
LOG(WARNING) << "Snapshot " << name
@@ -780,8 +778,8 @@
// After this, we return true because we technically did switch to a merge
// target. Everything else we do here is just informational.
- if (!RewriteSnapshotDeviceTable(name)) {
- return false;
+ if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
+ return code;
}
status.set_state(SnapshotState::MERGING);
@@ -795,26 +793,26 @@
if (!WriteSnapshotStatus(lock, status)) {
LOG(ERROR) << "Could not update status file for snapshot: " << name;
}
- return true;
+ return MergeFailureCode::Ok;
}
-bool SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
+MergeFailureCode SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
auto& dm = DeviceMapper::Instance();
std::vector<DeviceMapper::TargetInfo> old_targets;
if (!dm.GetTableInfo(name, &old_targets)) {
LOG(ERROR) << "Could not read snapshot device table: " << name;
- return false;
+ return MergeFailureCode::GetTableInfo;
}
if (old_targets.size() != 1 || DeviceMapper::GetTargetType(old_targets[0].spec) != "snapshot") {
LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << name;
- return false;
+ return MergeFailureCode::UnknownTable;
}
std::string base_device, cow_device;
if (!DmTargetSnapshot::GetDevicesFromParams(old_targets[0].data, &base_device, &cow_device)) {
LOG(ERROR) << "Could not derive underlying devices for snapshot: " << name;
- return false;
+ return MergeFailureCode::GetTableParams;
}
DmTable table;
@@ -822,10 +820,10 @@
SnapshotStorageMode::Merge, kSnapshotChunkSize);
if (!dm.LoadTableAndActivate(name, table)) {
LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << name;
- return false;
+ return MergeFailureCode::ActivateNewTable;
}
LOG(INFO) << "Successfully switched snapshot device to a merge target: " << name;
- return true;
+ return MergeFailureCode::Ok;
}
enum class TableQuery {
@@ -897,20 +895,20 @@
UpdateState SnapshotManager::ProcessUpdateState(const std::function<bool()>& callback,
const std::function<bool()>& before_cancel) {
while (true) {
- UpdateState state = CheckMergeState(before_cancel);
- LOG(INFO) << "ProcessUpdateState handling state: " << state;
+ auto result = CheckMergeState(before_cancel);
+ LOG(INFO) << "ProcessUpdateState handling state: " << result.state;
- if (state == UpdateState::MergeFailed) {
- AcknowledgeMergeFailure();
+ if (result.state == UpdateState::MergeFailed) {
+ AcknowledgeMergeFailure(result.failure_code);
}
- if (state != UpdateState::Merging) {
+ if (result.state != UpdateState::Merging) {
// Either there is no merge, or the merge was finished, so no need
// to keep waiting.
- return state;
+ return result.state;
}
if (callback && !callback()) {
- return state;
+ return result.state;
}
// This wait is not super time sensitive, so we have a relatively
@@ -919,36 +917,36 @@
}
}
-UpdateState SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) {
+auto SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) -> MergeResult {
auto lock = LockExclusive();
if (!lock) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::AcquireLock);
}
- UpdateState state = CheckMergeState(lock.get(), before_cancel);
- LOG(INFO) << "CheckMergeState for snapshots returned: " << state;
+ auto result = CheckMergeState(lock.get(), before_cancel);
+ LOG(INFO) << "CheckMergeState for snapshots returned: " << result.state;
- if (state == UpdateState::MergeCompleted) {
+ if (result.state == UpdateState::MergeCompleted) {
// Do this inside the same lock. Failures get acknowledged without the
// lock, because flock() might have failed.
AcknowledgeMergeSuccess(lock.get());
- } else if (state == UpdateState::Cancelled) {
+ } else if (result.state == UpdateState::Cancelled) {
if (!device_->IsRecovery() && !RemoveAllUpdateState(lock.get(), before_cancel)) {
LOG(ERROR) << "Failed to remove all update state after acknowleding cancelled update.";
}
}
- return state;
+ return result;
}
-UpdateState SnapshotManager::CheckMergeState(LockedFile* lock,
- const std::function<bool()>& before_cancel) {
+auto SnapshotManager::CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel)
+ -> MergeResult {
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
switch (update_status.state()) {
case UpdateState::None:
case UpdateState::MergeCompleted:
// Harmless races are allowed between two callers of WaitForMerge,
// so in both of these cases we just propagate the state.
- return update_status.state();
+ return MergeResult(update_status.state());
case UpdateState::Merging:
case UpdateState::MergeNeedsReboot:
@@ -963,26 +961,26 @@
// via the merge poll below, but if we never started a merge, we
// need to also check here.
if (HandleCancelledUpdate(lock, before_cancel)) {
- return UpdateState::Cancelled;
+ return MergeResult(UpdateState::Cancelled);
}
- return update_status.state();
+ return MergeResult(update_status.state());
default:
- return update_status.state();
+ return MergeResult(update_status.state());
}
std::vector<std::string> snapshots;
if (!ListSnapshots(lock, &snapshots)) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ListSnapshots);
}
auto other_suffix = device_->GetOtherSlotSuffix();
bool cancelled = false;
- bool failed = false;
bool merging = false;
bool needs_reboot = false;
bool wrong_phase = false;
+ MergeFailureCode failure_code = MergeFailureCode::Ok;
for (const auto& snapshot : snapshots) {
if (android::base::EndsWith(snapshot, other_suffix)) {
// This will have triggered an error message in InitiateMerge already.
@@ -990,12 +988,15 @@
continue;
}
- UpdateState snapshot_state = CheckTargetMergeState(lock, snapshot, update_status);
- LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << snapshot_state;
+ auto result = CheckTargetMergeState(lock, snapshot, update_status);
+ LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << result.state;
- switch (snapshot_state) {
+ switch (result.state) {
case UpdateState::MergeFailed:
- failed = true;
+ // Take the first failure code in case other failures compound.
+ if (failure_code == MergeFailureCode::Ok) {
+ failure_code = result.failure_code;
+ }
break;
case UpdateState::Merging:
merging = true;
@@ -1013,8 +1014,10 @@
break;
default:
LOG(ERROR) << "Unknown merge status for \"" << snapshot << "\": "
- << "\"" << snapshot_state << "\"";
- failed = true;
+ << "\"" << result.state << "\"";
+ if (failure_code == MergeFailureCode::Ok) {
+ failure_code = MergeFailureCode::UnexpectedMergeState;
+ }
break;
}
}
@@ -1023,24 +1026,25 @@
// Note that we handle "Merging" before we handle anything else. We
// want to poll until *nothing* is merging if we can, so everything has
// a chance to get marked as completed or failed.
- return UpdateState::Merging;
+ return MergeResult(UpdateState::Merging);
}
- if (failed) {
+ if (failure_code != MergeFailureCode::Ok) {
// Note: since there are many drop-out cases for failure, we acknowledge
// it in WaitForMerge rather than here and elsewhere.
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, failure_code);
}
if (wrong_phase) {
// If we got here, no other partitions are being merged, and nothing
// failed to merge. It's safe to move to the next merge phase.
- if (!MergeSecondPhaseSnapshots(lock)) {
- return UpdateState::MergeFailed;
+ auto code = MergeSecondPhaseSnapshots(lock);
+ if (code != MergeFailureCode::Ok) {
+ return MergeResult(UpdateState::MergeFailed, code);
}
- return UpdateState::Merging;
+ return MergeResult(UpdateState::Merging);
}
if (needs_reboot) {
WriteUpdateState(lock, UpdateState::MergeNeedsReboot);
- return UpdateState::MergeNeedsReboot;
+ return MergeResult(UpdateState::MergeNeedsReboot);
}
if (cancelled) {
// This is an edge case, that we handle as correctly as we sensibly can.
@@ -1048,16 +1052,17 @@
// removed the snapshot as a result. The exact state of the update is
// undefined now, but this can only happen on an unlocked device where
// partitions can be flashed without wiping userdata.
- return UpdateState::Cancelled;
+ return MergeResult(UpdateState::Cancelled);
}
- return UpdateState::MergeCompleted;
+ return MergeResult(UpdateState::MergeCompleted);
}
-UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name,
- const SnapshotUpdateStatus& update_status) {
+auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name,
+ const SnapshotUpdateStatus& update_status)
+ -> MergeResult {
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock, name, &snapshot_status)) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ReadStatus);
}
std::unique_ptr<LpMetadata> current_metadata;
@@ -1070,7 +1075,7 @@
if (!current_metadata ||
GetMetadataPartitionState(*current_metadata, name) != MetadataPartitionState::Updated) {
DeleteSnapshot(lock, name);
- return UpdateState::Cancelled;
+ return MergeResult(UpdateState::Cancelled);
}
// During a check, we decided the merge was complete, but we were unable to
@@ -1081,11 +1086,11 @@
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
// NB: It's okay if this fails now, we gave cleanup our best effort.
OnSnapshotMergeComplete(lock, name, snapshot_status);
- return UpdateState::MergeCompleted;
+ return MergeResult(UpdateState::MergeCompleted);
}
LOG(ERROR) << "Expected snapshot or snapshot-merge for device: " << name;
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
// This check is expensive so it is only enabled for debugging.
@@ -1095,29 +1100,30 @@
std::string target_type;
DmTargetSnapshot::Status status;
if (!QuerySnapshotStatus(name, &target_type, &status)) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (target_type == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
- return UpdateState::None;
+ return MergeResult(UpdateState::None);
}
if (target_type != "snapshot-merge") {
// We can get here if we failed to rewrite the target type in
// InitiateMerge(). If we failed to create the target in first-stage
// init, boot would not succeed.
LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
}
// These two values are equal when merging is complete.
if (status.sectors_allocated != status.metadata_sectors) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed,
+ MergeFailureCode::UnmergedSectorsAfterCompletion);
}
- return UpdateState::Merging;
+ return MergeResult(UpdateState::Merging);
}
// Merging is done. First, update the status file to indicate the merge
@@ -1130,18 +1136,18 @@
// snapshot device for this partition.
snapshot_status.set_state(SnapshotState::MERGE_COMPLETED);
if (!WriteSnapshotStatus(lock, snapshot_status)) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::WriteStatus);
}
if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
- return UpdateState::MergeNeedsReboot;
+ return MergeResult(UpdateState::MergeNeedsReboot);
}
- return UpdateState::MergeCompleted;
+ return MergeResult(UpdateState::MergeCompleted, MergeFailureCode::Ok);
}
-bool SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
+MergeFailureCode SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
std::vector<std::string> snapshots;
if (!ListSnapshots(lock, &snapshots)) {
- return UpdateState::MergeFailed;
+ return MergeFailureCode::ListSnapshots;
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
@@ -1150,24 +1156,27 @@
update_status.set_merge_phase(MergePhase::SECOND_PHASE);
if (!WriteSnapshotUpdateStatus(lock, update_status)) {
- return false;
+ return MergeFailureCode::WriteStatus;
}
- bool rewrote_all = true;
+ MergeFailureCode result = MergeFailureCode::Ok;
for (const auto& snapshot : snapshots) {
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock, snapshot, &snapshot_status)) {
- return UpdateState::MergeFailed;
+ return MergeFailureCode::ReadStatus;
}
if (DecideMergePhase(snapshot_status) != MergePhase::SECOND_PHASE) {
continue;
}
- if (!SwitchSnapshotToMerge(lock, snapshot)) {
+ auto code = SwitchSnapshotToMerge(lock, snapshot);
+ if (code != MergeFailureCode::Ok) {
LOG(ERROR) << "Failed to switch snapshot to a second-phase merge target: " << snapshot;
- rewrote_all = false;
+ if (result == MergeFailureCode::Ok) {
+ result = code;
+ }
}
}
- return rewrote_all;
+ return result;
}
std::string SnapshotManager::GetSnapshotBootIndicatorPath() {
@@ -1199,7 +1208,7 @@
RemoveAllUpdateState(lock);
}
-void SnapshotManager::AcknowledgeMergeFailure() {
+void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) {
// Log first, so worst case, we always have a record of why the calls below
// were being made.
LOG(ERROR) << "Merge could not be completed and will be marked as failed.";
@@ -1216,7 +1225,7 @@
return;
}
- WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+ WriteUpdateState(lock.get(), UpdateState::MergeFailed, failure_code);
}
bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
@@ -1252,25 +1261,6 @@
return true;
}
-static bool DeleteDmDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms) {
- auto start = std::chrono::steady_clock::now();
- auto& dm = DeviceMapper::Instance();
- while (true) {
- if (dm.DeleteDeviceIfExists(name)) {
- break;
- }
- auto now = std::chrono::steady_clock::now();
- auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
- if (elapsed >= timeout_ms) {
- LOG(ERROR) << "DeleteDevice timeout: " << name;
- return false;
- }
- std::this_thread::sleep_for(400ms);
- }
-
- return true;
-}
-
bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
const SnapshotStatus& status) {
auto& dm = DeviceMapper::Instance();
@@ -1326,11 +1316,11 @@
UnmapDmUserDevice(name);
}
auto base_name = GetBaseDeviceName(name);
- if (!dm.DeleteDeviceIfExists(base_name)) {
+ if (!DeleteDeviceIfExists(base_name)) {
LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
}
- if (!DeleteDmDevice(GetSourceDeviceName(name), 4000ms)) {
+ if (!DeleteDeviceIfExists(GetSourceDeviceName(name), 4000ms)) {
LOG(ERROR) << "Unable to delete source device for snapshot: " << GetSourceDeviceName(name);
}
@@ -1841,6 +1831,10 @@
return false;
}
+ if (!EnsureImageManager()) {
+ return false;
+ }
+
for (const auto& partition : metadata->partitions) {
if (GetPartitionGroupName(metadata->groups[partition.group_index]) == kCowGroupName) {
LOG(INFO) << "Skip mapping partition " << GetPartitionName(partition) << " in group "
@@ -2083,15 +2077,14 @@
return false;
}
- auto& dm = DeviceMapper::Instance();
auto base_name = GetBaseDeviceName(target_partition_name);
- if (!dm.DeleteDeviceIfExists(base_name)) {
+ if (!DeleteDeviceIfExists(base_name)) {
LOG(ERROR) << "Cannot delete base device: " << base_name;
return false;
}
auto source_name = GetSourceDeviceName(target_partition_name);
- if (!dm.DeleteDeviceIfExists(source_name)) {
+ if (!DeleteDeviceIfExists(source_name)) {
LOG(ERROR) << "Cannot delete source device: " << source_name;
return false;
}
@@ -2181,7 +2174,7 @@
return false;
}
- if (!DeleteDmDevice(GetCowName(name), 4000ms)) {
+ if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
LOG(ERROR) << "Cannot unmap: " << GetCowName(name);
return false;
}
@@ -2202,7 +2195,7 @@
return true;
}
- if (!dm.DeleteDeviceIfExists(dm_user_name)) {
+ if (!DeleteDeviceIfExists(dm_user_name)) {
LOG(ERROR) << "Cannot unmap " << dm_user_name;
return false;
}
@@ -2434,10 +2427,15 @@
return status;
}
-bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state) {
+bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state,
+ MergeFailureCode failure_code) {
SnapshotUpdateStatus status;
status.set_state(state);
+ if (state == UpdateState::MergeFailed) {
+ status.set_merge_failure_code(failure_code);
+ }
+
// If we're transitioning between two valid states (eg, we're not beginning
// or ending an OTA), then make sure to propagate the compression bit.
if (!(state == UpdateState::Initiated || state == UpdateState::None)) {
@@ -2557,8 +2555,7 @@
bool SnapshotManager::EnsureImageManager() {
if (images_) return true;
- // For now, use a preset timeout.
- images_ = android::fiemap::IImageManager::Open(gsid_dir_, 15000ms);
+ images_ = device_->OpenImageManager();
if (!images_) {
LOG(ERROR) << "Could not open ImageManager";
return false;
@@ -2583,21 +2580,10 @@
return true;
}
-bool SnapshotManager::ForceLocalImageManager() {
- images_ = android::fiemap::ImageManager::Open(gsid_dir_);
- if (!images_) {
- LOG(ERROR) << "Could not open ImageManager";
- return false;
- }
- has_local_image_manager_ = true;
- return true;
-}
-
-static void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
- auto& dm = DeviceMapper::Instance();
+void SnapshotManager::UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
std::vector<std::string> to_delete;
for (auto* existing_cow_partition : current_metadata->ListPartitionsInGroup(kCowGroupName)) {
- if (!dm.DeleteDeviceIfExists(existing_cow_partition->name())) {
+ if (!DeleteDeviceIfExists(existing_cow_partition->name())) {
LOG(WARNING) << existing_cow_partition->name()
<< " cannot be unmapped and its space cannot be reclaimed";
continue;
@@ -2694,8 +2680,18 @@
AutoDeviceList created_devices;
const auto& dap_metadata = manifest.dynamic_partition_metadata();
- bool use_compression =
- IsCompressionEnabled() && dap_metadata.vabc_enabled() && !device_->IsRecovery();
+ CowOptions options;
+ CowWriter writer(options);
+ bool cow_format_support = true;
+ if (dap_metadata.cow_version() < writer.GetCowVersion()) {
+ cow_format_support = false;
+ }
+
+ LOG(INFO) << " dap_metadata.cow_version(): " << dap_metadata.cow_version()
+ << " writer.GetCowVersion(): " << writer.GetCowVersion();
+
+ bool use_compression = IsCompressionEnabled() && dap_metadata.vabc_enabled() &&
+ !device_->IsRecovery() && cow_format_support;
std::string compression_algorithm;
if (use_compression) {
@@ -2962,7 +2958,13 @@
return Return::Error();
}
- CowWriter writer(CowOptions{.compression = it->second.compression_algorithm()});
+ CowOptions options;
+ if (device()->IsTestDevice()) {
+ options.scratch_space = false;
+ }
+ options.compression = it->second.compression_algorithm();
+
+ CowWriter writer(options);
if (!writer.Initialize(fd) || !writer.Finalize()) {
LOG(ERROR) << "Could not initialize COW device for " << target_partition->name();
return Return::Error();
@@ -3071,6 +3073,10 @@
CowOptions cow_options;
cow_options.compression = status.compression_algorithm();
cow_options.max_blocks = {status.device_size() / cow_options.block_size};
+ // Disable scratch space for vts tests
+ if (device()->IsTestDevice()) {
+ cow_options.scratch_space = false;
+ }
// Currently we don't support partial snapshots, since partition_cow_creator
// never creates this scenario.
@@ -3626,5 +3632,82 @@
stats->set_estimated_cow_size_bytes(estimated_cow_size);
}
+bool SnapshotManager::DeleteDeviceIfExists(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms) {
+ auto& dm = DeviceMapper::Instance();
+ auto start = std::chrono::steady_clock::now();
+ while (true) {
+ if (dm.DeleteDeviceIfExists(name)) {
+ return true;
+ }
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+ if (elapsed >= timeout_ms) {
+ break;
+ }
+ std::this_thread::sleep_for(400ms);
+ }
+
+ // Try to diagnose why this failed. First get the actual device path.
+ std::string full_path;
+ if (!dm.GetDmDevicePathByName(name, &full_path)) {
+ LOG(ERROR) << "Unable to diagnose DM_DEV_REMOVE failure.";
+ return false;
+ }
+
+ // Check for child dm-devices.
+ std::string block_name = android::base::Basename(full_path);
+ std::string sysfs_holders = "/sys/class/block/" + block_name + "/holders";
+
+ std::error_code ec;
+ std::filesystem::directory_iterator dir_iter(sysfs_holders, ec);
+ if (auto begin = std::filesystem::begin(dir_iter); begin != std::filesystem::end(dir_iter)) {
+ LOG(ERROR) << "Child device-mapper device still mapped: " << begin->path();
+ return false;
+ }
+
+ // Check for mounted partitions.
+ android::fs_mgr::Fstab fstab;
+ android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab);
+ for (const auto& entry : fstab) {
+ if (android::base::Basename(entry.blk_device) == block_name) {
+ LOG(ERROR) << "Partition still mounted: " << entry.mount_point;
+ return false;
+ }
+ }
+
+ // Check for detached mounted partitions.
+ for (const auto& fs : std::filesystem::directory_iterator("/sys/fs", ec)) {
+ std::string fs_type = android::base::Basename(fs.path().c_str());
+ if (!(fs_type == "ext4" || fs_type == "f2fs")) {
+ continue;
+ }
+
+ std::string path = fs.path().c_str() + "/"s + block_name;
+ if (access(path.c_str(), F_OK) == 0) {
+ LOG(ERROR) << "Block device was lazily unmounted and is still in-use: " << full_path
+ << "; possibly open file descriptor or attached loop device.";
+ return false;
+ }
+ }
+
+ LOG(ERROR) << "Device-mapper device " << name << "(" << full_path << ")"
+ << " still in use."
+ << " Probably a file descriptor was leaked or held open, or a loop device is"
+ << " attached.";
+ return false;
+}
+
+MergeFailureCode SnapshotManager::ReadMergeFailureCode() {
+ auto lock = LockExclusive();
+ if (!lock) return MergeFailureCode::AcquireLock;
+
+ SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
+ if (status.state() != UpdateState::MergeFailed) {
+ return MergeFailureCode::Ok;
+ }
+ return status.merge_failure_code();
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index 8926535..0096f85 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -381,8 +381,9 @@
CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)});
}
-std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager(
- const std::string& metadata_dir, const std::string& data_dir) {
+std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager() {
+ auto metadata_dir = fake_root_->tmp_path() + "/images_manager_metadata";
+ auto data_dir = fake_data_mount_point_ + "/image_manager_data";
PCHECK(Mkdir(metadata_dir));
PCHECK(Mkdir(data_dir));
return SnapshotFuzzImageManager::Open(metadata_dir, data_dir);
@@ -428,13 +429,9 @@
PCHECK(Mkdir(metadata_dir + "/snapshots"));
}
- ret.device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(),
+ ret.device_info = new SnapshotFuzzDeviceInfo(this, data.device_info_data(),
std::move(partition_opener), metadata_dir);
auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */);
- snapshot->images_ =
- CheckCreateFakeImageManager(fake_root_->tmp_path() + "/images_manager_metadata",
- fake_data_mount_point_ + "/image_manager_data");
- snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager();
ret.snapshot = std::move(snapshot);
return ret;
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index 5319e69..3ed27c8 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -65,6 +65,8 @@
// ISnapshotManager.
SnapshotTestModule CheckCreateSnapshotManager(const SnapshotFuzzData& data);
+ std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager();
+
// Return path to super partition.
const std::string& super() const;
@@ -79,8 +81,6 @@
std::string fake_data_block_device_;
std::unique_ptr<AutoDevice> mounted_data_;
- static std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager(
- const std::string& metadata_dir, const std::string& data_dir);
static std::unique_ptr<AutoDevice> CheckMapImage(const std::string& fake_persist_path,
uint64_t size,
android::dm::LoopControl* control,
@@ -95,15 +95,15 @@
class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
public:
// Client is responsible for maintaining the lifetime of |data|.
- SnapshotFuzzDeviceInfo(const FuzzDeviceInfoData& data,
+ SnapshotFuzzDeviceInfo(SnapshotFuzzEnv* env, const FuzzDeviceInfoData& data,
std::unique_ptr<TestPartitionOpener>&& partition_opener,
const std::string& metadata_dir)
- : data_(&data),
+ : env_(env),
+ data_(&data),
partition_opener_(std::move(partition_opener)),
metadata_dir_(metadata_dir) {}
// Following APIs are mocked.
- std::string GetGsidDir() const override { return "fuzz_ota"; }
std::string GetMetadataDir() const override { return metadata_dir_; }
std::string GetSuperDevice(uint32_t) const override {
// TestPartitionOpener can recognize this.
@@ -124,10 +124,15 @@
return data_->allow_set_slot_as_unbootable();
}
bool IsRecovery() const override { return data_->is_recovery(); }
+ bool IsFirstStageInit() const override { return false; }
+ std::unique_ptr<IImageManager> OpenImageManager() const {
+ return env_->CheckCreateFakeImageManager();
+ }
void SwitchSlot() { switched_slot_ = !switched_slot_; }
private:
+ SnapshotFuzzEnv* env_;
const FuzzDeviceInfoData* data_;
std::unique_ptr<TestPartitionOpener> partition_opener_;
std::string metadata_dir_;
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 4202d22..9373059 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -150,6 +150,7 @@
CowOptions options;
options.compression = "gz";
options.max_blocks = {kBlockCount};
+ options.scratch_space = false;
unique_fd cow_fd(dup(cow_->fd));
ASSERT_GE(cow_fd, 0);
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 7fcfcea..4a93d65 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -130,6 +130,14 @@
return report_.boot_complete_to_merge_start_time_ms();
}
+void SnapshotMergeStats::set_merge_failure_code(MergeFailureCode code) {
+ report_.set_merge_failure_code(code);
+}
+
+MergeFailureCode SnapshotMergeStats::merge_failure_code() {
+ return report_.merge_failure_code();
+}
+
class SnapshotMergeStatsResultImpl : public SnapshotMergeStats::Result {
public:
SnapshotMergeStatsResultImpl(const SnapshotMergeReport& report,
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 43825cc..1a9eda5 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -135,6 +135,8 @@
uint32_t boot_complete_time_ms() override { return 0; }
void set_boot_complete_to_merge_start_time_ms(uint32_t) override {}
uint32_t boot_complete_to_merge_start_time_ms() override { return 0; }
+ void set_merge_failure_code(MergeFailureCode) override {}
+ MergeFailureCode merge_failure_code() { return MergeFailureCode::Ok; }
};
ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -163,4 +165,9 @@
LOG(ERROR) << __FUNCTION__ << " should never be called.";
}
+auto SnapshotManagerStub::ReadMergeFailureCode() -> MergeFailureCode {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return MergeFailureCode::Ok;
+}
+
} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 8fae00b..6018643 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <libsnapshot/cow_format.h>
#include <libsnapshot/snapshot.h>
#include <fcntl.h>
@@ -327,6 +328,7 @@
auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata();
dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+ dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
auto group = dynamic_partition_metadata->add_groups();
group->set_name("group");
@@ -401,6 +403,7 @@
}
std::unique_ptr<SnapshotManager> NewManagerForFirstStageMount(TestDeviceInfo* info) {
+ info->set_first_stage_init(true);
auto init = SnapshotManager::NewForFirstStageMount(info);
if (!init) {
return nullptr;
@@ -676,6 +679,18 @@
ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
}
+TEST_F(SnapshotTest, MergeFailureCode) {
+ ASSERT_TRUE(AcquireLock());
+
+ ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed,
+ MergeFailureCode::ListSnapshots));
+ ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
+
+ SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get());
+ ASSERT_EQ(status.state(), UpdateState::MergeFailed);
+ ASSERT_EQ(status.merge_failure_code(), MergeFailureCode::ListSnapshots);
+}
+
enum class Request { UNKNOWN, LOCK_SHARED, LOCK_EXCLUSIVE, UNLOCK, EXIT };
std::ostream& operator<<(std::ostream& os, Request request) {
switch (request) {
@@ -841,6 +856,7 @@
auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+ dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
// Create a fake update package metadata.
// Not using full name "system", "vendor", "product" because these names collide with the
@@ -1845,6 +1861,67 @@
}
}
+// Test update package that requests data wipe.
+TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) {
+ AddOperationForPartitions();
+
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+ // Write some data to target partitions.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
+ }
+
+ // Create a stale snapshot that should not exist.
+ {
+ ASSERT_TRUE(AcquireLock());
+
+ PartitionCowCreator cow_creator = {
+ .compression_enabled = IsCompressionEnabled(),
+ .compression_algorithm = IsCompressionEnabled() ? "gz" : "none",
+ };
+ SnapshotStatus status;
+ status.set_name("sys_a");
+ status.set_device_size(1_MiB);
+ status.set_snapshot_size(2_MiB);
+ status.set_cow_partition_size(2_MiB);
+
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
+ lock_ = nullptr;
+
+ ASSERT_TRUE(sm->EnsureImageManager());
+ ASSERT_TRUE(sm->image_manager()->CreateBackingImage("sys_a", 1_MiB, 0));
+ }
+
+ ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */));
+
+ // Simulate shutting down the device.
+ ASSERT_TRUE(UnmapAll());
+
+ // Simulate a reboot into recovery.
+ auto test_device = new TestDeviceInfo(fake_super, "_b");
+ test_device->set_recovery(true);
+ auto new_sm = NewManagerForFirstStageMount(test_device);
+
+ ASSERT_TRUE(new_sm->HandleImminentDataWipe());
+ // Manually mount metadata so that we can call GetUpdateState() below.
+ MountMetadata();
+ EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
+ ASSERT_FALSE(test_device->IsSlotUnbootable(1));
+ ASSERT_FALSE(test_device->IsSlotUnbootable(0));
+
+ // Now reboot into new slot.
+ test_device = new TestDeviceInfo(fake_super, "_b");
+ auto init = NewManagerForFirstStageMount(test_device);
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+ // Verify that we are on the downgraded build.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name)) << name;
+ }
+}
+
TEST_F(SnapshotUpdateTest, Hashtree) {
constexpr auto partition_size = 4_MiB;
constexpr auto data_size = 3_MiB;
@@ -2232,9 +2309,27 @@
void TearDown() override;
private:
+ bool CreateFakeSuper();
+
std::unique_ptr<IImageManager> super_images_;
};
+bool SnapshotTestEnvironment::CreateFakeSuper() {
+ // Create and map the fake super partition.
+ static constexpr int kImageFlags =
+ IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
+ if (!super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) {
+ LOG(ERROR) << "Could not create fake super partition";
+ return false;
+ }
+ if (!super_images_->MapImageDevice("fake-super", 10s, &fake_super)) {
+ LOG(ERROR) << "Could not map fake super partition";
+ return false;
+ }
+ test_device->set_fake_super(fake_super);
+ return true;
+}
+
void SnapshotTestEnvironment::SetUp() {
// b/163082876: GTEST_SKIP in Environment will make atest report incorrect results. Until
// that is fixed, don't call GTEST_SKIP here, but instead call GTEST_SKIP in individual test
@@ -2260,27 +2355,36 @@
sm = SnapshotManager::New(test_device);
ASSERT_NE(nullptr, sm) << "Could not create snapshot manager";
+ // Use a separate image manager for our fake super partition.
+ super_images_ = IImageManager::Open("ota/test/super", 10s);
+ ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
+
+ // Map the old image if one exists so we can safely unmap everything that
+ // depends on it.
+ bool recreate_fake_super;
+ if (super_images_->BackingImageExists("fake-super")) {
+ if (super_images_->IsImageMapped("fake-super")) {
+ ASSERT_TRUE(super_images_->GetMappedImageDevice("fake-super", &fake_super));
+ } else {
+ ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super));
+ }
+ test_device->set_fake_super(fake_super);
+ recreate_fake_super = true;
+ } else {
+ ASSERT_TRUE(CreateFakeSuper());
+ recreate_fake_super = false;
+ }
+
// Clean up previous run.
MetadataMountedTest().TearDown();
SnapshotUpdateTest().Cleanup();
SnapshotTest().Cleanup();
- // Use a separate image manager for our fake super partition.
- super_images_ = IImageManager::Open("ota/test/super", 10s);
- ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
-
- // Clean up any old copy.
- DeleteBackingImage(super_images_.get(), "fake-super");
-
- // Create and map the fake super partition.
- static constexpr int kImageFlags =
- IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
- ASSERT_TRUE(super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags))
- << "Could not create fake super partition";
-
- ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super))
- << "Could not map fake super partition";
- test_device->set_fake_super(fake_super);
+ if (recreate_fake_super) {
+ // Clean up any old copy.
+ DeleteBackingImage(super_images_.get(), "fake-super");
+ ASSERT_TRUE(CreateFakeSuper());
+ }
}
void SnapshotTestEnvironment::TearDown() {
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 8e379e4..080f3b7 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -90,7 +90,9 @@
}
const auto& cow_options = options();
- reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+ if (cow_options.max_blocks) {
+ reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+ }
return reader;
}
diff --git a/fs_mgr/libsnapshot/snapshot_writer_test.cpp b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
new file mode 100644
index 0000000..da48eb9
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
@@ -0,0 +1,62 @@
+//
+// 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 <libsnapshot/snapshot.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/snapshot_writer.h>
+#include <payload_consumer/file_descriptor.h>
+
+namespace android::snapshot {
+class CompressedSnapshotWriterTest : public ::testing::Test {
+ public:
+ static constexpr size_t BLOCK_SIZE = 4096;
+};
+
+TEST_F(CompressedSnapshotWriterTest, ReadAfterWrite) {
+ TemporaryFile cow_device_file{};
+ android::snapshot::CowOptions options{.block_size = BLOCK_SIZE};
+ android::snapshot::CompressedSnapshotWriter snapshot_writer{options};
+ snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd});
+ snapshot_writer.Initialize();
+ std::vector<unsigned char> buffer;
+ buffer.resize(BLOCK_SIZE);
+ std::fill(buffer.begin(), buffer.end(), 123);
+
+ ASSERT_TRUE(snapshot_writer.AddRawBlocks(0, buffer.data(), buffer.size()));
+ ASSERT_TRUE(snapshot_writer.Finalize());
+ auto cow_reader = snapshot_writer.OpenReader();
+ ASSERT_NE(cow_reader, nullptr);
+ ASSERT_TRUE(snapshot_writer.AddRawBlocks(1, buffer.data(), buffer.size()));
+ ASSERT_TRUE(snapshot_writer.AddRawBlocks(2, buffer.data(), buffer.size()));
+ ASSERT_TRUE(snapshot_writer.Finalize());
+ // After wrigin some data, if we call OpenReader() again, writes should
+ // be visible to the newly opened reader. update_engine relies on this
+ // behavior for verity writes.
+ cow_reader = snapshot_writer.OpenReader();
+ ASSERT_NE(cow_reader, nullptr);
+ std::vector<unsigned char> read_back;
+ read_back.resize(buffer.size());
+ cow_reader->Seek(BLOCK_SIZE, SEEK_SET);
+ const auto bytes_read = cow_reader->Read(read_back.data(), read_back.size());
+ ASSERT_EQ((size_t)(bytes_read), BLOCK_SIZE);
+ ASSERT_EQ(read_back, buffer);
+}
+
+} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index 5eb2003..67189d4 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -36,7 +36,9 @@
" dump\n"
" Print snapshot states.\n"
" merge\n"
- " Deprecated.\n";
+ " Deprecated.\n"
+ " map\n"
+ " Map all partitions at /dev/block/mapper\n";
return EX_USAGE;
}
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 5ef9e29..03c2ef6 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -47,28 +47,205 @@
worker_threads_.push_back(std::move(wt));
}
+
+ read_ahead_thread_ = std::make_unique<ReadAheadThread>(cow_device_, backing_store_device_,
+ misc_name_, GetSharedPtr());
return true;
}
bool Snapuserd::CommitMerge(int num_merge_ops) {
- {
- std::lock_guard<std::mutex> lock(lock_);
- CowHeader header;
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ ch->num_merge_ops += num_merge_ops;
- reader_->GetHeader(&header);
- header.num_merge_ops += num_merge_ops;
- reader_->UpdateMergeProgress(num_merge_ops);
- if (!writer_->CommitMerge(num_merge_ops)) {
- SNAP_LOG(ERROR) << "CommitMerge failed... merged_ops_cur_iter: " << num_merge_ops
- << " Total-merged-ops: " << header.num_merge_ops;
- return false;
- }
- merge_initiated_ = true;
+ if (read_ahead_feature_ && read_ahead_ops_.size() > 0) {
+ struct BufferState* ra_state = GetBufferState();
+ ra_state->read_ahead_state = kCowReadAheadInProgress;
}
+ int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+ if (ret < 0) {
+ PLOG(ERROR) << "msync header failed: " << ret;
+ return false;
+ }
+
+ merge_initiated_ = true;
+
return true;
}
+void Snapuserd::PrepareReadAhead() {
+ if (!read_ahead_feature_) {
+ return;
+ }
+
+ struct BufferState* ra_state = GetBufferState();
+ // Check if the data has to be re-constructed from COW device
+ if (ra_state->read_ahead_state == kCowReadAheadDone) {
+ populate_data_from_cow_ = true;
+ } else {
+ populate_data_from_cow_ = false;
+ }
+
+ StartReadAhead();
+}
+
+bool Snapuserd::GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer) {
+ if (!lock->owns_lock()) {
+ SNAP_LOG(ERROR) << "GetRABuffer - Lock not held";
+ return false;
+ }
+ std::unordered_map<uint64_t, void*>::iterator it = read_ahead_buffer_map_.find(block);
+
+ // This will be true only for IO's generated as part of reading a root
+ // filesystem. IO's related to merge should always be in read-ahead cache.
+ if (it == read_ahead_buffer_map_.end()) {
+ return false;
+ }
+
+ // Theoretically, we can send the data back from the read-ahead buffer
+ // all the way to the kernel without memcpy. However, if the IO is
+ // un-aligned, the wrapper function will need to touch the read-ahead
+ // buffers and transitions will be bit more complicated.
+ memcpy(buffer, it->second, BLOCK_SZ);
+ return true;
+}
+
+// ========== State transition functions for read-ahead operations ===========
+
+bool Snapuserd::GetReadAheadPopulatedBuffer(uint64_t block, void* buffer) {
+ if (!read_ahead_feature_) {
+ return false;
+ }
+
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
+ return false;
+ }
+
+ if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS) {
+ return GetRABuffer(&lock, block, buffer);
+ }
+ }
+
+ {
+ // Read-ahead thread IO is in-progress. Wait for it to complete
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE ||
+ io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS)) {
+ cv.wait(lock);
+ }
+
+ return GetRABuffer(&lock, block, buffer);
+ }
+}
+
+// This is invoked by read-ahead thread waiting for merge IO's
+// to complete
+bool Snapuserd::WaitForMergeToComplete() {
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN ||
+ io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED)) {
+ cv.wait(lock);
+ }
+
+ if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED) {
+ return false;
+ }
+
+ io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_IN_PROGRESS;
+ return true;
+ }
+}
+
+// This is invoked during the launch of worker threads. We wait
+// for read-ahead thread to by fully up before worker threads
+// are launched; else we will have a race between worker threads
+// and read-ahead thread specifically during re-construction.
+bool Snapuserd::WaitForReadAheadToStart() {
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS ||
+ io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE)) {
+ cv.wait(lock);
+ }
+
+ if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+// Invoked by worker threads when a sequence of merge operation
+// is complete notifying read-ahead thread to make forward
+// progress.
+void Snapuserd::StartReadAhead() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN;
+ }
+
+ cv.notify_one();
+}
+
+void Snapuserd::MergeCompleted() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = READ_AHEAD_IO_TRANSITION::IO_TERMINATED;
+ }
+
+ cv.notify_one();
+}
+
+bool Snapuserd::ReadAheadIOCompleted(bool sync) {
+ if (sync) {
+ // Flush the entire buffer region
+ int ret = msync(mapped_addr_, total_mapped_addr_length_, MS_SYNC);
+ if (ret < 0) {
+ PLOG(ERROR) << "msync failed after ReadAheadIOCompleted: " << ret;
+ return false;
+ }
+
+ // Metadata and data are synced. Now, update the state.
+ // We need to update the state after flushing data; if there is a crash
+ // when read-ahead IO is in progress, the state of data in the COW file
+ // is unknown. kCowReadAheadDone acts as a checkpoint wherein the data
+ // in the scratch space is good and during next reboot, read-ahead thread
+ // can safely re-construct the data.
+ struct BufferState* ra_state = GetBufferState();
+ ra_state->read_ahead_state = kCowReadAheadDone;
+
+ ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+ if (ret < 0) {
+ PLOG(ERROR) << "msync failed to flush Readahead completion state...";
+ return false;
+ }
+ }
+
+ // Notify the worker threads
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS;
+ }
+
+ cv.notify_all();
+ return true;
+}
+
+void Snapuserd::ReadAheadIOFailed() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE;
+ }
+
+ cv.notify_all();
+}
+
+//========== End of state transition functions ====================
+
bool Snapuserd::IsChunkIdMetadata(chunk_t chunk) {
uint32_t stride = exceptions_per_area_ + 1;
lldiv_t divresult = lldiv(chunk, stride);
@@ -93,9 +270,9 @@
return;
}
- CowHeader header;
- reader_->GetHeader(&header);
- SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << header.num_merge_ops
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+
+ SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops
<< " Total-data-ops: " << reader_->total_data_ops();
}
@@ -170,13 +347,18 @@
return false;
}
- CHECK(header.block_size == BLOCK_SZ);
+ if (!(header.block_size == BLOCK_SZ)) {
+ SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
+ return false;
+ }
reader_->InitializeMerge();
SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
- writer_ = std::make_unique<CowWriter>(options);
- writer_->InitializeMerge(cow_fd_.get(), &header);
+ if (!MmapMetadata()) {
+ SNAP_LOG(ERROR) << "mmap failed";
+ return false;
+ }
// Initialize the iterator for reading metadata
cowop_riter_ = reader_->GetRevOpIter();
@@ -258,13 +440,16 @@
data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
}
+ int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
std::optional<chunk_t> prev_id = {};
std::map<uint64_t, const CowOperation*> map;
std::set<uint64_t> dest_blocks;
size_t pending_copy_ops = exceptions_per_area_ - num_ops;
- SNAP_LOG(INFO) << " Processing copy-ops at Area: " << vec_.size()
- << " Number of replace/zero ops completed in this area: " << num_ops
- << " Pending copy ops for this area: " << pending_copy_ops;
+ uint64_t total_copy_ops = reader_->total_copy_ops();
+
+ SNAP_LOG(DEBUG) << " Processing copy-ops at Area: " << vec_.size()
+ << " Number of replace/zero ops completed in this area: " << num_ops
+ << " Pending copy ops for this area: " << pending_copy_ops;
while (!cowop_riter_->Done()) {
do {
const CowOperation* cow_op = &cowop_riter_->Get();
@@ -300,41 +485,20 @@
// Op-6: 15 -> 18
//
// Note that the blocks numbers are contiguous. Hence, all 6 copy
- // operations can potentially be batch merged. However, that will be
+ // operations can be batch merged. However, that will be
// problematic if we have a crash as block 20, 19, 18 would have
// been overwritten and hence subsequent recovery may end up with
// a silent data corruption when op-1, op-2 and op-3 are
// re-executed.
//
- // We will split these 6 operations into two batches viz:
- //
- // Batch-1:
- // ===================
- // Op-1: 20 -> 23
- // Op-2: 19 -> 22
- // Op-3: 18 -> 21
- // ===================
- //
- // Batch-2:
- // ==================
- // Op-4: 17 -> 20
- // Op-5: 16 -> 19
- // Op-6: 15 -> 18
- // ==================
- //
- // Now, merge sequence will look like:
- //
- // 1: Merge Batch-1 { op-1, op-2, op-3 }
- // 2: Update Metadata in COW File that op-1, op-2, op-3 merge is
- // done.
- // 3: Merge Batch-2
- // 4: Update Metadata in COW File that op-4, op-5, op-6 merge is
- // done.
- //
- // Note, that the order of block operations are still the same.
- // However, we have two batch merge operations. Any crash between
- // either of this sequence should be safe as each of these
- // batches are self-contained.
+ // To address the above problem, read-ahead thread will
+ // read all the 6 source blocks, cache them in the scratch
+ // space of the COW file. During merge, read-ahead
+ // thread will serve the blocks from the read-ahead cache.
+ // If there is a crash during merge; on subsequent reboot,
+ // read-ahead thread will recover the data from the
+ // scratch space and re-construct it thereby there
+ // is no loss of data.
//
//===========================================================
//
@@ -398,6 +562,7 @@
if (diff != 1) {
break;
}
+
if (dest_blocks.count(cow_op->new_block) || map.count(cow_op->source) > 0) {
break;
}
@@ -426,6 +591,9 @@
offset += sizeof(struct disk_exception);
num_ops += 1;
copy_ops++;
+ if (read_ahead_feature_) {
+ read_ahead_ops_.push_back(it->second);
+ }
SNAP_LOG(DEBUG) << num_ops << ":"
<< " Copy-op: "
@@ -448,11 +616,24 @@
SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
}
- CHECK(pending_copy_ops == 0);
+ if (!(pending_copy_ops == 0)) {
+ SNAP_LOG(ERROR)
+ << "Invalid pending_copy_ops: expected: 0 found: " << pending_copy_ops;
+ return false;
+ }
pending_copy_ops = exceptions_per_area_;
}
data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+ total_copy_ops -= 1;
+ /*
+ * Split the number of ops based on the size of read-ahead buffer
+ * region. We need to ensure that kernel doesn't issue IO on blocks
+ * which are not read by the read-ahead thread.
+ */
+ if (read_ahead_feature_ && (total_copy_ops % num_ra_ops_per_iter == 0)) {
+ data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+ }
}
map.clear();
dest_blocks.clear();
@@ -470,6 +651,7 @@
chunk_vec_.shrink_to_fit();
vec_.shrink_to_fit();
+ read_ahead_ops_.shrink_to_fit();
// Sort the vector based on sectors as we need this during un-aligned access
std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare);
@@ -484,9 +666,41 @@
// Total number of sectors required for creating dm-user device
num_sectors_ = ChunkToSector(data_chunk_id);
merge_initiated_ = false;
+ PrepareReadAhead();
+
return true;
}
+bool Snapuserd::MmapMetadata() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ if (header.major_version >= 2 && header.buffer_size > 0) {
+ total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+ read_ahead_feature_ = true;
+ } else {
+ // mmap the first 4k page - older COW format
+ total_mapped_addr_length_ = BLOCK_SZ;
+ read_ahead_feature_ = false;
+ }
+
+ mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED,
+ cow_fd_.get(), 0);
+ if (mapped_addr_ == MAP_FAILED) {
+ SNAP_LOG(ERROR) << "mmap metadata failed";
+ return false;
+ }
+
+ return true;
+}
+
+void Snapuserd::UnmapBufferRegion() {
+ int ret = munmap(mapped_addr_, total_mapped_addr_length_);
+ if (ret < 0) {
+ SNAP_PLOG(ERROR) << "munmap failed";
+ }
+}
+
void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
unsigned int, const char* message) {
if (severity == android::base::ERROR) {
@@ -507,11 +721,28 @@
}
/*
- * Entry point to launch worker threads
+ * Entry point to launch threads
*/
bool Snapuserd::Start() {
std::vector<std::future<bool>> threads;
+ std::future<bool> ra_thread;
+ bool rathread = (read_ahead_feature_ && (read_ahead_ops_.size() > 0));
+ // Start the read-ahead thread and wait
+ // for it as the data has to be re-constructed
+ // from COW device.
+ if (rathread) {
+ ra_thread = std::async(std::launch::async, &ReadAheadThread::RunThread,
+ read_ahead_thread_.get());
+ if (!WaitForReadAheadToStart()) {
+ SNAP_LOG(ERROR) << "Failed to start Read-ahead thread...";
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "Read-ahead thread started...";
+ }
+
+ // Launch worker threads
for (int i = 0; i < worker_threads_.size(); i++) {
threads.emplace_back(
std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get()));
@@ -522,8 +753,69 @@
ret = t.get() && ret;
}
+ if (rathread) {
+ // Notify the read-ahead thread that all worker threads
+ // are done. We need this explicit notification when
+ // there is an IO failure or there was a switch
+ // of dm-user table; thus, forcing the read-ahead
+ // thread to wake up.
+ MergeCompleted();
+ ret = ret && ra_thread.get();
+ }
+
return ret;
}
+uint64_t Snapuserd::GetBufferMetadataOffset() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ size_t size = header.header_size + sizeof(BufferState);
+ return size;
+}
+
+/*
+ * Metadata for read-ahead is 16 bytes. For a 2 MB region, we will
+ * end up with 8k (2 PAGE) worth of metadata. Thus, a 2MB buffer
+ * region is split into:
+ *
+ * 1: 8k metadata
+ *
+ */
+size_t Snapuserd::GetBufferMetadataSize() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ size_t metadata_bytes = (header.buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ;
+ return metadata_bytes;
+}
+
+size_t Snapuserd::GetBufferDataOffset() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ return (header.header_size + GetBufferMetadataSize());
+}
+
+/*
+ * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
+ */
+size_t Snapuserd::GetBufferDataSize() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ size_t size = header.buffer_size - GetBufferMetadataSize();
+ return size;
+}
+
+struct BufferState* Snapuserd::GetBufferState() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ struct BufferState* ra_state =
+ reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+ return ra_state;
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h
index 87c5528..212c78e 100644
--- a/fs_mgr/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd.h
@@ -17,8 +17,10 @@
#include <linux/types.h>
#include <stdint.h>
#include <stdlib.h>
+#include <sys/mman.h>
#include <bitset>
+#include <condition_variable>
#include <csignal>
#include <cstring>
#include <future>
@@ -29,6 +31,7 @@
#include <string>
#include <thread>
#include <unordered_map>
+#include <unordered_set>
#include <vector>
#include <android-base/file.h>
@@ -56,6 +59,35 @@
*/
static constexpr int NUM_THREADS_PER_PARTITION = 4;
+/*
+ * State transitions between worker threads and read-ahead
+ * threads.
+ *
+ * READ_AHEAD_BEGIN: Worker threads initiates the read-ahead
+ * thread to begin reading the copy operations
+ * for each bounded region.
+ *
+ * READ_AHEAD_IN_PROGRESS: When read ahead thread is in-flight
+ * and reading the copy operations.
+ *
+ * IO_IN_PROGRESS: Merge operation is in-progress by worker threads.
+ *
+ * IO_TERMINATED: When all the worker threads are done, request the
+ * read-ahead thread to terminate
+ *
+ * READ_AHEAD_FAILURE: If there are any IO failures when read-ahead
+ * thread is reading from COW device.
+ *
+ * The transition of each states is described in snapuserd_readahead.cpp
+ */
+enum class READ_AHEAD_IO_TRANSITION {
+ READ_AHEAD_BEGIN,
+ READ_AHEAD_IN_PROGRESS,
+ IO_IN_PROGRESS,
+ IO_TERMINATED,
+ READ_AHEAD_FAILURE,
+};
+
class BufferSink : public IByteSink {
public:
void Initialize(size_t size);
@@ -67,6 +99,7 @@
struct dm_user_header* GetHeaderPtr();
bool ReturnData(void*, size_t) override { return true; }
void ResetBufferOffset() { buffer_offset_ = 0; }
+ void* GetPayloadBufPtr();
private:
std::unique_ptr<uint8_t[]> buffer_;
@@ -76,6 +109,47 @@
class Snapuserd;
+class ReadAheadThread {
+ public:
+ ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
+ const std::string& misc_name, std::shared_ptr<Snapuserd> snapuserd);
+ bool RunThread();
+
+ private:
+ void InitializeIter();
+ bool IterDone();
+ void IterNext();
+ const CowOperation* GetIterOp();
+ void InitializeBuffer();
+
+ bool InitializeFds();
+ void CloseFds() {
+ cow_fd_ = {};
+ backing_store_fd_ = {};
+ }
+
+ bool ReadAheadIOStart();
+ void PrepareReadAhead(uint64_t* source_block, int* pending_ops, std::vector<uint64_t>& blocks);
+ bool ReconstructDataFromCow();
+ void CheckOverlap(const CowOperation* cow_op);
+
+ void* read_ahead_buffer_;
+ void* metadata_buffer_;
+ std::vector<const CowOperation*>::reverse_iterator read_ahead_iter_;
+ std::string cow_device_;
+ std::string backing_store_device_;
+ std::string misc_name_;
+
+ unique_fd cow_fd_;
+ unique_fd backing_store_fd_;
+
+ std::shared_ptr<Snapuserd> snapuserd_;
+
+ std::unordered_set<uint64_t> dest_blocks_;
+ std::unordered_set<uint64_t> source_blocks_;
+ bool overlap_;
+};
+
class WorkerThread {
public:
WorkerThread(const std::string& cow_device, const std::string& backing_device,
@@ -98,7 +172,7 @@
bool DmuserReadRequest();
bool DmuserWriteRequest();
bool ReadDmUserPayload(void* buffer, size_t size);
- bool WriteDmUserPayload(size_t size);
+ bool WriteDmUserPayload(size_t size, bool header_response);
bool ReadDiskExceptions(chunk_t chunk, size_t size);
bool ZerofillDiskExceptions(size_t read_size);
@@ -116,12 +190,16 @@
bool ProcessCopyOp(const CowOperation* cow_op);
bool ProcessZeroOp();
+ bool ReadFromBaseDevice(const CowOperation* cow_op);
+ bool GetReadAheadPopulatedBuffer(const CowOperation* cow_op);
+
// Merge related functions
bool ProcessMergeComplete(chunk_t chunk, void* buffer);
loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
int* unmerged_exceptions);
+
int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
- int unmerged_exceptions);
+ int unmerged_exceptions, bool* copy_op, bool* commit);
sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
@@ -158,7 +236,10 @@
bool CommitMerge(int num_merge_ops);
void CloseFds() { cow_fd_ = {}; }
- void FreeResources() { worker_threads_.clear(); }
+ void FreeResources() {
+ worker_threads_.clear();
+ read_ahead_thread_ = nullptr;
+ }
size_t GetMetadataAreaSize() { return vec_.size(); }
void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
@@ -173,16 +254,47 @@
return p1.first < p2.first;
}
- private:
- std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+ void UnmapBufferRegion();
+ bool MmapMetadata();
+ // Read-ahead related functions
+ std::vector<const CowOperation*>& GetReadAheadOpsVec() { return read_ahead_ops_; }
+ std::unordered_map<uint64_t, void*>& GetReadAheadMap() { return read_ahead_buffer_map_; }
+ void* GetMappedAddr() { return mapped_addr_; }
+ bool IsReadAheadFeaturePresent() { return read_ahead_feature_; }
+ void PrepareReadAhead();
+ void StartReadAhead();
+ void MergeCompleted();
+ bool ReadAheadIOCompleted(bool sync);
+ void ReadAheadIOFailed();
+ bool WaitForMergeToComplete();
+ bool GetReadAheadPopulatedBuffer(uint64_t block, void* buffer);
+ bool ReconstructDataFromCow() { return populate_data_from_cow_; }
+ void ReconstructDataFromCowFinish() { populate_data_from_cow_ = false; }
+ bool WaitForReadAheadToStart();
+
+ uint64_t GetBufferMetadataOffset();
+ size_t GetBufferMetadataSize();
+ size_t GetBufferDataOffset();
+ size_t GetBufferDataSize();
+
+ // Final block to be merged in a given read-ahead buffer region
+ void SetFinalBlockMerged(uint64_t x) { final_block_merged_ = x; }
+ uint64_t GetFinalBlockMerged() { return final_block_merged_; }
+ // Total number of blocks to be merged in a given read-ahead buffer region
+ void SetTotalRaBlocksMerged(int x) { total_ra_blocks_merged_ = x; }
+ int GetTotalRaBlocksMerged() { return total_ra_blocks_merged_; }
+
+ private:
bool IsChunkIdMetadata(chunk_t chunk);
chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
+ bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
bool ReadMetadata();
sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
+ struct BufferState* GetBufferState();
std::string cow_device_;
std::string backing_store_device_;
@@ -197,7 +309,6 @@
std::unique_ptr<ICowOpIter> cowop_iter_;
std::unique_ptr<ICowOpReverseIter> cowop_riter_;
std::unique_ptr<CowReader> reader_;
- std::unique_ptr<CowWriter> writer_;
// Vector of disk exception which is a
// mapping of old-chunk to new-chunk
@@ -208,6 +319,21 @@
std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
std::mutex lock_;
+ std::condition_variable cv;
+
+ void* mapped_addr_;
+ size_t total_mapped_addr_length_;
+
+ std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+ // Read-ahead related
+ std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
+ std::vector<const CowOperation*> read_ahead_ops_;
+ bool populate_data_from_cow_ = false;
+ bool read_ahead_feature_;
+ uint64_t final_block_merged_;
+ int total_ra_blocks_merged_ = 0;
+ READ_AHEAD_IO_TRANSITION io_state_;
+ std::unique_ptr<ReadAheadThread> read_ahead_thread_;
bool merge_initiated_ = false;
bool attached_ = false;
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index 16d02e4..41ab344 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -113,7 +113,7 @@
bool SnapuserdClient::Sendmsg(const std::string& msg) {
LOG(DEBUG) << "Sendmsg: msg " << msg << " sockfd: " << sockfd_;
- ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), 0));
+ ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), MSG_NOSIGNAL));
if (numBytesSent < 0) {
PLOG(ERROR) << "Send failed";
return false;
diff --git a/fs_mgr/libsnapshot/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
new file mode 100644
index 0000000..16d5919
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
@@ -0,0 +1,463 @@
+/*
+ * 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 "snapuserd.h"
+
+#include <csignal>
+#include <optional>
+#include <set>
+
+#include <libsnapshot/snapuserd_client.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
+
+/*
+ * Merging a copy operation involves the following flow:
+ *
+ * 1: dm-snapshot layer requests merge for a 4k block. dm-user sends the request
+ * to the daemon
+ * 2: daemon reads the source block
+ * 3: daemon copies the source data
+ * 4: IO completion sent back to dm-user (a switch from user space to kernel)
+ * 5: dm-snapshot merges the data to base device
+ * 6: dm-snapshot sends the merge-completion IO to dm-user
+ * 7: dm-user re-directs the merge completion IO to daemon (one more switch)
+ * 8: daemon updates the COW file about the completed merge request (a write syscall) and followed
+ * by a fysnc. 9: Send the IO completion back to dm-user
+ *
+ * The above sequence is a significant overhead especially when merging one 4k
+ * block at a time.
+ *
+ * Read-ahead layer will optimize the above path by reading the data from base
+ * device in the background so that merging thread can retrieve the data from
+ * the read-ahead cache. Additionally, syncing of merged data is deferred to
+ * read-ahead thread threadby the IO path is not bottlenecked.
+ *
+ * We create a scratch space of 2MB to store the read-ahead data in the COW
+ * device.
+ *
+ * +-----------------------+
+ * | Header (fixed) |
+ * +-----------------------+
+ * | Scratch space | <-- 2MB
+ * +-----------------------+
+ *
+ * Scratch space is as follows:
+ *
+ * +-----------------------+
+ * | Metadata | <- 4k page
+ * +-----------------------+
+ * | Metadata | <- 4k page
+ * +-----------------------+
+ * | |
+ * | Read-ahead data |
+ * | |
+ * +-----------------------+
+ *
+ * State transitions and communication between read-ahead thread and worker
+ * thread during merge:
+ * =====================================================================
+ *
+ * Worker Threads Read-Ahead thread
+ * ------------------------------------------------------------------
+ *
+ * |
+ * |
+ * --> -----------------READ_AHEAD_BEGIN------------->|
+ * | | | READ_AHEAD_IN_PROGRESS
+ * | WAIT |
+ * | | |
+ * | |<-----------------IO_IN_PROGRESS---------------
+ * | | |
+ * | | IO_IN_PRGRESS WAIT
+ * | | |
+ * |<--| |
+ * | |
+ * ------------------IO_TERMINATED--------------->|
+ * END
+ *
+ *
+ * ===================================================================
+ *
+ * Example:
+ *
+ * We have 6 copy operations to be executed in OTA and there is a overlap. Update-engine
+ * will write to COW file as follows:
+ *
+ * Op-1: 20 -> 23
+ * Op-2: 19 -> 22
+ * Op-3: 18 -> 21
+ * Op-4: 17 -> 20
+ * Op-5: 16 -> 19
+ * Op-6: 15 -> 18
+ *
+ * Read-ahead thread will read all the 6 source blocks and store the data in the
+ * scratch space. Metadata will contain the destination block numbers. Thus,
+ * scratch space will look something like this:
+ *
+ * +--------------+
+ * | Block 23 |
+ * | offset - 1 |
+ * +--------------+
+ * | Block 22 |
+ * | offset - 2 |
+ * +--------------+
+ * | Block 21 |
+ * | offset - 3 |
+ * +--------------+
+ * ...
+ * ...
+ * +--------------+
+ * | Data-Block 20| <-- offset - 1
+ * +--------------+
+ * | Data-Block 19| <-- offset - 2
+ * +--------------+
+ * | Data-Block 18| <-- offset - 3
+ * +--------------+
+ * ...
+ * ...
+ *
+ * ====================================================================
+ * IO Path:
+ *
+ * Read-ahead will serve the data to worker threads during merge only
+ * after metadata and data are persisted to the scratch space. Worker
+ * threads during merge will always retrieve the data from cache; if the
+ * cache is not populated, it will wait for the read-ahead thread to finish.
+ * Furthermore, the number of operations merged will by synced to the header
+ * only when all the blocks in the read-ahead cache are merged. In the above
+ * case, when all 6 operations are merged, COW Header is updated with
+ * num_merge_ops = 6.
+ *
+ * Merge resume after crash:
+ *
+ * Let's say we have a crash after 5 operations are merged. i.e. after
+ * Op-5: 16->19 is completed but before the Op-6 is merged. Thus, COW Header
+ * num_merge_ops will be 0 as the all the ops were not merged yet. During next
+ * reboot, read-ahead thread will re-construct the data in-memory from the
+ * scratch space; when merge resumes, Op-1 will be re-exectued. However,
+ * data will be served from read-ahead cache safely even though, block 20
+ * was over-written by Op-4.
+ *
+ */
+
+ReadAheadThread::ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
+ const std::string& misc_name,
+ std::shared_ptr<Snapuserd> snapuserd) {
+ cow_device_ = cow_device;
+ backing_store_device_ = backing_device;
+ misc_name_ = misc_name;
+ snapuserd_ = snapuserd;
+}
+
+void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
+ if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(cow_op->source)) {
+ overlap_ = true;
+ }
+
+ dest_blocks_.insert(cow_op->source);
+ source_blocks_.insert(cow_op->new_block);
+}
+
+void ReadAheadThread::PrepareReadAhead(uint64_t* source_block, int* pending_ops,
+ std::vector<uint64_t>& blocks) {
+ int num_ops = *pending_ops;
+ int nr_consecutive = 0;
+
+ if (!IterDone() && num_ops) {
+ // Get the first block
+ const CowOperation* cow_op = GetIterOp();
+ *source_block = cow_op->source;
+ IterNext();
+ num_ops -= 1;
+ nr_consecutive = 1;
+ blocks.push_back(cow_op->new_block);
+
+ if (!overlap_) {
+ CheckOverlap(cow_op);
+ }
+
+ /*
+ * Find number of consecutive blocks working backwards.
+ */
+ while (!IterDone() && num_ops) {
+ const CowOperation* op = GetIterOp();
+ if (op->source != (*source_block - nr_consecutive)) {
+ break;
+ }
+ nr_consecutive += 1;
+ num_ops -= 1;
+ blocks.push_back(op->new_block);
+ IterNext();
+
+ if (!overlap_) {
+ CheckOverlap(op);
+ }
+ }
+ }
+}
+
+bool ReadAheadThread::ReconstructDataFromCow() {
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ read_ahead_buffer_map.clear();
+ loff_t metadata_offset = 0;
+ loff_t start_data_offset = snapuserd_->GetBufferDataOffset();
+ int num_ops = 0;
+ int total_blocks_merged = 0;
+
+ while (true) {
+ struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+ (char*)metadata_buffer_ + metadata_offset);
+
+ // Done reading metadata
+ if (bm->new_block == 0 && bm->file_offset == 0) {
+ break;
+ }
+
+ loff_t buffer_offset = bm->file_offset - start_data_offset;
+ void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + buffer_offset);
+ read_ahead_buffer_map[bm->new_block] = bufptr;
+ num_ops += 1;
+ total_blocks_merged += 1;
+
+ metadata_offset += sizeof(struct ScratchMetadata);
+ }
+
+ // We are done re-constructing the mapping; however, we need to make sure
+ // all the COW operations to-be merged are present in the re-constructed
+ // mapping.
+ while (!IterDone()) {
+ const CowOperation* op = GetIterOp();
+ if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
+ num_ops -= 1;
+ snapuserd_->SetFinalBlockMerged(op->new_block);
+ IterNext();
+ } else {
+ // Verify that we have covered all the ops which were re-constructed
+ // from COW device - These are the ops which are being
+ // re-constructed after crash.
+ if (!(num_ops == 0)) {
+ SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
+ << " Pending ops: " << num_ops;
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+ break;
+ }
+ }
+
+ snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
+
+ snapuserd_->ReconstructDataFromCowFinish();
+
+ if (!snapuserd_->ReadAheadIOCompleted(true)) {
+ SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+ return true;
+}
+
+bool ReadAheadThread::ReadAheadIOStart() {
+ // Check if the data has to be constructed from the COW file.
+ // This will be true only once during boot up after a crash
+ // during merge.
+ if (snapuserd_->ReconstructDataFromCow()) {
+ return ReconstructDataFromCow();
+ }
+
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ read_ahead_buffer_map.clear();
+
+ int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
+ loff_t metadata_offset = 0;
+
+ struct ScratchMetadata* bm =
+ reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
+
+ bm->new_block = 0;
+ bm->file_offset = 0;
+
+ std::vector<uint64_t> blocks;
+
+ loff_t buffer_offset = 0;
+ loff_t offset = 0;
+ loff_t file_offset = snapuserd_->GetBufferDataOffset();
+ int total_blocks_merged = 0;
+ overlap_ = false;
+ dest_blocks_.clear();
+ source_blocks_.clear();
+
+ while (true) {
+ uint64_t source_block;
+ int linear_blocks;
+
+ PrepareReadAhead(&source_block, &num_ops, blocks);
+ linear_blocks = blocks.size();
+ if (linear_blocks == 0) {
+ // No more blocks to read
+ SNAP_LOG(DEBUG) << " Read-ahead completed....";
+ break;
+ }
+
+ // Get the first block in the consecutive set of blocks
+ source_block = source_block + 1 - linear_blocks;
+ size_t io_size = (linear_blocks * BLOCK_SZ);
+ num_ops -= linear_blocks;
+ total_blocks_merged += linear_blocks;
+
+ // Mark the block number as the one which will
+ // be the final block to be merged in this entire region.
+ // Read-ahead thread will get
+ // notified when this block is merged to make
+ // forward progress
+ snapuserd_->SetFinalBlockMerged(blocks.back());
+
+ while (linear_blocks) {
+ uint64_t new_block = blocks.back();
+ blocks.pop_back();
+ // Assign the mapping
+ void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
+ read_ahead_buffer_map[new_block] = bufptr;
+ offset += BLOCK_SZ;
+
+ bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ +
+ metadata_offset);
+ bm->new_block = new_block;
+ bm->file_offset = file_offset;
+
+ metadata_offset += sizeof(struct ScratchMetadata);
+ file_offset += BLOCK_SZ;
+
+ linear_blocks -= 1;
+ }
+
+ // Read from the base device consecutive set of blocks in one shot
+ if (!android::base::ReadFullyAtOffset(backing_store_fd_,
+ (char*)read_ahead_buffer_ + buffer_offset, io_size,
+ source_block * BLOCK_SZ)) {
+ SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
+ << "at block :" << source_block << " buffer_offset : " << buffer_offset
+ << " io_size : " << io_size << " buf-addr : " << read_ahead_buffer_;
+
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ // This is important - explicitly set the contents to zero. This is used
+ // when re-constructing the data after crash. This indicates end of
+ // reading metadata contents when re-constructing the data
+ bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
+ bm->new_block = 0;
+ bm->file_offset = 0;
+
+ buffer_offset += io_size;
+ }
+
+ snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
+
+ // Flush the data only if we have a overlapping blocks in the region
+ if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
+ SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadAheadThread::RunThread() {
+ if (!InitializeFds()) {
+ return false;
+ }
+
+ InitializeIter();
+ InitializeBuffer();
+
+ while (!IterDone()) {
+ if (!ReadAheadIOStart()) {
+ return false;
+ }
+
+ bool status = snapuserd_->WaitForMergeToComplete();
+
+ if (status && !snapuserd_->CommitMerge(snapuserd_->GetTotalRaBlocksMerged())) {
+ return false;
+ }
+
+ if (!status) break;
+ }
+
+ CloseFds();
+ SNAP_LOG(INFO) << " ReadAhead thread terminating....";
+ return true;
+}
+
+// Initialization
+bool ReadAheadThread::InitializeFds() {
+ backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+ if (backing_store_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+ return false;
+ }
+
+ cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+ if (cow_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+ return false;
+ }
+
+ return true;
+}
+
+void ReadAheadThread::InitializeIter() {
+ std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
+ read_ahead_iter_ = read_ahead_ops.rbegin();
+}
+
+bool ReadAheadThread::IterDone() {
+ std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
+ return read_ahead_iter_ == read_ahead_ops.rend();
+}
+
+void ReadAheadThread::IterNext() {
+ read_ahead_iter_++;
+}
+
+const CowOperation* ReadAheadThread::GetIterOp() {
+ return *read_ahead_iter_;
+}
+
+void ReadAheadThread::InitializeBuffer() {
+ void* mapped_addr = snapuserd_->GetMappedAddr();
+ // Map the scratch space region into memory
+ metadata_buffer_ =
+ static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
+ read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 64332d1..8339690 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -81,7 +81,7 @@
: snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
- ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), 0));
+ ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
if (ret < 0) {
PLOG(ERROR) << "Snapuserd:server: send() failed";
return false;
@@ -191,7 +191,7 @@
}
case DaemonOperations::DETACH: {
terminating_ = true;
- return Sendmsg(fd, "success");
+ return true;
}
default: {
LOG(ERROR) << "Received unknown message type from client";
@@ -209,10 +209,11 @@
}
handler->snapuserd()->CloseFds();
+ handler->snapuserd()->CheckMergeCompletionStatus();
+ handler->snapuserd()->UnmapBufferRegion();
auto misc_name = handler->misc_name();
LOG(INFO) << "Handler thread about to exit: " << misc_name;
- handler->snapuserd()->CheckMergeCompletionStatus();
{
std::lock_guard<std::mutex> lock(lock_);
@@ -377,7 +378,10 @@
}
bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
- CHECK(!handler->snapuserd()->IsAttached());
+ if (handler->snapuserd()->IsAttached()) {
+ LOG(ERROR) << "Handler already attached";
+ return false;
+ }
handler->snapuserd()->AttachControlDevice();
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
index 1002569..682f9da 100644
--- a/fs_mgr/libsnapshot/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -57,12 +57,20 @@
}
struct dm_user_header* BufferSink::GetHeaderPtr() {
- CHECK(sizeof(struct dm_user_header) <= buffer_size_);
+ if (!(sizeof(struct dm_user_header) <= buffer_size_)) {
+ return nullptr;
+ }
char* buf = reinterpret_cast<char*>(GetBufPtr());
struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
return header;
}
+void* BufferSink::GetPayloadBufPtr() {
+ char* buffer = reinterpret_cast<char*>(GetBufPtr());
+ struct dm_user_message* msg = reinterpret_cast<struct dm_user_message*>(&(buffer[0]));
+ return msg->payload.buf;
+}
+
WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device,
const std::string& control_device, const std::string& misc_name,
std::shared_ptr<Snapuserd> snapuserd) {
@@ -111,7 +119,6 @@
// the header, zero out the remaining block.
void WorkerThread::ConstructKernelCowHeader() {
void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
memset(buffer, 0, BLOCK_SZ);
@@ -135,14 +142,14 @@
return true;
}
-// Start the copy operation. This will read the backing
-// block device which is represented by cow_op->source.
-bool WorkerThread::ProcessCopyOp(const CowOperation* cow_op) {
+bool WorkerThread::ReadFromBaseDevice(const CowOperation* cow_op) {
void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
-
- // Issue a single 4K IO. However, this can be optimized
- // if the successive blocks are contiguous.
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+ return false;
+ }
+ SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
+ << " Source: " << cow_op->source;
if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ,
cow_op->source * BLOCK_SZ)) {
SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
@@ -153,17 +160,51 @@
return true;
}
+bool WorkerThread::GetReadAheadPopulatedBuffer(const CowOperation* cow_op) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "GetReadAheadPopulatedBuffer: Failed to get payload buffer";
+ return false;
+ }
+
+ if (!snapuserd_->GetReadAheadPopulatedBuffer(cow_op->new_block, buffer)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Start the copy operation. This will read the backing
+// block device which is represented by cow_op->source.
+bool WorkerThread::ProcessCopyOp(const CowOperation* cow_op) {
+ if (!GetReadAheadPopulatedBuffer(cow_op)) {
+ SNAP_LOG(DEBUG) << " GetReadAheadPopulatedBuffer failed..."
+ << " new_block: " << cow_op->new_block;
+ if (!ReadFromBaseDevice(cow_op)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool WorkerThread::ProcessZeroOp() {
// Zero out the entire block
void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessZeroOp: Failed to get payload buffer";
+ return false;
+ }
memset(buffer, 0, BLOCK_SZ);
return true;
}
bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) {
- CHECK(cow_op != nullptr);
+ if (cow_op == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op";
+ return false;
+ }
switch (cow_op->type) {
case kCowReplaceOp: {
@@ -194,7 +235,8 @@
<< " Aligned sector: " << it->first;
if (!ProcessCowOp(it->second)) {
- SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
+ SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size
+ << " Aligned sector: " << it->first;
return -1;
}
@@ -205,6 +247,12 @@
char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr());
struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+ if (skip_sector_size == BLOCK_SZ) {
+ SNAP_LOG(ERROR) << "Invalid un-aligned IO request at sector: " << sector
+ << " Base-sector: " << it->first;
+ return -1;
+ }
+
memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
(BLOCK_SZ - skip_sector_size));
}
@@ -239,7 +287,10 @@
it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
Snapuserd::compare);
- CHECK(it != chunk_vec.end());
+ if (!(it != chunk_vec.end())) {
+ SNAP_LOG(ERROR) << "ReadData: Sector " << sector << " not found in chunk_vec";
+ return -1;
+ }
// We didn't find the required sector; hence find the previous sector
// as lower_bound will gives us the value greater than
@@ -276,16 +327,30 @@
}
int num_ops = DIV_ROUND_UP(size, BLOCK_SZ);
+ sector_t read_sector = sector;
while (num_ops) {
- if (!ProcessCowOp(it->second)) {
+ // We have to make sure that the reads are
+ // sequential; there shouldn't be a data
+ // request merged with a metadata IO.
+ if (it->first != read_sector) {
+ SNAP_LOG(ERROR) << "Invalid IO request: read_sector: " << read_sector
+ << " cow-op sector: " << it->first;
+ return -1;
+ } else if (!ProcessCowOp(it->second)) {
return -1;
}
num_ops -= 1;
+ read_sector += (BLOCK_SZ >> SECTOR_SHIFT);
+
it++;
+
+ if (it == chunk_vec.end() && num_ops) {
+ SNAP_LOG(ERROR) << "Invalid IO request at sector " << sector
+ << " COW ops completed; pending read-request: " << num_ops;
+ return -1;
+ }
// Update the buffer offset
bufsink_.UpdateBufferOffset(BLOCK_SZ);
-
- SNAP_LOG(DEBUG) << "ReadData at sector: " << sector << " size: " << size;
}
// Reset the buffer offset
@@ -312,7 +377,10 @@
}
void* buffer = bufsink_.GetPayloadBuffer(size);
- CHECK(buffer != nullptr);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ZerofillDiskExceptions: Failed to get payload buffer";
+ return false;
+ }
memset(buffer, 0, size);
return true;
@@ -342,10 +410,17 @@
if (divresult.quot < vec.size()) {
size = exceptions_per_area_ * sizeof(struct disk_exception);
- CHECK(read_size == size);
+ if (read_size != size) {
+ SNAP_LOG(ERROR) << "ReadDiskExceptions: read_size: " << read_size
+ << " does not match with size: " << size;
+ return false;
+ }
void* buffer = bufsink_.GetPayloadBuffer(size);
- CHECK(buffer != nullptr);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ReadDiskExceptions: Failed to get payload buffer of size: " << size;
+ return false;
+ }
memcpy(buffer, vec[divresult.quot].get(), size);
} else {
@@ -368,8 +443,19 @@
// Unmerged op by the kernel
if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) {
- CHECK(merged_de->old_chunk == cow_de->old_chunk);
- CHECK(merged_de->new_chunk == cow_de->new_chunk);
+ if (!(merged_de->old_chunk == cow_de->old_chunk)) {
+ SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->old_chunk: "
+ << merged_de->old_chunk
+ << "cow_de->old_chunk: " << cow_de->old_chunk;
+ return -1;
+ }
+
+ if (!(merged_de->new_chunk == cow_de->new_chunk)) {
+ SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->new_chunk: "
+ << merged_de->new_chunk
+ << "cow_de->new_chunk: " << cow_de->new_chunk;
+ return -1;
+ }
offset += sizeof(struct disk_exception);
*unmerged_exceptions += 1;
@@ -379,15 +465,15 @@
break;
}
- CHECK(!(*unmerged_exceptions == exceptions_per_area_));
-
SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
return offset;
}
int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
- int unmerged_exceptions) {
+ int unmerged_exceptions, bool* copy_op, bool* commit) {
int merged_ops_cur_iter = 0;
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ *copy_op = false;
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
// Find the operations which are merged in this cycle.
@@ -397,8 +483,15 @@
struct disk_exception* cow_de =
reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
- CHECK(merged_de->new_chunk == 0);
- CHECK(merged_de->old_chunk == 0);
+ if (!(merged_de->new_chunk == 0)) {
+ SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid new-chunk: " << merged_de->new_chunk;
+ return -1;
+ }
+
+ if (!(merged_de->old_chunk == 0)) {
+ SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid old-chunk: " << merged_de->old_chunk;
+ return -1;
+ }
if (cow_de->new_chunk != 0) {
merged_ops_cur_iter += 1;
@@ -406,13 +499,36 @@
auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
std::make_pair(ChunkToSector(cow_de->new_chunk), nullptr),
Snapuserd::compare);
- CHECK(it != chunk_vec.end());
- CHECK(it->first == ChunkToSector(cow_de->new_chunk));
+
+ if (!(it != chunk_vec.end())) {
+ SNAP_LOG(ERROR) << "Sector not found: " << ChunkToSector(cow_de->new_chunk);
+ return -1;
+ }
+
+ if (!(it->first == ChunkToSector(cow_de->new_chunk))) {
+ SNAP_LOG(ERROR) << "Invalid sector: " << ChunkToSector(cow_de->new_chunk);
+ return -1;
+ }
const CowOperation* cow_op = it->second;
- CHECK(cow_op != nullptr);
+ if (snapuserd_->IsReadAheadFeaturePresent() && cow_op->type == kCowCopyOp) {
+ *copy_op = true;
+ // Every single copy operation has to come from read-ahead
+ // cache.
+ if (read_ahead_buffer_map.find(cow_op->new_block) == read_ahead_buffer_map.end()) {
+ SNAP_LOG(ERROR)
+ << " Block: " << cow_op->new_block << " not found in read-ahead cache"
+ << " Source: " << cow_op->source;
+ return -1;
+ }
+ // If this is a final block merged in the read-ahead buffer
+ // region, notify the read-ahead thread to make forward
+ // progress
+ if (cow_op->new_block == snapuserd_->GetFinalBlockMerged()) {
+ *commit = true;
+ }
+ }
- CHECK(cow_op->new_block == cow_de->old_chunk);
// zero out to indicate that operation is merged.
cow_de->old_chunk = 0;
cow_de->new_chunk = 0;
@@ -422,7 +538,6 @@
//
// If the op was merged in previous cycle, we don't have
// to count them.
- CHECK(cow_de->new_chunk == 0);
break;
} else {
SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
@@ -442,25 +557,53 @@
bool WorkerThread::ProcessMergeComplete(chunk_t chunk, void* buffer) {
uint32_t stride = exceptions_per_area_ + 1;
const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
+ bool copy_op = false;
+ bool commit = false;
// ChunkID to vector index
lldiv_t divresult = lldiv(chunk, stride);
- CHECK(divresult.quot < vec.size());
+
+ if (!(divresult.quot < vec.size())) {
+ SNAP_LOG(ERROR) << "ProcessMergeComplete: Invalid chunk: " << chunk
+ << " Metadata-Index: " << divresult.quot << " Area-size: " << vec.size();
+ return false;
+ }
+
SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
<< " Metadata-Index: " << divresult.quot;
int unmerged_exceptions = 0;
loff_t offset = GetMergeStartOffset(buffer, vec[divresult.quot].get(), &unmerged_exceptions);
- int merged_ops_cur_iter =
- GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset, unmerged_exceptions);
+ if (offset < 0) {
+ SNAP_LOG(ERROR) << "GetMergeStartOffset failed: unmerged_exceptions: "
+ << unmerged_exceptions;
+ return false;
+ }
+
+ int merged_ops_cur_iter = GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset,
+ unmerged_exceptions, ©_op, &commit);
// There should be at least one operation merged in this cycle
- CHECK(merged_ops_cur_iter > 0);
- if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
+ if (!(merged_ops_cur_iter > 0)) {
+ SNAP_LOG(ERROR) << "Merge operation failed: " << merged_ops_cur_iter;
return false;
}
+ if (copy_op) {
+ if (commit) {
+ // Push the flushing logic to read-ahead thread so that merge thread
+ // can make forward progress. Sync will happen in the background
+ snapuserd_->StartReadAhead();
+ }
+ } else {
+ // Non-copy ops and all ops in older COW format
+ if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
+ SNAP_LOG(ERROR) << "CommitMerge failed...";
+ return false;
+ }
+ }
+
SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
return true;
}
@@ -472,6 +615,7 @@
if (errno != ENOTBLK) {
SNAP_PLOG(ERROR) << "Control-read failed";
}
+
return false;
}
@@ -479,10 +623,16 @@
}
// Send the payload/data back to dm-user misc device.
-bool WorkerThread::WriteDmUserPayload(size_t size) {
- if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
- sizeof(struct dm_user_header) + size)) {
- SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << size;
+bool WorkerThread::WriteDmUserPayload(size_t size, bool header_response) {
+ size_t payload_size = size;
+ void* buf = bufsink_.GetPayloadBufPtr();
+ if (header_response) {
+ payload_size += sizeof(struct dm_user_header);
+ buf = bufsink_.GetBufPtr();
+ }
+
+ if (!android::base::WriteFully(ctrl_fd_, buf, payload_size)) {
+ SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << payload_size;
return false;
}
@@ -516,9 +666,14 @@
// REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
// to flush per se; hence, just respond back with a success message.
if (header->sector == 0) {
- CHECK(header->len == 0);
- header->type = DM_USER_RESP_SUCCESS;
- if (!WriteDmUserPayload(0)) {
+ if (!(header->len == 0)) {
+ SNAP_LOG(ERROR) << "Invalid header length received from sector 0: " << header->len;
+ header->type = DM_USER_RESP_ERROR;
+ } else {
+ header->type = DM_USER_RESP_SUCCESS;
+ }
+
+ if (!WriteDmUserPayload(0, true)) {
return false;
}
return true;
@@ -527,36 +682,40 @@
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
size_t remaining_size = header->len;
size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
- CHECK(read_size == BLOCK_SZ) << "DmuserWriteRequest: read_size: " << read_size;
- CHECK(header->sector > 0);
chunk_t chunk = SectorToChunk(header->sector);
auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
std::make_pair(header->sector, nullptr), Snapuserd::compare);
bool not_found = (it == chunk_vec.end() || it->first != header->sector);
- CHECK(not_found);
- void* buffer = bufsink_.GetPayloadBuffer(read_size);
- CHECK(buffer != nullptr);
- header->type = DM_USER_RESP_SUCCESS;
+ if (not_found) {
+ void* buffer = bufsink_.GetPayloadBuffer(read_size);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "DmuserWriteRequest: Failed to get payload buffer of size: "
+ << read_size;
+ header->type = DM_USER_RESP_ERROR;
+ } else {
+ header->type = DM_USER_RESP_SUCCESS;
- if (!ReadDmUserPayload(buffer, read_size)) {
- SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
- << "Sector: " << header->sector;
- header->type = DM_USER_RESP_ERROR;
- }
+ if (!ReadDmUserPayload(buffer, read_size)) {
+ SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ header->type = DM_USER_RESP_ERROR;
+ }
- if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
- SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
- << "Sector: " << header->sector;
- header->type = DM_USER_RESP_ERROR;
+ if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
+ SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ header->type = DM_USER_RESP_ERROR;
+ }
+ }
} else {
- SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk
- << "Sector: " << header->sector;
+ SNAP_LOG(ERROR) << "DmuserWriteRequest: Invalid sector received: header->sector";
+ header->type = DM_USER_RESP_ERROR;
}
- if (!WriteDmUserPayload(0)) {
+ if (!WriteDmUserPayload(0, true)) {
return false;
}
@@ -569,6 +728,7 @@
loff_t offset = 0;
sector_t sector = header->sector;
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+ bool header_response = true;
do {
size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
@@ -582,9 +742,13 @@
// never see multiple IO requests. Additionally this IO
// will always be a single 4k.
if (header->sector == 0) {
- CHECK(read_size == BLOCK_SZ) << " Sector 0 read request of size: " << read_size;
- ConstructKernelCowHeader();
- SNAP_LOG(DEBUG) << "Kernel header constructed";
+ if (read_size == BLOCK_SZ) {
+ ConstructKernelCowHeader();
+ SNAP_LOG(DEBUG) << "Kernel header constructed";
+ } else {
+ SNAP_LOG(ERROR) << "Invalid read_size: " << read_size << " for sector 0";
+ header->type = DM_USER_RESP_ERROR;
+ }
} else {
auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
std::make_pair(header->sector, nullptr), Snapuserd::compare);
@@ -600,6 +764,7 @@
}
} else {
chunk_t num_sectors_read = (offset >> SECTOR_SHIFT);
+
ret = ReadData(sector + num_sectors_read, read_size);
if (ret < 0) {
SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
@@ -613,14 +778,31 @@
}
}
+ // Just return the header if it is an error
+ if (header->type == DM_USER_RESP_ERROR) {
+ SNAP_LOG(ERROR) << "IO read request failed...";
+ ret = 0;
+ }
+
+ if (!header_response) {
+ CHECK(header->type == DM_USER_RESP_SUCCESS)
+ << " failed for sector: " << sector << " header->len: " << header->len
+ << " remaining_size: " << remaining_size;
+ }
+
// Daemon will not be terminated if there is any error. We will
// just send the error back to dm-user.
- if (!WriteDmUserPayload(ret)) {
+ if (!WriteDmUserPayload(ret, header_response)) {
return false;
}
+ if (header->type == DM_USER_RESP_ERROR) {
+ break;
+ }
+
remaining_size -= ret;
offset += ret;
+ header_response = false;
} while (remaining_size > 0);
return true;
@@ -666,11 +848,11 @@
return false;
}
- SNAP_LOG(DEBUG) << "msg->seq: " << std::hex << header->seq;
- SNAP_LOG(DEBUG) << "msg->type: " << std::hex << header->type;
- SNAP_LOG(DEBUG) << "msg->flags: " << std::hex << header->flags;
- SNAP_LOG(DEBUG) << "msg->sector: " << std::hex << header->sector;
- SNAP_LOG(DEBUG) << "msg->len: " << std::hex << header->len;
+ SNAP_LOG(DEBUG) << "Daemon: msg->seq: " << std::dec << header->seq;
+ SNAP_LOG(DEBUG) << "Daemon: msg->len: " << std::dec << header->len;
+ SNAP_LOG(DEBUG) << "Daemon: msg->sector: " << std::dec << header->sector;
+ SNAP_LOG(DEBUG) << "Daemon: msg->type: " << std::dec << header->type;
+ SNAP_LOG(DEBUG) << "Daemon: msg->flags: " << std::dec << header->flags;
switch (header->type) {
case DM_USER_REQ_MAP_READ: {
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index f31ee31..69d72e1 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -75,6 +75,7 @@
repeated DynamicPartitionGroup groups = 1;
optional bool vabc_enabled = 3;
optional string vabc_compression_param = 4;
+ optional uint32 cow_version = 5;
}
message DeltaArchiveManifest {
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 242fa93..9542bc1 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -735,23 +735,46 @@
fi
}
+[ "USAGE: join_with <delimiter> <strings>
+
+Joins strings with delimiter" ]
+join_with() {
+ if [ "${#}" -lt 2 ]; then
+ echo
+ return
+ fi
+ local delimiter="${1}"
+ local result="${2}"
+ shift 2
+ for element in "${@}"; do
+ result+="${delimiter}${element}"
+ done
+ echo "${result}"
+}
+
[ "USAGE: skip_administrative_mounts [data] < /proc/mounts
Filters out all administrative (eg: sysfs) mounts uninteresting to the test" ]
skip_administrative_mounts() {
+ local exclude_filesystems=(
+ "overlay" "tmpfs" "none" "sysfs" "proc" "selinuxfs" "debugfs" "bpf"
+ "binfmt_misc" "cg2_bpf" "pstore" "tracefs" "adb" "mtp" "ptp" "devpts"
+ "ramdumpfs" "binder" "securityfs" "functionfs" "rootfs"
+ )
+ local exclude_devices=(
+ "\/sys\/kernel\/debug" "\/data\/media" "\/dev\/block\/loop[0-9]*"
+ "${exclude_filesystems[@]}"
+ )
+ local exclude_mount_points=(
+ "\/cache" "\/mnt\/scratch" "\/mnt\/vendor\/persist" "\/persist"
+ "\/metadata"
+ )
if [ "data" = "${1}" ]; then
- grep -v " /data "
- else
- cat -
- fi |
- grep -v \
- -e "^\(overlay\|tmpfs\|none\|sysfs\|proc\|selinuxfs\|debugfs\|bpf\) " \
- -e "^\(binfmt_misc\|cg2_bpf\|pstore\|tracefs\|adb\|mtp\|ptp\|devpts\) " \
- -e "^\(ramdumpfs\|binder\|/sys/kernel/debug\|securityfs\) " \
- -e " functionfs " \
- -e "^\(/data/media\|/dev/block/loop[0-9]*\) " \
- -e "^rootfs / rootfs rw," \
- -e " /\(cache\|mnt/scratch\|mnt/vendor/persist\|persist\|metadata\) "
+ exclude_mount_points+=("\/data")
+ fi
+ awk '$1 !~ /^('"$(join_with "|" "${exclude_devices[@]}")"')$/ &&
+ $2 !~ /^('"$(join_with "|" "${exclude_mount_points[@]}")"')$/ &&
+ $3 !~ /^('"$(join_with "|" "${exclude_filesystems[@]}")"')$/'
}
[ "USAGE: skip_unrelated_mounts < /proc/mounts
@@ -907,9 +930,11 @@
# Acquire list of system partitions
+# KISS (assume system partition mount point is "/<partition name>")
PARTITIONS=`adb_su cat /vendor/etc/fstab* </dev/null |
+ grep -v "^[#${SPACE}${TAB}]" |
skip_administrative_mounts |
- sed -n "s@^\([^ ${TAB}/][^ ${TAB}/]*\)[ ${TAB}].*[, ${TAB}]ro[, ${TAB}].*@\1@p" |
+ awk '$1 ~ /^[^\/]+$/ && "/"$1 == $2 && $4 ~ /(^|,)ro(,|$)/ { print $1 }' |
sort -u |
tr '\n' ' '`
PARTITIONS="${PARTITIONS:-system vendor}"
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 9adb6bd..a1b020a 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -120,7 +120,7 @@
};
const std::string bootconfig =
- "androidboot.bootdevice = \" \"1d84000.ufshc\"\n"
+ "androidboot.bootdevice = \"1d84000.ufshc\"\n"
"androidboot.boot_devices = \"dev1\", \"dev2,withcomma\", \"dev3\"\n"
"androidboot.baseband = \"sdy\"\n"
"androidboot.keymaster = \"1\"\n"
@@ -184,6 +184,36 @@
{"androidboot.space", "sha256 5248 androidboot.nospace = nope"},
};
+bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
+ // clang-format off
+ return lhs.wait == rhs.wait &&
+ lhs.check == rhs.check &&
+ lhs.crypt == rhs.crypt &&
+ lhs.nonremovable == rhs.nonremovable &&
+ lhs.vold_managed == rhs.vold_managed &&
+ lhs.recovery_only == rhs.recovery_only &&
+ lhs.verify == rhs.verify &&
+ lhs.force_crypt == rhs.force_crypt &&
+ lhs.no_emulated_sd == rhs.no_emulated_sd &&
+ lhs.no_trim == rhs.no_trim &&
+ lhs.file_encryption == rhs.file_encryption &&
+ lhs.formattable == rhs.formattable &&
+ lhs.slot_select == rhs.slot_select &&
+ lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
+ lhs.late_mount == rhs.late_mount &&
+ lhs.no_fail == rhs.no_fail &&
+ lhs.verify_at_boot == rhs.verify_at_boot &&
+ lhs.quota == rhs.quota &&
+ lhs.avb == rhs.avb &&
+ lhs.logical == rhs.logical &&
+ lhs.checkpoint_blk == rhs.checkpoint_blk &&
+ lhs.checkpoint_fs == rhs.checkpoint_fs &&
+ lhs.first_stage_mount == rhs.first_stage_mount &&
+ lhs.slot_select_other == rhs.slot_select_other &&
+ lhs.fs_verity == rhs.fs_verity;
+ // clang-format on
+}
+
} // namespace
TEST(fs_mgr, fs_mgr_parse_cmdline) {
@@ -291,19 +321,18 @@
EXPECT_EQ(i, fstab.size());
}
-// TODO(124837435): enable it later when it can pass TreeHugger.
-TEST(fs_mgr, DISABLED_ReadFstabFromFile_MountOptions) {
+TEST(fs_mgr, ReadFstabFromFile_MountOptions) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
std::string fstab_contents = R"fs(
-source / ext4 ro,barrier=1 wait,slotselect,avb
+source / ext4 ro,barrier=1 wait,avb
source /metadata ext4 noatime,nosuid,nodev,discard wait,formattable
source /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,sysfs_path=/sys/devices/platform/soc/1d84000.ufshc,reservedsize=128M
source /misc emmc defaults defaults
-source /vendor/firmware_mnt vfat ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait,slotselect
+source /vendor/firmware_mnt vfat ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait
source auto vfat defaults voldmanaged=usb:auto
source none swap defaults zramsize=1073741824,max_comp_streams=8
@@ -316,94 +345,75 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(11U, fstab.size());
+ ASSERT_LE(11U, fstab.size());
- EXPECT_EQ("/", fstab[0].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), fstab[0].flags);
- EXPECT_EQ("barrier=1", fstab[0].fs_options);
+ FstabEntry* entry = GetEntryForMountPoint(&fstab, "/");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
+ EXPECT_EQ("barrier=1", entry->fs_options);
- EXPECT_EQ("/metadata", fstab[1].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), fstab[1].flags);
- EXPECT_EQ("discard", fstab[1].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "/metadata");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
+ EXPECT_EQ("discard", entry->fs_options);
- EXPECT_EQ("/data", fstab[2].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), fstab[2].flags);
- EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", fstab[2].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "/data");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
+ EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", entry->fs_options);
- EXPECT_EQ("/misc", fstab[3].mount_point);
- EXPECT_EQ(0U, fstab[3].flags);
- EXPECT_EQ("", fstab[3].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "/misc");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(0U, entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("/vendor/firmware_mnt", fstab[4].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), fstab[4].flags);
+ entry = GetEntryForMountPoint(&fstab, "/vendor/firmware_mnt");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
EXPECT_EQ(
"shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,"
"context=u:object_r:firmware_file:s0",
- fstab[4].fs_options);
+ entry->fs_options);
- EXPECT_EQ("auto", fstab[5].mount_point);
- EXPECT_EQ(0U, fstab[5].flags);
- EXPECT_EQ("", fstab[5].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "auto");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(0U, entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none", fstab[6].mount_point);
- EXPECT_EQ(0U, fstab[6].flags);
- EXPECT_EQ("", fstab[6].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(0U, entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none2", fstab[7].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), fstab[7].flags);
- EXPECT_EQ("", fstab[7].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none2");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none3", fstab[8].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), fstab[8].flags);
- EXPECT_EQ("", fstab[8].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none3");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none4", fstab[9].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), fstab[9].flags);
- EXPECT_EQ("", fstab[9].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none4");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none5", fstab[10].mount_point);
- EXPECT_EQ(0U, fstab[10].flags); // rw is the same as defaults
- EXPECT_EQ("", fstab[10].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none5");
+ ASSERT_NE(nullptr, entry);
+ // rw is the default.
+ EXPECT_EQ(0U, entry->flags);
+ EXPECT_EQ("", entry->fs_options);
}
-static bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
- // clang-format off
- return lhs.wait == rhs.wait &&
- lhs.check == rhs.check &&
- lhs.crypt == rhs.crypt &&
- lhs.nonremovable == rhs.nonremovable &&
- lhs.vold_managed == rhs.vold_managed &&
- lhs.recovery_only == rhs.recovery_only &&
- lhs.verify == rhs.verify &&
- lhs.force_crypt == rhs.force_crypt &&
- lhs.no_emulated_sd == rhs.no_emulated_sd &&
- lhs.no_trim == rhs.no_trim &&
- lhs.file_encryption == rhs.file_encryption &&
- lhs.formattable == rhs.formattable &&
- lhs.slot_select == rhs.slot_select &&
- lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
- lhs.late_mount == rhs.late_mount &&
- lhs.no_fail == rhs.no_fail &&
- lhs.verify_at_boot == rhs.verify_at_boot &&
- lhs.quota == rhs.quota &&
- lhs.avb == rhs.avb &&
- lhs.logical == rhs.logical &&
- lhs.checkpoint_blk == rhs.checkpoint_blk &&
- lhs.checkpoint_fs == rhs.checkpoint_fs &&
- lhs.first_stage_mount == rhs.first_stage_mount &&
- lhs.slot_select_other == rhs.slot_select_other &&
- lhs.fs_verity == rhs.fs_verity;
- // clang-format on
-}
-
-// TODO(124837435): enable it later when it can pass TreeHugger.
-TEST(fs_mgr, DISABLED_ReadFstabFromFile_FsMgrFlags) {
+TEST(fs_mgr, ReadFstabFromFile_FsMgrFlags) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
std::string fstab_contents = R"fs(
source none0 swap defaults wait,check,nonremovable,recoveryonly,verifyatboot,verify
-source none1 swap defaults avb,noemulatedsd,notrim,formattable,slotselect,nofail
-source none2 swap defaults first_stage_mount,latemount,quota,logical,slotselect_other
+source none1 swap defaults avb,noemulatedsd,notrim,formattable,nofail
+source none2 swap defaults first_stage_mount,latemount,quota,logical
source none3 swap defaults checkpoint=block
source none4 swap defaults checkpoint=fs
source none5 swap defaults defaults
@@ -412,10 +422,10 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(6U, fstab.size());
+ ASSERT_LE(6U, fstab.size());
- auto entry = fstab.begin();
- EXPECT_EQ("none0", entry->mount_point);
+ FstabEntry* entry = GetEntryForMountPoint(&fstab, "none0");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.wait = true;
@@ -426,50 +436,48 @@
flags.verify = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none1", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none1");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.avb = true;
flags.no_emulated_sd = true;
flags.no_trim = true;
flags.formattable = true;
- flags.slot_select = true;
flags.no_fail = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none2", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none2");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.first_stage_mount = true;
flags.late_mount = true;
flags.quota = true;
flags.logical = true;
- flags.slot_select_other = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none3", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none3");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.checkpoint_blk = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none4", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none4");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.checkpoint_fs = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none5", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none5");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
@@ -491,7 +499,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(3U, fstab.size());
+ ASSERT_LE(3U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -561,7 +569,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
flags.crypt = true;
@@ -585,7 +593,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
flags.vold_managed = true;
@@ -626,7 +634,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -652,7 +660,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -682,7 +690,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(6U, fstab.size());
+ ASSERT_LE(6U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -728,7 +736,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -751,7 +759,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -775,7 +783,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
flags.file_encryption = true;
@@ -797,7 +805,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -825,7 +833,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -863,7 +871,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -901,7 +909,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -938,7 +946,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -967,7 +975,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -989,7 +997,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("adiantum", entry->metadata_encryption);
@@ -1006,7 +1014,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("aes-256-xts:wrappedkey_v0", entry->metadata_encryption);
@@ -1027,7 +1035,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -1053,7 +1061,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
auto entry = fstab.begin();
@@ -1114,7 +1122,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(7U, fstab.size());
+ ASSERT_LE(7U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
diff --git a/init/README.md b/init/README.md
index 4a262c9..75dc328 100644
--- a/init/README.md
+++ b/init/README.md
@@ -277,6 +277,8 @@
CLD_EXITED or an status other than '0', reboot the system with the target specified in
_target_. _target_ takes the same format as the parameter to sys.powerctl. This is particularly
intended to be used with the `exec_start` builtin for any must-have checks during boot.
+ A service being stopped by init (e.g. using the `stop` or `class_reset` commands) is not
+ considered a failure for the purpose of this setting.
`restart_period <seconds>`
> If a non-oneshot service exits, it will be restarted at its start time plus
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
index 3ffca88..2401da3 100644
--- a/init/README.ueventd.md
+++ b/init/README.ueventd.md
@@ -123,13 +123,20 @@
The exact firmware file to be served can be customized by running an external program by a
`external_firmware_handler` line in a ueventd.rc file. This line takes the format of
- external_firmware_handler <devpath> <user name to run as> <path to external program>
+ external_firmware_handler <devpath> <user [group]> <path to external program>
+
+The handler will be run as the given user, or if a group is provided, as the given user and group.
+
For example
external_firmware_handler /devices/leds/red/firmware/coeffs.bin system /vendor/bin/led_coeffs.bin
Will launch `/vendor/bin/led_coeffs.bin` as the system user instead of serving the default firmware
for `/devices/leds/red/firmware/coeffs.bin`.
+The `devpath` argument may include asterisks (`*`) to match multiple paths. For example, the string
+`/dev/*/red` will match `/dev/leds/red` as well as `/dev/lights/red`. The pattern matching follows
+the rules of the fnmatch() function.
+
Ueventd will provide the uevent `DEVPATH` and `FIRMWARE` to this external program on the environment
via environment variables with the same names. Ueventd will use the string written to stdout as the
new name of the firmware to load. It will still look for the new firmware in the list of firmware
@@ -156,3 +163,13 @@
from enabling the parallelization option:
parallel_restorecon enabled
+
+Do parallel restorecon to speed up boot process, subdirectories under `/sys`
+can be sliced by ueventd.rc, and run on multiple process.
+ parallel_restorecon_dir <directory>
+
+For example
+ parallel_restorecon_dir /sys
+ parallel_restorecon_dir /sys/devices
+ parallel_restorecon_dir /sys/devices/platform
+ parallel_restorecon_dir /sys/devices/platform/soc
diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp
index ba7e6bd..30e808d 100644
--- a/init/firmware_handler.cpp
+++ b/init/firmware_handler.cpp
@@ -17,7 +17,9 @@
#include "firmware_handler.h"
#include <fcntl.h>
+#include <fnmatch.h>
#include <glob.h>
+#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
@@ -46,6 +48,20 @@
namespace android {
namespace init {
+namespace {
+bool PrefixMatch(const std::string& pattern, const std::string& path) {
+ return android::base::StartsWith(path, pattern);
+}
+
+bool FnMatch(const std::string& pattern, const std::string& path) {
+ return fnmatch(pattern.c_str(), path.c_str(), 0) == 0;
+}
+
+bool EqualMatch(const std::string& pattern, const std::string& path) {
+ return pattern == path;
+}
+} // namespace
+
static void LoadFirmware(const std::string& firmware, const std::string& root, int fw_fd,
size_t fw_size, int loading_fd, int data_fd) {
// Start transfer.
@@ -66,13 +82,33 @@
return access("/dev/.booting", F_OK) == 0;
}
+ExternalFirmwareHandler::ExternalFirmwareHandler(std::string devpath, uid_t uid, gid_t gid,
+ std::string handler_path)
+ : devpath(std::move(devpath)), uid(uid), gid(gid), handler_path(std::move(handler_path)) {
+ auto wildcard_position = this->devpath.find('*');
+ if (wildcard_position != std::string::npos) {
+ if (wildcard_position == this->devpath.length() - 1) {
+ this->devpath.pop_back();
+ match = std::bind(PrefixMatch, this->devpath, std::placeholders::_1);
+ } else {
+ match = std::bind(FnMatch, this->devpath, std::placeholders::_1);
+ }
+ } else {
+ match = std::bind(EqualMatch, this->devpath, std::placeholders::_1);
+ }
+}
+
+ExternalFirmwareHandler::ExternalFirmwareHandler(std::string devpath, uid_t uid,
+ std::string handler_path)
+ : ExternalFirmwareHandler(devpath, uid, 0, handler_path) {}
+
FirmwareHandler::FirmwareHandler(std::vector<std::string> firmware_directories,
std::vector<ExternalFirmwareHandler> external_firmware_handlers)
: firmware_directories_(std::move(firmware_directories)),
external_firmware_handlers_(std::move(external_firmware_handlers)) {}
Result<std::string> FirmwareHandler::RunExternalHandler(const std::string& handler, uid_t uid,
- const Uevent& uevent) const {
+ gid_t gid, const Uevent& uevent) const {
unique_fd child_stdout;
unique_fd parent_stdout;
if (!Socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &child_stdout, &parent_stdout)) {
@@ -109,6 +145,13 @@
}
c_args.emplace_back(nullptr);
+ if (gid != 0) {
+ if (setgid(gid) != 0) {
+ fprintf(stderr, "setgid() failed: %s", strerror(errno));
+ _exit(EXIT_FAILURE);
+ }
+ }
+
if (setuid(uid) != 0) {
fprintf(stderr, "setuid() failed: %s", strerror(errno));
_exit(EXIT_FAILURE);
@@ -160,13 +203,13 @@
std::string FirmwareHandler::GetFirmwarePath(const Uevent& uevent) const {
for (const auto& external_handler : external_firmware_handlers_) {
- if (external_handler.devpath == uevent.path) {
+ if (external_handler.match(uevent.path)) {
LOG(INFO) << "Launching external firmware handler '" << external_handler.handler_path
<< "' for devpath: '" << uevent.path << "' firmware: '" << uevent.firmware
<< "'";
- auto result =
- RunExternalHandler(external_handler.handler_path, external_handler.uid, uevent);
+ auto result = RunExternalHandler(external_handler.handler_path, external_handler.uid,
+ external_handler.gid, uevent);
if (!result.ok()) {
LOG(ERROR) << "Using default firmware; External firmware handler failed: "
<< result.error();
diff --git a/init/firmware_handler.h b/init/firmware_handler.h
index 8b758ae..d2f7347 100644
--- a/init/firmware_handler.h
+++ b/init/firmware_handler.h
@@ -16,6 +16,7 @@
#pragma once
+#include <grp.h>
#include <pwd.h>
#include <functional>
@@ -30,11 +31,15 @@
namespace init {
struct ExternalFirmwareHandler {
- ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path)
- : devpath(std::move(devpath)), uid(uid), handler_path(std::move(handler_path)) {}
+ ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path);
+ ExternalFirmwareHandler(std::string devpath, uid_t uid, gid_t gid, std::string handler_path);
+
std::string devpath;
uid_t uid;
+ gid_t gid;
std::string handler_path;
+
+ std::function<bool(const std::string&)> match;
};
class FirmwareHandler : public UeventHandler {
@@ -49,7 +54,7 @@
friend void FirmwareTestWithExternalHandler(const std::string& test_name,
bool expect_new_firmware);
- Result<std::string> RunExternalHandler(const std::string& handler, uid_t uid,
+ Result<std::string> RunExternalHandler(const std::string& handler, uid_t uid, gid_t gid,
const Uevent& uevent) const;
std::string GetFirmwarePath(const Uevent& uevent) const;
void ProcessFirmwareEvent(const std::string& root, const std::string& firmware) const;
diff --git a/init/firmware_handler_test.cpp b/init/firmware_handler_test.cpp
index 5124a6f..f6e75b0 100644
--- a/init/firmware_handler_test.cpp
+++ b/init/firmware_handler_test.cpp
@@ -103,6 +103,23 @@
return 0;
}
+TEST(firmware_handler, Matching) {
+ ExternalFirmwareHandler h("/dev/path/a.bin", getuid(), "/test");
+ ASSERT_TRUE(h.match("/dev/path/a.bin"));
+ ASSERT_FALSE(h.match("/dev/path/a.bi"));
+
+ h = ExternalFirmwareHandler("/dev/path/a.*", getuid(), "/test");
+ ASSERT_TRUE(h.match("/dev/path/a.bin"));
+ ASSERT_TRUE(h.match("/dev/path/a.bix"));
+ ASSERT_FALSE(h.match("/dev/path/b.bin"));
+
+ h = ExternalFirmwareHandler("/dev/*/a.bin", getuid(), "/test");
+ ASSERT_TRUE(h.match("/dev/path/a.bin"));
+ ASSERT_TRUE(h.match("/dev/other/a.bin"));
+ ASSERT_FALSE(h.match("/dev/other/c.bin"));
+ ASSERT_FALSE(h.match("/dev/path/b.bin"));
+}
+
} // namespace init
} // namespace android
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 5cde167..78e5b60 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -327,6 +327,19 @@
LOG(INFO) << "Copied ramdisk prop to " << dest;
}
+ // If "/force_debuggable" is present, the second-stage init will use a userdebug
+ // sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked.
+ if (access("/force_debuggable", F_OK) == 0) {
+ std::error_code ec; // to invoke the overloaded copy_file() that won't throw.
+ if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
+ !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
+ LOG(ERROR) << "Failed to setup debug ramdisk";
+ } else {
+ // setenv for second-stage init to read above kDebugRamdisk* files.
+ setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
+ }
+ }
+
if (ForceNormalBoot(cmdline, bootconfig)) {
mkdir("/first_stage_ramdisk", 0755);
// SwitchRoot() must be called with a mount point as the target, so we bind mount the
@@ -337,28 +350,6 @@
SwitchRoot("/first_stage_ramdisk");
}
- std::string force_debuggable("/force_debuggable");
- std::string adb_debug_prop("/adb_debug.prop");
- std::string userdebug_sepolicy("/userdebug_plat_sepolicy.cil");
- if (IsRecoveryMode()) {
- // Update these file paths since we didn't switch root
- force_debuggable.insert(0, "/first_stage_ramdisk");
- adb_debug_prop.insert(0, "/first_stage_ramdisk");
- userdebug_sepolicy.insert(0, "/first_stage_ramdisk");
- }
- // If this file is present, the second-stage init will use a userdebug sepolicy
- // and load adb_debug.prop to allow adb root, if the device is unlocked.
- if (access(force_debuggable.c_str(), F_OK) == 0) {
- std::error_code ec; // to invoke the overloaded copy_file() that won't throw.
- if (!fs::copy_file(adb_debug_prop, kDebugRamdiskProp, ec) ||
- !fs::copy_file(userdebug_sepolicy, kDebugRamdiskSEPolicy, ec)) {
- LOG(ERROR) << "Failed to setup debug ramdisk";
- } else {
- // setenv for second-stage init to read above kDebugRamdisk* files.
- setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
- }
- }
-
if (!DoFirstStageMount(!created_devices)) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 3faf430..f5c10bb 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -420,6 +420,10 @@
*end = begin + 1;
}
+ if (!fs_mgr_create_canonical_mount_point(begin->mount_point)) {
+ return false;
+ }
+
if (begin->fs_mgr_flags.logical) {
if (!fs_mgr_update_logical_partition(&(*begin))) {
return false;
@@ -542,6 +546,12 @@
continue;
}
+ // Handle overlayfs entries later.
+ if (current->fs_type == "overlay") {
+ ++current;
+ continue;
+ }
+
// Skip raw partition entries such as boot, dtbo, etc.
// Having emmc fstab entries allows us to probe current->vbmeta_partition
// in InitDevices() when they are AVB chained partitions.
@@ -566,6 +576,12 @@
current = end;
}
+ for (const auto& entry : fstab_) {
+ if (entry.fs_type == "overlay") {
+ fs_mgr_mount_overlayfs_fstab_entry(entry);
+ }
+ }
+
// If we don't see /system or / in the fstab, then we need to create an root entry for
// overlayfs.
if (!GetEntryForMountPoint(&fstab_, "/system") && !GetEntryForMountPoint(&fstab_, "/")) {
@@ -677,6 +693,10 @@
// Includes the partition names of fstab records.
// Notes that fstab_rec->blk_device has A/B suffix updated by fs_mgr when A/B is used.
for (const auto& fstab_entry : fstab_) {
+ // Skip pseudo filesystems.
+ if (fstab_entry.fs_type == "overlay") {
+ continue;
+ }
if (!fstab_entry.fs_mgr_flags.logical) {
devices->emplace(basename(fstab_entry.blk_device.c_str()));
}
@@ -739,6 +759,10 @@
if (fstab_entry.fs_mgr_flags.avb) {
need_dm_verity_ = true;
}
+ // Skip pseudo filesystems.
+ if (fstab_entry.fs_type == "overlay") {
+ continue;
+ }
if (fstab_entry.fs_mgr_flags.logical) {
// Don't try to find logical partitions via uevent regeneration.
logical_partitions.emplace(basename(fstab_entry.blk_device.c_str()));
diff --git a/init/init.cpp b/init/init.cpp
index 7264b22..a7325ca 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -849,21 +849,6 @@
auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
SetProperty(gsi::kGsiInstalledProp, is_installed);
- /*
- * For debug builds of S launching devices, init mounts debugfs for
- * enabling vendor debug data collection setup at boot time. Init will unmount it on
- * boot-complete after vendor code has performed the required initializations
- * during boot. Dumpstate will then mount debugfs in order to read data
- * from the same using the dumpstate HAL during bugreport creation.
- * Dumpstate will also unmount debugfs after bugreport creation.
- * first_api_level comparison is done here instead
- * of init.rc since init.rc parser does not support >/< operators.
- */
- auto api_level = android::base::GetIntProperty("ro.product.first_api_level", 0);
- bool is_debuggable = android::base::GetBoolProperty("ro.debuggable", false);
- auto mount_debugfs = (is_debuggable && (api_level >= 31)) ? "1" : "0";
- SetProperty("init.mount_debugfs", mount_debugfs);
-
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
diff --git a/init/property_service.cpp b/init/property_service.cpp
index c2eb73c..ff9da42 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -94,6 +94,12 @@
namespace android {
namespace init {
+constexpr auto FINGERPRINT_PROP = "ro.build.fingerprint";
+constexpr auto LEGACY_FINGERPRINT_PROP = "ro.build.legacy.fingerprint";
+constexpr auto ID_PROP = "ro.build.id";
+constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
+constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
+constexpr auto DIGEST_SIZE_USED = 8;
static bool persistent_properties_loaded = false;
@@ -857,15 +863,33 @@
}
}
-// If the ro.build.fingerprint property has not been set, derive it from constituent pieces
-static void property_derive_build_fingerprint() {
- std::string build_fingerprint = GetProperty("ro.build.fingerprint", "");
- if (!build_fingerprint.empty()) {
+static void property_initialize_build_id() {
+ std::string build_id = GetProperty(ID_PROP, "");
+ if (!build_id.empty()) {
return;
}
+ std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, "");
+ std::string vbmeta_digest = GetProperty(VBMETA_DIGEST_PROP, "");
+ if (vbmeta_digest.size() < DIGEST_SIZE_USED) {
+ LOG(ERROR) << "vbmeta digest size too small " << vbmeta_digest;
+ // Still try to set the id field in the unexpected case.
+ build_id = legacy_build_id;
+ } else {
+ // Derive the ro.build.id by appending the vbmeta digest to the base value.
+ build_id = legacy_build_id + "." + vbmeta_digest.substr(0, DIGEST_SIZE_USED);
+ }
+
+ std::string error;
+ auto res = PropertySet(ID_PROP, build_id, &error);
+ if (res != PROP_SUCCESS) {
+ LOG(ERROR) << "Failed to set " << ID_PROP << " to " << build_id;
+ }
+}
+
+static std::string ConstructBuildFingerprint(bool legacy) {
const std::string UNKNOWN = "unknown";
- build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
+ std::string build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
build_fingerprint += '/';
build_fingerprint += GetProperty("ro.product.name", UNKNOWN);
build_fingerprint += '/';
@@ -873,7 +897,10 @@
build_fingerprint += ':';
build_fingerprint += GetProperty("ro.build.version.release_or_codename", UNKNOWN);
build_fingerprint += '/';
- build_fingerprint += GetProperty("ro.build.id", UNKNOWN);
+
+ std::string build_id =
+ legacy ? GetProperty(LEGACY_ID_PROP, UNKNOWN) : GetProperty(ID_PROP, UNKNOWN);
+ build_fingerprint += build_id;
build_fingerprint += '/';
build_fingerprint += GetProperty("ro.build.version.incremental", UNKNOWN);
build_fingerprint += ':';
@@ -881,13 +908,49 @@
build_fingerprint += '/';
build_fingerprint += GetProperty("ro.build.tags", UNKNOWN);
- LOG(INFO) << "Setting property 'ro.build.fingerprint' to '" << build_fingerprint << "'";
+ return build_fingerprint;
+}
+
+// Derive the legacy build fingerprint if we overwrite the build id at runtime.
+static void property_derive_legacy_build_fingerprint() {
+ std::string legacy_build_fingerprint = GetProperty(LEGACY_FINGERPRINT_PROP, "");
+ if (!legacy_build_fingerprint.empty()) {
+ return;
+ }
+
+ // The device doesn't have a legacy build id, skipping the legacy fingerprint.
+ std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, "");
+ if (legacy_build_id.empty()) {
+ return;
+ }
+
+ legacy_build_fingerprint = ConstructBuildFingerprint(true /* legacy fingerprint */);
+ LOG(INFO) << "Setting property '" << LEGACY_FINGERPRINT_PROP << "' to '"
+ << legacy_build_fingerprint << "'";
std::string error;
- uint32_t res = PropertySet("ro.build.fingerprint", build_fingerprint, &error);
+ uint32_t res = PropertySet(LEGACY_FINGERPRINT_PROP, legacy_build_fingerprint, &error);
if (res != PROP_SUCCESS) {
- LOG(ERROR) << "Error setting property 'ro.build.fingerprint': err=" << res << " (" << error
- << ")";
+ LOG(ERROR) << "Error setting property '" << LEGACY_FINGERPRINT_PROP << "': err=" << res
+ << " (" << error << ")";
+ }
+}
+
+// If the ro.build.fingerprint property has not been set, derive it from constituent pieces
+static void property_derive_build_fingerprint() {
+ std::string build_fingerprint = GetProperty("ro.build.fingerprint", "");
+ if (!build_fingerprint.empty()) {
+ return;
+ }
+
+ build_fingerprint = ConstructBuildFingerprint(false /* legacy fingerprint */);
+ LOG(INFO) << "Setting property '" << FINGERPRINT_PROP << "' to '" << build_fingerprint << "'";
+
+ std::string error;
+ uint32_t res = PropertySet(FINGERPRINT_PROP, build_fingerprint, &error);
+ if (res != PROP_SUCCESS) {
+ LOG(ERROR) << "Error setting property '" << FINGERPRINT_PROP << "': err=" << res << " ("
+ << error << ")";
}
}
@@ -1035,7 +1098,9 @@
}
property_initialize_ro_product_props();
+ property_initialize_build_id();
property_derive_build_fingerprint();
+ property_derive_legacy_build_fingerprint();
property_initialize_ro_cpu_abilist();
update_sys_usb_config();
@@ -1165,68 +1230,28 @@
constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv;
-// emulator specific, should be removed once emulator is migrated to
-// bootconfig, see b/182291166.
-static std::string RemapEmulatorPropertyName(const std::string_view qemu_key) {
- if (StartsWith(qemu_key, "dalvik."sv) || StartsWith(qemu_key, "opengles."sv) ||
- StartsWith(qemu_key, "config."sv)) {
- return std::string(qemu_key);
- } else if (qemu_key == "uirenderer"sv) {
- return "debug.hwui.renderer"s;
- } else if (qemu_key == "media.ccodec"sv) {
- return "debug.stagefright.ccodec"s;
- } else {
- return "qemu."s + std::string(qemu_key);
- }
-}
-
static void ProcessKernelCmdline() {
ImportKernelCmdline([&](const std::string& key, const std::string& value) {
- constexpr auto qemu_prefix = "qemu."sv;
-
if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
- } else if (StartsWith(key, qemu_prefix)) {
- InitPropertySet("ro.kernel." + key, value); // emulator specific, deprecated
-
- // emulator specific, should be retired once emulator migrates to
- // androidboot.
- const auto new_name =
- RemapEmulatorPropertyName(std::string_view(key).substr(qemu_prefix.size()));
- if (!new_name.empty()) {
- InitPropertySet("ro.boot." + new_name, value);
- }
- } else if (key == "qemu") {
- // emulator specific, should be retired once emulator migrates to
- // androidboot.
- InitPropertySet("ro.boot." + key, value);
- } else if (key == "android.bootanim" && value == "0") {
- // emulator specific, should be retired once emulator migrates to
- // androidboot.
- InitPropertySet("ro.boot.debug.sf.nobootanimation", "1");
- } else if (key == "android.checkjni") {
- // emulator specific, should be retired once emulator migrates to
- // androidboot.
- std::string value_bool;
- if (value == "0") {
- value_bool = "false";
- } else if (value == "1") {
- value_bool = "true";
- } else {
- value_bool = value;
- }
- InitPropertySet("ro.boot.dalvik.vm.checkjni", value_bool);
}
});
}
+// bootconfig does not allow to populate `key=value` simultaneously with
+// `key.subkey=value` which does not work with the existing code for
+// `hardware` (e.g. we want both `ro.boot.hardware=value` and
+// `ro.boot.hardware.sku=value`) and for `qemu` (Android Stidio Emulator
+// specific).
+static bool IsAllowedBootconfigKey(const std::string_view key) {
+ return (key == "hardware"sv) || (key == "qemu"sv);
+}
+
static void ProcessBootconfig() {
ImportBootconfig([&](const std::string& key, const std::string& value) {
if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
- } else if (key == "hardware") {
- // "hardware" in bootconfig replaces "androidboot.hardware" kernel
- // cmdline parameter
+ } else if (IsAllowedBootconfigKey(key)) {
InitPropertySet("ro.boot." + key, value);
}
});
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index c6dcfa2..ac6b7b2 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -23,6 +23,7 @@
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
+#include <android-base/strings.h>
#include <gtest/gtest.h>
using android::base::GetProperty;
@@ -90,5 +91,39 @@
EXPECT_FALSE(SetProperty("sys.powerctl", "reboot,userspace"));
}
+TEST(property_service, check_fingerprint_with_legacy_build_id) {
+ std::string legacy_build_id = GetProperty("ro.build.legacy.id", "");
+ if (legacy_build_id.empty()) {
+ GTEST_SKIP() << "Skipping test, legacy build id isn't set.";
+ }
+
+ std::string vbmeta_digest = GetProperty("ro.boot.vbmeta.digest", "");
+ ASSERT_GE(vbmeta_digest.size(), 8u);
+ std::string build_id = GetProperty("ro.boot.build.id", "");
+ // Check that the build id is constructed with the prefix of vbmeta digest
+ std::string expected_build_id = legacy_build_id + "." + vbmeta_digest.substr(0, 8);
+ ASSERT_EQ(expected_build_id, build_id);
+ // Check that the fingerprint is constructed with the expected format.
+ std::string fingerprint = GetProperty("ro.build.fingerprint", "");
+ std::vector<std::string> fingerprint_fields = {
+ GetProperty("ro.product.brand", ""),
+ "/",
+ GetProperty("ro.product.name", ""),
+ "/",
+ GetProperty("ro.product.device", ""),
+ ":",
+ GetProperty("ro.build.version.release_or_codename", ""),
+ "/",
+ expected_build_id,
+ "/",
+ GetProperty("ro.build.version.incremental", ""),
+ ":",
+ GetProperty("ro.build.type", ""),
+ "/",
+ GetProperty("ro.build.tags", "")};
+
+ ASSERT_EQ(android::base::Join(fingerprint_fields, ""), fingerprint);
+}
+
} // namespace init
} // namespace android
diff --git a/init/reboot.cpp b/init/reboot.cpp
index d9acee5..0e788e4 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -450,10 +450,22 @@
// zram is able to use backing device on top of a loopback device.
// In order to unmount /data successfully, we have to kill the loopback device first
-#define ZRAM_DEVICE "/dev/block/zram0"
-#define ZRAM_RESET "/sys/block/zram0/reset"
-#define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev"
+#define ZRAM_DEVICE "/dev/block/zram0"
+#define ZRAM_RESET "/sys/block/zram0/reset"
+#define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev"
+#define ZRAM_INITSTATE "/sys/block/zram0/initstate"
static Result<void> KillZramBackingDevice() {
+ std::string zram_initstate;
+ if (!android::base::ReadFileToString(ZRAM_INITSTATE, &zram_initstate)) {
+ return ErrnoError() << "Failed to read " << ZRAM_INITSTATE;
+ }
+
+ zram_initstate.erase(zram_initstate.length() - 1);
+ if (zram_initstate == "0") {
+ LOG(INFO) << "Zram has not been swapped on";
+ return {};
+ }
+
if (access(ZRAM_BACK_DEV, F_OK) != 0 && errno == ENOENT) {
LOG(INFO) << "No zram backing device configured";
return {};
@@ -466,6 +478,11 @@
// cut the last "\n"
backing_dev.erase(backing_dev.length() - 1);
+ if (android::base::StartsWith(backing_dev, "none")) {
+ LOG(INFO) << "No zram backing device configured";
+ return {};
+ }
+
// shutdown zram handle
Timer swap_timer;
LOG(INFO) << "swapoff() start...";
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 2d3e06e..42d3023 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -240,25 +240,25 @@
}
// Use precompiled sepolicy only when all corresponding hashes are equal.
- // plat_sepolicy is always checked, while system_ext and product are checked only when they
- // exist.
std::vector<std::pair<std::string, std::string>> sepolicy_hashes{
{"/system/etc/selinux/plat_sepolicy_and_mapping.sha256",
precompiled_sepolicy + ".plat_sepolicy_and_mapping.sha256"},
+ {"/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
+ precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256"},
+ {"/product/etc/selinux/product_sepolicy_and_mapping.sha256",
+ precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256"},
};
- if (access("/system_ext/etc/selinux/system_ext_sepolicy.cil", F_OK) == 0) {
- sepolicy_hashes.emplace_back(
- "/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
- precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256");
- }
-
- if (access("/product/etc/selinux/product_sepolicy.cil", F_OK) == 0) {
- sepolicy_hashes.emplace_back("/product/etc/selinux/product_sepolicy_and_mapping.sha256",
- precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256");
- }
-
for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) {
+ // Both of them should exist or both of them shouldn't exist.
+ if (access(actual_id_path.c_str(), R_OK) != 0) {
+ if (access(precompiled_id_path.c_str(), R_OK) == 0) {
+ return Error() << precompiled_id_path << " exists but " << actual_id_path
+ << " doesn't";
+ }
+ continue;
+ }
+
std::string actual_id;
if (!ReadFirstLine(actual_id_path.c_str(), &actual_id)) {
return ErrnoError() << "Failed to read " << actual_id_path;
@@ -372,6 +372,12 @@
system_ext_mapping_file.clear();
}
+ std::string system_ext_compat_cil_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +
+ ".compat.cil");
+ if (access(system_ext_compat_cil_file.c_str(), F_OK) == -1) {
+ system_ext_compat_cil_file.clear();
+ }
+
std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");
if (access(product_policy_cil_file.c_str(), F_OK) == -1) {
product_policy_cil_file.clear();
@@ -426,6 +432,9 @@
if (!system_ext_mapping_file.empty()) {
compile_args.push_back(system_ext_mapping_file.c_str());
}
+ if (!system_ext_compat_cil_file.empty()) {
+ compile_args.push_back(system_ext_compat_cil_file.c_str());
+ }
if (!product_policy_cil_file.empty()) {
compile_args.push_back(product_policy_cil_file.c_str());
}
diff --git a/init/service.cpp b/init/service.cpp
index 836dc47..5af81bf 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -194,6 +194,8 @@
<< ") process group...";
int max_processes = 0;
int r;
+
+ flags_ |= SVC_STOPPING;
if (signal == SIGTERM) {
r = killProcessGroupOnce(proc_attr_.uid, pid_, signal, &max_processes);
} else {
@@ -277,7 +279,8 @@
f(siginfo);
}
- if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_) {
+ if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_ &&
+ !(flags_ & SVC_STOPPING)) {
LOG(ERROR) << "Service with 'reboot_on_failure' option failed, shutting down system.";
trigger_shutdown(*on_failure_reboot_target_);
}
@@ -287,7 +290,7 @@
if (flags_ & SVC_TEMPORARY) return;
pid_ = 0;
- flags_ &= (~SVC_RUNNING);
+ flags_ &= ~(SVC_RUNNING | SVC_STOPPING);
start_order_ = 0;
// Oneshot processes go into the disabled state on exit,
@@ -411,7 +414,8 @@
bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET));
// Starting a service removes it from the disabled or reset state and
// immediately takes it out of the restarting state if it was in there.
- flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
+ flags_ &= (~(SVC_DISABLED | SVC_RESTARTING | SVC_RESET | SVC_RESTART | SVC_DISABLED_START |
+ SVC_STOPPING));
// Running processes require no additional work --- if they're in the
// process of exiting, we've ensured that they will immediately restart
@@ -460,7 +464,11 @@
scon = *result;
}
- if (!IsDefaultMountNamespaceReady() && name_ != "apexd") {
+ // APEXd is always started in the "current" namespace because it is the process to set up
+ // the current namespace.
+ const bool is_apexd = args_[0] == "/system/bin/apexd";
+
+ if (!IsDefaultMountNamespaceReady() && !is_apexd) {
// If this service is started before APEXes and corresponding linker configuration
// get available, mark it as pre-apexd one. Note that this marking is
// permanent. So for example, if the service is re-launched (e.g., due
diff --git a/init/service.h b/init/service.h
index 043555f..89b1f09 100644
--- a/init/service.h
+++ b/init/service.h
@@ -54,6 +54,7 @@
// should not be killed during shutdown
#define SVC_TEMPORARY 0x1000 // This service was started by 'exec' and should be removed from the
// service list once it is reaped.
+#define SVC_STOPPING 0x2000 // service is being stopped by init
#define NR_SVC_SUPP_GIDS 12 // twelve supplementary groups
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 331255b..68c6b51 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -115,11 +115,13 @@
public:
ColdBoot(UeventListener& uevent_listener,
std::vector<std::unique_ptr<UeventHandler>>& uevent_handlers,
- bool enable_parallel_restorecon)
+ bool enable_parallel_restorecon,
+ std::vector<std::string> parallel_restorecon_queue)
: uevent_listener_(uevent_listener),
uevent_handlers_(uevent_handlers),
num_handler_subprocesses_(std::thread::hardware_concurrency() ?: 4),
- enable_parallel_restorecon_(enable_parallel_restorecon) {}
+ enable_parallel_restorecon_(enable_parallel_restorecon),
+ parallel_restorecon_queue_(parallel_restorecon_queue) {}
void Run();
@@ -142,6 +144,8 @@
std::set<pid_t> subprocess_pids_;
std::vector<std::string> restorecon_queue_;
+
+ std::vector<std::string> parallel_restorecon_queue_;
};
void ColdBoot::UeventHandlerMain(unsigned int process_num, unsigned int total_processes) {
@@ -155,17 +159,34 @@
}
void ColdBoot::RestoreConHandler(unsigned int process_num, unsigned int total_processes) {
+ android::base::Timer t_process;
+
for (unsigned int i = process_num; i < restorecon_queue_.size(); i += total_processes) {
+ android::base::Timer t;
auto& dir = restorecon_queue_[i];
selinux_android_restorecon(dir.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE);
+
+ //Mark a dir restorecon operation for 50ms,
+ //Maybe you can add this dir to the ueventd.rc script to parallel processing
+ if (t.duration() > 50ms) {
+ LOG(INFO) << "took " << t.duration().count() <<"ms restorecon '"
+ << dir.c_str() << "' on process '" << process_num <<"'";
+ }
}
+
+ //Calculate process restorecon time
+ LOG(VERBOSE) << "took " << t_process.duration().count() << "ms on process '"
+ << process_num << "'";
}
void ColdBoot::GenerateRestoreCon(const std::string& directory) {
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(directory.c_str()), &closedir);
- if (!dir) return;
+ if (!dir) {
+ PLOG(WARNING) << "opendir " << directory.c_str();
+ return;
+ }
struct dirent* dent;
while ((dent = readdir(dir.get())) != NULL) {
@@ -176,7 +197,10 @@
if (S_ISDIR(st.st_mode)) {
std::string fullpath = directory + "/" + dent->d_name;
- if (fullpath != "/sys/devices") {
+ auto parallel_restorecon =
+ std::find(parallel_restorecon_queue_.begin(),
+ parallel_restorecon_queue_.end(), fullpath);
+ if (parallel_restorecon == parallel_restorecon_queue_.end()) {
restorecon_queue_.emplace_back(fullpath);
}
}
@@ -248,11 +272,16 @@
RegenerateUevents();
if (enable_parallel_restorecon_) {
- selinux_android_restorecon("/sys", 0);
- selinux_android_restorecon("/sys/devices", 0);
- GenerateRestoreCon("/sys");
- // takes long time for /sys/devices, parallelize it
- GenerateRestoreCon("/sys/devices");
+ if (parallel_restorecon_queue_.empty()) {
+ parallel_restorecon_queue_.emplace_back("/sys");
+ // takes long time for /sys/devices, parallelize it
+ parallel_restorecon_queue_.emplace_back("/sys/devices");
+ LOG(INFO) << "Parallel processing directory is not set, set the default";
+ }
+ for (const auto& dir : parallel_restorecon_queue_) {
+ selinux_android_restorecon(dir.c_str(), 0);
+ GenerateRestoreCon(dir);
+ }
}
ForkSubProcesses();
@@ -268,14 +297,27 @@
}
static UeventdConfiguration GetConfiguration() {
- // TODO: Remove these legacy paths once Android S is no longer supported.
+ auto hardware = android::base::GetProperty("ro.hardware", "");
+ std::vector<std::string> legacy_paths{"/vendor/ueventd.rc", "/odm/ueventd.rc",
+ "/ueventd." + hardware + ".rc"};
+
+ std::vector<std::string> canonical{"/system/etc/ueventd.rc"};
+
if (android::base::GetIntProperty("ro.product.first_api_level", 10000) <= __ANDROID_API_S__) {
- auto hardware = android::base::GetProperty("ro.hardware", "");
- return ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc", "/odm/ueventd.rc",
- "/ueventd." + hardware + ".rc"});
+ // TODO: Remove these legacy paths once Android S is no longer supported.
+ canonical.insert(canonical.end(), legacy_paths.begin(), legacy_paths.end());
+ } else {
+ // Warn if newer device is using legacy paths.
+ for (const auto& path : legacy_paths) {
+ if (access(path.c_str(), F_OK) == 0) {
+ LOG(FATAL_WITHOUT_ABORT)
+ << "Legacy ueventd configuration file detected and will not be parsed: "
+ << path;
+ }
+ }
}
- return ParseConfig({"/system/etc/ueventd.rc"});
+ return ParseConfig(canonical);
}
int ueventd_main(int argc, char** argv) {
@@ -313,7 +355,8 @@
if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) {
ColdBoot cold_boot(uevent_listener, uevent_handlers,
- ueventd_configuration.enable_parallel_restorecon);
+ ueventd_configuration.enable_parallel_restorecon,
+ ueventd_configuration.parallel_restorecon_dirs);
cold_boot.Run();
}
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index 2221228..d34672e 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -101,8 +101,8 @@
Result<void> ParseExternalFirmwareHandlerLine(
std::vector<std::string>&& args,
std::vector<ExternalFirmwareHandler>* external_firmware_handlers) {
- if (args.size() != 4) {
- return Error() << "external_firmware_handler lines must have exactly 3 parameters";
+ if (args.size() != 4 && args.size() != 5) {
+ return Error() << "external_firmware_handler lines must have 3 or 4 parameters";
}
if (std::find_if(external_firmware_handlers->begin(), external_firmware_handlers->end(),
@@ -117,7 +117,19 @@
return ErrnoError() << "invalid handler uid'" << args[2] << "'";
}
- ExternalFirmwareHandler handler(std::move(args[1]), pwd->pw_uid, std::move(args[3]));
+ gid_t gid = 0;
+ int handler_index = 3;
+ if (args.size() == 5) {
+ struct group* grp = getgrnam(args[3].c_str());
+ if (!grp) {
+ return ErrnoError() << "invalid handler gid '" << args[3] << "'";
+ }
+ gid = grp->gr_gid;
+ handler_index = 4;
+ }
+
+ ExternalFirmwareHandler handler(std::move(args[1]), pwd->pw_uid, gid,
+ std::move(args[handler_index]));
external_firmware_handlers->emplace_back(std::move(handler));
return {};
@@ -139,6 +151,17 @@
return {};
}
+Result<void> ParseParallelRestoreconDirsLine(std::vector<std::string>&& args,
+ std::vector<std::string>* parallel_restorecon_dirs) {
+ if (args.size() != 2) {
+ return Error() << "parallel_restorecon_dir lines must have exactly 2 parameters";
+ }
+
+ std::move(std::next(args.begin()), args.end(), std::back_inserter(*parallel_restorecon_dirs));
+
+ return {};
+}
+
Result<void> ParseUeventSocketRcvbufSizeLine(std::vector<std::string>&& args,
size_t* uevent_socket_rcvbuf_size) {
if (args.size() != 2) {
@@ -256,6 +279,9 @@
parser.AddSingleLineParser("uevent_socket_rcvbuf_size",
std::bind(ParseUeventSocketRcvbufSizeLine, _1,
&ueventd_configuration.uevent_socket_rcvbuf_size));
+ parser.AddSingleLineParser("parallel_restorecon_dir",
+ std::bind(ParseParallelRestoreconDirsLine, _1,
+ &ueventd_configuration.parallel_restorecon_dirs));
parser.AddSingleLineParser("parallel_restorecon",
std::bind(ParseEnabledDisabledLine, _1,
&ueventd_configuration.enable_parallel_restorecon));
diff --git a/init/ueventd_parser.h b/init/ueventd_parser.h
index eaafa5a..81f4e9d 100644
--- a/init/ueventd_parser.h
+++ b/init/ueventd_parser.h
@@ -31,6 +31,7 @@
std::vector<Permissions> dev_permissions;
std::vector<std::string> firmware_directories;
std::vector<ExternalFirmwareHandler> external_firmware_handlers;
+ std::vector<std::string> parallel_restorecon_dirs;
bool enable_modalias_handling = false;
size_t uevent_socket_rcvbuf_size = 0;
bool enable_parallel_restorecon = false;
diff --git a/init/ueventd_parser_test.cpp b/init/ueventd_parser_test.cpp
index 4e63ba5..41924e2 100644
--- a/init/ueventd_parser_test.cpp
+++ b/init/ueventd_parser_test.cpp
@@ -49,6 +49,7 @@
const ExternalFirmwareHandler& test) {
EXPECT_EQ(expected.devpath, test.devpath) << expected.devpath;
EXPECT_EQ(expected.uid, test.uid) << expected.uid;
+ EXPECT_EQ(expected.gid, test.gid) << expected.gid;
EXPECT_EQ(expected.handler_path, test.handler_path) << expected.handler_path;
}
@@ -76,6 +77,7 @@
EXPECT_EQ(expected.firmware_directories, result.firmware_directories);
TestVector(expected.external_firmware_handlers, result.external_firmware_handlers,
TestExternalFirmwareHandler);
+ EXPECT_EQ(expected.parallel_restorecon_dirs, result.parallel_restorecon_dirs);
}
TEST(ueventd_parser, EmptyFile) {
@@ -104,7 +106,7 @@
{"test_devname2", Subsystem::DEVNAME_UEVENT_DEVNAME, "/dev"},
{"test_devpath_dirname", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/graphics"}};
- TestUeventdFile(ueventd_file, {subsystems, {}, {}, {}, {}});
+ TestUeventdFile(ueventd_file, {subsystems, {}, {}, {}, {}, {}});
}
TEST(ueventd_parser, Permissions) {
@@ -130,7 +132,7 @@
{"/sys/devices/virtual/*/input", "poll_delay", 0660, AID_ROOT, AID_INPUT, true},
};
- TestUeventdFile(ueventd_file, {{}, sysfs_permissions, permissions, {}, {}});
+ TestUeventdFile(ueventd_file, {{}, sysfs_permissions, permissions, {}, {}, {}});
}
TEST(ueventd_parser, FirmwareDirectories) {
@@ -146,7 +148,7 @@
"/more",
};
- TestUeventdFile(ueventd_file, {{}, {}, {}, firmware_directories, {}});
+ TestUeventdFile(ueventd_file, {{}, {}, {}, firmware_directories, {}, {}});
}
TEST(ueventd_parser, ExternalFirmwareHandlers) {
@@ -154,27 +156,65 @@
external_firmware_handler devpath root handler_path
external_firmware_handler /devices/path/firmware/something001.bin system /vendor/bin/firmware_handler.sh
external_firmware_handler /devices/path/firmware/something002.bin radio "/vendor/bin/firmware_handler.sh --has --arguments"
+external_firmware_handler /devices/path/firmware/* root "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/firmware/something* system "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/*/firmware/something*.bin radio "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/firmware/something003.bin system system /vendor/bin/firmware_handler.sh
+external_firmware_handler /devices/path/firmware/something004.bin radio radio "/vendor/bin/firmware_handler.sh --has --arguments"
)";
auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
{
"devpath",
AID_ROOT,
+ AID_ROOT,
"handler_path",
},
{
"/devices/path/firmware/something001.bin",
AID_SYSTEM,
+ AID_ROOT,
"/vendor/bin/firmware_handler.sh",
},
{
"/devices/path/firmware/something002.bin",
AID_RADIO,
+ AID_ROOT,
+ "/vendor/bin/firmware_handler.sh --has --arguments",
+ },
+ {
+ "/devices/path/firmware/",
+ AID_ROOT,
+ AID_ROOT,
+ "/vendor/bin/firmware_handler.sh",
+ },
+ {
+ "/devices/path/firmware/something",
+ AID_SYSTEM,
+ AID_ROOT,
+ "/vendor/bin/firmware_handler.sh",
+ },
+ {
+ "/devices/path/*/firmware/something*.bin",
+ AID_RADIO,
+ AID_ROOT,
+ "/vendor/bin/firmware_handler.sh",
+ },
+ {
+ "/devices/path/firmware/something003.bin",
+ AID_SYSTEM,
+ AID_SYSTEM,
+ "/vendor/bin/firmware_handler.sh",
+ },
+ {
+ "/devices/path/firmware/something004.bin",
+ AID_RADIO,
+ AID_RADIO,
"/vendor/bin/firmware_handler.sh --has --arguments",
},
};
- TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers});
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers, {}});
}
TEST(ueventd_parser, ExternalFirmwareHandlersDuplicate) {
@@ -187,11 +227,26 @@
{
"devpath",
AID_ROOT,
+ AID_ROOT,
"handler_path",
},
};
- TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers});
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers, {}});
+}
+
+TEST(ueventd_parser, ParallelRestoreconDirs) {
+ auto ueventd_file = R"(
+parallel_restorecon_dir /sys
+parallel_restorecon_dir /sys/devices
+)";
+
+ auto parallel_restorecon_dirs = std::vector<std::string>{
+ "/sys",
+ "/sys/devices",
+ };
+
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, parallel_restorecon_dirs});
}
TEST(ueventd_parser, UeventSocketRcvbufSize) {
@@ -200,7 +255,7 @@
uevent_socket_rcvbuf_size 8M
)";
- TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, false, 8 * 1024 * 1024});
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, {}, false, 8 * 1024 * 1024});
}
TEST(ueventd_parser, EnabledDisabledLines) {
@@ -210,7 +265,7 @@
modalias_handling disabled
)";
- TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, false, 0, true});
+ TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, {}, false, 0, true});
auto ueventd_file2 = R"(
parallel_restorecon enabled
@@ -218,7 +273,7 @@
parallel_restorecon disabled
)";
- TestUeventdFile(ueventd_file2, {{}, {}, {}, {}, {}, true, 0, false});
+ TestUeventdFile(ueventd_file2, {{}, {}, {}, {}, {}, {}, true, 0, false});
}
TEST(ueventd_parser, AllTogether) {
@@ -258,6 +313,9 @@
modalias_handling enabled
parallel_restorecon enabled
+parallel_restorecon_dir /sys
+parallel_restorecon_dir /sys/devices
+
#ending comment
)";
@@ -287,14 +345,20 @@
};
auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
- {"/devices/path/firmware/firmware001.bin", AID_ROOT, "/vendor/bin/touch.sh"},
+ {"/devices/path/firmware/firmware001.bin", AID_ROOT, AID_ROOT, "/vendor/bin/touch.sh"},
+ };
+
+ auto parallel_restorecon_dirs = std::vector<std::string>{
+ "/sys",
+ "/sys/devices",
};
size_t uevent_socket_rcvbuf_size = 6 * 1024 * 1024;
TestUeventdFile(ueventd_file,
{subsystems, sysfs_permissions, permissions, firmware_directories,
- external_firmware_handlers, true, uevent_socket_rcvbuf_size, true});
+ external_firmware_handlers, parallel_restorecon_dirs, true,
+ uevent_socket_rcvbuf_size, true});
}
// All of these lines are ill-formed, so test that there is 0 output.
@@ -326,6 +390,9 @@
external_firmware_handler blah blah
external_firmware_handler blah blah blah blah
+parallel_restorecon_dir
+parallel_restorecon_dir /sys /sys/devices
+
)";
TestUeventdFile(ueventd_file, {});
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0f3763c..68b21c6 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -362,9 +362,10 @@
source_stem: "bindings",
local_include_dirs: ["include"],
bindgen_flags: [
- "--whitelist-function", "multiuser_get_app_id",
- "--whitelist-function", "multiuser_get_user_id",
- "--whitelist-function", "multiuser_get_uid",
- "--whitelist-var", "AID_USER_OFFSET",
+ "--allowlist-function", "multiuser_get_app_id",
+ "--allowlist-function", "multiuser_get_uid",
+ "--allowlist-function", "multiuser_get_user_id",
+ "--allowlist-var", "AID_KEYSTORE",
+ "--allowlist-var", "AID_USER_OFFSET",
],
}
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 7489281..c471fa0 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -127,7 +127,9 @@
#define AID_EXT_DATA_RW 1078 /* GID for app-private data directories on external storage */
#define AID_EXT_OBB_RW 1079 /* GID for OBB directories on external storage */
#define AID_CONTEXT_HUB 1080 /* GID for access to the Context Hub */
-#define AID_VIRTMANAGER 1081 /* VirtManager daemon */
+#define AID_VIRTUALIZATIONSERVICE 1081 /* VirtualizationService daemon */
+#define AID_ARTD 1082 /* ART Service daemon */
+#define AID_UWB 1083 /* UWB subsystem */
/* Changes to this file must be made in AOSP, *not* in internal branches. */
#define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libprocessgroup/cgrouprc/Android.bp b/libprocessgroup/cgrouprc/Android.bp
index 0cbe0cc..7522cfe 100644
--- a/libprocessgroup/cgrouprc/Android.bp
+++ b/libprocessgroup/cgrouprc/Android.bp
@@ -28,7 +28,9 @@
// defined below. The static library is built for tests.
vendor_available: false,
native_bridge_supported: true,
- llndk_stubs: "libcgrouprc.llndk",
+ llndk: {
+ symbol_file: "libcgrouprc.map.txt",
+ },
srcs: [
"cgroup_controller.cpp",
"cgroup_file.cpp",
@@ -59,12 +61,3 @@
},
},
}
-
-llndk_library {
- name: "libcgrouprc.llndk",
- symbol_file: "libcgrouprc.map.txt",
- native_bridge_supported: true,
- export_include_dirs: [
- "include",
- ],
-}
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 5b57bdd..bd94621 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -70,11 +70,11 @@
"Name": "Frozen",
"Actions": [
{
- "Name": "JoinCgroup",
+ "Name": "SetAttribute",
"Params":
{
- "Controller": "freezer",
- "Path": ""
+ "Name": "FreezerState",
+ "Value": "1"
}
}
]
@@ -83,11 +83,11 @@
"Name": "Unfrozen",
"Actions": [
{
- "Name": "JoinCgroup",
+ "Name": "SetAttribute",
"Params":
{
- "Controller": "freezer",
- "Path": "../"
+ "Name": "FreezerState",
+ "Value": "0"
}
}
]
@@ -558,32 +558,6 @@
}
]
},
- {
- "Name": "FreezerDisabled",
- "Actions": [
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "FreezerState",
- "Value": "0"
- }
- }
- ]
- },
- {
- "Name": "FreezerEnabled",
- "Actions": [
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "FreezerState",
- "Value": "1"
- }
- }
- ]
- }
],
"AggregateProfiles": [
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index c51ee61..1a4196a 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -159,10 +159,9 @@
if (!controller.IsUsable()) return -1;
- if (!controller.GetTaskGroup(tid, &subgroup)) {
- LOG(ERROR) << "Failed to find cgroup for tid " << tid;
+ if (!controller.GetTaskGroup(tid, &subgroup))
return -1;
- }
+
return 0;
}
@@ -174,11 +173,16 @@
std::string group;
if (schedboost_enabled()) {
if ((getCGroupSubsys(tid, "schedtune", group) < 0) &&
- (getCGroupSubsys(tid, "cpu", group) < 0))
- return -1;
+ (getCGroupSubsys(tid, "cpu", group) < 0)) {
+ LOG(ERROR) << "Failed to find cpu cgroup for tid " << tid;
+ return -1;
+ }
}
if (group.empty() && cpusets_enabled()) {
- if (getCGroupSubsys(tid, "cpuset", group) < 0) return -1;
+ if (getCGroupSubsys(tid, "cpuset", group) < 0) {
+ LOG(ERROR) << "Failed to find cpuset cgroup for tid " << tid;
+ return -1;
+ }
}
// TODO: replace hardcoded directories
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index f13a681..cf74e65 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -308,9 +308,7 @@
bool ApplyProfileAction::ExecuteForTask(int tid) const {
for (const auto& profile : profiles_) {
- if (!profile->ExecuteForTask(tid)) {
- PLOG(WARNING) << "ExecuteForTask failed for aggregate profile";
- }
+ profile->ExecuteForTask(tid);
}
return true;
}
@@ -518,10 +516,10 @@
std::string attr_filepath = params_val["FilePath"].asString();
std::string attr_value = params_val["Value"].asString();
if (!attr_filepath.empty() && !attr_value.empty()) {
- const Json::Value& logfailures = params_val["LogFailures"];
- bool attr_logfailures = logfailures.isNull() || logfailures.asBool();
+ std::string attr_logfailures = params_val["LogFailures"].asString();
+ bool logfailures = attr_logfailures.empty() || attr_logfailures == "true";
profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value,
- attr_logfailures));
+ logfailures));
} else if (attr_filepath.empty()) {
LOG(WARNING) << "WriteFile: invalid parameter: "
<< "empty filepath";
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index 354c7b3..2a89e29 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -25,10 +25,10 @@
source_stem: "bindings",
bindgen_flags: [
"--size_t-is-usize",
- "--whitelist-function=AStatsEventList_addStatsEvent",
- "--whitelist-function=AStatsEvent_.*",
- "--whitelist-function=AStatsManager_.*",
- "--whitelist-var=AStatsManager_.*",
+ "--allowlist-function=AStatsEventList_addStatsEvent",
+ "--allowlist-function=AStatsEvent_.*",
+ "--allowlist-function=AStatsManager_.*",
+ "--allowlist-var=AStatsManager_.*",
],
target: {
android: {
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
index 4b2f40e..819066e 100644
--- a/libstats/push_compat/Android.bp
+++ b/libstats/push_compat/Android.bp
@@ -55,7 +55,7 @@
export_header_lib_headers: [
"libstatssocket_headers",
],
- static_libs: ["libgtest_prod"],
+ header_libs: ["libgtest_prod_headers"],
apex_available: ["com.android.resolv"],
min_sdk_version: "29",
}
diff --git a/libsync/Android.bp b/libsync/Android.bp
index 540a246..99c88cf 100644
--- a/libsync/Android.bp
+++ b/libsync/Android.bp
@@ -42,7 +42,9 @@
recovery_available: true,
native_bridge_supported: true,
defaults: ["libsync_defaults"],
- llndk_stubs: "libsync.llndk",
+ llndk: {
+ symbol_file: "libsync.map.txt",
+ },
stubs: {
symbol_file: "libsync.map.txt",
versions: [
@@ -51,12 +53,6 @@
},
}
-llndk_library {
- name: "libsync.llndk",
- symbol_file: "libsync.map.txt",
- export_include_dirs: ["include"],
-}
-
cc_test {
name: "sync-unit-tests",
shared_libs: ["libsync"],
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 6201569..13e4c02 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -141,6 +141,7 @@
"Errors.cpp",
"FileMap.cpp",
"JenkinsHash.cpp",
+ "LightRefBase.cpp",
"NativeHandle.cpp",
"Printer.cpp",
"RefBase.cpp",
@@ -274,12 +275,6 @@
}
cc_fuzz {
- name: "libutils_fuzz_stopwatch",
- defaults: ["libutils_fuzz_defaults"],
- srcs: ["StopWatch_fuzz.cpp"],
-}
-
-cc_fuzz {
name: "libutils_fuzz_refbase",
defaults: ["libutils_fuzz_defaults"],
srcs: ["RefBase_fuzz.cpp"],
diff --git a/libutils/LightRefBase.cpp b/libutils/LightRefBase.cpp
new file mode 100644
index 0000000..e08ffec
--- /dev/null
+++ b/libutils/LightRefBase.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "LightRefBase"
+
+#include <utils/LightRefBase.h>
+
+#include <log/log.h>
+
+namespace android {
+
+void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz) {
+ LOG_ALWAYS_FATAL("incStrongRequireStrong() called on %p which isn't already owned", thiz);
+}
+
+} // namespace android
diff --git a/libutils/README b/libutils/README
deleted file mode 100644
index 01741e0..0000000
--- a/libutils/README
+++ /dev/null
@@ -1,289 +0,0 @@
-Android Utility Function Library
-================================
-
-
-If you need a feature that is native to Linux but not present on other
-platforms, construct a platform-dependent implementation that shares
-the Linux interface. That way the actual device runs as "light" as
-possible.
-
-If that isn't feasible, create a system-independent interface and hide
-the details.
-
-The ultimate goal is *not* to create a super-duper platform abstraction
-layer. The goal is to provide an optimized solution for Linux with
-reasonable implementations for other platforms.
-
-
-
-Resource overlay
-================
-
-
-Introduction
-------------
-
-Overlay packages are special .apk files which provide no code but
-additional resource values (and possibly new configurations) for
-resources in other packages. When an application requests resources,
-the system will return values from either the application's original
-package or any associated overlay package. Any redirection is completely
-transparent to the calling application.
-
-Resource values have the following precedence table, listed in
-descending precedence.
-
- * overlay package, matching config (eg res/values-en-land)
-
- * original package, matching config
-
- * overlay package, no config (eg res/values)
-
- * original package, no config
-
-During compilation, overlay packages are differentiated from regular
-packages by passing the -o flag to aapt.
-
-
-Background
-----------
-
-This section provides generic background material on resources in
-Android.
-
-
-How resources are bundled in .apk files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Android .apk files are .zip files, usually housing .dex code,
-certificates and resources, though packages containing resources but
-no code are possible. Resources can be divided into the following
-categories; a `configuration' indicates a set of phone language, display
-density, network operator, etc.
-
- * assets: uncompressed, raw files packaged as part of an .apk and
- explicitly referenced by filename. These files are
- independent of configuration.
-
- * res/drawable: bitmap or xml graphics. Each file may have different
- values depending on configuration.
-
- * res/values: integers, strings, etc. Each resource may have different
- values depending on configuration.
-
-Resource meta information and information proper is stored in a binary
-format in a named file resources.arsc, bundled as part of the .apk.
-
-Resource IDs and lookup
-~~~~~~~~~~~~~~~~~~~~~~~
-During compilation, the aapt tool gathers application resources and
-generates a resources.arsc file. Each resource name is assigned an
-integer ID 0xppttiii (translated to a symbolic name via R.java), where
-
- * pp: corresponds to the package namespace (details below).
-
- * tt: corresponds to the resource type (string, int, etc). Every
- resource of the same type within the same package has the same
- tt value, but depending on available types, the actual numerical
- value may be different between packages.
-
- * iiii: sequential number, assigned in the order resources are found.
-
-Resource values are specified paired with a set of configuration
-constraints (the default being the empty set), eg res/values-sv-port
-which imposes restrictions on language (Swedish) and display orientation
-(portrait). During lookup, every constraint set is matched against the
-current configuration, and the value corresponding to the best matching
-constraint set is returned (ResourceTypes.{h,cpp}).
-
-Parsing of resources.arsc is handled by ResourceTypes.cpp; this utility
-is governed by AssetManager.cpp, which tracks loaded resources per
-process.
-
-Assets are looked up by path and filename in AssetManager.cpp. The path
-to resources in res/drawable are located by ResourceTypes.cpp and then
-handled like assets by AssetManager.cpp. Other resources are handled
-solely by ResourceTypes.cpp.
-
-Package ID as namespace
-~~~~~~~~~~~~~~~~~~~~~~~
-The pp part of a resource ID defines a namespace. Android currently
-defines two namespaces:
-
- * 0x01: system resources (pre-installed in framework-res.apk)
-
- * 0x7f: application resources (bundled in the application .apk)
-
-ResourceTypes.cpp supports package IDs between 0x01 and 0x7f
-(inclusive); values outside this range are invalid.
-
-Each running (Dalvik) process is assigned a unique instance of
-AssetManager, which in turn keeps a forest structure of loaded
-resource.arsc files. Normally, this forest is structured as follows,
-where mPackageMap is the internal vector employed in ResourceTypes.cpp.
-
-mPackageMap[0x00] -> system package
-mPackageMap[0x01] -> NULL
-mPackageMap[0x02] -> NULL
-...
-mPackageMap[0x7f - 2] -> NULL
-mPackageMap[0x7f - 1] -> application package
-
-
-
-The resource overlay extension
-------------------------------
-
-The resource overlay mechanism aims to (partly) shadow and extend
-existing resources with new values for defined and new configurations.
-Technically, this is achieved by adding resource-only packages (called
-overlay packages) to existing resource namespaces, like so:
-
-mPackageMap[0x00] -> system package -> system overlay package
-mPackageMap[0x01] -> NULL
-mPackageMap[0x02] -> NULL
-...
-mPackageMap[0x7f - 2] -> NULL
-mPackageMap[0x7f - 1] -> application package -> overlay 1 -> overlay 2
-
-The use of overlay resources is completely transparent to
-applications; no additional resource identifiers are introduced, only
-configuration/value pairs. Any number of overlay packages may be loaded
-at a time; overlay packages are agnostic to what they target -- both
-system and application resources are fair game.
-
-The package targeted by an overlay package is called the target or
-original package.
-
-Resource overlay operates on symbolic resources names. Hence, to
-override the string/str1 resources in a package, the overlay package
-would include a resource also named string/str1. The end user does not
-have to worry about the numeric resources IDs assigned by aapt, as this
-is resolved automatically by the system.
-
-As of this writing, the use of resource overlay has not been fully
-explored. Until it has, only OEMs are trusted to use resource overlay.
-For this reason, overlay packages must reside in /system/overlay.
-
-
-Resource ID mapping
-~~~~~~~~~~~~~~~~~~~
-Resource identifiers must be coherent within the same namespace (ie
-PackageGroup in ResourceTypes.cpp). Calling applications will refer to
-resources using the IDs defined in the original package, but there is no
-guarantee aapt has assigned the same ID to the corresponding resource in
-an overlay package. To translate between the two, a resource ID mapping
-{original ID -> overlay ID} is created during package installation
-(PackageManagerService.java) and used during resource lookup. The
-mapping is stored in /data/resource-cache, with a @idmap file name
-suffix.
-
-The idmap file format is documented in a separate section, below.
-
-
-Package management
-~~~~~~~~~~~~~~~~~~
-Packages are managed by the PackageManagerService. Addition and removal
-of packages are monitored via the inotify framework, exposed via
-android.os.FileObserver.
-
-During initialization of a Dalvik process, ActivityThread.java requests
-the process' AssetManager (by proxy, via AssetManager.java and JNI)
-to load a list of packages. This list includes overlay packages, if
-present.
-
-When a target package or a corresponding overlay package is installed,
-the target package's process is stopped and a new idmap is generated.
-This is similar to how applications are stopped when their packages are
-upgraded.
-
-
-Creating overlay packages
--------------------------
-
-Overlay packages should contain no code, define (some) resources with
-the same type and name as in the original package, and be compiled with
-the -o flag passed to aapt.
-
-The aapt -o flag instructs aapt to create an overlay package.
-Technically, this means the package will be assigned package id 0x00.
-
-There are no restrictions on overlay packages names, though the naming
-convention <original.package.name>.overlay.<name> is recommended.
-
-
-Example overlay package
-~~~~~~~~~~~~~~~~~~~~~~~
-
-To overlay the resource bool/b in package com.foo.bar, to be applied
-when the display is in landscape mode, create a new package with
-no source code and a single .xml file under res/values-land, with
-an entry for bool/b. Compile with aapt -o and place the results in
-/system/overlay by adding the following to Android.mk:
-
-LOCAL_AAPT_FLAGS := -o com.foo.bar
-LOCAL_MODULE_PATH := $(TARGET_OUT)/overlay
-
-
-The ID map (idmap) file format
-------------------------------
-
-The idmap format is designed for lookup performance. However, leading
-and trailing undefined overlay values are discarded to reduce the memory
-footprint.
-
-
-idmap grammar
-~~~~~~~~~~~~~
-All atoms (names in square brackets) are uint32_t integers. The
-idmap-magic constant spells "idmp" in ASCII. Offsets are given relative
-to the data_header, not to the beginning of the file.
-
-map := header data
-header := idmap-magic <crc32-original-pkg> <crc32-overlay-pkg>
-idmap-magic := <0x706d6469>
-data := data_header type_block+
-data_header := <m> header_block{m}
-header_block := <0> | <type_block_offset>
-type_block := <n> <id_offset> entry{n}
-entry := <resource_id_in_target_package>
-
-
-idmap example
-~~~~~~~~~~~~~
-Given a pair of target and overlay packages with CRC sums 0x216a8fe2
-and 0x6b9beaec, each defining the following resources
-
-Name Target package Overlay package
-string/str0 0x7f010000 -
-string/str1 0x7f010001 0x7f010000
-string/str2 0x7f010002 -
-string/str3 0x7f010003 0x7f010001
-string/str4 0x7f010004 -
-bool/bool0 0x7f020000 -
-integer/int0 0x7f030000 0x7f020000
-integer/int1 0x7f030001 -
-
-the corresponding resource map is
-
-0x706d6469 0x216a8fe2 0x6b9beaec 0x00000003 \
-0x00000004 0x00000000 0x00000009 0x00000003 \
-0x00000001 0x7f010000 0x00000000 0x7f010001 \
-0x00000001 0x00000000 0x7f020000
-
-or, formatted differently
-
-0x706d6469 # magic: all idmap files begin with this constant
-0x216a8fe2 # CRC32 of the resources.arsc file in the original package
-0x6b9beaec # CRC32 of the resources.arsc file in the overlay package
-0x00000003 # header; three types (string, bool, integer) in the target package
-0x00000004 # header_block for type 0 (string) is located at offset 4
-0x00000000 # no bool type exists in overlay package -> no header_block
-0x00000009 # header_block for type 2 (integer) is located at offset 9
-0x00000003 # header_block for string; overlay IDs span 3 elements
-0x00000001 # the first string in target package is entry 1 == offset
-0x7f010000 # target 0x7f01001 -> overlay 0x7f010000
-0x00000000 # str2 not defined in overlay package
-0x7f010001 # target 0x7f010003 -> overlay 0x7f010001
-0x00000001 # header_block for integer; overlay IDs span 1 element
-0x00000000 # offset == 0
-0x7f020000 # target 0x7f030000 -> overlay 0x7f020000
diff --git a/libutils/SharedBuffer_test.cpp b/libutils/SharedBuffer_test.cpp
index 3f960d2..1d6317f 100644
--- a/libutils/SharedBuffer_test.cpp
+++ b/libutils/SharedBuffer_test.cpp
@@ -32,10 +32,25 @@
EXPECT_DEATH(android::SharedBuffer::alloc(SIZE_MAX - sizeof(android::SharedBuffer)), "");
}
-TEST(SharedBufferTest, alloc_null) {
- // Big enough to fail, not big enough to abort.
+TEST(SharedBufferTest, alloc_max) {
SKIP_WITH_HWASAN; // hwasan has a 2GiB allocation limit.
- ASSERT_EQ(nullptr, android::SharedBuffer::alloc(SIZE_MAX / 2));
+
+ android::SharedBuffer* buf =
+ android::SharedBuffer::alloc(SIZE_MAX - sizeof(android::SharedBuffer) - 1);
+ if (buf != nullptr) {
+ EXPECT_NE(nullptr, buf->data());
+ buf->release();
+ }
+}
+
+TEST(SharedBufferTest, alloc_big) {
+ SKIP_WITH_HWASAN; // hwasan has a 2GiB allocation limit.
+
+ android::SharedBuffer* buf = android::SharedBuffer::alloc(SIZE_MAX / 2);
+ if (buf != nullptr) {
+ EXPECT_NE(nullptr, buf->data());
+ buf->release();
+ }
}
TEST(SharedBufferTest, alloc_zero_size) {
@@ -56,7 +71,13 @@
// Big enough to fail, not big enough to abort.
SKIP_WITH_HWASAN; // hwasan has a 2GiB allocation limit.
android::SharedBuffer* buf = android::SharedBuffer::alloc(10);
- ASSERT_EQ(nullptr, buf->editResize(SIZE_MAX / 2));
+ android::SharedBuffer* buf2 = buf->editResize(SIZE_MAX / 2);
+ if (buf2 == nullptr) {
+ buf->release();
+ } else {
+ EXPECT_NE(nullptr, buf2->data());
+ buf2->release();
+ }
}
TEST(SharedBufferTest, editResize_zero_size) {
diff --git a/libutils/StopWatch.cpp b/libutils/StopWatch.cpp
index d01865e..28e2d76 100644
--- a/libutils/StopWatch.cpp
+++ b/libutils/StopWatch.cpp
@@ -26,58 +26,26 @@
#include <utils/Log.h>
-/*****************************************************************************/
-
namespace android {
StopWatch::StopWatch(const char* name, int clock) : mName(name), mClock(clock) {
reset();
}
-StopWatch::~StopWatch()
-{
- nsecs_t elapsed = elapsedTime();
- const int n = mNumLaps;
- ALOGD("StopWatch %s (us): %" PRId64 " ", mName, ns2us(elapsed));
- for (int i=0 ; i<n ; i++) {
- const nsecs_t soFar = mLaps[i].soFar;
- const nsecs_t thisLap = mLaps[i].thisLap;
- ALOGD(" [%d: %" PRId64 ", %" PRId64, i, ns2us(soFar), ns2us(thisLap));
- }
+StopWatch::~StopWatch() {
+ ALOGD("StopWatch %s (us): %" PRId64 " ", name(), ns2us(elapsedTime()));
}
-const char* StopWatch::name() const
-{
+const char* StopWatch::name() const {
return mName;
}
-nsecs_t StopWatch::lap()
-{
- nsecs_t elapsed = elapsedTime();
- if (mNumLaps >= 8) {
- elapsed = 0;
- } else {
- const int n = mNumLaps;
- mLaps[n].soFar = elapsed;
- mLaps[n].thisLap = n ? (elapsed - mLaps[n-1].soFar) : elapsed;
- mNumLaps = n+1;
- }
- return elapsed;
-}
-
-nsecs_t StopWatch::elapsedTime() const
-{
+nsecs_t StopWatch::elapsedTime() const {
return systemTime(mClock) - mStartTime;
}
-void StopWatch::reset()
-{
- mNumLaps = 0;
+void StopWatch::reset() {
mStartTime = systemTime(mClock);
}
-
-/*****************************************************************************/
-
-}; // namespace android
-
+} // namespace android
diff --git a/libutils/StopWatch_fuzz.cpp b/libutils/StopWatch_fuzz.cpp
deleted file mode 100644
index 63d8a28..0000000
--- a/libutils/StopWatch_fuzz.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2020 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 "fuzzer/FuzzedDataProvider.h"
-#include "utils/StopWatch.h"
-
-static constexpr int MAX_OPERATIONS = 100;
-static constexpr int MAX_NAME_LEN = 2048;
-
-static const std::vector<std::function<void(android::StopWatch)>> operations = {
- [](android::StopWatch stopWatch) -> void { stopWatch.reset(); },
- [](android::StopWatch stopWatch) -> void { stopWatch.lap(); },
- [](android::StopWatch stopWatch) -> void { stopWatch.elapsedTime(); },
- [](android::StopWatch stopWatch) -> void { stopWatch.name(); },
-};
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
- FuzzedDataProvider dataProvider(data, size);
- std::string nameStr = dataProvider.ConsumeRandomLengthString(MAX_NAME_LEN);
- int clockVal = dataProvider.ConsumeIntegral<int>();
- android::StopWatch stopWatch = android::StopWatch(nameStr.c_str(), clockVal);
- std::vector<uint8_t> opsToRun = dataProvider.ConsumeRemainingBytes<uint8_t>();
- int opsRun = 0;
- for (auto it : opsToRun) {
- if (opsRun++ >= MAX_OPERATIONS) {
- break;
- }
- it = it % operations.size();
- operations[it](stopWatch);
- }
- return 0;
-}
diff --git a/libutils/String16.cpp b/libutils/String16.cpp
index e3e5f11..faf90c2 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -411,36 +411,4 @@
return OK;
}
-status_t String16::remove(size_t len, size_t begin)
-{
- const size_t N = size();
- if (begin >= N) {
- release();
- mString = getEmptyString();
- return OK;
- }
- if (len > N || len > N - begin) len = N - begin;
- if (begin == 0 && len == N) {
- return OK;
- }
-
- if (begin > 0) {
- SharedBuffer* buf = static_cast<SharedBuffer*>(editResize((N + 1) * sizeof(char16_t)));
- if (!buf) {
- return NO_MEMORY;
- }
- char16_t* str = (char16_t*)buf->data();
- memmove(str, str+begin, (N-begin+1)*sizeof(char16_t));
- mString = str;
- }
- SharedBuffer* buf = static_cast<SharedBuffer*>(editResize((len + 1) * sizeof(char16_t)));
- if (buf) {
- char16_t* str = (char16_t*)buf->data();
- str[len] = 0;
- mString = str;
- return OK;
- }
- return NO_MEMORY;
-}
-
}; // namespace android
diff --git a/libutils/String16_fuzz.cpp b/libutils/String16_fuzz.cpp
index defa0f5..d7e5ec7 100644
--- a/libutils/String16_fuzz.cpp
+++ b/libutils/String16_fuzz.cpp
@@ -72,12 +72,6 @@
char16_t replaceChar = dataProvider.ConsumeIntegral<char16_t>();
str1.replaceAll(findChar, replaceChar);
}),
- ([](FuzzedDataProvider& dataProvider, android::String16 str1,
- android::String16) -> void {
- size_t len = dataProvider.ConsumeIntegral<size_t>();
- size_t begin = dataProvider.ConsumeIntegral<size_t>();
- str1.remove(len, begin);
- }),
};
void callFunc(uint8_t index, FuzzedDataProvider& dataProvider, android::String16 str1,
@@ -111,7 +105,5 @@
callFunc(op, dataProvider, str_one_utf16, str_two_utf16);
}
- str_one_utf16.remove(0, str_one_utf16.size());
- str_two_utf16.remove(0, str_two_utf16.size());
return 0;
}
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index c2e9b02..54662ac 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -90,13 +90,6 @@
EXPECT_STR16EQ(u"VerifyInsert me", tmp);
}
-TEST(String16Test, Remove) {
- String16 tmp("Verify me");
- tmp.remove(2, 6);
- EXPECT_EQ(2U, tmp.size());
- EXPECT_STR16EQ(u" m", tmp);
-}
-
TEST(String16Test, ReplaceAll) {
String16 tmp("Verify verify Verify");
tmp.replaceAll(u'r', u'!');
@@ -161,14 +154,6 @@
EXPECT_FALSE(tmp.isStaticString());
}
-TEST(String16Test, StaticStringRemove) {
- StaticString16 tmp(u"Verify me");
- tmp.remove(2, 6);
- EXPECT_EQ(2U, tmp.size());
- EXPECT_STR16EQ(u" m", tmp);
- EXPECT_FALSE(tmp.isStaticString());
-}
-
TEST(String16Test, StaticStringReplaceAll) {
StaticString16 tmp(u"Verify verify Verify");
tmp.replaceAll(u'r', u'!');
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 3dc2026..195e122 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -25,6 +25,8 @@
#include <ctype.h>
+#include <string>
+
#include "SharedBuffer.h"
/*
@@ -163,9 +165,7 @@
}
String8::String8(const char32_t* o)
- : mString(allocFromUTF32(o, strlen32(o)))
-{
-}
+ : mString(allocFromUTF32(o, std::char_traits<char32_t>::length(o))) {}
String8::String8(const char32_t* o, size_t len)
: mString(allocFromUTF32(o, len))
@@ -415,50 +415,15 @@
void String8::toLower()
{
- toLower(0, size());
-}
+ const size_t length = size();
+ if (length == 0) return;
-void String8::toLower(size_t start, size_t length)
-{
- const size_t len = size();
- if (start >= len) {
- return;
- }
- if (start+length > len) {
- length = len-start;
- }
- char* buf = lockBuffer(len);
- buf += start;
- while (length > 0) {
+ char* buf = lockBuffer(length);
+ for (size_t i = length; i > 0; --i) {
*buf = static_cast<char>(tolower(*buf));
buf++;
- length--;
}
- unlockBuffer(len);
-}
-
-void String8::toUpper()
-{
- toUpper(0, size());
-}
-
-void String8::toUpper(size_t start, size_t length)
-{
- const size_t len = size();
- if (start >= len) {
- return;
- }
- if (start+length > len) {
- length = len-start;
- }
- char* buf = lockBuffer(len);
- buf += start;
- while (length > 0) {
- *buf = static_cast<char>(toupper(*buf));
- buf++;
- length--;
- }
- unlockBuffer(len);
+ unlockBuffer(length);
}
// ---------------------------------------------------------------------------
diff --git a/libutils/String8_fuzz.cpp b/libutils/String8_fuzz.cpp
index b02683c..a45d675 100644
--- a/libutils/String8_fuzz.cpp
+++ b/libutils/String8_fuzz.cpp
@@ -42,9 +42,6 @@
// Casing
[](FuzzedDataProvider*, android::String8* str1, android::String8*) -> void {
- str1->toUpper();
- },
- [](FuzzedDataProvider*, android::String8* str1, android::String8*) -> void {
str1->toLower();
},
[](FuzzedDataProvider*, android::String8* str1, android::String8* str2) -> void {
diff --git a/libutils/StrongPointer_test.cpp b/libutils/StrongPointer_test.cpp
index 29f6bd4..f27c1f1 100644
--- a/libutils/StrongPointer_test.cpp
+++ b/libutils/StrongPointer_test.cpp
@@ -30,17 +30,34 @@
~SPFoo() {
*mDeleted = true;
}
-private:
+
+ private:
bool* mDeleted;
};
-TEST(StrongPointer, move) {
+class SPLightFoo : virtual public VirtualLightRefBase {
+ public:
+ explicit SPLightFoo(bool* deleted_check) : mDeleted(deleted_check) { *mDeleted = false; }
+
+ ~SPLightFoo() { *mDeleted = true; }
+
+ private:
+ bool* mDeleted;
+};
+
+template <typename T>
+class StrongPointer : public ::testing::Test {};
+
+using RefBaseTypes = ::testing::Types<SPFoo, SPLightFoo>;
+TYPED_TEST_CASE(StrongPointer, RefBaseTypes);
+
+TYPED_TEST(StrongPointer, move) {
bool isDeleted;
- sp<SPFoo> sp1 = sp<SPFoo>::make(&isDeleted);
- SPFoo* foo = sp1.get();
+ sp<TypeParam> sp1 = sp<TypeParam>::make(&isDeleted);
+ TypeParam* foo = sp1.get();
ASSERT_EQ(1, foo->getStrongCount());
{
- sp<SPFoo> sp2 = std::move(sp1);
+ sp<TypeParam> sp2 = std::move(sp1);
ASSERT_EQ(1, foo->getStrongCount()) << "std::move failed, incremented refcnt";
ASSERT_EQ(nullptr, sp1.get()) << "std::move failed, sp1 is still valid";
// The strong count isn't increasing, let's double check the old object
@@ -50,33 +67,42 @@
ASSERT_FALSE(isDeleted) << "deleted too early! still has a reference!";
{
// Now let's double check it deletes on time
- sp<SPFoo> sp2 = std::move(sp1);
+ sp<TypeParam> sp2 = std::move(sp1);
}
ASSERT_TRUE(isDeleted) << "foo was leaked!";
}
-TEST(StrongPointer, NullptrComparison) {
- sp<SPFoo> foo;
+TYPED_TEST(StrongPointer, NullptrComparison) {
+ sp<TypeParam> foo;
ASSERT_EQ(foo, nullptr);
ASSERT_EQ(nullptr, foo);
}
-TEST(StrongPointer, PointerComparison) {
+TYPED_TEST(StrongPointer, PointerComparison) {
bool isDeleted;
- sp<SPFoo> foo = sp<SPFoo>::make(&isDeleted);
+ sp<TypeParam> foo = sp<TypeParam>::make(&isDeleted);
ASSERT_EQ(foo.get(), foo);
ASSERT_EQ(foo, foo.get());
ASSERT_NE(nullptr, foo);
ASSERT_NE(foo, nullptr);
}
-TEST(StrongPointer, AssertStrongRefExists) {
- // uses some other refcounting method, or non at all
+TYPED_TEST(StrongPointer, Deleted) {
bool isDeleted;
- SPFoo* foo = new SPFoo(&isDeleted);
+ sp<TypeParam> foo = sp<TypeParam>::make(&isDeleted);
- // can only get a valid sp<> object when you construct it as an sp<> object
- EXPECT_DEATH(sp<SPFoo>::fromExisting(foo), "");
+ auto foo2 = sp<TypeParam>::fromExisting(foo.get());
+ EXPECT_FALSE(isDeleted);
+ foo = nullptr;
+ EXPECT_FALSE(isDeleted);
+ foo2 = nullptr;
+ EXPECT_TRUE(isDeleted);
+}
+
+TYPED_TEST(StrongPointer, AssertStrongRefExists) {
+ bool isDeleted;
+ TypeParam* foo = new TypeParam(&isDeleted);
+ EXPECT_DEATH(sp<TypeParam>::fromExisting(foo), "");
delete foo;
}
diff --git a/libutils/Timers.cpp b/libutils/Timers.cpp
index fd3f4a9..4cfac57 100644
--- a/libutils/Timers.cpp
+++ b/libutils/Timers.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-//
-// Timer functions.
-//
#include <utils/Timers.h>
#include <limits.h>
@@ -24,11 +21,12 @@
#include <time.h>
#include <android-base/macros.h>
+#include <utils/Log.h>
static constexpr size_t clock_id_max = 5;
static void checkClockId(int clock) {
- if (clock < 0 || clock >= clock_id_max) abort();
+ LOG_ALWAYS_FATAL_IF(clock < 0 || clock >= clock_id_max, "invalid clock id");
}
#if defined(__linux__)
@@ -56,18 +54,10 @@
}
#endif
-int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime)
-{
- nsecs_t timeoutDelayMillis;
- if (timeoutTime > referenceTime) {
- uint64_t timeoutDelay = uint64_t(timeoutTime - referenceTime);
- if (timeoutDelay > uint64_t((INT_MAX - 1) * 1000000LL)) {
- timeoutDelayMillis = -1;
- } else {
- timeoutDelayMillis = (timeoutDelay + 999999LL) / 1000000LL;
- }
- } else {
- timeoutDelayMillis = 0;
- }
- return (int)timeoutDelayMillis;
+int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime) {
+ if (timeoutTime <= referenceTime) return 0;
+
+ uint64_t timeoutDelay = uint64_t(timeoutTime - referenceTime);
+ if (timeoutDelay > uint64_t((INT_MAX - 1) * 1000000LL)) return -1;
+ return (timeoutDelay + 999999LL) / 1000000LL;
}
diff --git a/libutils/Timers_test.cpp b/libutils/Timers_test.cpp
index ec0051e..c0a6d49 100644
--- a/libutils/Timers_test.cpp
+++ b/libutils/Timers_test.cpp
@@ -27,3 +27,12 @@
systemTime(SYSTEM_TIME_BOOTTIME);
EXPECT_EXIT(systemTime(SYSTEM_TIME_BOOTTIME + 1), testing::KilledBySignal(SIGABRT), "");
}
+
+TEST(Timers, toMillisecondTimeoutDelay) {
+ EXPECT_EQ(0, toMillisecondTimeoutDelay(100, 100));
+ EXPECT_EQ(0, toMillisecondTimeoutDelay(100, 10));
+
+ EXPECT_EQ(-1, toMillisecondTimeoutDelay(0, INT_MAX * 1000000LL));
+
+ EXPECT_EQ(123, toMillisecondTimeoutDelay(0, 123000000));
+}
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 843a81a..3ffcf7e 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -22,20 +22,6 @@
#include <log/log.h>
-#if defined(_WIN32)
-# undef nhtol
-# undef htonl
-# undef nhtos
-# undef htons
-
-# define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) )
-# define htonl(x) ntohl(x)
-# define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) )
-# define htons(x) ntohs(x)
-#else
-# include <netinet/in.h>
-#endif
-
extern "C" {
static const char32_t kByteMask = 0x000000BF;
@@ -115,24 +101,6 @@
}
}
-size_t strlen32(const char32_t *s)
-{
- const char32_t *ss = s;
- while ( *ss )
- ss++;
- return ss-s;
-}
-
-size_t strnlen32(const char32_t *s, size_t maxlen)
-{
- const char32_t *ss = s;
- while ((maxlen > 0) && *ss) {
- ss++;
- maxlen--;
- }
- return ss-s;
-}
-
static inline int32_t utf32_at_internal(const char* cur, size_t *num_read)
{
const char first_char = *cur;
@@ -254,19 +222,6 @@
return d;
}
-char16_t *strcpy16(char16_t *dst, const char16_t *src)
-{
- char16_t *q = dst;
- const char16_t *p = src;
- char16_t ch;
-
- do {
- *q++ = ch = *p++;
- } while ( ch );
-
- return dst;
-}
-
size_t strlen16(const char16_t *s)
{
const char16_t *ss = s;
diff --git a/libutils/include/utils/KeyedVector.h b/libutils/include/utils/KeyedVector.h
index 7bda99b..5cf7a11 100644
--- a/libutils/include/utils/KeyedVector.h
+++ b/libutils/include/utils/KeyedVector.h
@@ -47,7 +47,7 @@
inline void clear() { mVector.clear(); }
- /*!
+ /*!
* vector stats
*/
@@ -63,14 +63,14 @@
// returns true if the arguments is known to be identical to this vector
inline bool isIdenticalTo(const KeyedVector& rhs) const;
- /*!
+ /*!
* accessors
*/
- const VALUE& valueFor(const KEY& key) const;
- const VALUE& valueAt(size_t index) const;
- const KEY& keyAt(size_t index) const;
- ssize_t indexOfKey(const KEY& key) const;
- const VALUE& operator[] (size_t index) const;
+ const VALUE& valueFor(const KEY& key) const;
+ const VALUE& valueAt(size_t index) const;
+ const KEY& keyAt(size_t index) const;
+ ssize_t indexOfKey(const KEY& key) const;
+ const VALUE& operator[](size_t index) const;
/*!
* modifying the array
@@ -79,10 +79,10 @@
VALUE& editValueFor(const KEY& key);
VALUE& editValueAt(size_t index);
- /*!
+ /*!
* add/insert/replace items
*/
-
+
ssize_t add(const KEY& key, const VALUE& item);
ssize_t replaceValueFor(const KEY& key, const VALUE& item);
ssize_t replaceValueAt(size_t index, const VALUE& item);
@@ -93,7 +93,7 @@
ssize_t removeItem(const KEY& key);
ssize_t removeItemsAt(size_t index, size_t count = 1);
-
+
private:
SortedVector< key_value_pair_t<KEY, VALUE> > mVector;
};
@@ -208,7 +208,7 @@
template<typename KEY, typename VALUE> inline
const VALUE& DefaultKeyedVector<KEY,VALUE>::valueFor(const KEY& key) const {
ssize_t i = this->indexOfKey(key);
- return i >= 0 ? KeyedVector<KEY,VALUE>::valueAt(i) : mDefault;
+ return i >= 0 ? KeyedVector<KEY, VALUE>::valueAt(static_cast<size_t>(i)) : mDefault;
}
} // namespace android
diff --git a/libutils/include/utils/LightRefBase.h b/libutils/include/utils/LightRefBase.h
index b04e5c1..40edf67 100644
--- a/libutils/include/utils/LightRefBase.h
+++ b/libutils/include/utils/LightRefBase.h
@@ -28,6 +28,8 @@
class ReferenceRenamer;
+void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz);
+
template <class T>
class LightRefBase
{
@@ -36,6 +38,11 @@
inline void incStrong(__attribute__((unused)) const void* id) const {
mCount.fetch_add(1, std::memory_order_relaxed);
}
+ inline void incStrongRequireStrong(__attribute__((unused)) const void* id) const {
+ if (0 == mCount.fetch_add(1, std::memory_order_relaxed)) {
+ LightRefBase_reportIncStrongRequireStrongFailed(this);
+ }
+ }
inline void decStrong(__attribute__((unused)) const void* id) const {
if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
@@ -59,7 +66,6 @@
mutable std::atomic<int32_t> mCount;
};
-
// This is a wrapper around LightRefBase that simply enforces a virtual
// destructor to eliminate the template requirement of LightRefBase
class VirtualLightRefBase : public LightRefBase<VirtualLightRefBase> {
diff --git a/libutils/include/utils/RefBase.h b/libutils/include/utils/RefBase.h
index 7148949..e07f574 100644
--- a/libutils/include/utils/RefBase.h
+++ b/libutils/include/utils/RefBase.h
@@ -416,13 +416,16 @@
wp(std::nullptr_t) : wp() {}
#else
wp(T* other); // NOLINT(implicit)
+ template <typename U>
+ wp(U* other); // NOLINT(implicit)
+ wp& operator=(T* other);
+ template <typename U>
+ wp& operator=(U* other);
#endif
+
wp(const wp<T>& other);
explicit wp(const sp<T>& other);
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- template<typename U> wp(U* other); // NOLINT(implicit)
-#endif
template<typename U> wp(const sp<U>& other); // NOLINT(implicit)
template<typename U> wp(const wp<U>& other); // NOLINT(implicit)
@@ -430,15 +433,9 @@
// Assignment
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- wp& operator = (T* other);
-#endif
wp& operator = (const wp<T>& other);
wp& operator = (const sp<T>& other);
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- template<typename U> wp& operator = (U* other);
-#endif
template<typename U> wp& operator = (const wp<U>& other);
template<typename U> wp& operator = (const sp<U>& other);
@@ -559,6 +556,31 @@
{
m_refs = other ? m_refs = other->createWeak(this) : nullptr;
}
+
+template <typename T>
+template <typename U>
+wp<T>::wp(U* other) : m_ptr(other) {
+ m_refs = other ? other->createWeak(this) : nullptr;
+}
+
+template <typename T>
+wp<T>& wp<T>::operator=(T* other) {
+ weakref_type* newRefs = other ? other->createWeak(this) : nullptr;
+ if (m_ptr) m_refs->decWeak(this);
+ m_ptr = other;
+ m_refs = newRefs;
+ return *this;
+}
+
+template <typename T>
+template <typename U>
+wp<T>& wp<T>::operator=(U* other) {
+ weakref_type* newRefs = other ? other->createWeak(this) : 0;
+ if (m_ptr) m_refs->decWeak(this);
+ m_ptr = other;
+ m_refs = newRefs;
+ return *this;
+}
#endif
template<typename T>
@@ -575,15 +597,6 @@
m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T> template<typename U>
-wp<T>::wp(U* other)
- : m_ptr(other)
-{
- m_refs = other ? other->createWeak(this) : nullptr;
-}
-#endif
-
template<typename T> template<typename U>
wp<T>::wp(const wp<U>& other)
: m_ptr(other.m_ptr)
@@ -609,19 +622,6 @@
if (m_ptr) m_refs->decWeak(this);
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T>
-wp<T>& wp<T>::operator = (T* other)
-{
- weakref_type* newRefs =
- other ? other->createWeak(this) : nullptr;
- if (m_ptr) m_refs->decWeak(this);
- m_ptr = other;
- m_refs = newRefs;
- return *this;
-}
-#endif
-
template<typename T>
wp<T>& wp<T>::operator = (const wp<T>& other)
{
@@ -646,19 +646,6 @@
return *this;
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T> template<typename U>
-wp<T>& wp<T>::operator = (U* other)
-{
- weakref_type* newRefs =
- other ? other->createWeak(this) : 0;
- if (m_ptr) m_refs->decWeak(this);
- m_ptr = other;
- m_refs = newRefs;
- return *this;
-}
-#endif
-
template<typename T> template<typename U>
wp<T>& wp<T>::operator = (const wp<U>& other)
{
diff --git a/libutils/include/utils/StopWatch.h b/libutils/include/utils/StopWatch.h
index 9b14ac8..4e53eda 100644
--- a/libutils/include/utils/StopWatch.h
+++ b/libutils/include/utils/StopWatch.h
@@ -14,46 +14,30 @@
* limitations under the License.
*/
-#ifndef ANDROID_STOPWATCH_H
-#define ANDROID_STOPWATCH_H
+#pragma once
#include <stdint.h>
#include <sys/types.h>
#include <utils/Timers.h>
-// ---------------------------------------------------------------------------
-
namespace android {
-class StopWatch
-{
-public:
- StopWatch(const char* name, int clock = SYSTEM_TIME_MONOTONIC);
- ~StopWatch();
+class StopWatch {
+ public:
+ StopWatch(const char* name, int clock = SYSTEM_TIME_MONOTONIC);
+ ~StopWatch();
- const char* name() const;
- nsecs_t lap();
- nsecs_t elapsedTime() const;
+ const char* name() const;
+ nsecs_t elapsedTime() const;
- void reset();
+ void reset();
-private:
- const char* mName;
- int mClock;
-
- struct lap_t {
- nsecs_t soFar;
- nsecs_t thisLap;
- };
-
- nsecs_t mStartTime;
- lap_t mLaps[8];
- int mNumLaps;
+ private:
+ const char* mName;
+ int mClock;
+
+ nsecs_t mStartTime;
};
} // namespace android
-
-// ---------------------------------------------------------------------------
-
-#endif // ANDROID_STOPWATCH_H
diff --git a/libutils/include/utils/String16.h b/libutils/include/utils/String16.h
index 5ce48c6..60d523a 100644
--- a/libutils/include/utils/String16.h
+++ b/libutils/include/utils/String16.h
@@ -88,8 +88,6 @@
status_t replaceAll(char16_t replaceThis,
char16_t withThis);
- status_t remove(size_t len, size_t begin=0);
-
inline int compare(const String16& other) const;
inline bool operator<(const String16& other) const;
diff --git a/libutils/include/utils/String8.h b/libutils/include/utils/String8.h
index 0bcb716..cee5dc6 100644
--- a/libutils/include/utils/String8.h
+++ b/libutils/include/utils/String8.h
@@ -130,9 +130,6 @@
bool removeAll(const char* other);
void toLower();
- void toLower(size_t start, size_t numChars);
- void toUpper();
- void toUpper(size_t start, size_t numChars);
/*
diff --git a/libutils/include/utils/StrongPointer.h b/libutils/include/utils/StrongPointer.h
index dd53b9e..bb1941b 100644
--- a/libutils/include/utils/StrongPointer.h
+++ b/libutils/include/utils/StrongPointer.h
@@ -62,13 +62,16 @@
sp(std::nullptr_t) : sp() {}
#else
sp(T* other); // NOLINT(implicit)
+ template <typename U>
+ sp(U* other); // NOLINT(implicit)
+ sp& operator=(T* other);
+ template <typename U>
+ sp& operator=(U* other);
#endif
+
sp(const sp<T>& other);
sp(sp<T>&& other) noexcept;
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- template<typename U> sp(U* other); // NOLINT(implicit)
-#endif
template<typename U> sp(const sp<U>& other); // NOLINT(implicit)
template<typename U> sp(sp<U>&& other); // NOLINT(implicit)
@@ -82,17 +85,11 @@
// Assignment
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- sp& operator = (T* other);
-#endif
sp& operator = (const sp<T>& other);
sp& operator=(sp<T>&& other) noexcept;
template<typename U> sp& operator = (const sp<U>& other);
template<typename U> sp& operator = (sp<U>&& other);
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- template<typename U> sp& operator = (U* other);
-#endif
//! Special optimization for use by ProcessState (and nobody else).
void force_set(T* other);
@@ -247,6 +244,28 @@
other->incStrong(this);
}
}
+
+template <typename T>
+template <typename U>
+sp<T>::sp(U* other) : m_ptr(other) {
+ if (other) {
+ check_not_on_stack(other);
+ (static_cast<T*>(other))->incStrong(this);
+ }
+}
+
+template <typename T>
+sp<T>& sp<T>::operator=(T* other) {
+ T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
+ if (other) {
+ check_not_on_stack(other);
+ other->incStrong(this);
+ }
+ if (oldPtr) oldPtr->decStrong(this);
+ if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
+ m_ptr = other;
+ return *this;
+}
#endif
template<typename T>
@@ -261,17 +280,6 @@
other.m_ptr = nullptr;
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T> template<typename U>
-sp<T>::sp(U* other)
- : m_ptr(other) {
- if (other) {
- check_not_on_stack(other);
- (static_cast<T*>(other))->incStrong(this);
- }
-}
-#endif
-
template<typename T> template<typename U>
sp<T>::sp(const sp<U>& other)
: m_ptr(other.m_ptr) {
@@ -319,21 +327,6 @@
return *this;
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T>
-sp<T>& sp<T>::operator =(T* other) {
- T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
- if (other) {
- check_not_on_stack(other);
- other->incStrong(this);
- }
- if (oldPtr) oldPtr->decStrong(this);
- if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
- m_ptr = other;
- return *this;
-}
-#endif
-
template<typename T> template<typename U>
sp<T>& sp<T>::operator =(const sp<U>& other) {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index 0087383..d60d5d6 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -27,7 +27,6 @@
int strncmp16(const char16_t *s1, const char16_t *s2, size_t n);
size_t strlen16(const char16_t *);
size_t strnlen16(const char16_t *, size_t);
-char16_t *strcpy16(char16_t *, const char16_t *);
char16_t *strstr16(const char16_t*, const char16_t*);
// Version of comparison that supports embedded NULs.
@@ -39,10 +38,6 @@
// equivalent result as strcmp16 (unlike strncmp16).
int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2);
-// Standard string functions on char32_t strings.
-size_t strlen32(const char32_t *);
-size_t strnlen32(const char32_t *, size_t);
-
/**
* Measure the length of a UTF-32 string in UTF-8. If the string is invalid
* such as containing a surrogate character, -1 will be returned.
diff --git a/libvndksupport/Android.bp b/libvndksupport/Android.bp
index 11c75f7..f800bf7 100644
--- a/libvndksupport/Android.bp
+++ b/libvndksupport/Android.bp
@@ -5,7 +5,9 @@
cc_library {
name: "libvndksupport",
native_bridge_supported: true,
- llndk_stubs: "libvndksupport.llndk",
+ llndk: {
+ symbol_file: "libvndksupport.map.txt",
+ },
srcs: ["linker.cpp"],
cflags: [
"-Wall",
@@ -23,10 +25,3 @@
versions: ["29"],
},
}
-
-llndk_library {
- name: "libvndksupport.llndk",
- native_bridge_supported: true,
- symbol_file: "libvndksupport.map.txt",
- export_include_dirs: ["include"],
-}
diff --git a/cpio/Android.bp b/mkbootfs/Android.bp
similarity index 75%
rename from cpio/Android.bp
rename to mkbootfs/Android.bp
index 16af079..cd2a624 100644
--- a/cpio/Android.bp
+++ b/mkbootfs/Android.bp
@@ -8,7 +8,11 @@
name: "mkbootfs",
srcs: ["mkbootfs.c"],
cflags: ["-Werror"],
- shared_libs: ["libcutils"],
+ static_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ ],
dist: {
targets: ["dist_files"],
},
diff --git a/cpio/mkbootfs.c b/mkbootfs/mkbootfs.c
similarity index 100%
rename from cpio/mkbootfs.c
rename to mkbootfs/mkbootfs.c
diff --git a/qemu_pipe/Android.bp b/qemu_pipe/Android.bp
deleted file mode 100644
index 42a69db..0000000
--- a/qemu_pipe/Android.bp
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2011 The Android Open Source Project
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_library_static {
- name: "libqemu_pipe",
- vendor_available: true,
- recovery_available: true,
- apex_available: [
- "com.android.adbd",
- // TODO(b/151398197) remove the below
- "//apex_available:platform",
- ],
- sanitize: {
- misc_undefined: ["integer"],
- },
- srcs: ["qemu_pipe.cpp"],
- local_include_dirs: ["include"],
- static_libs: ["libbase"],
- export_include_dirs: ["include"],
- cflags: ["-Werror"],
-}
diff --git a/qemu_pipe/OWNERS b/qemu_pipe/OWNERS
deleted file mode 100644
index d67a329..0000000
--- a/qemu_pipe/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-bohu@google.com
-lfy@google.com
-rkir@google.com
diff --git a/qemu_pipe/include/qemu_pipe.h b/qemu_pipe/include/qemu_pipe.h
deleted file mode 100644
index 0987498..0000000
--- a/qemu_pipe/include/qemu_pipe.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-#ifndef ANDROID_CORE_INCLUDE_QEMU_PIPE_H
-#define ANDROID_CORE_INCLUDE_QEMU_PIPE_H
-
-#include <stddef.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-// Try to open a new Qemu fast-pipe. This function returns a file descriptor
-// that can be used to communicate with a named service managed by the
-// emulator.
-//
-// This file descriptor can be used as a standard pipe/socket descriptor.
-//
-// 'pipeName' is the name of the emulator service you want to connect to,
-// and should begin with 'pipe:' (e.g. 'pipe:camera' or 'pipe:opengles').
-// For backward compatibility, the 'pipe:' prefix can be omitted, and in
-// that case, qemu_pipe_open will add it for you.
-
-// On success, return a valid file descriptor, or -1/errno on failure. E.g.:
-//
-// EINVAL -> unknown/unsupported pipeName
-// ENOSYS -> fast pipes not available in this system.
-//
-// ENOSYS should never happen, except if you're trying to run within a
-// misconfigured emulator.
-//
-// You should be able to open several pipes to the same pipe service,
-// except for a few special cases (e.g. GSM modem), where EBUSY will be
-// returned if more than one client tries to connect to it.
-int qemu_pipe_open(const char* pipeName);
-
-// Send a framed message |buff| of |len| bytes through the |fd| descriptor.
-// This really adds a 4-hexchar prefix describing the payload size.
-// Returns 0 on success, and -1 on error.
-int qemu_pipe_frame_send(int fd, const void* buff, size_t len);
-
-// Read a frame message from |fd|, and store it into |buff| of |len| bytes.
-// If the framed message is larger than |len|, then this returns -1 and the
-// content is lost. Otherwise, this returns the size of the message. NOTE:
-// empty messages are possible in a framed wire protocol and do not mean
-// end-of-stream.
-int qemu_pipe_frame_recv(int fd, void* buff, size_t len);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* ANDROID_CORE_INCLUDE_QEMU_PIPE_H */
diff --git a/qemu_pipe/qemu_pipe.cpp b/qemu_pipe/qemu_pipe.cpp
deleted file mode 100644
index 03afb21..0000000
--- a/qemu_pipe/qemu_pipe.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2011 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 "qemu_pipe.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-#include <stdio.h>
-
-#include <android-base/file.h>
-
-using android::base::ReadFully;
-using android::base::WriteFully;
-
-// Define QEMU_PIPE_DEBUG if you want to print error messages when an error
-// occurs during pipe operations. The macro should simply take a printf-style
-// formatting string followed by optional arguments.
-#ifndef QEMU_PIPE_DEBUG
-# define QEMU_PIPE_DEBUG(...) (void)0
-#endif
-
-int qemu_pipe_open(const char* pipeName) {
- if (!pipeName) {
- errno = EINVAL;
- return -1;
- }
-
- int fd = TEMP_FAILURE_RETRY(open("/dev/qemu_pipe", O_RDWR));
- if (fd < 0) {
- QEMU_PIPE_DEBUG("%s: Could not open /dev/qemu_pipe: %s", __FUNCTION__,
- strerror(errno));
- return -1;
- }
-
- // Write the pipe name, *including* the trailing zero which is necessary.
- size_t pipeNameLen = strlen(pipeName);
- if (WriteFully(fd, pipeName, pipeNameLen + 1U)) {
- return fd;
- }
-
- // now, add 'pipe:' prefix and try again
- // Note: host side will wait for the trailing '\0' to start
- // service lookup.
- const char pipe_prefix[] = "pipe:";
- if (WriteFully(fd, pipe_prefix, strlen(pipe_prefix)) &&
- WriteFully(fd, pipeName, pipeNameLen + 1U)) {
- return fd;
- }
- QEMU_PIPE_DEBUG("%s: Could not write to %s pipe service: %s",
- __FUNCTION__, pipeName, strerror(errno));
- close(fd);
- return -1;
-}
-
-int qemu_pipe_frame_send(int fd, const void* buff, size_t len) {
- char header[5];
- snprintf(header, sizeof(header), "%04zx", len);
- if (!WriteFully(fd, header, 4)) {
- QEMU_PIPE_DEBUG("Can't write qemud frame header: %s", strerror(errno));
- return -1;
- }
- if (!WriteFully(fd, buff, len)) {
- QEMU_PIPE_DEBUG("Can't write qemud frame payload: %s", strerror(errno));
- return -1;
- }
- return 0;
-}
-
-int qemu_pipe_frame_recv(int fd, void* buff, size_t len) {
- char header[5];
- if (!ReadFully(fd, header, 4)) {
- QEMU_PIPE_DEBUG("Can't read qemud frame header: %s", strerror(errno));
- return -1;
- }
- header[4] = '\0';
- size_t size;
- if (sscanf(header, "%04zx", &size) != 1) {
- QEMU_PIPE_DEBUG("Malformed qemud frame header: [%.*s]", 4, header);
- return -1;
- }
- if (size > len) {
- QEMU_PIPE_DEBUG("Oversized qemud frame (% bytes, expected <= %)", size,
- len);
- return -1;
- }
- if (!ReadFully(fd, buff, size)) {
- QEMU_PIPE_DEBUG("Could not read qemud frame payload: %s",
- strerror(errno));
- return -1;
- }
- return size;
-}
diff --git a/rootdir/Android.bp b/rootdir/Android.bp
index 6a80808..ae21633 100644
--- a/rootdir/Android.bp
+++ b/rootdir/Android.bp
@@ -20,7 +20,10 @@
name: "init.rc",
src: "init.rc",
sub_dir: "init/hw",
- required: ["fsverity_init"],
+ required: [
+ "fsverity_init",
+ "platform-bootclasspath",
+ ],
}
prebuilt_etc {
@@ -35,3 +38,11 @@
src: "etc/linker.config.json",
installable: false,
}
+
+// TODO(b/185211376) Scope the native APIs that microdroid will provide to the app payload
+prebuilt_etc {
+ name: "public.libraries.android.txt",
+ src: "etc/public.libraries.android.txt",
+ filename: "public.libraries.txt",
+ installable: false,
+}
\ No newline at end of file
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 5503cc1..99d8f9a 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -56,7 +56,6 @@
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
-LOCAL_REQUIRED_MODULES := etc_classpath
EXPORT_GLOBAL_ASAN_OPTIONS :=
ifneq ($(filter address,$(SANITIZE_TARGET)),)
@@ -186,21 +185,6 @@
endef
#######################################
-# /etc/classpath
-include $(CLEAR_VARS)
-LOCAL_MODULE := etc_classpath
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := classpath
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE):
- @echo "Generate: $@"
- @mkdir -p $(dir $@)
- $(hide) echo "export BOOTCLASSPATH $(PRODUCT_BOOTCLASSPATH)" > $@
- $(hide) echo "export DEX2OATBOOTCLASSPATH $(PRODUCT_DEX2OAT_BOOTCLASSPATH)" >> $@
- $(hide) echo "export SYSTEMSERVERCLASSPATH $(PRODUCT_SYSTEM_SERVER_CLASSPATH)" >> $@
-
-#######################################
# sanitizer.libraries.txt
include $(CLEAR_VARS)
LOCAL_MODULE := sanitizer.libraries.txt
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
index 83cb6ff..c58f298 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -1,14 +1,14 @@
{
"requireLibs": [
- // Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
- "libdexfile_external.so",
- "libdexfiled_external.so",
+ "libandroidicu.so",
+ "libdexfile.so",
+ "libdexfiled.so",
+ "libicu.so",
+ "libjdwp.so",
"libnativebridge.so",
"libnativehelper.so",
"libnativeloader.so",
"libsigchain.so",
- "libandroidicu.so",
- "libicu.so",
// TODO(b/122876336): Remove libpac.so once it's migrated to Webview
"libpac.so",
// TODO(b/120786417 or b/134659294): libicuuc.so
diff --git a/rootdir/init-debug.rc b/rootdir/init-debug.rc
index 435d4cb..77a80cd 100644
--- a/rootdir/init-debug.rc
+++ b/rootdir/init-debug.rc
@@ -6,3 +6,11 @@
on property:persist.mmc.cache_size=*
write /sys/block/mmcblk0/cache_size ${persist.mmc.cache_size}
+
+on early-init && property:ro.product.debugfs_restrictions.enabled=true
+ mount debugfs debugfs /sys/kernel/debug
+ chmod 0755 /sys/kernel/debug
+
+on property:sys.boot_completed=1 && property:ro.product.debugfs_restrictions.enabled=true && \
+ property:persist.dbg.keep_debugfs_mounted=""
+ umount /sys/kernel/debug
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 9a30ead..c6b74bc 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -471,9 +471,6 @@
chmod 0664 /sys/module/lowmemorykiller/parameters/minfree
start lmkd
- # Set an initial boot level - start at 10 in case we need to add earlier ones.
- setprop keystore.boot_level 10
-
# Start essential services.
start servicemanager
start hwservicemanager
@@ -630,8 +627,6 @@
write /sys/kernel/tracing/instances/bootreceiver/events/error_report/error_report_end/enable 1
on post-fs-data
- # Boot level 30 - at this point daemons like apexd and odsign run
- setprop keystore.boot_level 30
mark_post_data
@@ -652,6 +647,9 @@
mkdir /data/bootchart 0755 shell shell encryption=Require
bootchart start
+ # Avoid predictable entropy pool. Carry over entropy from previous boot.
+ copy /data/system/entropy.dat /dev/urandom
+
mkdir /data/vendor 0771 root root encryption=Require
mkdir /data/vendor_ce 0771 root root encryption=None
mkdir /data/vendor_de 0771 root root encryption=None
@@ -667,22 +665,42 @@
# Make sure that apexd is started in the default namespace
enter_default_mount_ns
+ # set up keystore directory structure first so that we can end early boot
+ # and start apexd
+ mkdir /data/misc 01771 system misc encryption=Require
+ mkdir /data/misc/keystore 0700 keystore keystore
+ # work around b/183668221
+ restorecon /data/misc /data/misc/keystore
+
+ # Boot level 30
+ # odsign signing keys have MAX_BOOT_LEVEL=30
+ # This is currently the earliest boot level, but we start at 30
+ # to leave room for earlier levels.
+ setprop keystore.boot_level 30
+
+ # Now that /data is mounted and we have created /data/misc/keystore,
+ # we can tell keystore to stop allowing use of early-boot keys,
+ # and access its database for the first time to support creation and
+ # use of MAX_BOOT_LEVEL keys.
+ exec - system system -- /system/bin/vdc keymaster earlyBootEnded
+
# /data/apex is now available. Start apexd to scan and activate APEXes.
+ #
+ # To handle userspace reboots as well as devices that use FDE, make sure
+ # that apexd is started cleanly here (set apexd.status="") and that it is
+ # restarted if it's already running.
mkdir /data/apex 0755 root system encryption=None
mkdir /data/apex/active 0755 root system
mkdir /data/apex/backup 0700 root system
- mkdir /data/apex/decompressed 0700 root system encryption=Require
+ mkdir /data/apex/decompressed 0755 root system encryption=Require
mkdir /data/apex/hashtree 0700 root system
mkdir /data/apex/sessions 0700 root system
mkdir /data/app-staging 0751 system system encryption=DeleteIfNecessary
mkdir /data/apex/ota_reserved 0700 root system encryption=Require
- start apexd
+ setprop apexd.status ""
+ restart apexd
- # Avoid predictable entropy pool. Carry over entropy from previous boot.
- copy /data/system/entropy.dat /dev/urandom
-
- # create basic filesystem structure
- mkdir /data/misc 01771 system misc encryption=Require
+ # create rest of basic filesystem structure
mkdir /data/misc/recovery 0770 system log
copy /data/misc/recovery/ro.build.fingerprint /data/misc/recovery/ro.build.fingerprint.1
chmod 0440 /data/misc/recovery/ro.build.fingerprint.1
@@ -706,7 +724,6 @@
mkdir /data/misc/nfc 0770 nfc nfc
mkdir /data/misc/nfc/logs 0770 nfc nfc
mkdir /data/misc/credstore 0700 credstore credstore
- mkdir /data/misc/keystore 0700 keystore keystore
mkdir /data/misc/gatekeeper 0700 system system
mkdir /data/misc/keychain 0771 system system
mkdir /data/misc/net 0750 root shell
@@ -745,7 +762,7 @@
# profile file layout
mkdir /data/misc/profiles 0771 system system
mkdir /data/misc/profiles/cur 0771 system system
- mkdir /data/misc/profiles/ref 0770 system system
+ mkdir /data/misc/profiles/ref 0771 system system
mkdir /data/misc/profman 0770 system shell
mkdir /data/misc/gcov 0770 root root
mkdir /data/misc/installd 0700 root root
@@ -755,8 +772,13 @@
mkdir /data/misc/snapshotctl_log 0755 root root
# create location to store pre-reboot information
mkdir /data/misc/prereboot 0700 system system
+ # directory used for on-device refresh metrics file.
+ mkdir /data/misc/odrefresh 0777 system system
# directory used for on-device signing key blob
mkdir /data/misc/odsign 0700 root root
+ # Directory for VirtualizationService temporary image files. Ensure that it is empty.
+ exec -- /bin/rm -rf /data/misc/virtualizationservice
+ mkdir /data/misc/virtualizationservice 0700 virtualizationservice virtualizationservice
mkdir /data/preloads 0775 system system encryption=None
@@ -903,7 +925,6 @@
# Must start before 'odsign', as odsign depends on *CLASSPATH variables
exec_start derive_classpath
load_exports /data/system/environ/classpath
- rm /data/system/environ/classpath
# Start the on-device signing daemon, and wait for it to finish, to ensure
# ART artifacts are generated if needed.
@@ -914,14 +935,13 @@
# odsign to be done with the key
wait_for_prop odsign.key.done 1
- # After apexes are mounted, tell keymaster early boot has ended, so it will
- # stop allowing use of early-boot keys
- exec - system system -- /system/bin/vdc keymaster earlyBootEnded
-
# Lock the fs-verity keyring, so no more keys can be added
exec -- /system/bin/fsverity_init --lock
- setprop keystore.boot_level 40
+ # Bump the boot level to 1000000000; this prevents further on-device signing.
+ # This is a special value that shuts down the thread which listens for
+ # further updates.
+ setprop keystore.boot_level 1000000000
# Allow apexd to snapshot and restore device encrypted apex data in the case
# of a rollback. This should be done immediately after DE_user data keys
@@ -985,9 +1005,6 @@
write /proc/sys/vm/dirty_expire_centisecs 200
write /proc/sys/vm/dirty_background_ratio 5
-on property:sys.boot_completed=1 && property:init.mount_debugfs=1
- umount /sys/kernel/debug
-
on boot
# basic network init
ifup lo
@@ -1231,7 +1248,6 @@
setprop dev.bootcomplete ""
setprop sys.init.updatable_crashing ""
setprop sys.init.updatable_crashing_process_name ""
- setprop apexd.status ""
setprop sys.user.0.ce_available ""
setprop sys.shutdown.requested ""
setprop service.bootanim.exit ""
@@ -1263,10 +1279,6 @@
on property:sys.boot_completed=1 && property:sys.init.userspace_reboot.in_progress=1
setprop sys.init.userspace_reboot.in_progress ""
-on early-init && property:init.mount_debugfs=1
- mount debugfs debugfs /sys/kernel/debug
- chmod 0755 /sys/kernel/debug
-
# Migrate tasks again in case kernel threads are created during boot
on property:sys.boot_completed=1
copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 65e29c1..3101974 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -67,6 +67,10 @@
# CDMA radio interface MUX
/dev/ppp 0660 radio vpn
+# Virtualization is managed by VirtualizationService.
+/dev/kvm 0600 virtualizationservice root
+/dev/vhost-vsock 0600 virtualizationservice root
+
# sysfs properties
/sys/devices/platform/trusty.* trusty_version 0440 root log
/sys/devices/virtual/input/input* enable 0660 root input
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index 4aca375..e4d9b39 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -220,6 +220,9 @@
case APPLOADER_ERR_INTERNAL:
LOG(ERROR) << "Error: internal apploader error";
break;
+ case APPLOADER_ERR_INVALID_VERSION:
+ LOG(ERROR) << "Error: invalid application version";
+ break;
default:
LOG(ERROR) << "Unrecognized error: " << resp.error;
break;
diff --git a/trusty/apploader/apploader_ipc.h b/trusty/apploader/apploader_ipc.h
index d8c915e..6cda7c1 100644
--- a/trusty/apploader/apploader_ipc.h
+++ b/trusty/apploader/apploader_ipc.h
@@ -44,6 +44,7 @@
* @APPLOADER_ERR_ALREADY_EXISTS: application has already been loaded
* @APPLOADER_ERR_INTERNAL: miscellaneous or internal apploader
* error not covered by the above
+ * @APPLOADER_ERR_INVALID_VERSION: invalid application version
*/
enum apploader_error : uint32_t {
APPLOADER_NO_ERROR = 0,
@@ -54,6 +55,7 @@
APPLOADER_ERR_LOADING_FAILED,
APPLOADER_ERR_ALREADY_EXISTS,
APPLOADER_ERR_INTERNAL,
+ APPLOADER_ERR_INVALID_VERSION,
};
/**
diff --git a/trusty/fuzz/include/trusty/fuzz/utils.h b/trusty/fuzz/include/trusty/fuzz/utils.h
index bca84e9..c906412 100644
--- a/trusty/fuzz/include/trusty/fuzz/utils.h
+++ b/trusty/fuzz/include/trusty/fuzz/utils.h
@@ -34,6 +34,7 @@
android::base::Result<void> Connect();
android::base::Result<void> Read(void* buf, size_t len);
android::base::Result<void> Write(const void* buf, size_t len);
+ void Disconnect();
android::base::Result<int> GetRawFd();
diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp
index 3258944..f265ced 100644
--- a/trusty/fuzz/tipc_fuzzer.cpp
+++ b/trusty/fuzz/tipc_fuzzer.cpp
@@ -41,6 +41,7 @@
#error "Binary file name must be parameterized using -DTRUSTY_APP_FILENAME."
#endif
+static TrustyApp kTrustyApp(TIPC_DEV, TRUSTY_APP_PORT);
static std::unique_ptr<CoverageRecord> record;
extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
@@ -52,8 +53,7 @@
}
/* Make sure lazy-loaded TAs have started and connected to coverage service. */
- TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
- auto ret = ta.Connect();
+ auto ret = kTrustyApp.Connect();
if (!ret.ok()) {
std::cerr << ret.error() << std::endl;
exit(-1);
@@ -79,22 +79,18 @@
ExtraCounters counters(record.get());
counters.Reset();
- TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
- auto ret = ta.Connect();
+ auto ret = kTrustyApp.Write(data, size);
+ if (ret.ok()) {
+ ret = kTrustyApp.Read(&buf, sizeof(buf));
+ }
+
+ // Reconnect to ensure that the service is still up
+ kTrustyApp.Disconnect();
+ ret = kTrustyApp.Connect();
if (!ret.ok()) {
std::cerr << ret.error() << std::endl;
android::trusty::fuzz::Abort();
}
- ret = ta.Write(data, size);
- if (!ret.ok()) {
- return -1;
- }
-
- ret = ta.Read(&buf, sizeof(buf));
- if (!ret.ok()) {
- return -1;
- }
-
- return 0;
+ return ret.ok() ? 0 : -1;
}
diff --git a/trusty/fuzz/utils.cpp b/trusty/fuzz/utils.cpp
index 3526337..bb096be 100644
--- a/trusty/fuzz/utils.cpp
+++ b/trusty/fuzz/utils.cpp
@@ -127,6 +127,10 @@
return ta_fd_;
}
+void TrustyApp::Disconnect() {
+ ta_fd_.reset();
+}
+
void Abort() {
PrintTrustyLog();
exit(-1);
diff --git a/trusty/metrics/Android.bp b/trusty/metrics/Android.bp
new file mode 100644
index 0000000..e0533bc
--- /dev/null
+++ b/trusty/metrics/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "libtrusty_metrics",
+ vendor: true,
+ srcs: [
+ "metrics.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libtrusty",
+ ],
+}
+
+cc_test {
+ name: "libtrusty_metrics_test",
+ vendor: true,
+ srcs: [
+ "metrics_test.cpp",
+ ],
+ static_libs: [
+ "libtrusty_metrics",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "liblog",
+ "libtrusty",
+ ],
+ require_root: true,
+}
diff --git a/trusty/metrics/include/trusty/metrics/metrics.h b/trusty/metrics/include/trusty/metrics/metrics.h
new file mode 100644
index 0000000..6949e9b
--- /dev/null
+++ b/trusty/metrics/include/trusty/metrics/metrics.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::Result;
+using android::base::unique_fd;
+
+class TrustyMetrics {
+ public:
+ /* Wait for next event with a given timeout. Negative timeout means infinite timeout. */
+ Result<void> WaitForEvent(int timeout_ms = -1);
+ /* Attempt to handle an event from Metrics TA in a non-blocking manner. */
+ Result<void> HandleEvent();
+ /* Expose TIPC channel so that client can integrate it into an event loop with other fds. */
+ int GetRawFd() { return metrics_fd_; };
+
+ protected:
+ TrustyMetrics(std::string tipc_dev) : tipc_dev_(std::move(tipc_dev)), metrics_fd_(-1) {}
+ virtual ~TrustyMetrics(){};
+
+ Result<void> Open();
+ virtual void HandleCrash(const std::string& app_id) = 0;
+ virtual void HandleEventDrop() = 0;
+
+ private:
+ std::string tipc_dev_;
+ unique_fd metrics_fd_;
+};
+
+} // namespace metrics
+} // namespace trusty
+} // namespace android
diff --git a/trusty/metrics/include/trusty/metrics/tipc.h b/trusty/metrics/include/trusty/metrics/tipc.h
new file mode 100644
index 0000000..66d0876
--- /dev/null
+++ b/trusty/metrics/include/trusty/metrics/tipc.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/**
+ * DOC: Metrics
+ *
+ * Metrics interface provides a way for Android to get Trusty metrics data.
+ *
+ * Currently, only "push" model is supported. Clients are expected to connect to
+ * metrics service, listen for events, e.g. app crash events, and respond to
+ * every event with a &struct metrics_req.
+ *
+ * Communication is driven by metrics service, i.e. requests/responses are all
+ * sent from/to metrics service.
+ *
+ * Note that the type of the event is not known to the client ahead of time.
+ *
+ * In the future, if we need to have Android "pull" metrics data from Trusty,
+ * that can be done by introducing a separate port.
+ *
+ * This interface is shared between Android and Trusty. There is a copy in each
+ * repository. They must be kept in sync.
+ */
+
+#define METRICS_PORT "com.android.trusty.metrics"
+
+/**
+ * enum metrics_cmd - command identifiers for metrics interface
+ * @METRICS_CMD_RESP_BIT: message is a response
+ * @METRICS_CMD_REQ_SHIFT: number of bits used by @METRICS_CMD_RESP_BIT
+ * @METRICS_CMD_REPORT_EVENT_DROP: report gaps in the event stream
+ * @METRICS_CMD_REPORT_CRASH: report an app crash event
+ */
+enum metrics_cmd {
+ METRICS_CMD_RESP_BIT = 1,
+ METRICS_CMD_REQ_SHIFT = 1,
+
+ METRICS_CMD_REPORT_EVENT_DROP = (1 << METRICS_CMD_REQ_SHIFT),
+ METRICS_CMD_REPORT_CRASH = (2 << METRICS_CMD_REQ_SHIFT),
+};
+
+/**
+ * enum metrics_error - metrics error codes
+ * @METRICS_NO_ERROR: no error
+ * @METRICS_ERR_UNKNOWN_CMD: unknown or not implemented command
+ */
+enum metrics_error {
+ METRICS_NO_ERROR = 0,
+ METRICS_ERR_UNKNOWN_CMD = 1,
+};
+
+/**
+ * struct metrics_req - common structure for metrics requests
+ * @cmd: command identifier - one of &enum metrics_cmd
+ * @reserved: must be 0
+ */
+struct metrics_req {
+ uint32_t cmd;
+ uint32_t reserved;
+} __attribute__((__packed__));
+
+/**
+ * struct metrics_resp - common structure for metrics responses
+ * @cmd: command identifier - %METRICS_CMD_RESP_BIT or'ed with a cmd in
+ * one of &enum metrics_cmd
+ * @status: response status, one of &enum metrics_error
+ */
+struct metrics_resp {
+ uint32_t cmd;
+ uint32_t status;
+} __attribute__((__packed__));
+
+/**
+ * struct metrics_report_crash_req - arguments of %METRICS_CMD_REPORT_CRASH
+ * requests
+ * @app_id_len: length of app ID that follows this structure
+ */
+struct metrics_report_crash_req {
+ uint32_t app_id_len;
+} __attribute__((__packed__));
+
+#define METRICS_MAX_APP_ID_LEN 256
+
+#define METRICS_MAX_MSG_SIZE \
+ (sizeof(struct metrics_req) + sizeof(struct metrics_report_crash_req) + \
+ METRICS_MAX_APP_ID_LEN)
diff --git a/trusty/metrics/metrics.cpp b/trusty/metrics/metrics.cpp
new file mode 100644
index 0000000..3ac128a
--- /dev/null
+++ b/trusty/metrics/metrics.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Sourete 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.
+ */
+
+#define LOG_TAG "metrics"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <trusty/metrics/metrics.h>
+#include <trusty/metrics/tipc.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::ErrnoError;
+using android::base::Error;
+
+Result<void> TrustyMetrics::Open() {
+ int fd = tipc_connect(tipc_dev_.c_str(), METRICS_PORT);
+ if (fd < 0) {
+ return ErrnoError() << "failed to connect to Trusty metrics TA";
+ }
+
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0) {
+ return ErrnoError() << "failed F_GETFL";
+ }
+
+ int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if (rc < 0) {
+ return ErrnoError() << "failed F_SETFL";
+ }
+
+ metrics_fd_.reset(fd);
+ return {};
+}
+
+Result<void> TrustyMetrics::WaitForEvent(int timeout_ms) {
+ if (!metrics_fd_.ok()) {
+ return Error() << "connection to Metrics TA has not been initialized yet";
+ }
+
+ struct pollfd pfd = {
+ .fd = metrics_fd_,
+ .events = POLLIN,
+ };
+
+ int rc = poll(&pfd, 1, timeout_ms);
+ if (rc != 1) {
+ return ErrnoError() << "failed poll()";
+ }
+
+ if (!(pfd.revents & POLLIN)) {
+ return ErrnoError() << "channel not ready";
+ }
+
+ return {};
+}
+
+Result<void> TrustyMetrics::HandleEvent() {
+ if (!metrics_fd_.ok()) {
+ return Error() << "connection to Metrics TA has not been initialized yet";
+ }
+
+ uint8_t msg[METRICS_MAX_MSG_SIZE];
+
+ auto rc = read(metrics_fd_, msg, sizeof(msg));
+ if (rc < 0) {
+ return ErrnoError() << "failed to read metrics message";
+ }
+ size_t msg_len = rc;
+
+ if (msg_len < sizeof(metrics_req)) {
+ return Error() << "message too small: " << rc;
+ }
+ auto req = reinterpret_cast<metrics_req*>(msg);
+ size_t offset = sizeof(metrics_req);
+ uint32_t status = METRICS_NO_ERROR;
+
+ switch (req->cmd) {
+ case METRICS_CMD_REPORT_CRASH: {
+ if (msg_len < offset + sizeof(metrics_report_crash_req)) {
+ return Error() << "message too small: " << rc;
+ }
+ auto crash_args = reinterpret_cast<metrics_report_crash_req*>(msg + offset);
+ offset += sizeof(metrics_report_crash_req);
+
+ if (msg_len < offset + crash_args->app_id_len) {
+ return Error() << "message too small: " << rc;
+ }
+ auto app_id_ptr = reinterpret_cast<char*>(msg + offset);
+ std::string app_id(app_id_ptr, crash_args->app_id_len);
+
+ HandleCrash(app_id);
+ break;
+ }
+
+ case METRICS_CMD_REPORT_EVENT_DROP:
+ HandleEventDrop();
+ break;
+
+ default:
+ status = METRICS_ERR_UNKNOWN_CMD;
+ break;
+ }
+
+ metrics_resp resp = {
+ .cmd = req->cmd | METRICS_CMD_RESP_BIT,
+ .status = status,
+ };
+
+ rc = write(metrics_fd_, &resp, sizeof(resp));
+ if (rc < 0) {
+ return ErrnoError() << "failed to request next metrics event";
+ }
+
+ if (rc != (int)sizeof(resp)) {
+ return Error() << "unexpected number of bytes sent event: " << rc;
+ }
+
+ return {};
+}
+
+} // namespace metrics
+} // namespace trusty
+} // namespace android
diff --git a/trusty/metrics/metrics_test.cpp b/trusty/metrics/metrics_test.cpp
new file mode 100644
index 0000000..407ddf2
--- /dev/null
+++ b/trusty/metrics/metrics_test.cpp
@@ -0,0 +1,139 @@
+/*
+ * 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 <android-base/unique_fd.h>
+#include <binder/IPCThreadState.h>
+#include <gtest/gtest.h>
+#include <poll.h>
+#include <trusty/metrics/metrics.h>
+#include <trusty/tipc.h>
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define CRASHER_PORT "com.android.trusty.metrics.test.crasher"
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::unique_fd;
+
+static void TriggerCrash() {
+ size_t num_retries = 3;
+ int fd = -1;
+
+ for (size_t i = 0; i < num_retries; i++) {
+ /* It's possible to time out waiting for crasher TA to restart. */
+ fd = tipc_connect(TIPC_DEV, CRASHER_PORT);
+ if (fd >= 0) {
+ break;
+ }
+ }
+
+ unique_fd crasher(fd);
+ ASSERT_GE(crasher, 0);
+
+ int msg = 0;
+ int rc = write(crasher, &msg, sizeof(msg));
+ ASSERT_EQ(rc, sizeof(msg));
+}
+
+class TrustyMetricsTest : public TrustyMetrics, public ::testing::Test {
+ public:
+ TrustyMetricsTest() : TrustyMetrics(TIPC_DEV) {}
+
+ virtual void HandleCrash(const std::string& app_id) override { crashed_app_ = app_id; }
+
+ virtual void HandleEventDrop() override { event_drop_count_++; }
+
+ virtual void SetUp() override {
+ auto ret = Open();
+ ASSERT_TRUE(ret.ok()) << ret.error();
+ }
+
+ void WaitForAndHandleEvent() {
+ auto ret = WaitForEvent(30000 /* 30 second timeout */);
+ ASSERT_TRUE(ret.ok()) << ret.error();
+
+ ret = HandleEvent();
+ ASSERT_TRUE(ret.ok()) << ret.error();
+ }
+
+ std::string crashed_app_;
+ size_t event_drop_count_;
+};
+
+TEST_F(TrustyMetricsTest, Crash) {
+ TriggerCrash();
+ WaitForAndHandleEvent();
+
+ /* Check that correct TA crashed. */
+ ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
+}
+
+TEST_F(TrustyMetricsTest, PollSet) {
+ int binder_fd;
+ int rc = IPCThreadState::self()->setupPolling(&binder_fd);
+ ASSERT_EQ(rc, 0);
+ ASSERT_GE(binder_fd, 0);
+
+ TriggerCrash();
+
+ struct pollfd pfds[] = {
+ {
+ .fd = binder_fd,
+ .events = POLLIN,
+ },
+ {
+ .fd = GetRawFd(),
+ .events = POLLIN,
+ },
+ };
+
+ rc = poll(pfds, 2, 30000 /* 30 second timeout */);
+ /* We expect one event on the metrics fd. */
+ ASSERT_EQ(rc, 1);
+ ASSERT_TRUE(pfds[1].revents & POLLIN);
+
+ auto ret = HandleEvent();
+ ASSERT_TRUE(ret.ok()) << ret.error();
+
+ /* Check that correct TA crashed. */
+ ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
+}
+
+TEST_F(TrustyMetricsTest, EventDrop) {
+ /* We know the size of the internal event queue is less than this. */
+ size_t num_events = 3;
+
+ ASSERT_EQ(event_drop_count_, 0);
+
+ for (auto i = 0; i < num_events; i++) {
+ TriggerCrash();
+ }
+
+ for (auto i = 0; i < num_events; i++) {
+ WaitForAndHandleEvent();
+ if (event_drop_count_ > 0) {
+ break;
+ }
+ }
+
+ ASSERT_EQ(event_drop_count_, 1);
+}
+
+} // namespace metrics
+} // namespace trusty
+} // namespace android