Merge "Configure Trusty KeyMint devices to use attest_keys."
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 0ff047f..edcea44 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -283,6 +283,7 @@
"libdebuggerd/test/log_fake.cpp",
"libdebuggerd/test/open_files_list_test.cpp",
"libdebuggerd/test/tombstone_test.cpp",
+ "libdebuggerd/test/utility_test.cpp",
],
target: {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 4394274..b107767 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -18,6 +18,7 @@
#include <dlfcn.h>
#include <err.h>
#include <fcntl.h>
+#include <linux/prctl.h>
#include <malloc.h>
#include <stdlib.h>
#include <sys/capability.h>
@@ -31,6 +32,7 @@
#include <chrono>
#include <regex>
+#include <set>
#include <string>
#include <thread>
@@ -54,6 +56,9 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <unwindstack/Elf.h>
+#include <unwindstack/Memory.h>
+
#include <libminijail.h>
#include <scoped_minijail.h>
@@ -340,11 +345,17 @@
std::string result;
ConsumeFd(std::move(output_fd), &result);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0xdead)");
+#ifdef __LP64__
+ ASSERT_MATCH(result,
+ R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x000000000000dead)");
+#else
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0000dead)");
+#endif
if (mte_supported()) {
// Test that the default TAGGED_ADDR_CTRL value is set.
- ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)");
+ ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)"
+ R"( \(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe\))");
}
}
@@ -370,8 +381,7 @@
// The address can either be tagged (new kernels) or untagged (old kernels).
ASSERT_MATCH(
- result,
- R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr (0x100000000000dead|0xdead))");
+ result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x[01]00000000000dead)");
}
// Marked as weak to prevent the compiler from removing the malloc in the caller. In theory, the
@@ -422,6 +432,12 @@
abort();
}
}
+
+static void SetTagCheckingLevelAsync() {
+ if (mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, M_HEAP_TAGGING_LEVEL_ASYNC) == 0) {
+ abort();
+ }
+}
#endif
// Number of iterations required to reliably guarantee a GWP-ASan crash.
@@ -653,6 +669,36 @@
#endif
}
+TEST_F(CrasherTest, mte_async) {
+#if defined(__aarch64__)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&]() {
+ SetTagCheckingLevelAsync();
+ volatile int* p = (volatile int*)malloc(16);
+ 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\), code 8 \(SEGV_MTEAERR\), fault addr --------)");
+#else
+ GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
TEST_F(CrasherTest, mte_multiple_causes) {
#if defined(__aarch64__)
if (!mte_supported()) {
@@ -703,7 +749,7 @@
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."));
+ "listing them in decreasing order of likelihood."));
// 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,
@@ -890,7 +936,7 @@
std::string result;
ConsumeFd(std::move(output_fd), &result);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0xdead)");
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+dead)");
}
TEST_F(CrasherTest, abort) {
@@ -1940,7 +1986,7 @@
std::string result;
ConsumeFd(std::move(output_fd), &result);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x1024)");
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+1024)");
ASSERT_MATCH(result, R"(\nmemory map \(.*\):\n)");
@@ -1970,8 +2016,8 @@
std::string result;
ConsumeFd(std::move(output_fd), &result);
- std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr )";
- match_str += android::base::StringPrintf("0x%" PRIxPTR, crash_uptr);
+ std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x)";
+ match_str += format_full_pointer(crash_uptr);
ASSERT_MATCH(result, match_str);
ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
@@ -2018,8 +2064,8 @@
std::string result;
ConsumeFd(std::move(output_fd), &result);
- std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr )";
- match_str += android::base::StringPrintf("%p", middle_ptr);
+ std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x)";
+ match_str += format_full_pointer(reinterpret_cast<uintptr_t>(middle_ptr));
ASSERT_MATCH(result, match_str);
ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
@@ -2056,8 +2102,8 @@
std::string result;
ConsumeFd(std::move(output_fd), &result);
- std::string match_str = R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\), fault addr )";
- match_str += android::base::StringPrintf("%p", ptr);
+ std::string match_str = R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\), fault addr 0x)";
+ match_str += format_full_pointer(reinterpret_cast<uintptr_t>(ptr));
ASSERT_MATCH(result, match_str);
ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
@@ -2181,8 +2227,214 @@
ConsumeFd(std::move(output_fd), &result);
// Verify the process crashed properly.
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0)");
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0*)");
// Now verify that the dex_pc frame includes a proper function name.
ASSERT_MATCH(result, R"( \[anon:dex\] \(Main\.\<init\>\+2)");
}
+
+static std::string format_map_pointer(uintptr_t ptr) {
+#if defined(__LP64__)
+ return android::base::StringPrintf("%08x'%08x", static_cast<uint32_t>(ptr >> 32),
+ static_cast<uint32_t>(ptr & 0xffffffff));
+#else
+ return android::base::StringPrintf("%08x", ptr);
+#endif
+}
+
+// Verify that map data is properly formatted.
+TEST_F(CrasherTest, verify_map_format) {
+ // Create multiple maps to make sure that the map data is formatted properly.
+ void* none_map = mmap(nullptr, getpagesize(), 0, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ ASSERT_NE(MAP_FAILED, none_map);
+ void* r_map = mmap(nullptr, getpagesize(), PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ ASSERT_NE(MAP_FAILED, r_map);
+ void* w_map = mmap(nullptr, getpagesize(), PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ ASSERT_NE(MAP_FAILED, w_map);
+ void* x_map = mmap(nullptr, getpagesize(), PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ ASSERT_NE(MAP_FAILED, x_map);
+
+ TemporaryFile tf;
+ ASSERT_EQ(0x2000, lseek(tf.fd, 0x2000, SEEK_SET));
+ char c = 'f';
+ ASSERT_EQ(1, write(tf.fd, &c, 1));
+ ASSERT_EQ(0x5000, lseek(tf.fd, 0x5000, SEEK_SET));
+ ASSERT_EQ(1, write(tf.fd, &c, 1));
+ ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET));
+ void* file_map = mmap(nullptr, 0x3001, PROT_READ, MAP_PRIVATE, tf.fd, 0x2000);
+ ASSERT_NE(MAP_FAILED, file_map);
+
+ StartProcess([]() { abort(); });
+
+ ASSERT_EQ(0, munmap(none_map, getpagesize()));
+ ASSERT_EQ(0, munmap(r_map, getpagesize()));
+ ASSERT_EQ(0, munmap(w_map, getpagesize()));
+ ASSERT_EQ(0, munmap(x_map, getpagesize()));
+ ASSERT_EQ(0, munmap(file_map, 0x3001));
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGABRT);
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ std::string match_str;
+ // Verify none.
+ match_str = android::base::StringPrintf(
+ " %s-%s --- 0 1000\\n",
+ format_map_pointer(reinterpret_cast<uintptr_t>(none_map)).c_str(),
+ format_map_pointer(reinterpret_cast<uintptr_t>(none_map) + getpagesize() - 1).c_str());
+ ASSERT_MATCH(result, match_str);
+
+ // Verify read-only.
+ match_str = android::base::StringPrintf(
+ " %s-%s r-- 0 1000\\n",
+ format_map_pointer(reinterpret_cast<uintptr_t>(r_map)).c_str(),
+ format_map_pointer(reinterpret_cast<uintptr_t>(r_map) + getpagesize() - 1).c_str());
+ ASSERT_MATCH(result, match_str);
+
+ // Verify write-only.
+ match_str = android::base::StringPrintf(
+ " %s-%s -w- 0 1000\\n",
+ format_map_pointer(reinterpret_cast<uintptr_t>(w_map)).c_str(),
+ format_map_pointer(reinterpret_cast<uintptr_t>(w_map) + getpagesize() - 1).c_str());
+ ASSERT_MATCH(result, match_str);
+
+ // Verify exec-only.
+ match_str = android::base::StringPrintf(
+ " %s-%s --x 0 1000\\n",
+ format_map_pointer(reinterpret_cast<uintptr_t>(x_map)).c_str(),
+ format_map_pointer(reinterpret_cast<uintptr_t>(x_map) + getpagesize() - 1).c_str());
+ ASSERT_MATCH(result, match_str);
+
+ // Verify file map with non-zero offset and a name.
+ match_str = android::base::StringPrintf(
+ " %s-%s r-- 2000 4000 %s\\n",
+ format_map_pointer(reinterpret_cast<uintptr_t>(file_map)).c_str(),
+ format_map_pointer(reinterpret_cast<uintptr_t>(file_map) + 0x3fff).c_str(), tf.path);
+ ASSERT_MATCH(result, match_str);
+}
+
+// Verify that the tombstone map data is correct.
+TEST_F(CrasherTest, verify_header) {
+ StartProcess([]() { abort(); });
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGABRT);
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ std::string match_str = android::base::StringPrintf(
+ "Build fingerprint: '%s'\\nRevision: '%s'\\n",
+ android::base::GetProperty("ro.build.fingerprint", "unknown").c_str(),
+ android::base::GetProperty("ro.revision", "unknown").c_str());
+ match_str += android::base::StringPrintf("ABI: '%s'\n", ABI_STRING);
+ ASSERT_MATCH(result, match_str);
+}
+
+// Verify that the thread header is formatted properly.
+TEST_F(CrasherTest, verify_thread_header) {
+ void* shared_map =
+ mmap(nullptr, sizeof(pid_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(MAP_FAILED, shared_map);
+ memset(shared_map, 0, sizeof(pid_t));
+
+ StartProcess([&shared_map]() {
+ std::atomic_bool tid_written;
+ std::thread thread([&tid_written, &shared_map]() {
+ pid_t tid = gettid();
+ memcpy(shared_map, &tid, sizeof(pid_t));
+ tid_written = true;
+ volatile bool done = false;
+ while (!done)
+ ;
+ });
+ thread.detach();
+ while (!tid_written.load(std::memory_order_acquire))
+ ;
+ abort();
+ });
+
+ pid_t primary_pid = crasher_pid;
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGABRT);
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ // Read the tid data out.
+ pid_t tid;
+ memcpy(&tid, shared_map, sizeof(pid_t));
+ ASSERT_NE(0, tid);
+
+ ASSERT_EQ(0, munmap(shared_map, sizeof(pid_t)));
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ // Verify that there are two headers, one where the tid is "primary_pid"
+ // and the other where the tid is "tid".
+ std::string match_str = android::base::StringPrintf("pid: %d, tid: %d, name: .* >>> .* <<<\\n",
+ primary_pid, primary_pid);
+ ASSERT_MATCH(result, match_str);
+
+ match_str =
+ android::base::StringPrintf("pid: %d, tid: %d, name: .* >>> .* <<<\\n", primary_pid, tid);
+ ASSERT_MATCH(result, match_str);
+}
+
+// Verify that there is a BuildID present in the map section and set properly.
+TEST_F(CrasherTest, verify_build_id) {
+ StartProcess([]() { abort(); });
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGABRT);
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ // Find every /system or /apex lib and verify the BuildID is displayed
+ // properly.
+ bool found_valid_elf = false;
+ std::smatch match;
+ std::regex build_id_regex(R"( ((/system/|/apex/)\S+) \(BuildId: ([^\)]+)\))");
+ for (std::string prev_file; std::regex_search(result, match, build_id_regex);
+ result = match.suffix()) {
+ if (prev_file == match[1]) {
+ // Already checked this file.
+ continue;
+ }
+
+ prev_file = match[1];
+ unwindstack::Elf elf(unwindstack::Memory::CreateFileMemory(prev_file, 0).release());
+ if (!elf.Init() || !elf.valid()) {
+ // Skipping invalid elf files.
+ continue;
+ }
+ ASSERT_EQ(match[3], elf.GetPrintableBuildID());
+
+ found_valid_elf = true;
+ }
+ ASSERT_TRUE(found_valid_elf) << "Did not find any elf files with valid BuildIDs to check.";
+}
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 24ae169..002321f 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -92,6 +92,7 @@
void get_signal_sender(char* buf, size_t n, const siginfo_t*);
const char* get_signame(const siginfo_t*);
const char* get_sigcode(const siginfo_t*);
+std::string describe_tagged_addr_ctrl(long ctrl);
// Number of bytes per MTE granule.
constexpr size_t kTagGranuleSize = 16;
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index f4690ba..a89f385 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -135,7 +135,7 @@
if (error_info_.reports[1].error_type != UNKNOWN) {
_LOG(log, logtype::HEADER,
"\nNote: multiple potential causes for this crash were detected, listing them in "
- "decreasing order of probability.\n");
+ "decreasing order of likelihood.\n");
}
size_t report_num = 0;
diff --git a/debuggerd/libdebuggerd/test/UnwinderMock.h b/debuggerd/libdebuggerd/test/UnwinderMock.h
index 8f84346..1e3c559 100644
--- a/debuggerd/libdebuggerd/test/UnwinderMock.h
+++ b/debuggerd/libdebuggerd/test/UnwinderMock.h
@@ -16,6 +16,8 @@
#pragma once
+#include <memory>
+
#include <unwindstack/MapInfo.h>
#include <unwindstack/Maps.h>
#include <unwindstack/Unwinder.h>
@@ -31,7 +33,7 @@
}
void MockSetBuildID(uint64_t offset, const std::string& build_id) {
- unwindstack::MapInfo* map_info = GetMaps()->Find(offset);
+ std::shared_ptr<unwindstack::MapInfo> map_info = GetMaps()->Find(offset);
if (map_info != nullptr) {
map_info->SetBuildID(std::string(build_id));
}
diff --git a/debuggerd/libdebuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp
index a14dcb0..1cbfb56 100644
--- a/debuggerd/libdebuggerd/test/tombstone_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp
@@ -32,9 +32,6 @@
#include "host_signal_fixup.h"
#include "log_fake.h"
-// Include tombstone.cpp to define log_tag before GWP-ASan includes log.
-#include "tombstone.cpp"
-
#include "gwp_asan.cpp"
using ::testing::MatchesRegex;
@@ -82,283 +79,6 @@
std::string amfd_data_;
};
-TEST_F(TombstoneTest, single_map) {
-#if defined(__LP64__)
- unwinder_mock_->MockAddMap(0x123456789abcd000UL, 0x123456789abdf000UL, 0, 0, "", 0);
-#else
- unwinder_mock_->MockAddMap(0x1234000, 0x1235000, 0, 0, "", 0);
-#endif
-
- dump_all_maps(&log_, unwinder_mock_.get(), 0);
-
- std::string tombstone_contents;
- ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
- ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
- const char* expected_dump = \
-"\nmemory map (1 entry):\n"
-#if defined(__LP64__)
-" 12345678'9abcd000-12345678'9abdefff --- 0 12000\n";
-#else
-" 01234000-01234fff --- 0 1000\n";
-#endif
- ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
- ASSERT_STREQ("", amfd_data_.c_str());
-
- // Verify that the log buf is empty, and no error messages.
- ASSERT_STREQ("", getFakeLogBuf().c_str());
- ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, single_map_elf_build_id) {
- uint64_t build_id_offset;
-#if defined(__LP64__)
- build_id_offset = 0x123456789abcd000UL;
- unwinder_mock_->MockAddMap(build_id_offset, 0x123456789abdf000UL, 0, PROT_READ,
- "/system/lib/libfake.so", 0);
-#else
- build_id_offset = 0x1234000;
- unwinder_mock_->MockAddMap(0x1234000, 0x1235000, 0, PROT_READ, "/system/lib/libfake.so", 0);
-#endif
-
- unwinder_mock_->MockSetBuildID(
- build_id_offset,
- std::string{static_cast<char>(0xab), static_cast<char>(0xcd), static_cast<char>(0xef),
- static_cast<char>(0x12), static_cast<char>(0x34), static_cast<char>(0x56),
- static_cast<char>(0x78), static_cast<char>(0x90), static_cast<char>(0xab),
- static_cast<char>(0xcd), static_cast<char>(0xef), static_cast<char>(0x12),
- static_cast<char>(0x34), static_cast<char>(0x56), static_cast<char>(0x78),
- static_cast<char>(0x90)});
- dump_all_maps(&log_, unwinder_mock_.get(), 0);
-
- std::string tombstone_contents;
- ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
- ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
- const char* expected_dump = \
-"\nmemory map (1 entry):\n"
-#if defined(__LP64__)
-" 12345678'9abcd000-12345678'9abdefff r-- 0 12000 /system/lib/libfake.so (BuildId: abcdef1234567890abcdef1234567890)\n";
-#else
-" 01234000-01234fff r-- 0 1000 /system/lib/libfake.so (BuildId: abcdef1234567890abcdef1234567890)\n";
-#endif
- ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
- ASSERT_STREQ("", amfd_data_.c_str());
-
- // Verify that the log buf is empty, and no error messages.
- ASSERT_STREQ("", getFakeLogBuf().c_str());
- ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps) {
- unwinder_mock_->MockAddMap(0xa234000, 0xa235000, 0, 0, "", 0);
- unwinder_mock_->MockAddMap(0xa334000, 0xa335000, 0xf000, PROT_READ, "", 0);
- unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
- unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
- unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
- "/system/lib/fake.so", 0);
-
- dump_all_maps(&log_, unwinder_mock_.get(), 0);
-
- std::string tombstone_contents;
- ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
- ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
- const char* expected_dump =
- "\nmemory map (5 entries):\n"
-#if defined(__LP64__)
- " 00000000'0a234000-00000000'0a234fff --- 0 1000\n"
- " 00000000'0a334000-00000000'0a334fff r-- f000 1000\n"
- " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- " 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n";
-#else
- " 0a234000-0a234fff --- 0 1000\n"
- " 0a334000-0a334fff r-- f000 1000\n"
- " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- " 0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n";
-#endif
- ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
- ASSERT_STREQ("", amfd_data_.c_str());
-
- // Verify that the log buf is empty, and no error messages.
- ASSERT_STREQ("", getFakeLogBuf().c_str());
- ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps_fault_address_before) {
- unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
- unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
- unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
- "/system/lib/fake.so", 0);
-
- dump_all_maps(&log_, unwinder_mock_.get(), 0x1000);
-
- std::string tombstone_contents;
- ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
- ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
- const char* expected_dump =
- "\nmemory map (3 entries):\n"
-#if defined(__LP64__)
- "--->Fault address falls at 00000000'00001000 before any mapped regions\n"
- " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- " 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n";
-#else
- "--->Fault address falls at 00001000 before any mapped regions\n"
- " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- " 0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n";
-#endif
- ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
- ASSERT_STREQ("", amfd_data_.c_str());
-
- // Verify that the log buf is empty, and no error messages.
- ASSERT_STREQ("", getFakeLogBuf().c_str());
- ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps_fault_address_between) {
- unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
- unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
- unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
- "/system/lib/fake.so", 0);
-
- dump_all_maps(&log_, unwinder_mock_.get(), 0xa533000);
-
- std::string tombstone_contents;
- ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
- ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
- const char* expected_dump =
- "\nmemory map (3 entries): (fault address prefixed with --->)\n"
-#if defined(__LP64__)
- " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- "--->Fault address falls at 00000000'0a533000 between mapped regions\n"
- " 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n";
-#else
- " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- "--->Fault address falls at 0a533000 between mapped regions\n"
- " 0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n";
-#endif
- ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
- ASSERT_STREQ("", amfd_data_.c_str());
-
- // Verify that the log buf is empty, and no error messages.
- ASSERT_STREQ("", getFakeLogBuf().c_str());
- ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps_fault_address_in_map) {
- unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
- unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
- unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
- "/system/lib/fake.so", 0);
-
- dump_all_maps(&log_, unwinder_mock_.get(), 0xa534040);
-
- std::string tombstone_contents;
- ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
- ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
- const char* expected_dump =
- "\nmemory map (3 entries): (fault address prefixed with --->)\n"
-#if defined(__LP64__)
- " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- "--->00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n";
-#else
- " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- "--->0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n";
-#endif
- ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
- ASSERT_STREQ("", amfd_data_.c_str());
-
- // Verify that the log buf is empty, and no error messages.
- ASSERT_STREQ("", getFakeLogBuf().c_str());
- ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, multiple_maps_fault_address_after) {
- unwinder_mock_->MockAddMap(0xa434000, 0xa435000, 0x1000, PROT_WRITE, "", 0xd000);
- unwinder_mock_->MockAddMap(0xa534000, 0xa535000, 0x3000, PROT_EXEC, "", 0x2000);
- unwinder_mock_->MockAddMap(0xa634000, 0xa635000, 0, PROT_READ | PROT_WRITE | PROT_EXEC,
- "/system/lib/fake.so", 0);
-
-#if defined(__LP64__)
- uint64_t addr = 0x12345a534040UL;
-#else
- uint64_t addr = 0xf534040UL;
-#endif
- dump_all_maps(&log_, unwinder_mock_.get(), addr);
-
- std::string tombstone_contents;
- ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
- ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
- const char* expected_dump =
- "\nmemory map (3 entries): (fault address prefixed with --->)\n"
-#if defined(__LP64__)
- " 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- " 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n"
- "--->Fault address falls at 00001234'5a534040 after any mapped regions\n";
-#else
- " 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n"
- " 0a534000-0a534fff --x 3000 1000 (load bias 0x2000)\n"
- " 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n"
- "--->Fault address falls at 0f534040 after any mapped regions\n";
-#endif
- ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
-
- ASSERT_STREQ("", amfd_data_.c_str());
-
- // Verify that the log buf is empty, and no error messages.
- ASSERT_STREQ("", getFakeLogBuf().c_str());
- ASSERT_STREQ("", getFakeLogPrint().c_str());
-}
-
-TEST_F(TombstoneTest, dump_log_file_error) {
- log_.should_retrieve_logcat = true;
- dump_log_file(&log_, 123, "/fake/filename", 10);
-
- std::string tombstone_contents;
- ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
- ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
- ASSERT_STREQ("", tombstone_contents.c_str());
-
- ASSERT_STREQ("", getFakeLogBuf().c_str());
- ASSERT_STREQ("6 DEBUG Unable to open /fake/filename: Permission denied\n\n",
- getFakeLogPrint().c_str());
-
- ASSERT_STREQ("", amfd_data_.c_str());
-}
-
-TEST_F(TombstoneTest, dump_header_info) {
- dump_header_info(&log_);
-
- std::string expected = android::base::StringPrintf(
- "Build fingerprint: '%s'\nRevision: '%s'\n",
- android::base::GetProperty("ro.build.fingerprint", "unknown").c_str(),
- android::base::GetProperty("ro.revision", "unknown").c_str());
- expected += android::base::StringPrintf("ABI: '%s'\n", ABI_STRING);
- ASSERT_STREQ(expected.c_str(), amfd_data_.c_str());
-}
-
-TEST_F(TombstoneTest, dump_thread_info_uid) {
- std::vector<std::string> cmdline = {"some_process"};
- dump_thread_info(
- &log_,
- ThreadInfo{
- .uid = 1, .tid = 3, .thread_name = "some_thread", .pid = 2, .command_line = cmdline});
- std::string expected = "pid: 2, tid: 3, name: some_thread >>> some_process <<<\nuid: 1\n";
- ASSERT_STREQ(expected.c_str(), amfd_data_.c_str());
-}
-
class GwpAsanCrashDataTest : public GwpAsanCrashData {
public:
GwpAsanCrashDataTest(
@@ -483,4 +203,3 @@
"Cause: \\[GWP-ASan\\]: Invalid \\(Wild\\) Free, 33 bytes right of a 32-byte "
"allocation at 0x[a-fA-F0-9]+\n"));
}
-
diff --git a/debuggerd/libdebuggerd/test/utility_test.cpp b/debuggerd/libdebuggerd/test/utility_test.cpp
new file mode 100644
index 0000000..97328b7
--- /dev/null
+++ b/debuggerd/libdebuggerd/test/utility_test.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <sys/prctl.h>
+
+#include "libdebuggerd/utility.h"
+
+TEST(UtilityTest, describe_tagged_addr_ctrl) {
+ EXPECT_EQ("", describe_tagged_addr_ctrl(0));
+ EXPECT_EQ(" (PR_TAGGED_ADDR_ENABLE)", describe_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE));
+ EXPECT_EQ(" (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe)",
+ describe_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+ (0xfffe << PR_MTE_TAG_SHIFT)));
+ EXPECT_EQ(
+ " (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, PR_MTE_TCF_ASYNC, mask 0xfffe, unknown "
+ "0xf0000000)",
+ describe_tagged_addr_ctrl(0xf0000000 | PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+ PR_MTE_TCF_ASYNC | (0xfffe << PR_MTE_TAG_SHIFT)));
+}
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 534d7be..f21a203 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -18,565 +18,38 @@
#include "libdebuggerd/tombstone.h"
-#include <dirent.h>
#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/ptrace.h>
-#include <sys/stat.h>
-#include <time.h>
+#include <sys/types.h>
+#include <unistd.h>
#include <memory>
#include <string>
#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <android/log.h>
#include <async_safe/log.h>
-#include <bionic/macros.h>
#include <log/log.h>
-#include <log/log_read.h>
-#include <log/logprint.h>
#include <private/android_filesystem_config.h>
-#include <unwindstack/DexFiles.h>
-#include <unwindstack/JitDebug.h>
-#include <unwindstack/Maps.h>
#include <unwindstack/Memory.h>
#include <unwindstack/Regs.h>
#include <unwindstack/Unwinder.h>
#include "libdebuggerd/backtrace.h"
-#include "libdebuggerd/gwp_asan.h"
#include "libdebuggerd/open_files_list.h"
#include "libdebuggerd/utility.h"
#include "util.h"
-#if defined(USE_SCUDO)
-#include "libdebuggerd/scudo.h"
-#endif
-
-#include "gwp_asan/common.h"
-#include "gwp_asan/crash_handler.h"
-
#include "tombstone.pb.h"
-using android::base::GetBoolProperty;
-using android::base::GetProperty;
-using android::base::StringPrintf;
using android::base::unique_fd;
using namespace std::literals::string_literals;
-#define STACK_WORDS 16
-
-static void dump_header_info(log_t* log) {
- auto fingerprint = GetProperty("ro.build.fingerprint", "unknown");
- auto revision = GetProperty("ro.revision", "unknown");
-
- _LOG(log, logtype::HEADER, "Build fingerprint: '%s'\n", fingerprint.c_str());
- _LOG(log, logtype::HEADER, "Revision: '%s'\n", revision.c_str());
- _LOG(log, logtype::HEADER, "ABI: '%s'\n", ABI_STRING);
-}
-
-static std::string get_stack_overflow_cause(uint64_t fault_addr, uint64_t sp,
- unwindstack::Maps* maps) {
- static constexpr uint64_t kMaxDifferenceBytes = 256;
- uint64_t difference;
- if (sp >= fault_addr) {
- difference = sp - fault_addr;
- } else {
- difference = fault_addr - sp;
- }
- if (difference <= kMaxDifferenceBytes) {
- // The faulting address is close to the current sp, check if the sp
- // indicates a stack overflow.
- // On arm, the sp does not get updated when the instruction faults.
- // In this case, the sp will still be in a valid map, which is the
- // last case below.
- // On aarch64, the sp does get updated when the instruction faults.
- // In this case, the sp will be in either an invalid map if triggered
- // on the main thread, or in a guard map if in another thread, which
- // will be the first case or second case from below.
- 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)) {
- return "stack pointer is not in a rw map; likely due to stack overflow.";
- } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) {
- return "stack pointer is close to top of stack; likely stack overflow.";
- }
- }
- return "";
-}
-
-static void dump_probable_cause(log_t* log, unwindstack::Unwinder* unwinder,
- const ProcessInfo& process_info, const ThreadInfo& main_thread) {
-#if defined(USE_SCUDO)
- ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
- if (scudo_crash_data.CrashIsMine()) {
- scudo_crash_data.DumpCause(log, unwinder);
- return;
- }
-#endif
-
- GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
- main_thread);
- if (gwp_asan_crash_data.CrashIsMine()) {
- gwp_asan_crash_data.DumpCause(log);
- return;
- }
-
- unwindstack::Maps* maps = unwinder->GetMaps();
- unwindstack::Regs* regs = main_thread.registers.get();
- const siginfo_t* si = main_thread.siginfo;
- std::string cause;
- if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) {
- if (si->si_addr < reinterpret_cast<void*>(4096)) {
- cause = StringPrintf("null pointer dereference");
- } else if (si->si_addr == reinterpret_cast<void*>(0xffff0ffc)) {
- cause = "call to kuser_helper_version";
- } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fe0)) {
- cause = "call to kuser_get_tls";
- } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fc0)) {
- cause = "call to kuser_cmpxchg";
- } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fa0)) {
- cause = "call to kuser_memory_barrier";
- } else if (si->si_addr == reinterpret_cast<void*>(0xffff0f60)) {
- cause = "call to kuser_cmpxchg64";
- } else {
- cause = get_stack_overflow_cause(reinterpret_cast<uint64_t>(si->si_addr), regs->sp(), maps);
- }
- } 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) {
- 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);
- }
- } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
- cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
- si->si_syscall);
- }
-
- if (!cause.empty()) _LOG(log, logtype::HEADER, "Cause: %s\n", cause.c_str());
-}
-
-static void dump_signal_info(log_t* log, const ThreadInfo& thread_info,
- const ProcessInfo& process_info, unwindstack::Memory* process_memory) {
- char addr_desc[64]; // ", fault addr 0x1234"
- if (process_info.has_fault_address) {
- // SIGILL faults will never have tagged addresses, so okay to
- // indiscriminately use the tagged address here.
- size_t addr = process_info.maybe_tagged_fault_address;
- if (thread_info.siginfo->si_signo == SIGILL) {
- uint32_t instruction = {};
- process_memory->Read(addr, &instruction, sizeof(instruction));
- snprintf(addr_desc, sizeof(addr_desc), "0x%zx (*pc=%#08x)", addr, instruction);
- } else {
- snprintf(addr_desc, sizeof(addr_desc), "0x%zx", addr);
- }
- } else {
- snprintf(addr_desc, sizeof(addr_desc), "--------");
- }
-
- char sender_desc[32] = {}; // " from pid 1234, uid 666"
- if (signal_has_sender(thread_info.siginfo, thread_info.pid)) {
- get_signal_sender(sender_desc, sizeof(sender_desc), thread_info.siginfo);
- }
-
- _LOG(log, logtype::HEADER, "signal %d (%s), code %d (%s%s), fault addr %s\n",
- thread_info.siginfo->si_signo, get_signame(thread_info.siginfo),
- thread_info.siginfo->si_code, get_sigcode(thread_info.siginfo), sender_desc, addr_desc);
-}
-
-static void dump_thread_info(log_t* log, const ThreadInfo& thread_info) {
- // Don't try to collect logs from the threads that implement the logging system itself.
- if (thread_info.uid == AID_LOGD) log->should_retrieve_logcat = false;
-
- const char* process_name = "<unknown>";
- if (!thread_info.command_line.empty()) {
- process_name = thread_info.command_line[0].c_str();
- }
-
- _LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s >>> %s <<<\n", thread_info.pid,
- thread_info.tid, thread_info.thread_name.c_str(), process_name);
- _LOG(log, logtype::HEADER, "uid: %d\n", thread_info.uid);
- if (thread_info.tagged_addr_ctrl != -1) {
- _LOG(log, logtype::HEADER, "tagged_addr_ctrl: %016lx\n", thread_info.tagged_addr_ctrl);
- }
-}
-
-static std::string get_addr_string(uint64_t addr) {
- std::string addr_str;
-#if defined(__LP64__)
- addr_str = StringPrintf("%08x'%08x", static_cast<uint32_t>(addr >> 32),
- static_cast<uint32_t>(addr & 0xffffffff));
-#else
- addr_str = StringPrintf("%08x", static_cast<uint32_t>(addr));
-#endif
- return addr_str;
-}
-
-static void dump_abort_message(log_t* log, unwindstack::Memory* process_memory, uint64_t address) {
- if (address == 0) {
- return;
- }
-
- size_t length;
- if (!process_memory->ReadFully(address, &length, sizeof(length))) {
- _LOG(log, logtype::HEADER, "Failed to read abort message header: %s\n", strerror(errno));
- return;
- }
-
- // The length field includes the length of the length field itself.
- if (length < sizeof(size_t)) {
- _LOG(log, logtype::HEADER, "Abort message header malformed: claimed length = %zd\n", length);
- return;
- }
-
- length -= sizeof(size_t);
-
- // The abort message should be null terminated already, but reserve a spot for NUL just in case.
- std::vector<char> msg(length + 1);
- if (!process_memory->ReadFully(address + sizeof(length), &msg[0], length)) {
- _LOG(log, logtype::HEADER, "Failed to read abort message: %s\n", strerror(errno));
- return;
- }
-
- // Remove any trailing newlines.
- size_t index = length;
- while (index > 0 && (msg[index - 1] == '\0' || msg[index - 1] == '\n')) {
- --index;
- }
- msg[index] = '\0';
- _LOG(log, logtype::HEADER, "Abort message: '%s'\n", &msg[0]);
-}
-
-static void dump_all_maps(log_t* log, unwindstack::Unwinder* unwinder, uint64_t addr) {
- bool print_fault_address_marker = addr;
-
- unwindstack::Maps* maps = unwinder->GetMaps();
- _LOG(log, logtype::MAPS,
- "\n"
- "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()) {
- _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;
- } else {
- _LOG(log, logtype::MAPS, " (fault address prefixed with --->)\n");
- }
- } else {
- _LOG(log, logtype::MAPS, "\n");
- }
-
- std::shared_ptr<unwindstack::Memory>& process_memory = unwinder->GetProcessMemory();
-
- std::string line;
- for (auto const& map_info : *maps) {
- line = " ";
- if (print_fault_address_marker) {
- 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()) {
- 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 += 'r';
- } else {
- line += '-';
- }
- if (map_info->flags() & PROT_WRITE) {
- line += 'w';
- } else {
- line += '-';
- }
- 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());
- bool space_needed = true;
- if (!map_info->name().empty()) {
- space_needed = false;
- line += " " + map_info->name();
- std::string build_id = map_info->GetPrintableBuildID();
- if (!build_id.empty()) {
- line += " (BuildId: " + build_id + ")";
- }
- }
- uint64_t load_bias = map_info->GetLoadBias(process_memory);
- if (load_bias != 0) {
- if (space_needed) {
- line += ' ';
- }
- line += StringPrintf(" (load bias 0x%" PRIx64 ")", load_bias);
- }
- _LOG(log, logtype::MAPS, "%s\n", line.c_str());
- }
- if (print_fault_address_marker) {
- _LOG(log, logtype::MAPS, "--->Fault address falls at %s after any mapped regions\n",
- get_addr_string(addr).c_str());
- }
-}
-
-static void print_register_row(log_t* log,
- const std::vector<std::pair<std::string, uint64_t>>& registers) {
- std::string output;
- for (auto& [name, value] : registers) {
- output += android::base::StringPrintf(" %-3s %0*" PRIx64, name.c_str(),
- static_cast<int>(2 * sizeof(void*)),
- static_cast<uint64_t>(value));
- }
-
- _LOG(log, logtype::REGISTERS, " %s\n", output.c_str());
-}
-
-void dump_registers(log_t* log, unwindstack::Regs* regs) {
- // Split lr/sp/pc into their own special row.
- static constexpr size_t column_count = 4;
- std::vector<std::pair<std::string, uint64_t>> current_row;
- std::vector<std::pair<std::string, uint64_t>> special_row;
-
-#if defined(__arm__) || defined(__aarch64__)
- static constexpr const char* special_registers[] = {"ip", "lr", "sp", "pc", "pst"};
-#elif defined(__i386__)
- static constexpr const char* special_registers[] = {"ebp", "esp", "eip"};
-#elif defined(__x86_64__)
- static constexpr const char* special_registers[] = {"rbp", "rsp", "rip"};
-#else
- static constexpr const char* special_registers[] = {};
-#endif
-
- regs->IterateRegisters([log, ¤t_row, &special_row](const char* name, uint64_t value) {
- auto row = ¤t_row;
- for (const char* special_name : special_registers) {
- if (strcmp(special_name, name) == 0) {
- row = &special_row;
- break;
- }
- }
-
- row->emplace_back(name, value);
- if (current_row.size() == column_count) {
- print_register_row(log, current_row);
- current_row.clear();
- }
- });
-
- if (!current_row.empty()) {
- print_register_row(log, current_row);
- }
-
- print_register_row(log, special_row);
-}
-
-void dump_memory_and_code(log_t* log, unwindstack::Maps* maps, unwindstack::Memory* memory,
- unwindstack::Regs* regs) {
- regs->IterateRegisters([log, maps, memory](const char* reg_name, uint64_t reg_value) {
- 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() + ")";
- }
- }
- dump_memory(log, memory, reg_value, label);
- });
-}
-
-static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const ThreadInfo& thread_info,
- const ProcessInfo& process_info, bool primary_thread) {
- log->current_tid = thread_info.tid;
- if (!primary_thread) {
- _LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
- }
- dump_thread_info(log, thread_info);
-
- if (thread_info.siginfo) {
- dump_signal_info(log, thread_info, process_info, unwinder->GetProcessMemory().get());
- }
-
- if (primary_thread) {
- // The main thread must have a valid siginfo.
- CHECK(thread_info.siginfo != nullptr);
- dump_probable_cause(log, unwinder, process_info, thread_info);
-
- dump_abort_message(log, unwinder->GetProcessMemory().get(), process_info.abort_msg_address);
- }
-
- dump_registers(log, thread_info.registers.get());
-
- // Unwind will mutate the registers, so make a copy first.
- std::unique_ptr<unwindstack::Regs> regs_copy(thread_info.registers->Clone());
- unwinder->SetRegs(regs_copy.get());
- unwinder->Unwind();
- if (unwinder->NumFrames() == 0) {
- _LOG(log, logtype::THREAD, "Failed to unwind\n");
- if (unwinder->LastErrorCode() != unwindstack::ERROR_NONE) {
- _LOG(log, logtype::THREAD, " Error code: %s\n", unwinder->LastErrorCodeString());
- _LOG(log, logtype::THREAD, " Error address: 0x%" PRIx64 "\n", unwinder->LastErrorAddress());
- }
- } else {
- _LOG(log, logtype::BACKTRACE, "\nbacktrace:\n");
- log_backtrace(log, unwinder, " ");
- }
-
- if (primary_thread) {
- GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
- thread_info);
-
- if (gwp_asan_crash_data.HasDeallocationTrace()) {
- gwp_asan_crash_data.DumpDeallocationTrace(log, unwinder);
- }
-
- if (gwp_asan_crash_data.HasAllocationTrace()) {
- gwp_asan_crash_data.DumpAllocationTrace(log, unwinder);
- }
-
- unwindstack::Maps* maps = unwinder->GetMaps();
- dump_memory_and_code(log, maps, unwinder->GetProcessMemory().get(),
- thread_info.registers.get());
- if (maps != nullptr) {
- uint64_t addr = 0;
- if (process_info.has_fault_address) {
- addr = process_info.untagged_fault_address;
- }
- dump_all_maps(log, unwinder, addr);
- }
- }
-
- log->current_tid = log->crashed_tid;
- return true;
-}
-
-// Reads the contents of the specified log device, filters out the entries
-// that don't match the specified pid, and writes them to the tombstone file.
-//
-// If "tail" is non-zero, log the last "tail" number of lines.
-static void dump_log_file(log_t* log, pid_t pid, const char* filename, unsigned int tail) {
- bool first = true;
- logger_list* logger_list;
-
- if (!log->should_retrieve_logcat) {
- return;
- }
-
- logger_list =
- android_logger_list_open(android_name_to_log_id(filename), ANDROID_LOG_NONBLOCK, tail, pid);
-
- if (!logger_list) {
- ALOGE("Unable to open %s: %s\n", filename, strerror(errno));
- return;
- }
-
- while (true) {
- log_msg log_entry;
- ssize_t actual = android_logger_list_read(logger_list, &log_entry);
-
- if (actual < 0) {
- if (actual == -EINTR) {
- // interrupted by signal, retry
- continue;
- } else if (actual == -EAGAIN) {
- // non-blocking EOF; we're done
- break;
- } else {
- ALOGE("Error while reading log: %s\n", strerror(-actual));
- break;
- }
- } else if (actual == 0) {
- ALOGE("Got zero bytes while reading log: %s\n", strerror(errno));
- break;
- }
-
- // NOTE: if you ALOGV something here, this will spin forever,
- // because you will be writing as fast as you're reading. Any
- // high-frequency debug diagnostics should just be written to
- // the tombstone file.
-
- if (first) {
- _LOG(log, logtype::LOGS, "--------- %slog %s\n", tail ? "tail end of " : "", filename);
- first = false;
- }
-
- // Msg format is: <priority:1><tag:N>\0<message:N>\0
- //
- // We want to display it in the same format as "logcat -v threadtime"
- // (although in this case the pid is redundant).
- char timeBuf[32];
- time_t sec = static_cast<time_t>(log_entry.entry.sec);
- tm tm;
- localtime_r(&sec, &tm);
- strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", &tm);
-
- char* msg = log_entry.msg();
- if (msg == nullptr) {
- continue;
- }
- unsigned char prio = msg[0];
- char* tag = msg + 1;
- msg = tag + strlen(tag) + 1;
-
- // consume any trailing newlines
- char* nl = msg + strlen(msg) - 1;
- while (nl >= msg && *nl == '\n') {
- *nl-- = '\0';
- }
-
- static const char* kPrioChars = "!.VDIWEFS";
- char prioChar = (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?');
-
- // Look for line breaks ('\n') and display each text line
- // on a separate line, prefixed with the header, like logcat does.
- do {
- nl = strchr(msg, '\n');
- if (nl != nullptr) {
- *nl = '\0';
- ++nl;
- }
-
- _LOG(log, logtype::LOGS, "%s.%03d %5d %5d %c %-8s: %s\n", timeBuf,
- log_entry.entry.nsec / 1000000, log_entry.entry.pid, log_entry.entry.tid, prioChar, tag,
- msg);
- } while ((msg = nl));
- }
-
- android_logger_list_free(logger_list);
-}
-
-// Dumps the logs generated by the specified pid to the tombstone, from both
-// "system" and "main" log devices. Ideally we'd interleave the output.
-static void dump_logs(log_t* log, pid_t pid, unsigned int tail) {
- if (pid == getpid()) {
- // Cowardly refuse to dump logs while we're running in-process.
- return;
- }
-
- dump_log_file(log, pid, "system", tail);
- dump_log_file(log, pid, "main", tail);
-}
-
void engrave_tombstone_ucontext(int tombstone_fd, int proto_fd, uint64_t abort_msg_address,
siginfo_t* siginfo, ucontext_t* ucontext) {
pid_t uid = getuid();
@@ -645,45 +118,7 @@
log.tfd = output_fd.get();
log.amfd_data = amfd_data;
- bool translate_proto = GetBoolProperty("debug.debuggerd.translate_proto_to_text", true);
- if (translate_proto) {
- tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) {
- _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str());
- });
- } else {
- bool want_logs = GetBoolProperty("ro.debuggable", false);
-
- _LOG(&log, logtype::HEADER,
- "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
- dump_header_info(&log);
- _LOG(&log, logtype::HEADER, "Timestamp: %s\n", get_timestamp().c_str());
-
- auto it = threads.find(target_thread);
- if (it == threads.end()) {
- async_safe_fatal("failed to find target thread");
- }
-
- dump_thread(&log, unwinder, it->second, process_info, true);
-
- if (want_logs) {
- dump_logs(&log, it->second.pid, 50);
- }
-
- for (auto& [tid, thread_info] : threads) {
- if (tid == target_thread) {
- continue;
- }
-
- dump_thread(&log, unwinder, thread_info, process_info, false);
- }
-
- if (open_files) {
- _LOG(&log, logtype::OPEN_FILES, "\nopen files:\n");
- dump_open_files_list(&log, *open_files, " ");
- }
-
- if (want_logs) {
- dump_logs(&log, it->second.pid, 0);
- }
- }
+ tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) {
+ _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str());
+ });
}
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 6c380a1..b1c4ef3 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -103,7 +103,7 @@
// In this case, the sp will be in either an invalid map if triggered
// on the main thread, or in a guard map if in another thread, which
// will be the first case or second case from below.
- unwindstack::MapInfo* map_info = maps->Find(sp);
+ std::shared_ptr<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)) {
@@ -226,7 +226,7 @@
cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
}
} else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
- unwindstack::MapInfo* map_info = maps->Find(fault_addr);
+ auto map_info = maps->Find(fault_addr);
if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
cause = "execute-only (no-read) memory access error; likely due to data in .text.";
} else {
@@ -342,8 +342,8 @@
f->set_file_map_offset(frame.map_elf_start_offset);
- unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
- if (map_info) {
+ auto map_info = maps->Find(frame.map_start);
+ if (map_info.get() != nullptr) {
f->set_build_id(map_info->GetPrintableBuildID());
}
}
@@ -370,7 +370,7 @@
MemoryDump dump;
dump.set_register_name(name);
- unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
+ std::shared_ptr<unwindstack::MapInfo> map_info = maps->Find(untag_address(value));
if (map_info) {
dump.set_mapping_name(map_info->name());
}
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 681b963..de86b0a 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -82,7 +82,8 @@
thread.name().c_str(), process_name);
CB(should_log, "uid: %d", tombstone.uid());
if (thread.tagged_addr_ctrl() != -1) {
- CB(should_log, "tagged_addr_ctrl: %016" PRIx64, thread.tagged_addr_ctrl());
+ CB(should_log, "tagged_addr_ctrl: %016" PRIx64 "%s", thread.tagged_addr_ctrl(),
+ describe_tagged_addr_ctrl(thread.tagged_addr_ctrl()).c_str());
}
}
@@ -292,6 +293,7 @@
static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
const Thread& thread) {
+ int word_size = pointer_width(tombstone);
print_thread_header(callback, tombstone, thread, true);
const Signal& signal_info = tombstone.signal_info();
@@ -307,7 +309,7 @@
} else {
std::string fault_addr_desc;
if (signal_info.has_fault_address()) {
- fault_addr_desc = StringPrintf("0x%" PRIx64, signal_info.fault_address());
+ fault_addr_desc = StringPrintf("0x%0*" PRIx64, 2 * word_size, signal_info.fault_address());
} else {
fault_addr_desc = "--------";
}
@@ -331,7 +333,7 @@
if (tombstone.causes_size() > 1) {
CBS("");
CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing "
- "order of probability.");
+ "order of likelihood.");
}
for (const Cause& cause : tombstone.causes()) {
@@ -369,7 +371,6 @@
return;
}
- int word_size = pointer_width(tombstone);
const auto format_pointer = [word_size](uint64_t ptr) -> std::string {
if (word_size == 8) {
uint64_t top = ptr >> 32;
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index a7506b7..71f0c09 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -41,6 +41,7 @@
#include <unwindstack/Memory.h>
#include <unwindstack/Unwinder.h>
+using android::base::StringPrintf;
using android::base::unique_fd;
bool is_allowed_in_logcat(enum logtype ltype) {
@@ -275,9 +276,10 @@
case SIGBUS:
case SIGFPE:
case SIGILL:
- case SIGSEGV:
case SIGTRAP:
return true;
+ case SIGSEGV:
+ return si->si_code != SEGV_MTEAERR;
default:
return false;
}
@@ -444,6 +446,33 @@
return "?";
}
+std::string describe_tagged_addr_ctrl(long ctrl) {
+ std::string desc;
+ if (ctrl & PR_TAGGED_ADDR_ENABLE) {
+ desc += ", PR_TAGGED_ADDR_ENABLE";
+ ctrl &= ~PR_TAGGED_ADDR_ENABLE;
+ }
+ if (ctrl & PR_MTE_TCF_SYNC) {
+ desc += ", PR_MTE_TCF_SYNC";
+ ctrl &= ~PR_MTE_TCF_SYNC;
+ }
+ if (ctrl & PR_MTE_TCF_ASYNC) {
+ desc += ", PR_MTE_TCF_ASYNC";
+ ctrl &= ~PR_MTE_TCF_ASYNC;
+ }
+ if (ctrl & PR_MTE_TAG_MASK) {
+ desc += StringPrintf(", mask 0x%04lx", (ctrl & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
+ ctrl &= ~PR_MTE_TAG_MASK;
+ }
+ if (ctrl) {
+ desc += StringPrintf(", unknown 0x%lx", ctrl);
+ }
+ if (desc.empty()) {
+ return "";
+ }
+ return " (" + desc.substr(2) + ")";
+}
+
void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix) {
if (unwinder->elf_from_memory_not_file()) {
_LOG(log, logtype::BACKTRACE,
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 2c70778..339f392 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -408,3 +408,9 @@
":fastboot_test_vendor_boot_v4_with_frag"
],
}
+
+cc_library_headers {
+ name: "fastboot_headers",
+ host_supported: true,
+ export_include_dirs: ["."],
+}
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index 0e918a3..322fe5c 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -18,10 +18,10 @@
# Package fastboot-related executables.
#
-my_dist_files := $(SOONG_HOST_OUT_EXECUTABLES)/mke2fs
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/e2fsdroid
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/make_f2fs
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/make_f2fs_casefold
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/sload_f2fs
+my_dist_files := $(HOST_OUT_EXECUTABLES)/mke2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/e2fsdroid
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold
+my_dist_files += $(HOST_OUT_EXECUTABLES)/sload_f2fs
$(call dist-for-goals,dist_files sdk win_sdk,$(my_dist_files))
my_dist_files :=
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 0a72812..4042531 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -725,7 +725,7 @@
return false;
}
- if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) {
+ if (!OpenPartition(device_, partition_name_, &handle_, O_RDONLY)) {
ret_ = device_->WriteFail(
android::base::StringPrintf("Cannot open %s", partition_name_.c_str()));
return false;
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 9b5d2cd..3f9bcdc 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -16,6 +16,7 @@
#include "flashing.h"
#include <fcntl.h>
+#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -77,9 +78,20 @@
int FlashRawDataChunk(int fd, const char* data, size_t len) {
size_t ret = 0;
+ const size_t max_write_size = 1048576;
+ void* aligned_buffer;
+
+ if (posix_memalign(&aligned_buffer, 4096, max_write_size)) {
+ PLOG(ERROR) << "Failed to allocate write buffer";
+ return -ENOMEM;
+ }
+
+ auto aligned_buffer_unique_ptr = std::unique_ptr<void, decltype(&free)>{aligned_buffer, free};
+
while (ret < len) {
- int this_len = std::min(static_cast<size_t>(1048576UL * 8), len - ret);
- int this_ret = write(fd, data, this_len);
+ int this_len = std::min(max_write_size, len - ret);
+ memcpy(aligned_buffer_unique_ptr.get(), data, this_len);
+ int this_ret = write(fd, aligned_buffer_unique_ptr.get(), this_len);
if (this_ret < 0) {
PLOG(ERROR) << "Failed to flash data of len " << len;
return -1;
@@ -147,7 +159,7 @@
int Flash(FastbootDevice* device, const std::string& partition_name) {
PartitionHandle handle;
- if (!OpenPartition(device, partition_name, &handle)) {
+ if (!OpenPartition(device, partition_name, &handle, O_WRONLY | O_DIRECT)) {
return -ENOENT;
}
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 07ad902..97b5ad4 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -78,7 +78,7 @@
} // namespace
bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
- bool read) {
+ int flags) {
// We prioritize logical partitions over physical ones, and do this
// consistently for other partition operations (like getvar:partition-size).
if (LogicalPartitionExists(device, name)) {
@@ -90,7 +90,6 @@
return false;
}
- int flags = (read ? O_RDONLY : O_WRONLY);
flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
if (fd < 0) {
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index c2646d7..1d81b7a 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -76,9 +76,11 @@
bool LogicalPartitionExists(FastbootDevice* device, const std::string& name,
bool* is_zero_length = nullptr);
-// If read, partition is readonly. Else it is write only.
+// Partition is O_WRONLY by default, caller should pass O_RDONLY for reading.
+// Caller may pass additional flags if needed. (O_EXCL | O_CLOEXEC | O_BINARY)
+// will be logically ORed internally.
bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
- bool read = false);
+ int flags = O_WRONLY);
bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
std::vector<std::string> ListPartitions(FastbootDevice* device);
diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp
new file mode 100644
index 0000000..fcd3bd6
--- /dev/null
+++ b/fastboot/fuzzer/Android.bp
@@ -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.
+ *
+ */
+
+cc_fuzz {
+ name: "fastboot_fuzzer",
+ host_supported: true,
+ device_supported: false,
+ srcs: [
+ "fastboot_fuzzer.cpp",
+ "socket_mock_fuzz.cpp",
+ ],
+ header_libs: [
+ "bootimg_headers",
+ "fastboot_headers",
+ ],
+ static_libs: [
+ "libext4_utils",
+ "libcrypto",
+ "libfastboot",
+ "libbuildversion",
+ "libbase",
+ "libziparchive",
+ "libsparse",
+ "libutils",
+ "liblog",
+ "libz",
+ "libdiagnose_usb",
+ "libbase",
+ "libcutils",
+ "libgtest",
+ "libgtest_main",
+ "libbase",
+ "libadb_host",
+ "liblp",
+ "liblog",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 533764,
+ },
+}
diff --git a/fastboot/fuzzer/README.md b/fastboot/fuzzer/README.md
new file mode 100644
index 0000000..10b06ea
--- /dev/null
+++ b/fastboot/fuzzer/README.md
@@ -0,0 +1,51 @@
+# Fuzzer for libfastboot
+
+## Plugin Design Considerations
+The fuzzer plugin for libfastboot is designed based on the understanding of the
+source code and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+libfastboot supports the following parameters:
+1. Year (parameter name: `year`)
+2. Month (parameter name: `month`)
+3. Day (parameter name: `day`)
+4. Version (parameter name: `version`)
+5. Fs Option (parameter name: `fsOption`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `year` | `2000` to `2127` | Value obtained from FuzzedDataProvider|
+| `month` | `1` to `12` | Value obtained from FuzzedDataProvider|
+| `day` | `1` to `31` | Value obtained from FuzzedDataProvider|
+| `version` | `0` to `127` | Value obtained from FuzzedDataProvider|
+| `fsOption` | 0. `casefold` 1. `projid` 2. `compress` | Value obtained from FuzzedDataProvider|
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the module.
+This ensures that the plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesnt `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build fastboot_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) fastboot_fuzzer_fuzzer
+```
+#### Steps to run
+To run on host
+```
+ $ $ANDROID_HOST_OUT/fuzz/${TARGET_ARCH}/fastboot_fuzzer/fastboot_fuzzer CORPUS_DIR
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/fastboot/fuzzer/fastboot_fuzzer.cpp b/fastboot/fuzzer/fastboot_fuzzer.cpp
new file mode 100644
index 0000000..60940fe
--- /dev/null
+++ b/fastboot/fuzzer/fastboot_fuzzer.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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/file.h>
+#include "fastboot.h"
+#include "socket.h"
+#include "socket_mock_fuzz.h"
+#include "tcp.h"
+#include "udp.h"
+#include "vendor_boot_img_utils.h"
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+using namespace std;
+
+const size_t kYearMin = 2000;
+const size_t kYearMax = 2127;
+const size_t kMonthMin = 1;
+const size_t kMonthMax = 12;
+const size_t kDayMin = 1;
+const size_t kDayMax = 31;
+const size_t kVersionMin = 0;
+const size_t kVersionMax = 127;
+const size_t kMaxStringSize = 100;
+const size_t kMinTimeout = 10;
+const size_t kMaxTimeout = 3000;
+const uint16_t kValidUdpPacketSize = 512;
+const uint16_t kMinUdpPackets = 1;
+const uint16_t kMaxUdpPackets = 10;
+
+const string kValidTcpHandshakeString = "FB01";
+const string kInvalidTcpHandshakeString = "FB00";
+const string kValidRamdiskName = "default";
+const string kVendorBootFile = "/tmp/vendorBootFile";
+const string kRamdiskFile = "/tmp/ramdiskFile";
+const char* kFsOptionsArray[] = {"casefold", "projid", "compress"};
+
+class FastbootFuzzer {
+ public:
+ void Process(const uint8_t* data, size_t size);
+
+ private:
+ void InvokeParseApi();
+ void InvokeSocket();
+ void InvokeTcp();
+ void InvokeUdp();
+ void InvokeVendorBootImgUtils(const uint8_t* data, size_t size);
+ bool MakeConnectedSockets(Socket::Protocol protocol, unique_ptr<Socket>* server,
+ unique_ptr<Socket>* client, const string& hostname);
+ unique_ptr<FuzzedDataProvider> fdp_ = nullptr;
+};
+
+void FastbootFuzzer::InvokeParseApi() {
+ boot_img_hdr_v1 hdr = {};
+ FastBootTool fastBoot;
+
+ int32_t year = fdp_->ConsumeIntegralInRange<int32_t>(kYearMin, kYearMax);
+ int32_t month = fdp_->ConsumeIntegralInRange<int32_t>(kMonthMin, kMonthMax);
+ int32_t day = fdp_->ConsumeIntegralInRange<int32_t>(kDayMin, kDayMax);
+ string date = to_string(year) + "-" + to_string(month) + "-" + to_string(day);
+ fastBoot.ParseOsPatchLevel(&hdr, date.c_str());
+
+ int32_t major = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+ int32_t minor = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+ int32_t patch = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+ string version = to_string(major) + "." + to_string(minor) + "." + to_string(patch);
+ fastBoot.ParseOsVersion(&hdr, version.c_str());
+
+ fastBoot.ParseFsOption(fdp_->PickValueInArray(kFsOptionsArray));
+}
+
+bool FastbootFuzzer::MakeConnectedSockets(Socket::Protocol protocol, unique_ptr<Socket>* server,
+ unique_ptr<Socket>* client,
+ const string& hostname = "localhost") {
+ *server = Socket::NewServer(protocol, 0);
+ if (*server == nullptr) {
+ return false;
+ }
+ *client = Socket::NewClient(protocol, hostname, (*server)->GetLocalPort(), nullptr);
+ if (*client == nullptr) {
+ return false;
+ }
+ if (protocol == Socket::Protocol::kTcp) {
+ *server = (*server)->Accept();
+ if (*server == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void FastbootFuzzer::InvokeSocket() {
+ unique_ptr<Socket> server, client;
+
+ for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) {
+ if (MakeConnectedSockets(protocol, &server, &client)) {
+ string message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ client->Send(message.c_str(), message.length());
+ string received(message.length(), '\0');
+ if (fdp_->ConsumeBool()) {
+ client->Close();
+ }
+ if (fdp_->ConsumeBool()) {
+ server->Close();
+ }
+ server->ReceiveAll(&received[0], received.length(),
+ /* timeout_ms */
+ fdp_->ConsumeIntegralInRange<size_t>(kMinTimeout, kMaxTimeout));
+ server->Close();
+ client->Close();
+ }
+ }
+}
+
+void FastbootFuzzer::InvokeTcp() {
+ /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */
+ SocketMockFuzz* tcp_mock = new SocketMockFuzz;
+ tcp_mock->ExpectSend(fdp_->ConsumeBool() ? kValidTcpHandshakeString
+ : kInvalidTcpHandshakeString);
+ tcp_mock->AddReceive(fdp_->ConsumeBool() ? kValidTcpHandshakeString
+ : kInvalidTcpHandshakeString);
+
+ string error;
+ unique_ptr<Transport> transport = tcp::internal::Connect(unique_ptr<Socket>(tcp_mock), &error);
+
+ if (transport.get()) {
+ string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ if (fdp_->ConsumeBool()) {
+ tcp_mock->ExpectSend(write_message);
+ } else {
+ tcp_mock->ExpectSendFailure(write_message);
+ }
+ string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ if (fdp_->ConsumeBool()) {
+ tcp_mock->AddReceive(read_message);
+ } else {
+ tcp_mock->AddReceiveFailure();
+ }
+
+ transport->Write(write_message.data(), write_message.length());
+
+ string buffer(read_message.length(), '\0');
+ transport->Read(&buffer[0], buffer.length());
+
+ transport->Close();
+ }
+}
+
+static string PacketValue(uint16_t value) {
+ return string{static_cast<char>(value >> 8), static_cast<char>(value)};
+}
+
+static string ErrorPacket(uint16_t sequence, const string& message = "",
+ char flags = udp::internal::kFlagNone) {
+ return string{udp::internal::kIdError, flags} + PacketValue(sequence) + message;
+}
+
+static string InitPacket(uint16_t sequence, uint16_t version, uint16_t max_packet_size) {
+ return string{udp::internal::kIdInitialization, udp::internal::kFlagNone} +
+ PacketValue(sequence) + PacketValue(version) + PacketValue(max_packet_size);
+}
+
+static string QueryPacket(uint16_t sequence, uint16_t new_sequence) {
+ return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence) +
+ PacketValue(new_sequence);
+}
+
+static string QueryPacket(uint16_t sequence) {
+ return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence);
+}
+
+static string FastbootPacket(uint16_t sequence, const string& data = "",
+ char flags = udp::internal::kFlagNone) {
+ return string{udp::internal::kIdFastboot, flags} + PacketValue(sequence) + data;
+}
+
+void FastbootFuzzer::InvokeUdp() {
+ /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */
+ SocketMockFuzz* udp_mock = new SocketMockFuzz;
+ uint16_t starting_sequence = fdp_->ConsumeIntegral<uint16_t>();
+ int32_t device_max_packet_size = fdp_->ConsumeBool() ? kValidUdpPacketSize
+ : fdp_->ConsumeIntegralInRange<uint16_t>(
+ 0, kValidUdpPacketSize - 1);
+ udp_mock->ExpectSend(QueryPacket(0));
+ udp_mock->AddReceive(QueryPacket(0, starting_sequence));
+ udp_mock->ExpectSend(InitPacket(starting_sequence, udp::internal::kProtocolVersion,
+ udp::internal::kHostMaxPacketSize));
+ udp_mock->AddReceive(
+ InitPacket(starting_sequence, udp::internal::kProtocolVersion, device_max_packet_size));
+
+ string error;
+ unique_ptr<Transport> transport = udp::internal::Connect(unique_ptr<Socket>(udp_mock), &error);
+ bool is_transport_initialized = transport != nullptr && error.empty();
+
+ if (is_transport_initialized) {
+ uint16_t num_packets =
+ fdp_->ConsumeIntegralInRange<uint16_t>(kMinUdpPackets, kMaxUdpPackets);
+
+ for (uint16_t i = 0; i < num_packets; ++i) {
+ string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ if (fdp_->ConsumeBool()) {
+ udp_mock->ExpectSend(FastbootPacket(i, write_message));
+ } else {
+ udp_mock->ExpectSend(ErrorPacket(i, write_message));
+ }
+
+ if (fdp_->ConsumeBool()) {
+ udp_mock->AddReceive(FastbootPacket(i, read_message));
+ } else {
+ udp_mock->AddReceive(ErrorPacket(i, read_message));
+ }
+ transport->Write(write_message.data(), write_message.length());
+ string buffer(read_message.length(), '\0');
+ transport->Read(&buffer[0], buffer.length());
+ }
+ transport->Close();
+ }
+}
+
+void FastbootFuzzer::InvokeVendorBootImgUtils(const uint8_t* data, size_t size) {
+ int32_t vendor_boot_fd = open(kVendorBootFile.c_str(), O_CREAT | O_RDWR, 0644);
+ if (vendor_boot_fd < 0) {
+ return;
+ }
+ int32_t ramdisk_fd = open(kRamdiskFile.c_str(), O_CREAT | O_RDWR, 0644);
+ if (ramdisk_fd < 0) {
+ return;
+ }
+ write(vendor_boot_fd, data, size);
+ write(ramdisk_fd, data, size);
+ string ramdisk_name = fdp_->ConsumeBool() ? kValidRamdiskName
+ : fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ string content_vendor_boot_fd = {};
+ string content_ramdisk_fd = {};
+ lseek(vendor_boot_fd, 0, SEEK_SET);
+ lseek(ramdisk_fd, 0, SEEK_SET);
+ android::base::ReadFdToString(vendor_boot_fd, &content_vendor_boot_fd);
+ android::base::ReadFdToString(ramdisk_fd, &content_ramdisk_fd);
+ uint64_t vendor_boot_size =
+ fdp_->ConsumeBool() ? content_vendor_boot_fd.size() : fdp_->ConsumeIntegral<uint64_t>();
+ uint64_t ramdisk_size =
+ fdp_->ConsumeBool() ? content_ramdisk_fd.size() : fdp_->ConsumeIntegral<uint64_t>();
+ (void)replace_vendor_ramdisk(vendor_boot_fd, vendor_boot_size, ramdisk_name, ramdisk_fd,
+ ramdisk_size);
+ close(vendor_boot_fd);
+ close(ramdisk_fd);
+}
+
+void FastbootFuzzer::Process(const uint8_t* data, size_t size) {
+ fdp_ = make_unique<FuzzedDataProvider>(data, size);
+ InvokeParseApi();
+ InvokeSocket();
+ InvokeTcp();
+ InvokeUdp();
+ InvokeVendorBootImgUtils(data, size);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FastbootFuzzer fastbootFuzzer;
+ fastbootFuzzer.Process(data, size);
+ return 0;
+}
diff --git a/fastboot/fuzzer/socket_mock_fuzz.cpp b/fastboot/fuzzer/socket_mock_fuzz.cpp
new file mode 100644
index 0000000..df96eb0
--- /dev/null
+++ b/fastboot/fuzzer/socket_mock_fuzz.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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 "socket_mock_fuzz.h"
+
+SocketMockFuzz::SocketMockFuzz() : Socket(INVALID_SOCKET) {}
+
+SocketMockFuzz::~SocketMockFuzz() {}
+
+bool SocketMockFuzz::Send(const void* data, size_t length) {
+ if (events_.empty()) {
+ return false;
+ }
+
+ if (events_.front().type != EventType::kSend) {
+ return false;
+ }
+
+ std::string message(reinterpret_cast<const char*>(data), length);
+ if (events_.front().message != message) {
+ return false;
+ }
+
+ bool return_value = events_.front().status;
+ events_.pop();
+ return return_value;
+}
+
+// Mock out multi-buffer send to be one large send, since that's what it should looks like from
+// the user's perspective.
+bool SocketMockFuzz::Send(std::vector<cutils_socket_buffer_t> buffers) {
+ std::string data;
+ for (const auto& buffer : buffers) {
+ data.append(reinterpret_cast<const char*>(buffer.data), buffer.length);
+ }
+ return Send(data.data(), data.size());
+}
+
+ssize_t SocketMockFuzz::Receive(void* data, size_t length, int /*timeout_ms*/) {
+ if (events_.empty()) {
+ return -1;
+ }
+
+ const Event& event = events_.front();
+ if (event.type != EventType::kReceive) {
+ return -1;
+ }
+
+ const std::string& message = event.message;
+ if (message.length() > length) {
+ return -1;
+ }
+
+ receive_timed_out_ = event.status;
+ ssize_t return_value = message.length();
+
+ // Empty message indicates failure.
+ if (message.empty()) {
+ return_value = -1;
+ } else {
+ memcpy(data, message.data(), message.length());
+ }
+
+ events_.pop();
+ return return_value;
+}
+
+int SocketMockFuzz::Close() {
+ return 0;
+}
+
+std::unique_ptr<Socket> SocketMockFuzz::Accept() {
+ if (events_.empty()) {
+ return nullptr;
+ }
+
+ if (events_.front().type != EventType::kAccept) {
+ return nullptr;
+ }
+
+ std::unique_ptr<Socket> sock = std::move(events_.front().sock);
+ events_.pop();
+ return sock;
+}
+
+void SocketMockFuzz::ExpectSend(std::string message) {
+ events_.push(Event(EventType::kSend, std::move(message), true, nullptr));
+}
+
+void SocketMockFuzz::ExpectSendFailure(std::string message) {
+ events_.push(Event(EventType::kSend, std::move(message), false, nullptr));
+}
+
+void SocketMockFuzz::AddReceive(std::string message) {
+ events_.push(Event(EventType::kReceive, std::move(message), false, nullptr));
+}
+
+void SocketMockFuzz::AddReceiveTimeout() {
+ events_.push(Event(EventType::kReceive, "", true, nullptr));
+}
+
+void SocketMockFuzz::AddReceiveFailure() {
+ events_.push(Event(EventType::kReceive, "", false, nullptr));
+}
+
+void SocketMockFuzz::AddAccept(std::unique_ptr<Socket> sock) {
+ events_.push(Event(EventType::kAccept, "", false, std::move(sock)));
+}
+
+SocketMockFuzz::Event::Event(EventType _type, std::string _message, ssize_t _status,
+ std::unique_ptr<Socket> _sock)
+ : type(_type), message(_message), status(_status), sock(std::move(_sock)) {}
diff --git a/fastboot/fuzzer/socket_mock_fuzz.h b/fastboot/fuzzer/socket_mock_fuzz.h
new file mode 100644
index 0000000..67bd0d6
--- /dev/null
+++ b/fastboot/fuzzer/socket_mock_fuzz.h
@@ -0,0 +1,73 @@
+/*
+ * 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 <memory>
+#include <queue>
+#include <string>
+
+#include <android-base/macros.h>
+
+#include "socket.h"
+
+class SocketMockFuzz : public Socket {
+ public:
+ SocketMockFuzz();
+ ~SocketMockFuzz() override;
+
+ bool Send(const void* data, size_t length) override;
+ bool Send(std::vector<cutils_socket_buffer_t> buffers) override;
+ ssize_t Receive(void* data, size_t length, int timeout_ms) override;
+ int Close() override;
+ virtual std::unique_ptr<Socket> Accept();
+
+ // Adds an expectation for Send().
+ void ExpectSend(std::string message);
+
+ // Adds an expectation for Send() that returns false.
+ void ExpectSendFailure(std::string message);
+
+ // Adds data to provide for Receive().
+ void AddReceive(std::string message);
+
+ // Adds a Receive() timeout after which ReceiveTimedOut() will return true.
+ void AddReceiveTimeout();
+
+ // Adds a Receive() failure after which ReceiveTimedOut() will return false.
+ void AddReceiveFailure();
+
+ // Adds a Socket to return from Accept().
+ void AddAccept(std::unique_ptr<Socket> sock);
+
+ private:
+ enum class EventType { kSend, kReceive, kAccept };
+
+ struct Event {
+ Event(EventType _type, std::string _message, ssize_t _status,
+ std::unique_ptr<Socket> _sock);
+
+ EventType type;
+ std::string message;
+ bool status; // Return value for Send() or timeout status for Receive().
+ std::unique_ptr<Socket> sock;
+ };
+
+ std::queue<Event> events_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketMockFuzz);
+};
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index cb74ae0..5872dda 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -246,28 +246,12 @@
"-UALLOW_ADBD_DISABLE_VERITY",
"-DALLOW_ADBD_DISABLE_VERITY=1",
],
- },
- },
- required: [
- "clean_scratch_files",
- ],
-}
-
-cc_binary {
- name: "clean_scratch_files",
- defaults: ["fs_mgr_defaults"],
- shared_libs: [
- "libbase",
- "libfs_mgr_binder",
- ],
- srcs: [
- "clean_scratch_files.cpp",
- ],
- product_variables: {
- debuggable: {
init_rc: [
"clean_scratch_files.rc",
],
},
},
+ symlinks: [
+ "clean_scratch_files",
+ ],
}
diff --git a/fs_mgr/clean_scratch_files.cpp b/fs_mgr/clean_scratch_files.cpp
deleted file mode 100644
index 42fe35a..0000000
--- a/fs_mgr/clean_scratch_files.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 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 <fs_mgr_overlayfs.h>
-
-int main() {
- android::fs_mgr::CleanupOldScratchFiles();
- return 0;
-}
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 002b302..a320c0e 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -75,9 +75,6 @@
#include "blockdev.h"
#include "fs_mgr_priv.h"
-#define KEY_LOC_PROP "ro.crypto.keyfile.userdata"
-#define KEY_IN_FOOTER "footer"
-
#define E2FSCK_BIN "/system/bin/e2fsck"
#define F2FS_FSCK_BIN "/system/bin/fsck.f2fs"
#define MKSWAP_BIN "/system/bin/mkswap"
@@ -907,7 +904,7 @@
<< "(): skipping mount due to invalid magic, mountpoint=" << fstab[i].mount_point
<< " blk_dev=" << realpath(fstab[i].blk_device) << " rec[" << i
<< "].fs_type=" << fstab[i].fs_type;
- mount_errno = EINVAL; // continue bootup for FDE
+ mount_errno = EINVAL; // continue bootup for metadata encryption
continue;
}
@@ -1005,50 +1002,22 @@
return false;
}
-static bool needs_block_encryption(const FstabEntry& entry) {
- if (android::base::GetBoolProperty("ro.vold.forceencryption", false) && entry.is_encryptable())
- return true;
- if (entry.fs_mgr_flags.force_crypt) return true;
- if (entry.fs_mgr_flags.crypt) {
- // Check for existence of convert_fde breadcrumb file.
- auto convert_fde_name = entry.mount_point + "/misc/vold/convert_fde";
- if (access(convert_fde_name.c_str(), F_OK) == 0) return true;
- }
- if (entry.fs_mgr_flags.force_fde_or_fbe) {
- // Check for absence of convert_fbe breadcrumb file.
- auto convert_fbe_name = entry.mount_point + "/convert_fbe";
- if (access(convert_fbe_name.c_str(), F_OK) != 0) return true;
- }
- return false;
-}
-
static bool should_use_metadata_encryption(const FstabEntry& entry) {
- return !entry.metadata_key_dir.empty() &&
- (entry.fs_mgr_flags.file_encryption || entry.fs_mgr_flags.force_fde_or_fbe);
+ return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption;
}
// Check to see if a mountable volume has encryption requirements
static int handle_encryptable(const FstabEntry& entry) {
- // If this is block encryptable, need to trigger encryption.
- if (needs_block_encryption(entry)) {
- if (umount(entry.mount_point.c_str()) == 0) {
- return FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION;
- } else {
- PWARNING << "Could not umount " << entry.mount_point << " - allow continue unencrypted";
- return FS_MGR_MNTALL_DEV_NOT_ENCRYPTED;
- }
- } else if (should_use_metadata_encryption(entry)) {
+ if (should_use_metadata_encryption(entry)) {
if (umount(entry.mount_point.c_str()) == 0) {
return FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION;
} else {
PERROR << "Could not umount " << entry.mount_point << " - fail since can't encrypt";
return FS_MGR_MNTALL_FAIL;
}
- } else if (entry.fs_mgr_flags.file_encryption || entry.fs_mgr_flags.force_fde_or_fbe) {
+ } else if (entry.fs_mgr_flags.file_encryption) {
LINFO << entry.mount_point << " is file encrypted";
return FS_MGR_MNTALL_DEV_FILE_ENCRYPTED;
- } else if (entry.is_encryptable()) {
- return FS_MGR_MNTALL_DEV_NOT_ENCRYPTED;
} else {
return FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE;
}
@@ -1056,9 +1025,6 @@
static void set_type_property(int status) {
switch (status) {
- case FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED:
- SetProperty("ro.crypto.type", "block");
- break;
case FS_MGR_MNTALL_DEV_FILE_ENCRYPTED:
case FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED:
case FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION:
@@ -1532,7 +1498,6 @@
// Mounting failed, understand why and retry.
wiped = partition_wiped(current_entry.blk_device.c_str());
- bool crypt_footer = false;
if (mount_errno != EBUSY && mount_errno != EACCES &&
current_entry.fs_mgr_flags.formattable && wiped) {
// current_entry and attempted_entry point at the same partition, but sometimes
@@ -1544,19 +1509,6 @@
checkpoint_manager.Revert(¤t_entry);
- if (current_entry.is_encryptable() && current_entry.key_loc != KEY_IN_FOOTER) {
- unique_fd fd(TEMP_FAILURE_RETRY(
- open(current_entry.key_loc.c_str(), O_WRONLY | O_CLOEXEC)));
- if (fd >= 0) {
- LINFO << __FUNCTION__ << "(): also wipe " << current_entry.key_loc;
- wipe_block_device(fd, get_file_size(fd));
- } else {
- PERROR << __FUNCTION__ << "(): " << current_entry.key_loc << " wouldn't open";
- }
- } else if (current_entry.is_encryptable() && current_entry.key_loc == KEY_IN_FOOTER) {
- crypt_footer = true;
- }
-
// EncryptInplace will be used when vdc gives an error or needs to format partitions
// other than /data
if (should_use_metadata_encryption(current_entry) &&
@@ -1577,7 +1529,7 @@
}
}
- if (fs_mgr_do_format(current_entry, crypt_footer) == 0) {
+ if (fs_mgr_do_format(current_entry) == 0) {
// Let's replay the mount actions.
i = top_idx - 1;
continue;
@@ -1590,27 +1542,8 @@
}
// mount(2) returned an error, handle the encryptable/formattable case.
- if (mount_errno != EBUSY && mount_errno != EACCES && attempted_entry.is_encryptable()) {
- if (wiped) {
- LERROR << __FUNCTION__ << "(): " << attempted_entry.blk_device << " is wiped and "
- << attempted_entry.mount_point << " " << attempted_entry.fs_type
- << " is encryptable. Suggest recovery...";
- encryptable = FS_MGR_MNTALL_DEV_NEEDS_RECOVERY;
- continue;
- } else {
- // Need to mount a tmpfs at this mountpoint for now, and set
- // properties that vold will query later for decrypting
- LERROR << __FUNCTION__ << "(): possibly an encryptable blkdev "
- << attempted_entry.blk_device << " for mount " << attempted_entry.mount_point
- << " type " << attempted_entry.fs_type;
- if (fs_mgr_do_tmpfs_mount(attempted_entry.mount_point.c_str()) < 0) {
- ++error_count;
- continue;
- }
- }
- encryptable = FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED;
- } else if (mount_errno != EBUSY && mount_errno != EACCES &&
- should_use_metadata_encryption(attempted_entry)) {
+ if (mount_errno != EBUSY && mount_errno != EACCES &&
+ should_use_metadata_encryption(attempted_entry)) {
if (!call_vdc({"cryptfs", "mountFstab", attempted_entry.blk_device,
attempted_entry.mount_point},
nullptr)) {
@@ -2119,6 +2052,9 @@
PERROR << "Cannot open " << loop_device;
return false;
}
+ if (!LoopControl::SetAutoClearStatus(loop_fd.get())) {
+ PERROR << "Failed set LO_FLAGS_AUTOCLEAR for " << loop_device;
+ }
if (!LoopControl::EnableDirectIo(loop_fd.get())) {
return false;
}
diff --git a/fs_mgr/fs_mgr_format.cpp b/fs_mgr/fs_mgr_format.cpp
index 301c907..bb49873 100644
--- a/fs_mgr/fs_mgr_format.cpp
+++ b/fs_mgr/fs_mgr_format.cpp
@@ -34,7 +34,6 @@
#include <selinux/selinux.h>
#include "fs_mgr_priv.h"
-#include "cryptfs.h"
using android::base::unique_fd;
@@ -58,7 +57,7 @@
}
static int format_ext4(const std::string& fs_blkdev, const std::string& fs_mnt_point,
- bool crypt_footer, bool needs_projid, bool needs_metadata_csum) {
+ bool needs_projid, bool needs_metadata_csum) {
uint64_t dev_sz;
int rc = 0;
@@ -68,9 +67,6 @@
}
/* Format the partition using the calculated length */
- if (crypt_footer) {
- dev_sz -= CRYPT_FOOTER_OFFSET;
- }
std::string size_str = std::to_string(dev_sz / 4096);
@@ -120,8 +116,8 @@
return rc;
}
-static int format_f2fs(const std::string& fs_blkdev, uint64_t dev_sz, bool crypt_footer,
- bool needs_projid, bool needs_casefold, bool fs_compress) {
+static int format_f2fs(const std::string& fs_blkdev, uint64_t dev_sz, bool needs_projid,
+ bool needs_casefold, bool fs_compress) {
if (!dev_sz) {
int rc = get_dev_sz(fs_blkdev, &dev_sz);
if (rc) {
@@ -130,9 +126,6 @@
}
/* Format the partition using the calculated length */
- if (crypt_footer) {
- dev_sz -= CRYPT_FOOTER_OFFSET;
- }
std::string size_str = std::to_string(dev_sz / 4096);
@@ -159,7 +152,7 @@
return logwrap_fork_execvp(args.size(), args.data(), nullptr, false, LOG_KLOG, false, nullptr);
}
-int fs_mgr_do_format(const FstabEntry& entry, bool crypt_footer) {
+int fs_mgr_do_format(const FstabEntry& entry) {
LERROR << __FUNCTION__ << ": Format " << entry.blk_device << " as '" << entry.fs_type << "'";
bool needs_casefold = false;
@@ -171,10 +164,10 @@
}
if (entry.fs_type == "f2fs") {
- return format_f2fs(entry.blk_device, entry.length, crypt_footer, needs_projid,
- needs_casefold, entry.fs_mgr_flags.fs_compress);
+ return format_f2fs(entry.blk_device, entry.length, needs_projid, needs_casefold,
+ entry.fs_mgr_flags.fs_compress);
} else if (entry.fs_type == "ext4") {
- return format_ext4(entry.blk_device, entry.mount_point, crypt_footer, needs_projid,
+ return format_ext4(entry.blk_device, entry.mount_point, needs_projid,
entry.fs_mgr_flags.ext_meta_csum);
} else {
LERROR << "File system type '" << entry.fs_type << "' is not supported";
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 9b6c3dd..d68b7e8 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -130,13 +130,15 @@
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)) {
+ off64_t size_in_4k_blocks;
+ if (!ParseInt(arg, &size_in_4k_blocks, static_cast<off64_t>(0),
+ std::numeric_limits<off64_t>::max() >> 12)) {
LWARNING << "Warning: reserve_root= flag malformed: " << arg;
} else {
- entry->reserved_size <<= 12;
+ entry->reserved_size = size_in_4k_blocks << 12;
}
} else if (StartsWith(flag, "lowerdir=")) {
- entry->lowerdir = std::move(arg);
+ entry->lowerdir = arg;
}
}
}
@@ -144,7 +146,7 @@
entry->fs_options = std::move(fs_options);
}
-void ParseFsMgrFlags(const std::string& flags, FstabEntry* entry) {
+bool ParseFsMgrFlags(const std::string& flags, FstabEntry* entry) {
for (const auto& flag : Split(flags, ",")) {
if (flag.empty() || flag == "defaults") continue;
std::string arg;
@@ -187,9 +189,18 @@
// Then handle flags that take an argument.
if (StartsWith(flag, "encryptable=")) {
- // The encryptable flag is followed by an = and the location of the keys.
+ // The "encryptable" flag identifies adoptable storage volumes. The
+ // argument to this flag is ignored, but it should be "userdata".
+ //
+ // Historical note: this flag was originally meant just for /data,
+ // to indicate that FDE (full disk encryption) can be enabled.
+ // Unfortunately, it was also overloaded to identify adoptable
+ // storage volumes. Today, FDE is no longer supported, leaving only
+ // the adoptable storage volume meaning for this flag.
entry->fs_mgr_flags.crypt = true;
- entry->key_loc = arg;
+ } else if (StartsWith(flag, "forceencrypt=") || StartsWith(flag, "forcefdeorfbe=")) {
+ LERROR << "flag no longer supported: " << flag;
+ return false;
} else if (StartsWith(flag, "voldmanaged=")) {
// The voldmanaged flag is followed by an = and the label, a colon and the partition
// number or the word "auto", e.g. voldmanaged=sdcard:3
@@ -233,18 +244,8 @@
LWARNING << "Warning: zramsize= flag malformed: " << arg;
}
}
- } else if (StartsWith(flag, "forceencrypt=")) {
- // The forceencrypt flag is followed by an = and the location of the keys.
- entry->fs_mgr_flags.force_crypt = true;
- entry->key_loc = arg;
} else if (StartsWith(flag, "fileencryption=")) {
ParseFileEncryption(arg, entry);
- } else if (StartsWith(flag, "forcefdeorfbe=")) {
- // The forcefdeorfbe flag is followed by an = and the location of the keys. Get it and
- // return it.
- entry->fs_mgr_flags.force_fde_or_fbe = true;
- entry->key_loc = arg;
- entry->encryption_options = "aes-256-xts:aes-256-cts";
} else if (StartsWith(flag, "max_comp_streams=")) {
if (!ParseInt(arg, &entry->max_comp_streams)) {
LWARNING << "Warning: max_comp_streams= flag malformed: " << arg;
@@ -304,6 +305,19 @@
LWARNING << "Warning: unknown flag: " << flag;
}
}
+
+ // FDE is no longer supported, so reject "encryptable" when used without
+ // "vold_managed". For now skip this check when in recovery mode, since
+ // some recovery fstabs still contain the FDE options since they didn't do
+ // anything in recovery mode anyway (except possibly to cause the
+ // reservation of a crypto footer) and thus never got removed.
+ if (entry->fs_mgr_flags.crypt && !entry->fs_mgr_flags.vold_managed &&
+ access("/system/bin/recovery", F_OK) != 0) {
+ LERROR << "FDE is no longer supported; 'encryptable' can only be used for adoptable "
+ "storage";
+ return false;
+ }
+ return true;
}
std::string InitAndroidDtDir() {
@@ -442,92 +456,6 @@
return "";
}
-bool ReadFstabFile(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out) {
- ssize_t len;
- size_t alloc_len = 0;
- char *line = NULL;
- const char *delim = " \t";
- char *save_ptr, *p;
- Fstab fstab;
-
- while ((len = getline(&line, &alloc_len, fstab_file)) != -1) {
- /* if the last character is a newline, shorten the string by 1 byte */
- if (line[len - 1] == '\n') {
- line[len - 1] = '\0';
- }
-
- /* Skip any leading whitespace */
- p = line;
- while (isspace(*p)) {
- p++;
- }
- /* ignore comments or empty lines */
- if (*p == '#' || *p == '\0')
- continue;
-
- FstabEntry entry;
-
- if (!(p = strtok_r(line, delim, &save_ptr))) {
- LERROR << "Error parsing mount source";
- goto err;
- }
- entry.blk_device = p;
-
- if (!(p = strtok_r(NULL, delim, &save_ptr))) {
- LERROR << "Error parsing mount_point";
- goto err;
- }
- entry.mount_point = p;
-
- if (!(p = strtok_r(NULL, delim, &save_ptr))) {
- LERROR << "Error parsing fs_type";
- goto err;
- }
- entry.fs_type = p;
-
- if (!(p = strtok_r(NULL, delim, &save_ptr))) {
- LERROR << "Error parsing mount_flags";
- goto err;
- }
-
- ParseMountFlags(p, &entry);
-
- // For /proc/mounts, ignore everything after mnt_freq and mnt_passno
- if (proc_mounts) {
- p += strlen(p);
- } else if (!(p = strtok_r(NULL, delim, &save_ptr))) {
- LERROR << "Error parsing fs_mgr_options";
- goto err;
- }
-
- ParseFsMgrFlags(p, &entry);
-
- if (entry.fs_mgr_flags.logical) {
- entry.logical_partition_name = entry.blk_device;
- }
-
- fstab.emplace_back(std::move(entry));
- }
-
- if (fstab.empty()) {
- LERROR << "No entries found in fstab";
- goto err;
- }
-
- /* If an A/B partition, modify block device to be the real block device */
- if (!fs_mgr_update_for_slotselect(&fstab)) {
- LERROR << "Error updating for slotselect";
- goto err;
- }
- free(line);
- *fstab_out = std::move(fstab);
- return true;
-
-err:
- free(line);
- return false;
-}
-
/* Extracts <device>s from the by-name symlinks specified in a fstab:
* /dev/block/<type>/<device>/by-name/<partition>
*
@@ -602,6 +530,61 @@
} // namespace
+bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out) {
+ const int expected_fields = proc_mounts ? 4 : 5;
+
+ Fstab fstab;
+
+ for (const auto& line : android::base::Split(fstab_str, "\n")) {
+ auto fields = android::base::Tokenize(line, " \t");
+
+ // Ignore empty lines and comments.
+ if (fields.empty() || android::base::StartsWith(fields.front(), '#')) {
+ continue;
+ }
+
+ if (fields.size() < expected_fields) {
+ LERROR << "Error parsing fstab: expected " << expected_fields << " fields, got "
+ << fields.size();
+ return false;
+ }
+
+ FstabEntry entry;
+ auto it = fields.begin();
+
+ entry.blk_device = std::move(*it++);
+ entry.mount_point = std::move(*it++);
+ entry.fs_type = std::move(*it++);
+ ParseMountFlags(std::move(*it++), &entry);
+
+ // For /proc/mounts, ignore everything after mnt_freq and mnt_passno
+ if (!proc_mounts && !ParseFsMgrFlags(std::move(*it++), &entry)) {
+ LERROR << "Error parsing fs_mgr_flags";
+ return false;
+ }
+
+ if (entry.fs_mgr_flags.logical) {
+ entry.logical_partition_name = entry.blk_device;
+ }
+
+ fstab.emplace_back(std::move(entry));
+ }
+
+ if (fstab.empty()) {
+ LERROR << "No entries found in fstab";
+ return false;
+ }
+
+ /* If an A/B partition, modify block device to be the real block device */
+ if (!fs_mgr_update_for_slotselect(&fstab)) {
+ LERROR << "Error updating for slotselect";
+ return false;
+ }
+
+ *fstab_out = std::move(fstab);
+ return true;
+}
+
void TransformFstabForDsu(Fstab* fstab, const std::string& dsu_slot,
const std::vector<std::string>& dsu_partitions) {
static constexpr char kDsuKeysDir[] = "/avb";
@@ -698,16 +681,16 @@
}
bool ReadFstabFromFile(const std::string& path, Fstab* fstab_out) {
- auto fstab_file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
- if (!fstab_file) {
- PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'";
+ const bool is_proc_mounts = (path == "/proc/mounts");
+
+ std::string fstab_str;
+ if (!android::base::ReadFileToString(path, &fstab_str, /* follow_symlinks = */ true)) {
+ PERROR << __FUNCTION__ << "(): failed to read file: '" << path << "'";
return false;
}
- bool is_proc_mounts = path == "/proc/mounts";
-
Fstab fstab;
- if (!ReadFstabFile(fstab_file.get(), is_proc_mounts, &fstab)) {
+ if (!ParseFstabFromString(fstab_str, is_proc_mounts, &fstab)) {
LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'";
return false;
}
@@ -754,15 +737,7 @@
return false;
}
- std::unique_ptr<FILE, decltype(&fclose)> fstab_file(
- fmemopen(static_cast<void*>(const_cast<char*>(fstab_buf.c_str())),
- fstab_buf.length(), "r"), fclose);
- if (!fstab_file) {
- if (verbose) PERROR << __FUNCTION__ << "(): failed to create a file stream for fstab dt";
- return false;
- }
-
- if (!ReadFstabFile(fstab_file.get(), false, fstab)) {
+ if (!ParseFstabFromString(fstab_buf, /* proc_mounts = */ false, fstab)) {
if (verbose) {
LERROR << __FUNCTION__ << "(): failed to load fstab from kernel:" << std::endl
<< fstab_buf;
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 0522ac5..2b31119 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -322,6 +322,17 @@
const auto kLowerdirOption = "lowerdir="s;
const auto kUpperdirOption = "upperdir="s;
+static inline bool KernelSupportsUserXattrs() {
+ struct utsname uts;
+ uname(&uts);
+
+ int major, minor;
+ if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) {
+ return false;
+ }
+ return major > 5 || (major == 5 && minor >= 15);
+}
+
// default options for mount_point, returns empty string for none available.
std::string fs_mgr_get_overlayfs_options(const std::string& mount_point) {
auto candidate = fs_mgr_get_overlayfs_candidate(mount_point);
@@ -331,6 +342,9 @@
if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kOverrideCredsRequired) {
ret += ",override_creds=off";
}
+ if (KernelSupportsUserXattrs()) {
+ ret += ",userxattr";
+ }
return ret;
}
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 5411aca..deaf5f7 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -42,6 +42,8 @@
#include <libavb_user/libavb_user.h>
#include <libgsi/libgsid.h>
+using namespace std::literals;
+
namespace {
[[noreturn]] void usage(int exit_status) {
@@ -142,6 +144,7 @@
BINDER_ERROR,
CHECKPOINTING,
GSID_ERROR,
+ CLEAN_SCRATCH_FILES,
};
static int do_remount(int argc, char* argv[]) {
@@ -163,6 +166,7 @@
{"help", no_argument, nullptr, 'h'},
{"reboot", no_argument, nullptr, 'R'},
{"verbose", no_argument, nullptr, 'v'},
+ {"clean_scratch_files", no_argument, nullptr, 'C'},
{0, 0, nullptr, 0},
};
for (int opt; (opt = ::getopt_long(argc, argv, "hRT:v", longopts, nullptr)) != -1;) {
@@ -183,6 +187,8 @@
case 'v':
verbose = true;
break;
+ case 'C':
+ return CLEAN_SCRATCH_FILES;
default:
LOG(ERROR) << "Bad Argument -" << char(opt);
usage(BADARG);
@@ -476,13 +482,24 @@
return retval;
}
+static int do_clean_scratch_files() {
+ android::fs_mgr::CleanupOldScratchFiles();
+ return 0;
+}
+
int main(int argc, char* argv[]) {
android::base::InitLogging(argv, MyLogger);
+ if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) {
+ return do_clean_scratch_files();
+ }
int result = do_remount(argc, argv);
if (result == MUST_REBOOT) {
LOG(INFO) << "Now reboot your device for settings to take effect";
+ result = 0;
} else if (result == REMOUNT_SUCCESS) {
printf("remount succeeded\n");
+ } else if (result == CLEAN_SCRATCH_FILES) {
+ return do_clean_scratch_files();
} else {
printf("remount failed\n");
}
diff --git a/fs_mgr/fs_mgr_roots.cpp b/fs_mgr/fs_mgr_roots.cpp
index d275320..2ad8125 100644
--- a/fs_mgr/fs_mgr_roots.cpp
+++ b/fs_mgr/fs_mgr_roots.cpp
@@ -125,8 +125,7 @@
int result = fs_mgr_do_mount_one(*rec, mount_point);
if (result == -1 && rec->fs_mgr_flags.formattable) {
PERROR << "Failed to mount " << mount_point << "; formatting";
- bool crypt_footer = rec->is_encryptable() && rec->key_loc == "footer";
- if (fs_mgr_do_format(*rec, crypt_footer) != 0) {
+ if (fs_mgr_do_format(*rec) != 0) {
PERROR << "Failed to format " << mount_point;
return false;
}
diff --git a/fs_mgr/fuzz/Android.bp b/fs_mgr/fuzz/Android.bp
new file mode 100644
index 0000000..f0afd28
--- /dev/null
+++ b/fs_mgr/fuzz/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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.
+//
+
+cc_fuzz {
+ name: "libfstab_fuzzer",
+ srcs: [
+ "fs_mgr_fstab_fuzzer.cpp",
+ ],
+ static_libs: [
+ "libfstab",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+
+ dictionary: "fstab.dict",
+ fuzz_config: {
+ cc: [
+ "yochiang@google.com",
+ ],
+ },
+}
diff --git a/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
new file mode 100644
index 0000000..6a8a191
--- /dev/null
+++ b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
@@ -0,0 +1,26 @@
+//
+// 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 <cstdio>
+
+#include <fstab/fstab.h>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::string make_fstab_str(reinterpret_cast<const char*>(data), size);
+ android::fs_mgr::Fstab fstab;
+ android::fs_mgr::ParseFstabFromString(make_fstab_str, /* proc_mounts = */ false, &fstab);
+ return 0;
+}
diff --git a/fs_mgr/fuzz/fstab.dict b/fs_mgr/fuzz/fstab.dict
new file mode 100644
index 0000000..84dddf7
--- /dev/null
+++ b/fs_mgr/fuzz/fstab.dict
@@ -0,0 +1,70 @@
+"#"
+"="
+","
+"f2fs"
+
+# mount flags
+"noatime"
+"noexec"
+"nosuid"
+"nodev"
+"nodiratime"
+"ro"
+"rw"
+"sync"
+"remount"
+"bind"
+"rec"
+"unbindable"
+"private"
+"slave"
+"shared"
+"defaults"
+
+# fs_mgr flags
+"wait"
+"check"
+"nonremovable"
+"recoveryonly"
+"noemulatedsd"
+"notrim"
+"verify"
+"formattable"
+"slotselect"
+"latemount"
+"nofail"
+"verifyatboot"
+"quota"
+"avb"
+"logical"
+"checkpoint=block"
+"checkpoint=fs"
+"first_stage_mount"
+"slotselect_other"
+"fsverity"
+"metadata_csum"
+"fscompress"
+"overlayfs_remove_missing_lowerdir"
+
+# fs_mgr flags that expect an argument
+"reserve_root="
+"lowerdir="
+"encryptable="
+"voldmanaged="
+"length="
+"swapprio="
+"zramsize="
+"forceencrypt="
+"fileencryption="
+"forcefdeorfbe="
+"max_comp_streams="
+"reservedsize="
+"readahead_size_kb="
+"eraseblk="
+"logicalblk="
+"avb_keys="
+"avb="
+"keydirectory="
+"metadata_encryption="
+"sysfs_path="
+"zram_backingdev_size="
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 21c9989..29a5e60 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -56,9 +56,6 @@
#define FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION 6
#define FS_MGR_MNTALL_DEV_FILE_ENCRYPTED 5
#define FS_MGR_MNTALL_DEV_NEEDS_RECOVERY 4
-#define FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION 3
-#define FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED 2
-#define FS_MGR_MNTALL_DEV_NOT_ENCRYPTED 1
#define FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE 0
#define FS_MGR_MNTALL_FAIL (-1)
@@ -107,7 +104,7 @@
// device is in "check_at_most_once" mode.
bool fs_mgr_verity_is_check_at_most_once(const android::fs_mgr::FstabEntry& entry);
-int fs_mgr_do_format(const android::fs_mgr::FstabEntry& entry, bool reserve_footer);
+int fs_mgr_do_format(const android::fs_mgr::FstabEntry& entry);
#define FS_MGR_SETUP_VERITY_SKIPPED (-3)
#define FS_MGR_SETUP_VERITY_DISABLED (-2)
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 9a4ed46..054300e 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -37,7 +37,6 @@
unsigned long flags = 0;
std::string fs_options;
std::string fs_checkpoint_opts;
- std::string key_loc;
std::string metadata_key_dir;
std::string metadata_encryption;
off64_t length = 0;
@@ -60,19 +59,17 @@
struct FsMgrFlags {
bool wait : 1;
bool check : 1;
- bool crypt : 1;
+ bool crypt : 1; // Now only used to identify adoptable storage volumes
bool nonremovable : 1;
bool vold_managed : 1;
bool recovery_only : 1;
bool verify : 1;
- bool force_crypt : 1;
bool no_emulated_sd : 1; // No emulated sdcard daemon; sd card is the only external
// storage.
bool no_trim : 1;
bool file_encryption : 1;
bool formattable : 1;
bool slot_select : 1;
- bool force_fde_or_fbe : 1;
bool late_mount : 1;
bool no_fail : 1;
bool verify_at_boot : 1;
@@ -89,9 +86,7 @@
bool overlayfs_remove_missing_lowerdir : 1;
} fs_mgr_flags = {};
- bool is_encryptable() const {
- return fs_mgr_flags.crypt || fs_mgr_flags.force_crypt || fs_mgr_flags.force_fde_or_fbe;
- }
+ bool is_encryptable() const { return fs_mgr_flags.crypt; }
};
// An Fstab is a collection of FstabEntry structs.
@@ -99,6 +94,9 @@
// Unless explicitly requested, a lookup on mount point should always return the 1st one.
using Fstab = std::vector<FstabEntry>;
+// Exported for testability. Regular users should use ReadFstabFromFile().
+bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out);
+
bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
bool ReadFstabFromDt(Fstab* fstab, bool verbose = true);
bool ReadDefaultFstab(Fstab* fstab);
diff --git a/fs_mgr/libdm/include/libdm/loop_control.h b/fs_mgr/libdm/include/libdm/loop_control.h
index ad53c11..f519054 100644
--- a/fs_mgr/libdm/include/libdm/loop_control.h
+++ b/fs_mgr/libdm/include/libdm/loop_control.h
@@ -46,6 +46,9 @@
// Enable Direct I/O on a loop device. This requires kernel 4.9+.
static bool EnableDirectIo(int fd);
+ // Set LO_FLAGS_AUTOCLEAR on a loop device.
+ static bool SetAutoClearStatus(int fd);
+
LoopControl(const LoopControl&) = delete;
LoopControl& operator=(const LoopControl&) = delete;
LoopControl& operator=(LoopControl&&) = default;
diff --git a/fs_mgr/libdm/loop_control.cpp b/fs_mgr/libdm/loop_control.cpp
index 2e40a18..32d5f38 100644
--- a/fs_mgr/libdm/loop_control.cpp
+++ b/fs_mgr/libdm/loop_control.cpp
@@ -133,6 +133,16 @@
return true;
}
+bool LoopControl::SetAutoClearStatus(int fd) {
+ struct loop_info64 info = {};
+
+ info.lo_flags |= LO_FLAGS_AUTOCLEAR;
+ if (ioctl(fd, LOOP_SET_STATUS64, &info)) {
+ return false;
+ }
+ return true;
+}
+
LoopDevice::LoopDevice(android::base::borrowed_fd fd, const std::chrono::milliseconds& timeout_ms,
bool auto_close)
: fd_(fd), owned_fd_(-1) {
diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp
index 8acb885..275388e 100644
--- a/fs_mgr/libfiemap/fiemap_writer.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -498,24 +498,6 @@
return IsFilePinned(fd, file_path, sfs.f_type);
}
-static bool CountFiemapExtents(int file_fd, const std::string& file_path, uint32_t* num_extents) {
- struct fiemap fiemap = {};
- fiemap.fm_start = 0;
- fiemap.fm_length = UINT64_MAX;
- fiemap.fm_flags = FIEMAP_FLAG_SYNC;
- fiemap.fm_extent_count = 0;
-
- if (ioctl(file_fd, FS_IOC_FIEMAP, &fiemap)) {
- PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
- return false;
- }
-
- if (num_extents) {
- *num_extents = fiemap.fm_mapped_extents;
- }
- return true;
-}
-
static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) {
if (extent->fe_flags & kUnsupportedExtentFlags) {
LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path
@@ -530,12 +512,16 @@
}
static bool FiemapToExtents(struct fiemap* fiemap, std::vector<struct fiemap_extent>* extents,
- uint32_t num_extents, std::string_view file_path) {
- if (num_extents == 0) return false;
-
+ std::string_view file_path) {
+ uint32_t num_extents = fiemap->fm_mapped_extents;
+ if (num_extents == 0) {
+ LOG(ERROR) << "File " << file_path << " has zero extent";
+ return false;
+ }
const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1];
if (!IsLastExtent(last_extent)) {
- LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path;
+ LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path
+ << " num_extents=" << num_extents << " max_extents=" << kMaxExtents;
return false;
}
@@ -577,21 +563,7 @@
static bool ReadFiemap(int file_fd, const std::string& file_path,
std::vector<struct fiemap_extent>* extents) {
- uint32_t num_extents;
- if (!CountFiemapExtents(file_fd, file_path, &num_extents)) {
- return false;
- }
- if (num_extents == 0) {
- LOG(ERROR) << "File " << file_path << " has zero extents";
- return false;
- }
- if (num_extents > kMaxExtents) {
- LOG(ERROR) << "File has " << num_extents << ", maximum is " << kMaxExtents << ": "
- << file_path;
- return false;
- }
-
- uint64_t fiemap_size = sizeof(struct fiemap) + num_extents * sizeof(struct fiemap_extent);
+ uint64_t fiemap_size = sizeof(struct fiemap) + kMaxExtents * sizeof(struct fiemap_extent);
auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
if (buffer == nullptr) {
LOG(ERROR) << "Failed to allocate memory for fiemap";
@@ -603,19 +575,13 @@
fiemap->fm_length = UINT64_MAX;
// make sure file is synced to disk before we read the fiemap
fiemap->fm_flags = FIEMAP_FLAG_SYNC;
- fiemap->fm_extent_count = num_extents;
+ fiemap->fm_extent_count = kMaxExtents;
if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
return false;
}
- if (fiemap->fm_mapped_extents != num_extents) {
- LOG(ERROR) << "FIEMAP returned unexpected extent count (" << num_extents
- << " expected, got " << fiemap->fm_mapped_extents << ") for file: " << file_path;
- return false;
- }
-
- return FiemapToExtents(fiemap, extents, num_extents, file_path);
+ return FiemapToExtents(fiemap, extents, file_path);
}
static bool ReadFibmap(int file_fd, const std::string& file_path,
diff --git a/fs_mgr/libfiemap/fiemap_writer_test.cpp b/fs_mgr/libfiemap/fiemap_writer_test.cpp
index b31c78d..c65481b 100644
--- a/fs_mgr/libfiemap/fiemap_writer_test.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer_test.cpp
@@ -258,6 +258,13 @@
EXPECT_EQ(memcmp(actual.data(), data.data(), data.size()), 0);
}
+TEST_F(FiemapWriterTest, CheckEmptyFile) {
+ // Can't get any fiemap_extent out of a zero-sized file.
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 0);
+ EXPECT_EQ(fptr, nullptr);
+ EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+}
+
TEST_F(SplitFiemapTest, Create) {
auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
ASSERT_NE(ptr, nullptr);
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index dcbbc54..2c14c8a 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -696,7 +696,12 @@
bool ok = true;
for (const auto& partition : metadata->partitions) {
if (partition.attributes & LP_PARTITION_ATTR_DISABLED) {
- ok &= DeleteBackingImage(GetPartitionName(partition));
+ const auto name = GetPartitionName(partition);
+ if (!DeleteBackingImage(name)) {
+ ok = false;
+ } else {
+ LOG(INFO) << "Removed disabled partition image: " << name;
+ }
}
}
return ok;
diff --git a/fs_mgr/libsnapshot/OWNERS b/fs_mgr/libsnapshot/OWNERS
index 801c446..37319fe 100644
--- a/fs_mgr/libsnapshot/OWNERS
+++ b/fs_mgr/libsnapshot/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 30545
balsini@google.com
dvander@google.com
elsk@google.com
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 5306b28..20030b9 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -34,11 +34,12 @@
namespace android {
namespace snapshot {
-CowReader::CowReader()
+CowReader::CowReader(ReaderFlags reader_flag)
: fd_(-1),
header_(),
fd_size_(0),
- merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()) {}
+ merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()),
+ reader_flag_(reader_flag) {}
static void SHA256(const void*, size_t, uint8_t[]) {
#if 0
@@ -415,7 +416,7 @@
//==============================================================
bool CowReader::PrepMergeOps() {
auto merge_op_blocks = std::make_shared<std::vector<uint32_t>>();
- std::set<int, std::greater<int>> other_ops;
+ std::vector<int> other_ops;
auto seq_ops_set = std::unordered_set<uint32_t>();
auto block_map = std::make_shared<std::unordered_map<uint32_t, int>>();
size_t num_seqs = 0;
@@ -446,7 +447,7 @@
if (!has_seq_ops_ && IsOrderedOp(current_op)) {
merge_op_blocks->emplace_back(current_op.new_block);
} else if (seq_ops_set.count(current_op.new_block) == 0) {
- other_ops.insert(current_op.new_block);
+ other_ops.push_back(current_op.new_block);
}
block_map->insert({current_op.new_block, i});
}
@@ -462,6 +463,18 @@
} else {
num_ordered_ops_to_merge_ = 0;
}
+
+ // Sort the vector in increasing order if merging in user-space as
+ // we can batch merge them when iterating from forward.
+ //
+ // dm-snapshot-merge requires decreasing order as we iterate the blocks
+ // in reverse order.
+ if (reader_flag_ == ReaderFlags::USERSPACE_MERGE) {
+ std::sort(other_ops.begin(), other_ops.end());
+ } else {
+ std::sort(other_ops.begin(), other_ops.end(), std::greater<int>());
+ }
+
merge_op_blocks->reserve(merge_op_blocks->size() + other_ops.size());
for (auto block : other_ops) {
merge_op_blocks->emplace_back(block);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index c15682a..9f4ddbb 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -26,8 +26,8 @@
static constexpr uint32_t kCowVersionManifest = 2;
-static constexpr uint32_t BLOCK_SZ = 4096;
-static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+static constexpr size_t BLOCK_SZ = 4096;
+static constexpr size_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:
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 143f73c..d5b4335 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -104,7 +104,12 @@
class CowReader final : public ICowReader {
public:
- CowReader();
+ enum class ReaderFlags {
+ DEFAULT = 0,
+ USERSPACE_MERGE = 1,
+ };
+
+ CowReader(ReaderFlags reader_flag = ReaderFlags::DEFAULT);
~CowReader() { owned_fd_ = {}; }
// Parse the COW, optionally, up to the given label. If no label is
@@ -145,6 +150,8 @@
// Creates a clone of the current CowReader without the file handlers
std::unique_ptr<CowReader> CloneCowReader();
+ void UpdateMergeOpsCompleted(int num_merge_ops) { header_.num_merge_ops += num_merge_ops; }
+
private:
bool ParseOps(std::optional<uint64_t> label);
bool PrepMergeOps();
@@ -164,6 +171,7 @@
uint64_t num_ordered_ops_to_merge_;
bool has_seq_ops_;
std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+ ReaderFlags reader_flag_;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 55f4ed7..a49b026 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -408,6 +408,7 @@
FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
+ FRIEND_TEST(SnapshotUpdateTest, QueryStatusError);
FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate);
friend class SnapshotTest;
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 1f57bbc..c3b40dc 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -100,6 +100,9 @@
return IDeviceInfo::OpenImageManager("ota/test");
}
android::dm::IDeviceMapper& GetDeviceMapper() override {
+ if (dm_) {
+ return *dm_;
+ }
return android::dm::DeviceMapper::Instance();
}
@@ -111,6 +114,8 @@
}
void set_recovery(bool value) { recovery_ = value; }
void set_first_stage_init(bool value) { first_stage_init_ = value; }
+ void set_dm(android::dm::IDeviceMapper* dm) { dm_ = dm; }
+
MergeStatus merge_status() const { return merge_status_; }
private:
@@ -120,6 +125,45 @@
bool recovery_ = false;
bool first_stage_init_ = false;
std::unordered_set<uint32_t> unbootable_slots_;
+ android::dm::IDeviceMapper* dm_ = nullptr;
+};
+
+class DeviceMapperWrapper : public android::dm::IDeviceMapper {
+ using DmDeviceState = android::dm::DmDeviceState;
+ using DmTable = android::dm::DmTable;
+
+ public:
+ DeviceMapperWrapper() : impl_(android::dm::DeviceMapper::Instance()) {}
+ explicit DeviceMapperWrapper(android::dm::IDeviceMapper& impl) : impl_(impl) {}
+
+ virtual bool CreateDevice(const std::string& name, const DmTable& table, std::string* path,
+ const std::chrono::milliseconds& timeout_ms) override {
+ return impl_.CreateDevice(name, table, path, timeout_ms);
+ }
+ virtual DmDeviceState GetState(const std::string& name) const override {
+ return impl_.GetState(name);
+ }
+ virtual bool LoadTableAndActivate(const std::string& name, const DmTable& table) {
+ return impl_.LoadTableAndActivate(name, table);
+ }
+ virtual bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) {
+ return impl_.GetTableInfo(name, table);
+ }
+ virtual bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) {
+ return impl_.GetTableStatus(name, table);
+ }
+ virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) {
+ return impl_.GetDmDevicePathByName(name, path);
+ }
+ virtual bool GetDeviceString(const std::string& name, std::string* dev) {
+ return impl_.GetDeviceString(name, dev);
+ }
+ virtual bool DeleteDeviceIfExists(const std::string& name) {
+ return impl_.DeleteDeviceIfExists(name);
+ }
+
+ private:
+ android::dm::IDeviceMapper& impl_;
};
class SnapshotTestPropertyFetcher : public android::fs_mgr::testing::MockPropertyFetcher {
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index acee2f4..54c6a00 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -488,7 +488,7 @@
.fs_type = "ext4",
.mount_point = mount_point,
};
- CHECK(0 == fs_mgr_do_format(entry, false /* crypt_footer */));
+ CHECK(0 == fs_mgr_do_format(entry));
CHECK(0 == fs_mgr_do_mount_one(entry));
return std::make_unique<AutoUnmount>(mount_point);
}
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 43c7fe2..d78ba0a 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -56,6 +56,7 @@
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmDeviceState;
+using android::dm::IDeviceMapper;
using android::fiemap::FiemapStatus;
using android::fiemap::IImageManager;
using android::fs_mgr::BlockDeviceInfo;
@@ -911,6 +912,11 @@
ASSERT_TRUE(hash.has_value());
hashes_[name] = *hash;
}
+
+ // OTA client blindly unmaps all partitions that are possibly mapped.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+ }
}
void TearDown() override {
RETURN_IF_NON_VIRTUAL_AB();
@@ -925,6 +931,14 @@
MountMetadata();
for (const auto& suffix : {"_a", "_b"}) {
test_device->set_slot_suffix(suffix);
+
+ // Cheat our way out of merge failed states.
+ if (sm->ProcessUpdateState() == UpdateState::MergeFailed) {
+ ASSERT_TRUE(AcquireLock());
+ ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None));
+ lock_ = {};
+ }
+
EXPECT_TRUE(sm->CancelUpdate()) << suffix;
}
EXPECT_TRUE(UnmapAll());
@@ -1097,11 +1111,6 @@
// Also test UnmapUpdateSnapshot unmaps everything.
// Also test first stage mount and merge after this.
TEST_F(SnapshotUpdateTest, FullUpdateFlow) {
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
// Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
// fit in super, but not |prd|.
constexpr uint64_t partition_size = 3788_KiB;
@@ -1189,11 +1198,6 @@
GTEST_SKIP() << "Compression-only test";
}
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
@@ -1239,11 +1243,6 @@
GTEST_SKIP() << "Skipping Virtual A/B Compression test";
}
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
auto old_sys_size = GetSize(sys_);
auto old_prd_size = GetSize(prd_);
@@ -1630,11 +1629,6 @@
ASSERT_NE(nullptr, metadata);
ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
// Add operations for sys. The whole device is written.
AddOperation(sys_);
@@ -2074,11 +2068,6 @@
}
TEST_F(SnapshotUpdateTest, AddPartition) {
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
group_->add_partition_names("dlkm");
auto dlkm = manifest_.add_partitions();
@@ -2249,6 +2238,60 @@
ASSERT_TRUE(sm->BeginUpdate());
}
+TEST_F(SnapshotUpdateTest, QueryStatusError) {
+ // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
+ // fit in super, but not |prd|.
+ constexpr uint64_t partition_size = 3788_KiB;
+ SetSize(sys_, partition_size);
+
+ AddOperationForPartitions({sys_});
+
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+ ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+ ASSERT_TRUE(UnmapAll());
+
+ class DmStatusFailure final : public DeviceMapperWrapper {
+ public:
+ bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) override {
+ if (!DeviceMapperWrapper::GetTableStatus(name, table)) {
+ return false;
+ }
+ if (name == "sys_b" && !table->empty()) {
+ auto& info = table->at(0);
+ if (DeviceMapper::GetTargetType(info.spec) == "snapshot-merge") {
+ info.data = "Merge failed";
+ }
+ }
+ return true;
+ }
+ };
+ DmStatusFailure wrapper;
+
+ // After reboot, init does first stage mount.
+ auto info = new TestDeviceInfo(fake_super, "_b");
+ info->set_dm(&wrapper);
+
+ auto init = NewManagerForFirstStageMount(info);
+ ASSERT_NE(init, nullptr);
+
+ ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+ // Initiate the merge and wait for it to be completed.
+ ASSERT_TRUE(init->InitiateMerge());
+ ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState());
+
+ // Simulate a reboot that tries the merge again, with the non-failing dm.
+ ASSERT_TRUE(UnmapAll());
+ init = NewManagerForFirstStageMount("_b");
+ ASSERT_NE(init, nullptr);
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+ ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+}
+
class FlashAfterUpdateTest : public SnapshotUpdateTest,
public WithParamInterface<std::tuple<uint32_t, bool>> {
public:
@@ -2265,11 +2308,6 @@
};
TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) {
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 6bd5323..c9b0512 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -56,11 +56,18 @@
"fs_mgr_defaults",
],
srcs: [
- "snapuserd_server.cpp",
- "snapuserd.cpp",
+ "dm-snapshot-merge/snapuserd_server.cpp",
+ "dm-snapshot-merge/snapuserd.cpp",
+ "dm-snapshot-merge/snapuserd_worker.cpp",
+ "dm-snapshot-merge/snapuserd_readahead.cpp",
"snapuserd_daemon.cpp",
- "snapuserd_worker.cpp",
- "snapuserd_readahead.cpp",
+ "snapuserd_buffer.cpp",
+ "user-space-merge/snapuserd_core.cpp",
+ "user-space-merge/snapuserd_dm_user.cpp",
+ "user-space-merge/snapuserd_merge.cpp",
+ "user-space-merge/snapuserd_readahead.cpp",
+ "user-space-merge/snapuserd_transitions.cpp",
+ "user-space-merge/snapuserd_server.cpp",
],
cflags: [
@@ -101,9 +108,10 @@
"fs_mgr_defaults",
],
srcs: [
- "cow_snapuserd_test.cpp",
- "snapuserd.cpp",
- "snapuserd_worker.cpp",
+ "dm-snapshot-merge/cow_snapuserd_test.cpp",
+ "dm-snapshot-merge/snapuserd.cpp",
+ "dm-snapshot-merge/snapuserd_worker.cpp",
+ "snapuserd_buffer.cpp",
],
cflags: [
"-Wall",
diff --git a/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
similarity index 99%
rename from fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index bff0a50..b86a802 100644
--- a/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
@@ -33,6 +33,7 @@
#include <libdm/dm.h>
#include <libdm/loop_control.h>
#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
#include <snapuserd/snapuserd_client.h>
#include <storage_literals/storage_literals.h>
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
similarity index 100%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
similarity index 91%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd.h
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
index 6388a83..47b9b22 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
@@ -42,6 +42,7 @@
#include <libdm/dm.h>
#include <libsnapshot/cow_reader.h>
#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
#include <snapuserd/snapuserd_kernel.h>
namespace android {
@@ -89,39 +90,6 @@
READ_AHEAD_FAILURE,
};
-class BufferSink : public IByteSink {
- public:
- void Initialize(size_t size);
- void* GetBufPtr() { return buffer_.get(); }
- void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
- void* GetPayloadBuffer(size_t size);
- void* GetBuffer(size_t requested, size_t* actual) override;
- void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
- 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_;
- loff_t buffer_offset_;
- size_t buffer_size_;
-};
-
-class XorSink : public IByteSink {
- public:
- void Initialize(BufferSink* sink, size_t size);
- void Reset();
- void* GetBuffer(size_t requested, size_t* actual) override;
- bool ReturnData(void* buffer, size_t len) override;
-
- private:
- BufferSink* bufsink_;
- std::unique_ptr<uint8_t[]> buffer_;
- size_t buffer_size_;
- size_t returned_;
-};
-
class Snapuserd;
class ReadAheadThread {
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
similarity index 100%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
similarity index 99%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
index 2f87557..9ddc963 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
@@ -31,7 +31,7 @@
#include <android-base/scopeguard.h>
#include <fs_mgr/file_wait.h>
#include <snapuserd/snapuserd_client.h>
-#include "snapuserd.h"
+
#include "snapuserd_server.h"
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h
similarity index 100%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_server.h
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
similarity index 93%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 5d184ad..0e9f0f1 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -32,78 +32,6 @@
#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
-void BufferSink::Initialize(size_t size) {
- buffer_size_ = size;
- buffer_offset_ = 0;
- buffer_ = std::make_unique<uint8_t[]>(size);
-}
-
-void* BufferSink::GetPayloadBuffer(size_t size) {
- if ((buffer_size_ - buffer_offset_) < size) return nullptr;
-
- char* buffer = reinterpret_cast<char*>(GetBufPtr());
- struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
- return (char*)msg->payload.buf + buffer_offset_;
-}
-
-void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
- void* buf = GetPayloadBuffer(requested);
- if (!buf) {
- *actual = 0;
- return nullptr;
- }
- *actual = requested;
- return buf;
-}
-
-struct dm_user_header* BufferSink::GetHeaderPtr() {
- 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;
-}
-
-void XorSink::Initialize(BufferSink* sink, size_t size) {
- bufsink_ = sink;
- buffer_size_ = size;
- returned_ = 0;
- buffer_ = std::make_unique<uint8_t[]>(size);
-}
-
-void XorSink::Reset() {
- returned_ = 0;
-}
-
-void* XorSink::GetBuffer(size_t requested, size_t* actual) {
- if (requested > buffer_size_) {
- *actual = buffer_size_;
- } else {
- *actual = requested;
- }
- return buffer_.get();
-}
-
-bool XorSink::ReturnData(void* buffer, size_t len) {
- uint8_t* xor_data = reinterpret_cast<uint8_t*>(buffer);
- uint8_t* buff = reinterpret_cast<uint8_t*>(bufsink_->GetPayloadBuffer(len + returned_));
- if (buff == nullptr) {
- return false;
- }
- for (size_t i = 0; i < len; i++) {
- buff[returned_ + i] ^= xor_data[i];
- }
- returned_ += len;
- return true;
-}
-
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) {
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
new file mode 100644
index 0000000..2e4cac6
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
@@ -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.
+
+#pragma once
+
+#include <linux/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <iostream>
+
+#include <libsnapshot/cow_reader.h>
+
+namespace android {
+namespace snapshot {
+
+class BufferSink : public IByteSink {
+ public:
+ void Initialize(size_t size);
+ void* GetBufPtr() { return buffer_.get(); }
+ void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
+ void* GetPayloadBuffer(size_t size);
+ void* GetBuffer(size_t requested, size_t* actual) override;
+ void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
+ 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_;
+ loff_t buffer_offset_;
+ size_t buffer_size_;
+};
+
+class XorSink : public IByteSink {
+ public:
+ void Initialize(BufferSink* sink, size_t size);
+ void Reset();
+ void* GetBuffer(size_t requested, size_t* actual) override;
+ bool ReturnData(void* buffer, size_t len) override;
+
+ private:
+ BufferSink* bufsink_;
+ std::unique_ptr<uint8_t[]> buffer_;
+ size_t buffer_size_;
+ size_t returned_;
+};
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index aeecf41..6ed55af 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -79,6 +79,15 @@
// Returns true if the snapuserd instance supports bridging a socket to second-stage init.
bool SupportsSecondStageSocketHandoff();
+
+ // Returns true if the merge is started(or resumed from crash).
+ bool InitiateMerge(const std::string& misc_name);
+
+ // Returns Merge completion percentage
+ double GetMergePercent();
+
+ // Return the status of the snapshot
+ std::string QuerySnapshotStatus(const std::string& misc_name);
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp
new file mode 100644
index 0000000..ab763ab
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp
@@ -0,0 +1,96 @@
+/*
+ * 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/snapuserd_buffer.h>
+#include <snapuserd/snapuserd_kernel.h>
+
+namespace android {
+namespace snapshot {
+
+void BufferSink::Initialize(size_t size) {
+ buffer_size_ = size;
+ buffer_offset_ = 0;
+ buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void* BufferSink::GetPayloadBuffer(size_t size) {
+ if ((buffer_size_ - buffer_offset_) < size) return nullptr;
+
+ char* buffer = reinterpret_cast<char*>(GetBufPtr());
+ struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+ return (char*)msg->payload.buf + buffer_offset_;
+}
+
+void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
+ void* buf = GetPayloadBuffer(requested);
+ if (!buf) {
+ *actual = 0;
+ return nullptr;
+ }
+ *actual = requested;
+ return buf;
+}
+
+struct dm_user_header* BufferSink::GetHeaderPtr() {
+ 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;
+}
+
+void XorSink::Initialize(BufferSink* sink, size_t size) {
+ bufsink_ = sink;
+ buffer_size_ = size;
+ returned_ = 0;
+ buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void XorSink::Reset() {
+ returned_ = 0;
+}
+
+void* XorSink::GetBuffer(size_t requested, size_t* actual) {
+ if (requested > buffer_size_) {
+ *actual = buffer_size_;
+ } else {
+ *actual = requested;
+ }
+ return buffer_.get();
+}
+
+bool XorSink::ReturnData(void* buffer, size_t len) {
+ uint8_t* xor_data = reinterpret_cast<uint8_t*>(buffer);
+ uint8_t* buff = reinterpret_cast<uint8_t*>(bufsink_->GetPayloadBuffer(len + returned_));
+ if (buff == nullptr) {
+ return false;
+ }
+ for (size_t i = 0; i < len; i++) {
+ buff[returned_ + i] ^= xor_data[i];
+ }
+ returned_ += len;
+ return true;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 1ea05a3..e345269 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -231,5 +231,35 @@
return true;
}
+bool SnapuserdClient::InitiateMerge(const std::string& misc_name) {
+ std::string msg = "initiate_merge," + misc_name;
+ if (!Sendmsg(msg)) {
+ LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+ return false;
+ }
+ std::string response = Receivemsg();
+ return response == "success";
+}
+
+double SnapuserdClient::GetMergePercent() {
+ std::string msg = "merge_percent";
+ if (!Sendmsg(msg)) {
+ LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+ return false;
+ }
+ std::string response = Receivemsg();
+
+ return std::stod(response);
+}
+
+std::string SnapuserdClient::QuerySnapshotStatus(const std::string& misc_name) {
+ std::string msg = "getstatus," + misc_name;
+ if (!Sendmsg(msg)) {
+ LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+ return "snapshot-merge-failed";
+ }
+ return Receivemsg();
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index e05822e..912884f 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -21,8 +21,6 @@
#include <gflags/gflags.h>
#include <snapuserd/snapuserd_client.h>
-#include "snapuserd_server.h"
-
DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
DEFINE_bool(no_socket, false,
"If true, no socket is used. Each additional argument is an INIT message.");
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
index b660ba2..fbf57d9 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
@@ -19,7 +19,7 @@
#include <string>
#include <vector>
-#include "snapuserd_server.h"
+#include "dm-snapshot-merge/snapuserd_server.h"
namespace android {
namespace snapshot {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
new file mode 100644
index 0000000..57e47e7
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -0,0 +1,517 @@
+/*
+ * 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_core.h"
+
+#include <android-base/strings.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
+ std::string backing_device, std::string base_path_merge) {
+ misc_name_ = std::move(misc_name);
+ cow_device_ = std::move(cow_device);
+ backing_store_device_ = std::move(backing_device);
+ control_device_ = "/dev/dm-user/" + misc_name_;
+ base_path_merge_ = std::move(base_path_merge);
+}
+
+bool SnapshotHandler::InitializeWorkers() {
+ for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
+ std::unique_ptr<Worker> wt =
+ std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
+ misc_name_, base_path_merge_, GetSharedPtr());
+ if (!wt->Init()) {
+ SNAP_LOG(ERROR) << "Thread initialization failed";
+ return false;
+ }
+
+ worker_threads_.push_back(std::move(wt));
+ }
+
+ merge_thread_ = std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
+ misc_name_, base_path_merge_, GetSharedPtr());
+
+ read_ahead_thread_ = std::make_unique<ReadAhead>(cow_device_, backing_store_device_, misc_name_,
+ GetSharedPtr());
+ return true;
+}
+
+std::unique_ptr<CowReader> SnapshotHandler::CloneReaderForWorker() {
+ return reader_->CloneCowReader();
+}
+
+void SnapshotHandler::UpdateMergeCompletionPercentage() {
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ merge_completion_percentage_ = (ch->num_merge_ops * 100.0) / reader_->get_num_total_data_ops();
+
+ SNAP_LOG(DEBUG) << "Merge-complete %: " << merge_completion_percentage_
+ << " num_merge_ops: " << ch->num_merge_ops
+ << " total-ops: " << reader_->get_num_total_data_ops();
+}
+
+bool SnapshotHandler::CommitMerge(int num_merge_ops) {
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ ch->num_merge_ops += num_merge_ops;
+
+ if (scratch_space_) {
+ if (ra_thread_) {
+ struct BufferState* ra_state = GetBufferState();
+ ra_state->read_ahead_state = kCowReadAheadInProgress;
+ }
+
+ int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+ if (ret < 0) {
+ SNAP_PLOG(ERROR) << "msync header failed: " << ret;
+ return false;
+ }
+ } else {
+ reader_->UpdateMergeOpsCompleted(num_merge_ops);
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ if (lseek(cow_fd_.get(), 0, SEEK_SET) < 0) {
+ SNAP_PLOG(ERROR) << "lseek failed";
+ return false;
+ }
+
+ if (!android::base::WriteFully(cow_fd_, &header, sizeof(CowHeader))) {
+ SNAP_PLOG(ERROR) << "Write to header failed";
+ return false;
+ }
+
+ if (fsync(cow_fd_.get()) < 0) {
+ SNAP_PLOG(ERROR) << "fsync failed";
+ return false;
+ }
+ }
+
+ // Update the merge completion - this is used by update engine
+ // to track the completion. No need to take a lock. It is ok
+ // even if there is a miss on reading a latest updated value.
+ // Subsequent polling will eventually converge to completion.
+ UpdateMergeCompletionPercentage();
+
+ return true;
+}
+
+void SnapshotHandler::PrepareReadAhead() {
+ 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;
+ }
+
+ NotifyRAForMergeReady();
+}
+
+void SnapshotHandler::CheckMergeCompletionStatus() {
+ if (!merge_initiated_) {
+ SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: "
+ << reader_->get_num_total_data_ops();
+ return;
+ }
+
+ 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_->get_num_total_data_ops();
+}
+
+bool SnapshotHandler::ReadMetadata() {
+ reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE);
+ CowHeader header;
+ CowOptions options;
+
+ SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
+
+ if (!reader_->Parse(cow_fd_)) {
+ SNAP_LOG(ERROR) << "Failed to parse";
+ return false;
+ }
+
+ if (!reader_->GetHeader(&header)) {
+ SNAP_LOG(ERROR) << "Failed to get header";
+ return false;
+ }
+
+ if (!(header.block_size == BLOCK_SZ)) {
+ SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "Merge-ops: " << header.num_merge_ops;
+
+ if (!MmapMetadata()) {
+ SNAP_LOG(ERROR) << "mmap failed";
+ return false;
+ }
+
+ UpdateMergeCompletionPercentage();
+
+ // Initialize the iterator for reading metadata
+ std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+
+ int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
+ int ra_index = 0;
+
+ size_t copy_ops = 0, replace_ops = 0, zero_ops = 0, xor_ops = 0;
+
+ while (!cowop_iter->Done()) {
+ const CowOperation* cow_op = &cowop_iter->Get();
+
+ if (cow_op->type == kCowCopyOp) {
+ copy_ops += 1;
+ } else if (cow_op->type == kCowReplaceOp) {
+ replace_ops += 1;
+ } else if (cow_op->type == kCowZeroOp) {
+ zero_ops += 1;
+ } else if (cow_op->type == kCowXorOp) {
+ xor_ops += 1;
+ }
+
+ chunk_vec_.push_back(std::make_pair(ChunkToSector(cow_op->new_block), cow_op));
+
+ if (IsOrderedOp(*cow_op)) {
+ ra_thread_ = true;
+ block_to_ra_index_[cow_op->new_block] = ra_index;
+ num_ra_ops_per_iter -= 1;
+
+ if ((ra_index + 1) - merge_blk_state_.size() == 1) {
+ std::unique_ptr<MergeGroupState> blk_state = std::make_unique<MergeGroupState>(
+ MERGE_GROUP_STATE::GROUP_MERGE_PENDING, 0);
+
+ merge_blk_state_.push_back(std::move(blk_state));
+ }
+
+ // Move to next RA block
+ if (num_ra_ops_per_iter == 0) {
+ num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
+ ra_index += 1;
+ }
+ }
+ cowop_iter->Next();
+ }
+
+ chunk_vec_.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);
+
+ PrepareReadAhead();
+
+ SNAP_LOG(INFO) << "Merged-ops: " << header.num_merge_ops
+ << " Total-data-ops: " << reader_->get_num_total_data_ops()
+ << " Unmerged-ops: " << chunk_vec_.size() << " Copy-ops: " << copy_ops
+ << " Zero-ops: " << zero_ops << " Replace-ops: " << replace_ops
+ << " Xor-ops: " << xor_ops;
+
+ return true;
+}
+
+bool SnapshotHandler::MmapMetadata() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+
+ if (header.major_version >= 2 && header.buffer_size > 0) {
+ scratch_space_ = true;
+ }
+
+ if (scratch_space_) {
+ mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED,
+ cow_fd_.get(), 0);
+ } else {
+ mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ ch->num_merge_ops = header.num_merge_ops;
+ }
+
+ if (mapped_addr_ == MAP_FAILED) {
+ SNAP_LOG(ERROR) << "mmap metadata failed";
+ return false;
+ }
+
+ return true;
+}
+
+void SnapshotHandler::UnmapBufferRegion() {
+ int ret = munmap(mapped_addr_, total_mapped_addr_length_);
+ if (ret < 0) {
+ SNAP_PLOG(ERROR) << "munmap failed";
+ }
+}
+
+bool SnapshotHandler::InitCowDevice() {
+ cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+ if (cow_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+ return false;
+ }
+
+ unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge_.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (fd < 0) {
+ SNAP_LOG(ERROR) << "Cannot open block device";
+ return false;
+ }
+
+ uint64_t dev_sz = get_block_device_size(fd.get());
+ if (!dev_sz) {
+ SNAP_LOG(ERROR) << "Failed to find block device size: " << base_path_merge_;
+ return false;
+ }
+
+ num_sectors_ = dev_sz >> SECTOR_SHIFT;
+
+ return ReadMetadata();
+}
+
+void SnapshotHandler::ReadBlocksToCache(const std::string& dm_block_device,
+ const std::string& partition_name, off_t offset,
+ size_t size) {
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
+ if (fd.get() == -1) {
+ SNAP_PLOG(ERROR) << "Error reading " << dm_block_device
+ << " partition-name: " << partition_name;
+ return;
+ }
+
+ size_t remain = size;
+ off_t file_offset = offset;
+ // We pick 4M I/O size based on the fact that the current
+ // update_verifier has a similar I/O size.
+ size_t read_sz = 1024 * BLOCK_SZ;
+ std::vector<uint8_t> buf(read_sz);
+
+ while (remain > 0) {
+ size_t to_read = std::min(remain, read_sz);
+
+ if (!android::base::ReadFullyAtOffset(fd.get(), buf.data(), to_read, file_offset)) {
+ SNAP_PLOG(ERROR) << "Failed to read block from block device: " << dm_block_device
+ << " at offset: " << file_offset
+ << " partition-name: " << partition_name << " total-size: " << size
+ << " remain_size: " << remain;
+ return;
+ }
+
+ file_offset += to_read;
+ remain -= to_read;
+ }
+
+ SNAP_LOG(INFO) << "Finished reading block-device: " << dm_block_device
+ << " partition: " << partition_name << " size: " << size
+ << " offset: " << offset;
+}
+
+void SnapshotHandler::ReadBlocks(const std::string partition_name,
+ const std::string& dm_block_device) {
+ SNAP_LOG(DEBUG) << "Reading partition: " << partition_name
+ << " Block-Device: " << dm_block_device;
+
+ uint64_t dev_sz = 0;
+
+ unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (fd < 0) {
+ SNAP_LOG(ERROR) << "Cannot open block device";
+ return;
+ }
+
+ dev_sz = get_block_device_size(fd.get());
+ if (!dev_sz) {
+ SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
+ return;
+ }
+
+ int num_threads = 2;
+ size_t num_blocks = dev_sz >> BLOCK_SHIFT;
+ size_t num_blocks_per_thread = num_blocks / num_threads;
+ size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
+ off_t offset = 0;
+
+ for (int i = 0; i < num_threads; i++) {
+ std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this, dm_block_device,
+ partition_name, offset, read_sz_per_thread);
+
+ offset += read_sz_per_thread;
+ }
+}
+
+/*
+ * Entry point to launch threads
+ */
+bool SnapshotHandler::Start() {
+ std::vector<std::future<bool>> threads;
+ std::future<bool> ra_thread_status;
+
+ if (ra_thread_) {
+ ra_thread_status =
+ std::async(std::launch::async, &ReadAhead::RunThread, read_ahead_thread_.get());
+
+ 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, &Worker::RunThread, worker_threads_[i].get()));
+ }
+
+ bool second_stage_init = true;
+
+ // We don't want to read the blocks during first stage init.
+ if (android::base::EndsWith(misc_name_, "-init") || is_socket_present_) {
+ second_stage_init = false;
+ }
+
+ if (second_stage_init) {
+ SNAP_LOG(INFO) << "Reading blocks to cache....";
+ auto& dm = DeviceMapper::Instance();
+ auto dm_block_devices = dm.FindDmPartitions();
+ if (dm_block_devices.empty()) {
+ SNAP_LOG(ERROR) << "No dm-enabled block device is found.";
+ } else {
+ auto parts = android::base::Split(misc_name_, "-");
+ std::string partition_name = parts[0];
+
+ const char* suffix_b = "_b";
+ const char* suffix_a = "_a";
+
+ partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1);
+ partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1);
+
+ if (dm_block_devices.find(partition_name) == dm_block_devices.end()) {
+ SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name;
+ } else {
+ ReadBlocks(partition_name, dm_block_devices.at(partition_name));
+ }
+ }
+ } else {
+ SNAP_LOG(INFO) << "Not reading block device into cache";
+ }
+
+ std::future<bool> merge_thread =
+ std::async(std::launch::async, &Worker::RunMergeThread, merge_thread_.get());
+
+ bool ret = true;
+ for (auto& t : threads) {
+ ret = t.get() && ret;
+ }
+
+ // Worker threads are terminated by this point - this can only happen:
+ //
+ // 1: If dm-user device is destroyed
+ // 2: We had an I/O failure when reading root partitions
+ //
+ // In case (1), this would be a graceful shutdown. In this case, merge
+ // thread and RA thread should have already terminated by this point. We will be
+ // destroying the dm-user device only _after_ merge is completed.
+ //
+ // In case (2), if merge thread had started, then it will be
+ // continuing to merge; however, since we had an I/O failure and the
+ // I/O on root partitions are no longer served, we will terminate the
+ // merge
+
+ NotifyIOTerminated();
+
+ bool read_ahead_retval = false;
+
+ SNAP_LOG(INFO) << "Snapshot I/O terminated. Waiting for merge thread....";
+ bool merge_thread_status = merge_thread.get();
+
+ if (ra_thread_) {
+ read_ahead_retval = ra_thread_status.get();
+ }
+
+ SNAP_LOG(INFO) << "Worker threads terminated with ret: " << ret
+ << " Merge-thread with ret: " << merge_thread_status
+ << " RA-thread with ret: " << read_ahead_retval;
+ return ret;
+}
+
+uint64_t SnapshotHandler::GetBufferMetadataOffset() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ return (header.header_size + sizeof(BufferState));
+}
+
+/*
+ * 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
+ * 2: Scratch space
+ *
+ */
+size_t SnapshotHandler::GetBufferMetadataSize() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+ size_t buffer_size = header.buffer_size;
+
+ // If there is no scratch space, then just use the
+ // anonymous memory
+ if (buffer_size == 0) {
+ buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+ }
+
+ return ((buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ);
+}
+
+size_t SnapshotHandler::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 SnapshotHandler::GetBufferDataSize() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+ size_t buffer_size = header.buffer_size;
+
+ // If there is no scratch space, then just use the
+ // anonymous memory
+ if (buffer_size == 0) {
+ buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+ }
+
+ return (buffer_size - GetBufferMetadataSize());
+}
+
+struct BufferState* SnapshotHandler::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/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
new file mode 100644
index 0000000..13b56fa
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -0,0 +1,358 @@
+// 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 <linux/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#include <condition_variable>
+#include <cstring>
+#include <future>
+#include <iostream>
+#include <limits>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <libdm/dm.h>
+#include <libsnapshot/cow_reader.h>
+#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
+#include <snapuserd/snapuserd_kernel.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::unique_fd;
+using namespace std::chrono_literals;
+
+static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
+static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
+
+static constexpr int NUM_THREADS_PER_PARTITION = 1;
+
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
+
+enum class MERGE_IO_TRANSITION {
+ MERGE_READY,
+ MERGE_BEGIN,
+ MERGE_FAILED,
+ MERGE_COMPLETE,
+ IO_TERMINATED,
+ READ_AHEAD_FAILURE,
+};
+
+class SnapshotHandler;
+
+enum class MERGE_GROUP_STATE {
+ GROUP_MERGE_PENDING,
+ GROUP_MERGE_RA_READY,
+ GROUP_MERGE_IN_PROGRESS,
+ GROUP_MERGE_COMPLETED,
+ GROUP_MERGE_FAILED,
+ GROUP_INVALID,
+};
+
+struct MergeGroupState {
+ MERGE_GROUP_STATE merge_state_;
+ // Ref count I/O when group state
+ // is in "GROUP_MERGE_PENDING"
+ size_t num_ios_in_progress;
+ std::mutex m_lock;
+ std::condition_variable m_cv;
+
+ MergeGroupState(MERGE_GROUP_STATE state, size_t n_ios)
+ : merge_state_(state), num_ios_in_progress(n_ios) {}
+};
+
+class ReadAhead {
+ public:
+ ReadAhead(const std::string& cow_device, const std::string& backing_device,
+ const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd);
+ bool RunThread();
+
+ private:
+ void InitializeRAIter();
+ bool RAIterDone();
+ void RAIterNext();
+ const CowOperation* GetRAOpIter();
+
+ void InitializeBuffer();
+ bool InitReader();
+ bool InitializeFds();
+
+ void CloseFds() { backing_store_fd_ = {}; }
+
+ bool ReadAheadIOStart();
+ int PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops,
+ std::vector<uint64_t>& blocks,
+ std::vector<const CowOperation*>& xor_op_vec);
+ bool ReconstructDataFromCow();
+ void CheckOverlap(const CowOperation* cow_op);
+
+ void* read_ahead_buffer_;
+ void* metadata_buffer_;
+
+ std::unique_ptr<ICowOpIter> cowop_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<SnapshotHandler> snapuserd_;
+ std::unique_ptr<CowReader> reader_;
+
+ std::unordered_set<uint64_t> dest_blocks_;
+ std::unordered_set<uint64_t> source_blocks_;
+ bool overlap_;
+ BufferSink bufsink_;
+};
+
+class Worker {
+ public:
+ Worker(const std::string& cow_device, const std::string& backing_device,
+ const std::string& control_device, const std::string& misc_name,
+ const std::string& base_path_merge, std::shared_ptr<SnapshotHandler> snapuserd);
+ bool RunThread();
+ bool RunMergeThread();
+ bool Init();
+
+ private:
+ // Initialization
+ void InitializeBufsink();
+ bool InitializeFds();
+ bool InitReader();
+ void CloseFds() {
+ ctrl_fd_ = {};
+ backing_store_fd_ = {};
+ base_path_merge_fd_ = {};
+ }
+
+ // Functions interacting with dm-user
+ bool ReadDmUserHeader();
+ bool WriteDmUserPayload(size_t size, bool header_response);
+ bool DmuserReadRequest();
+
+ // IO Path
+ bool ProcessIORequest();
+ bool IsBlockAligned(size_t size) { return ((size & (BLOCK_SZ - 1)) == 0); }
+
+ bool ReadDataFromBaseDevice(sector_t sector, size_t read_size);
+ bool ReadFromSourceDevice(const CowOperation* cow_op);
+
+ bool ReadAlignedSector(sector_t sector, size_t sz, bool header_response);
+ bool ReadUnalignedSector(sector_t sector, size_t size);
+ int ReadUnalignedSector(sector_t sector, size_t size,
+ std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
+ bool RespondIOError(bool header_response);
+
+ // Processing COW operations
+ bool ProcessCowOp(const CowOperation* cow_op);
+ bool ProcessReplaceOp(const CowOperation* cow_op);
+ bool ProcessZeroOp();
+
+ // Handles Copy and Xor
+ bool ProcessCopyOp(const CowOperation* cow_op);
+ bool ProcessXorOp(const CowOperation* cow_op);
+ bool ProcessOrderedOp(const CowOperation* cow_op);
+
+ // Merge related ops
+ bool Merge();
+ bool MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+ bool MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+ int PrepareMerge(uint64_t* source_offset, int* pending_ops,
+ const std::unique_ptr<ICowOpIter>& cowop_iter,
+ std::vector<const CowOperation*>* replace_zero_vec = nullptr);
+
+ sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+ chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+
+ std::unique_ptr<CowReader> reader_;
+ BufferSink bufsink_;
+ XorSink xorsink_;
+
+ std::string cow_device_;
+ std::string backing_store_device_;
+ std::string control_device_;
+ std::string misc_name_;
+ std::string base_path_merge_;
+
+ unique_fd cow_fd_;
+ unique_fd backing_store_fd_;
+ unique_fd base_path_merge_fd_;
+ unique_fd ctrl_fd_;
+
+ std::shared_ptr<SnapshotHandler> snapuserd_;
+};
+
+class SnapshotHandler : public std::enable_shared_from_this<SnapshotHandler> {
+ public:
+ SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device,
+ std::string base_path_merge);
+ bool InitCowDevice();
+ bool Start();
+
+ const std::string& GetControlDevicePath() { return control_device_; }
+ const std::string& GetMiscName() { return misc_name_; }
+ const uint64_t& GetNumSectors() { return num_sectors_; }
+ const bool& IsAttached() const { return attached_; }
+ void AttachControlDevice() { attached_ = true; }
+
+ void CheckMergeCompletionStatus();
+ bool CommitMerge(int num_merge_ops);
+
+ void CloseFds() { cow_fd_ = {}; }
+ void FreeResources() {
+ worker_threads_.clear();
+ read_ahead_thread_ = nullptr;
+ merge_thread_ = nullptr;
+ }
+
+ bool InitializeWorkers();
+ std::unique_ptr<CowReader> CloneReaderForWorker();
+ std::shared_ptr<SnapshotHandler> GetSharedPtr() { return shared_from_this(); }
+
+ std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; }
+
+ static bool compare(std::pair<sector_t, const CowOperation*> p1,
+ std::pair<sector_t, const CowOperation*> p2) {
+ return p1.first < p2.first;
+ }
+
+ void UnmapBufferRegion();
+ bool MmapMetadata();
+
+ // Read-ahead related functions
+ void* GetMappedAddr() { return mapped_addr_; }
+ void PrepareReadAhead();
+ std::unordered_map<uint64_t, void*>& GetReadAheadMap() { return read_ahead_buffer_map_; }
+
+ // State transitions for merge
+ void InitiateMerge();
+ void WaitForMergeComplete();
+ bool WaitForMergeBegin();
+ void NotifyRAForMergeReady();
+ bool WaitForMergeReady();
+ void MergeFailed();
+ bool IsIOTerminated();
+ void MergeCompleted();
+ void NotifyIOTerminated();
+ bool ReadAheadIOCompleted(bool sync);
+ void ReadAheadIOFailed();
+
+ bool ShouldReconstructDataFromCow() { return populate_data_from_cow_; }
+ void FinishReconstructDataFromCow() { populate_data_from_cow_ = false; }
+ // Return the snapshot status
+ std::string GetMergeStatus();
+
+ // RA related functions
+ uint64_t GetBufferMetadataOffset();
+ size_t GetBufferMetadataSize();
+ size_t GetBufferDataOffset();
+ size_t GetBufferDataSize();
+
+ // Total number of blocks to be merged in a given read-ahead buffer region
+ void SetMergedBlockCountForNextCommit(int x) { total_ra_blocks_merged_ = x; }
+ int GetTotalBlocksToMerge() { return total_ra_blocks_merged_; }
+ void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
+ bool MergeInitiated() { return merge_initiated_; }
+ double GetMergePercentage() { return merge_completion_percentage_; }
+
+ // Merge Block State Transitions
+ void SetMergeCompleted(size_t block_index);
+ void SetMergeInProgress(size_t block_index);
+ void SetMergeFailed(size_t block_index);
+ void NotifyIOCompletion(uint64_t new_block);
+ bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
+ MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer);
+
+ private:
+ 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();
+ void UpdateMergeCompletionPercentage();
+
+ void ReadBlocks(const std::string partition_name, const std::string& dm_block_device);
+ void ReadBlocksToCache(const std::string& dm_block_device, const std::string& partition_name,
+ off_t offset, size_t size);
+
+ // COW device
+ std::string cow_device_;
+ // Source device
+ std::string backing_store_device_;
+ // dm-user control device
+ std::string control_device_;
+ std::string misc_name_;
+ // Base device for merging
+ std::string base_path_merge_;
+
+ unique_fd cow_fd_;
+
+ uint64_t num_sectors_;
+
+ std::unique_ptr<CowReader> reader_;
+
+ // chunk_vec stores the pseudo mapping of sector
+ // to COW operations.
+ 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<Worker>> worker_threads_;
+ // Read-ahead related
+ bool populate_data_from_cow_ = false;
+ bool ra_thread_ = false;
+ int total_ra_blocks_merged_ = 0;
+ MERGE_IO_TRANSITION io_state_;
+ std::unique_ptr<ReadAhead> read_ahead_thread_;
+ std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
+
+ // user-space-merging
+ std::unordered_map<uint64_t, int> block_to_ra_index_;
+
+ // Merge Block state
+ std::vector<std::unique_ptr<MergeGroupState>> merge_blk_state_;
+
+ std::unique_ptr<Worker> merge_thread_;
+ double merge_completion_percentage_;
+
+ bool merge_initiated_ = false;
+ bool attached_ = false;
+ bool is_socket_present_;
+ bool scratch_space_ = false;
+};
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
new file mode 100644
index 0000000..bfbacf9
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -0,0 +1,645 @@
+/*
+ * 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_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+Worker::Worker(const std::string& cow_device, const std::string& backing_device,
+ const std::string& control_device, const std::string& misc_name,
+ const std::string& base_path_merge, std::shared_ptr<SnapshotHandler> snapuserd) {
+ cow_device_ = cow_device;
+ backing_store_device_ = backing_device;
+ control_device_ = control_device;
+ misc_name_ = misc_name;
+ base_path_merge_ = base_path_merge;
+ snapuserd_ = snapuserd;
+}
+
+bool Worker::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;
+ }
+
+ ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
+ if (ctrl_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
+ return false;
+ }
+
+ // Base device used by merge thread
+ base_path_merge_fd_.reset(open(base_path_merge_.c_str(), O_RDWR));
+ if (base_path_merge_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << base_path_merge_;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::InitReader() {
+ reader_ = snapuserd_->CloneReaderForWorker();
+
+ if (!reader_->InitForMerge(std::move(cow_fd_))) {
+ return false;
+ }
+ return true;
+}
+
+// Start the replace operation. This will read the
+// internal COW format and if the block is compressed,
+// it will be de-compressed.
+bool Worker::ProcessReplaceOp(const CowOperation* cow_op) {
+ if (!reader_->ReadData(*cow_op, &bufsink_)) {
+ SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ReadFromSourceDevice(const CowOperation* cow_op) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ 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;
+ uint64_t offset = cow_op->source;
+ if (cow_op->type == kCowCopyOp) {
+ offset *= BLOCK_SZ;
+ }
+ if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
+ std::string op;
+ if (cow_op->type == kCowCopyOp)
+ op = "Copy-op";
+ else {
+ op = "Xor-op";
+ }
+ SNAP_PLOG(ERROR) << op << " failed. Read from backing store: " << backing_store_device_
+ << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ;
+ return false;
+ }
+
+ return true;
+}
+
+// Start the copy operation. This will read the backing
+// block device which is represented by cow_op->source.
+bool Worker::ProcessCopyOp(const CowOperation* cow_op) {
+ if (!ReadFromSourceDevice(cow_op)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ProcessXorOp(const CowOperation* cow_op) {
+ if (!ReadFromSourceDevice(cow_op)) {
+ return false;
+ }
+ xorsink_.Reset();
+ if (!reader_->ReadData(*cow_op, &xorsink_)) {
+ SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ProcessZeroOp() {
+ // Zero out the entire block
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessZeroOp: Failed to get payload buffer";
+ return false;
+ }
+
+ memset(buffer, 0, BLOCK_SZ);
+ return true;
+}
+
+bool Worker::ProcessOrderedOp(const CowOperation* cow_op) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessOrderedOp: Failed to get payload buffer";
+ return false;
+ }
+
+ MERGE_GROUP_STATE state = snapuserd_->ProcessMergingBlock(cow_op->new_block, buffer);
+
+ switch (state) {
+ case MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED: {
+ // Merge is completed for this COW op; just read directly from
+ // the base device
+ SNAP_LOG(DEBUG) << "Merge-completed: Reading from base device sector: "
+ << (cow_op->new_block >> SECTOR_SHIFT)
+ << " Block-number: " << cow_op->new_block;
+ if (!ReadDataFromBaseDevice(ChunkToSector(cow_op->new_block), BLOCK_SZ)) {
+ SNAP_LOG(ERROR) << "ReadDataFromBaseDevice at sector: "
+ << (cow_op->new_block >> SECTOR_SHIFT) << " after merge-complete.";
+ return false;
+ }
+ return true;
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
+ bool ret;
+ if (cow_op->type == kCowCopyOp) {
+ ret = ProcessCopyOp(cow_op);
+ } else {
+ ret = ProcessXorOp(cow_op);
+ }
+
+ // I/O is complete - decrement the refcount irrespective of the return
+ // status
+ snapuserd_->NotifyIOCompletion(cow_op->new_block);
+ return ret;
+ }
+ // We already have the data in the buffer retrieved from RA thread.
+ // Nothing to process further.
+ case MERGE_GROUP_STATE::GROUP_MERGE_RA_READY: {
+ [[fallthrough]];
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS: {
+ return true;
+ }
+ default: {
+ // All other states, fail the I/O viz (GROUP_MERGE_FAILED and GROUP_INVALID)
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool Worker::ProcessCowOp(const CowOperation* cow_op) {
+ if (cow_op == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op";
+ return false;
+ }
+
+ switch (cow_op->type) {
+ case kCowReplaceOp: {
+ return ProcessReplaceOp(cow_op);
+ }
+
+ case kCowZeroOp: {
+ return ProcessZeroOp();
+ }
+
+ case kCowCopyOp:
+ [[fallthrough]];
+ case kCowXorOp: {
+ return ProcessOrderedOp(cow_op);
+ }
+
+ default: {
+ SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+ }
+ }
+ return false;
+}
+
+void Worker::InitializeBufsink() {
+ // Allocate the buffer which is used to communicate between
+ // daemon and dm-user. The buffer comprises of header and a fixed payload.
+ // If the dm-user requests a big IO, the IO will be broken into chunks
+ // of PAYLOAD_SIZE.
+ size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
+ bufsink_.Initialize(buf_size);
+}
+
+bool Worker::Init() {
+ InitializeBufsink();
+ xorsink_.Initialize(&bufsink_, BLOCK_SZ);
+
+ if (!InitializeFds()) {
+ return false;
+ }
+
+ if (!InitReader()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::RunThread() {
+ SNAP_LOG(INFO) << "Processing snapshot I/O requests....";
+ // Start serving IO
+ while (true) {
+ if (!ProcessIORequest()) {
+ break;
+ }
+ }
+
+ CloseFds();
+ reader_->CloseCowFd();
+
+ return true;
+}
+
+// Read Header from dm-user misc device. This gives
+// us the sector number for which IO is issued by dm-snapshot device
+bool Worker::ReadDmUserHeader() {
+ if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
+ if (errno != ENOTBLK) {
+ SNAP_PLOG(ERROR) << "Control-read failed";
+ }
+
+ SNAP_PLOG(DEBUG) << "ReadDmUserHeader failed....";
+ return false;
+ }
+
+ return true;
+}
+
+// Send the payload/data back to dm-user misc device.
+bool Worker::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;
+ }
+
+ return true;
+}
+
+bool Worker::ReadDataFromBaseDevice(sector_t sector, size_t read_size) {
+ CHECK(read_size <= BLOCK_SZ);
+
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+ return false;
+ }
+
+ loff_t offset = sector << SECTOR_SHIFT;
+ if (!android::base::ReadFullyAtOffset(base_path_merge_fd_, buffer, read_size, offset)) {
+ SNAP_PLOG(ERROR) << "ReadDataFromBaseDevice failed. fd: " << base_path_merge_fd_
+ << "at sector :" << sector << " size: " << read_size;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ReadAlignedSector(sector_t sector, size_t sz, bool header_response) {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+ size_t remaining_size = sz;
+ std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+ bool io_error = false;
+ int ret = 0;
+
+ do {
+ // Process 1MB payload at a time
+ size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+
+ header->type = DM_USER_RESP_SUCCESS;
+ size_t total_bytes_read = 0;
+ io_error = false;
+ bufsink_.ResetBufferOffset();
+
+ while (read_size) {
+ // We need to check every 4k block to verify if it is
+ // present in the mapping.
+ size_t size = std::min(BLOCK_SZ, read_size);
+
+ auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+ std::make_pair(sector, nullptr), SnapshotHandler::compare);
+ bool not_found = (it == chunk_vec.end() || it->first != sector);
+
+ if (not_found) {
+ // Block not found in map - which means this block was not
+ // changed as per the OTA. Just route the I/O to the base
+ // device.
+ if (!ReadDataFromBaseDevice(sector, size)) {
+ SNAP_LOG(ERROR) << "ReadDataFromBaseDevice failed";
+ header->type = DM_USER_RESP_ERROR;
+ }
+
+ ret = size;
+ } else {
+ // We found the sector in mapping. Check the type of COW OP and
+ // process it.
+ if (!ProcessCowOp(it->second)) {
+ SNAP_LOG(ERROR) << "ProcessCowOp failed";
+ header->type = DM_USER_RESP_ERROR;
+ }
+
+ ret = BLOCK_SZ;
+ }
+
+ // Just return the header if it is an error
+ if (header->type == DM_USER_RESP_ERROR) {
+ if (!RespondIOError(header_response)) {
+ return false;
+ }
+
+ io_error = true;
+ break;
+ }
+
+ read_size -= ret;
+ total_bytes_read += ret;
+ sector += (ret >> SECTOR_SHIFT);
+ bufsink_.UpdateBufferOffset(ret);
+ }
+
+ if (!io_error) {
+ if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+ return false;
+ }
+
+ SNAP_LOG(DEBUG) << "WriteDmUserPayload success total_bytes_read: " << total_bytes_read
+ << " header-response: " << header_response
+ << " remaining_size: " << remaining_size;
+ header_response = false;
+ remaining_size -= total_bytes_read;
+ }
+ } while (remaining_size > 0 && !io_error);
+
+ return true;
+}
+
+int Worker::ReadUnalignedSector(
+ sector_t sector, size_t size,
+ std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) {
+ size_t skip_sector_size = 0;
+
+ SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
+ << " Aligned sector: " << it->first;
+
+ if (!ProcessCowOp(it->second)) {
+ SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size
+ << " Aligned sector: " << it->first;
+ return -1;
+ }
+
+ int num_sectors_skip = sector - it->first;
+
+ if (num_sectors_skip > 0) {
+ skip_sector_size = num_sectors_skip << SECTOR_SHIFT;
+ 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));
+ }
+
+ bufsink_.ResetBufferOffset();
+ return std::min(size, (BLOCK_SZ - skip_sector_size));
+}
+
+bool Worker::ReadUnalignedSector(sector_t sector, size_t size) {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+ header->type = DM_USER_RESP_SUCCESS;
+ bufsink_.ResetBufferOffset();
+ std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+
+ auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
+ SnapshotHandler::compare);
+
+ // |-------|-------|-------|
+ // 0 1 2 3
+ //
+ // Block 0 - op 1
+ // Block 1 - op 2
+ // Block 2 - op 3
+ //
+ // chunk_vec will have block 0, 1, 2 which maps to relavant COW ops.
+ //
+ // Each block is 4k bytes. Thus, the last block will span 8 sectors
+ // ranging till block 3 (However, block 3 won't be in chunk_vec as
+ // it doesn't have any mapping to COW ops. Now, if we get an I/O request for a sector
+ // spanning between block 2 and block 3, we need to step back
+ // and get hold of the last element.
+ //
+ // Additionally, we need to make sure that the requested sector is
+ // indeed within the range of the final sector. It is perfectly valid
+ // to get an I/O request for block 3 and beyond which are not mapped
+ // to any COW ops. In that case, we just need to read from the base
+ // device.
+ bool merge_complete = false;
+ bool header_response = true;
+ if (it == chunk_vec.end()) {
+ if (chunk_vec.size() > 0) {
+ // I/O request beyond the last mapped sector
+ it = std::prev(chunk_vec.end());
+ } else {
+ // This can happen when a partition merge is complete but snapshot
+ // state in /metadata is not yet deleted; during this window if the
+ // device is rebooted, subsequent attempt will mount the snapshot.
+ // However, since the merge was completed we wouldn't have any
+ // mapping to COW ops thus chunk_vec will be empty. In that case,
+ // mark this as merge_complete and route the I/O to the base device.
+ merge_complete = true;
+ }
+ } else if (it->first != sector) {
+ if (it != chunk_vec.begin()) {
+ --it;
+ }
+ } else {
+ return ReadAlignedSector(sector, size, header_response);
+ }
+
+ loff_t requested_offset = sector << SECTOR_SHIFT;
+
+ loff_t final_offset = 0;
+ if (!merge_complete) {
+ final_offset = it->first << SECTOR_SHIFT;
+ }
+
+ // Since a COW op span 4k block size, we need to make sure that the requested
+ // offset is within the 4k region. Consider the following case:
+ //
+ // |-------|-------|-------|
+ // 0 1 2 3
+ //
+ // Block 0 - op 1
+ // Block 1 - op 2
+ //
+ // We have an I/O request for a sector between block 2 and block 3. However,
+ // we have mapping to COW ops only for block 0 and block 1. Thus, the
+ // requested offset in this case is beyond the last mapped COW op size (which
+ // is block 1 in this case).
+
+ size_t total_bytes_read = 0;
+ size_t remaining_size = size;
+ int ret = 0;
+ if (!merge_complete && (requested_offset >= final_offset) &&
+ (requested_offset - final_offset) < BLOCK_SZ) {
+ // Read the partial un-aligned data
+ ret = ReadUnalignedSector(sector, remaining_size, it);
+ if (ret < 0) {
+ SNAP_LOG(ERROR) << "ReadUnalignedSector failed for sector: " << sector
+ << " size: " << size << " it->sector: " << it->first;
+ return RespondIOError(header_response);
+ }
+
+ remaining_size -= ret;
+ total_bytes_read += ret;
+ sector += (ret >> SECTOR_SHIFT);
+
+ // Send the data back
+ if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+ return false;
+ }
+
+ header_response = false;
+ // If we still have pending data to be processed, this will be aligned I/O
+ if (remaining_size) {
+ return ReadAlignedSector(sector, remaining_size, header_response);
+ }
+ } else {
+ // This is all about handling I/O request to be routed to base device
+ // as the I/O is not mapped to any of the COW ops.
+ loff_t aligned_offset = requested_offset;
+ // Align to nearest 4k
+ aligned_offset += BLOCK_SZ - 1;
+ aligned_offset &= ~(BLOCK_SZ - 1);
+ // Find the diff of the aligned offset
+ size_t diff_size = aligned_offset - requested_offset;
+ CHECK(diff_size <= BLOCK_SZ);
+ if (remaining_size < diff_size) {
+ if (!ReadDataFromBaseDevice(sector, remaining_size)) {
+ return RespondIOError(header_response);
+ }
+ total_bytes_read += remaining_size;
+
+ if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+ return false;
+ }
+ } else {
+ if (!ReadDataFromBaseDevice(sector, diff_size)) {
+ return RespondIOError(header_response);
+ }
+
+ total_bytes_read += diff_size;
+
+ if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+ return false;
+ }
+
+ remaining_size -= diff_size;
+ size_t num_sectors_read = (diff_size >> SECTOR_SHIFT);
+ sector += num_sectors_read;
+ CHECK(IsBlockAligned(sector << SECTOR_SHIFT));
+ header_response = false;
+
+ // If we still have pending data to be processed, this will be aligned I/O
+ return ReadAlignedSector(sector, remaining_size, header_response);
+ }
+ }
+
+ return true;
+}
+
+bool Worker::RespondIOError(bool header_response) {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+ header->type = DM_USER_RESP_ERROR;
+ // This is an issue with the dm-user interface. There
+ // is no way to propagate the I/O error back to dm-user
+ // if we have already communicated the header back. Header
+ // is responded once at the beginning; however I/O can
+ // be processed in chunks. If we encounter an I/O error
+ // somewhere in the middle of the processing, we can't communicate
+ // this back to dm-user.
+ //
+ // TODO: Fix the interface
+ CHECK(header_response);
+
+ if (!WriteDmUserPayload(0, header_response)) {
+ return false;
+ }
+
+ // There is no need to process further as we have already seen
+ // an I/O error
+ return true;
+}
+
+bool Worker::DmuserReadRequest() {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+ // Unaligned I/O request
+ if (!IsBlockAligned(header->sector << SECTOR_SHIFT)) {
+ return ReadUnalignedSector(header->sector, header->len);
+ }
+
+ return ReadAlignedSector(header->sector, header->len, true);
+}
+
+bool Worker::ProcessIORequest() {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+ if (!ReadDmUserHeader()) {
+ return false;
+ }
+
+ 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: {
+ if (!DmuserReadRequest()) {
+ return false;
+ }
+ break;
+ }
+
+ case DM_USER_REQ_MAP_WRITE: {
+ // TODO: We should not get any write request
+ // to dm-user as we mount all partitions
+ // as read-only. Need to verify how are TRIM commands
+ // handled during mount.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
new file mode 100644
index 0000000..47fc7db
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -0,0 +1,311 @@
+/*
+ * 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_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+int Worker::PrepareMerge(uint64_t* source_offset, int* pending_ops,
+ const std::unique_ptr<ICowOpIter>& cowop_iter,
+ std::vector<const CowOperation*>* replace_zero_vec) {
+ int num_ops = *pending_ops;
+ int nr_consecutive = 0;
+ bool checkOrderedOp = (replace_zero_vec == nullptr);
+
+ do {
+ if (!cowop_iter->Done() && num_ops) {
+ const CowOperation* cow_op = &cowop_iter->Get();
+ if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
+ break;
+ }
+
+ *source_offset = cow_op->new_block * BLOCK_SZ;
+ if (!checkOrderedOp) {
+ replace_zero_vec->push_back(cow_op);
+ }
+
+ cowop_iter->Next();
+ num_ops -= 1;
+ nr_consecutive = 1;
+
+ while (!cowop_iter->Done() && num_ops) {
+ const CowOperation* op = &cowop_iter->Get();
+ if (checkOrderedOp && !IsOrderedOp(*op)) {
+ break;
+ }
+
+ uint64_t next_offset = op->new_block * BLOCK_SZ;
+ if (next_offset != (*source_offset + nr_consecutive * BLOCK_SZ)) {
+ break;
+ }
+
+ if (!checkOrderedOp) {
+ replace_zero_vec->push_back(op);
+ }
+
+ nr_consecutive += 1;
+ num_ops -= 1;
+ cowop_iter->Next();
+ }
+ }
+ } while (0);
+
+ return nr_consecutive;
+}
+
+bool Worker::MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+ // Flush every 2048 ops. Since all ops are independent and there is no
+ // dependency between COW ops, we will flush the data and the number
+ // of ops merged in COW file for every 2048 ops. If there is a crash,
+ // we will end up replaying some of the COW ops which were already merged.
+ // That is ok.
+ //
+ // Why 2048 ops ? We can probably increase this to bigger value but just
+ // need to ensure that merge makes forward progress if there are
+ // crashes repeatedly which is highly unlikely.
+ int total_ops_merged_per_commit = (PAYLOAD_SIZE / BLOCK_SZ) * 8;
+ int num_ops_merged = 0;
+
+ while (!cowop_iter->Done()) {
+ int num_ops = PAYLOAD_SIZE / BLOCK_SZ;
+ std::vector<const CowOperation*> replace_zero_vec;
+ uint64_t source_offset;
+
+ int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter, &replace_zero_vec);
+ if (linear_blocks == 0) {
+ // Merge complete
+ CHECK(cowop_iter->Done());
+ break;
+ }
+
+ for (size_t i = 0; i < replace_zero_vec.size(); i++) {
+ const CowOperation* cow_op = replace_zero_vec[i];
+ if (cow_op->type == kCowReplaceOp) {
+ if (!ProcessReplaceOp(cow_op)) {
+ SNAP_LOG(ERROR) << "Merge - ReplaceOp failed for block: " << cow_op->new_block;
+ return false;
+ }
+ } else {
+ CHECK(cow_op->type == kCowZeroOp);
+ if (!ProcessZeroOp()) {
+ SNAP_LOG(ERROR) << "Merge ZeroOp failed.";
+ return false;
+ }
+ }
+
+ bufsink_.UpdateBufferOffset(BLOCK_SZ);
+ }
+
+ size_t io_size = linear_blocks * BLOCK_SZ;
+
+ // Merge - Write the contents back to base device
+ int ret = pwrite(base_path_merge_fd_.get(), bufsink_.GetPayloadBufPtr(), io_size,
+ source_offset);
+ if (ret < 0 || ret != io_size) {
+ SNAP_LOG(ERROR)
+ << "Merge: ReplaceZeroOps: Failed to write to backing device while merging "
+ << " at offset: " << source_offset << " io_size: " << io_size;
+ return false;
+ }
+
+ num_ops_merged += linear_blocks;
+
+ if (num_ops_merged == total_ops_merged_per_commit) {
+ // Flush the data
+ if (fsync(base_path_merge_fd_.get()) < 0) {
+ SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data";
+ return false;
+ }
+
+ // Track the merge completion
+ if (!snapuserd_->CommitMerge(num_ops_merged)) {
+ SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+ return false;
+ }
+
+ num_ops_merged = 0;
+ }
+
+ bufsink_.ResetBufferOffset();
+
+ if (snapuserd_->IsIOTerminated()) {
+ SNAP_LOG(ERROR)
+ << "MergeReplaceZeroOps: Worker threads terminated - shutting down merge";
+ return false;
+ }
+ }
+
+ // Any left over ops not flushed yet.
+ if (num_ops_merged) {
+ // Flush the data
+ if (fsync(base_path_merge_fd_.get()) < 0) {
+ SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data";
+ return false;
+ }
+
+ if (!snapuserd_->CommitMerge(num_ops_merged)) {
+ SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+ return false;
+ }
+
+ num_ops_merged = 0;
+ }
+
+ return true;
+}
+
+bool Worker::MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+ void* mapped_addr = snapuserd_->GetMappedAddr();
+ void* read_ahead_buffer =
+ static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+ size_t block_index = 0;
+
+ SNAP_LOG(INFO) << "MergeOrderedOps started....";
+
+ while (!cowop_iter->Done()) {
+ const CowOperation* cow_op = &cowop_iter->Get();
+ if (!IsOrderedOp(*cow_op)) {
+ break;
+ }
+
+ SNAP_LOG(DEBUG) << "Waiting for merge begin...";
+ // Wait for RA thread to notify that the merge window
+ // is ready for merging.
+ if (!snapuserd_->WaitForMergeBegin()) {
+ snapuserd_->SetMergeFailed(block_index);
+ return false;
+ }
+
+ snapuserd_->SetMergeInProgress(block_index);
+
+ loff_t offset = 0;
+ int num_ops = snapuserd_->GetTotalBlocksToMerge();
+ SNAP_LOG(DEBUG) << "Merging copy-ops of size: " << num_ops;
+ while (num_ops) {
+ uint64_t source_offset;
+
+ int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter);
+ if (linear_blocks == 0) {
+ break;
+ }
+
+ size_t io_size = (linear_blocks * BLOCK_SZ);
+ // Write to the base device. Data is already in the RA buffer. Note
+ // that XOR ops is already handled by the RA thread. We just write
+ // the contents out.
+ int ret = pwrite(base_path_merge_fd_.get(), (char*)read_ahead_buffer + offset, io_size,
+ source_offset);
+ if (ret < 0 || ret != io_size) {
+ SNAP_LOG(ERROR) << "Failed to write to backing device while merging "
+ << " at offset: " << source_offset << " io_size: " << io_size;
+ snapuserd_->SetMergeFailed(block_index);
+ return false;
+ }
+
+ offset += io_size;
+ num_ops -= linear_blocks;
+ }
+
+ // Verify all ops are merged
+ CHECK(num_ops == 0);
+
+ // Flush the data
+ if (fsync(base_path_merge_fd_.get()) < 0) {
+ SNAP_LOG(ERROR) << " Failed to fsync merged data";
+ snapuserd_->SetMergeFailed(block_index);
+ return false;
+ }
+
+ // Merge is done and data is on disk. Update the COW Header about
+ // the merge completion
+ if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) {
+ SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+ snapuserd_->SetMergeFailed(block_index);
+ return false;
+ }
+
+ SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge();
+ // Mark the block as merge complete
+ snapuserd_->SetMergeCompleted(block_index);
+
+ // Notify RA thread that the merge thread is ready to merge the next
+ // window
+ snapuserd_->NotifyRAForMergeReady();
+
+ // Get the next block
+ block_index += 1;
+ }
+
+ return true;
+}
+
+bool Worker::Merge() {
+ std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+
+ // Start with Copy and Xor ops
+ if (!MergeOrderedOps(cowop_iter)) {
+ SNAP_LOG(ERROR) << "Merge failed for ordered ops";
+ snapuserd_->MergeFailed();
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "MergeOrderedOps completed...";
+
+ // Replace and Zero ops
+ if (!MergeReplaceZeroOps(cowop_iter)) {
+ SNAP_LOG(ERROR) << "Merge failed for replace/zero ops";
+ snapuserd_->MergeFailed();
+ return false;
+ }
+
+ snapuserd_->MergeCompleted();
+
+ return true;
+}
+
+bool Worker::RunMergeThread() {
+ SNAP_LOG(DEBUG) << "Waiting for merge begin...";
+ if (!snapuserd_->WaitForMergeBegin()) {
+ SNAP_LOG(ERROR) << "Merge terminated early...";
+ return true;
+ }
+
+ SNAP_LOG(INFO) << "Merge starting..";
+
+ if (!Init()) {
+ SNAP_LOG(ERROR) << "Merge thread initialization failed...";
+ return false;
+ }
+
+ if (!Merge()) {
+ return false;
+ }
+
+ CloseFds();
+ reader_->CloseCowFd();
+
+ SNAP_LOG(INFO) << "Merge finish";
+
+ return true;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
new file mode 100644
index 0000000..0bcf26e
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -0,0 +1,436 @@
+/*
+ * 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_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+ReadAhead::ReadAhead(const std::string& cow_device, const std::string& backing_device,
+ const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd) {
+ cow_device_ = cow_device;
+ backing_store_device_ = backing_device;
+ misc_name_ = misc_name;
+ snapuserd_ = snapuserd;
+}
+
+void ReadAhead::CheckOverlap(const CowOperation* cow_op) {
+ uint64_t source_block = cow_op->source;
+ uint64_t source_offset = 0;
+ if (cow_op->type == kCowXorOp) {
+ source_block /= BLOCK_SZ;
+ source_offset = cow_op->source % BLOCK_SZ;
+ }
+ if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
+ (source_offset > 0 && source_blocks_.count(source_block + 1))) {
+ overlap_ = true;
+ }
+
+ dest_blocks_.insert(source_block);
+ if (source_offset > 0) {
+ dest_blocks_.insert(source_block + 1);
+ }
+ source_blocks_.insert(cow_op->new_block);
+}
+
+int ReadAhead::PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops,
+ std::vector<uint64_t>& blocks,
+ std::vector<const CowOperation*>& xor_op_vec) {
+ int num_ops = *pending_ops;
+ int nr_consecutive = 0;
+
+ bool is_ops_present = (!RAIterDone() && num_ops);
+
+ if (!is_ops_present) {
+ return nr_consecutive;
+ }
+
+ // Get the first block with offset
+ const CowOperation* cow_op = GetRAOpIter();
+ *source_offset = cow_op->source;
+
+ if (cow_op->type == kCowCopyOp) {
+ *source_offset *= BLOCK_SZ;
+ } else if (cow_op->type == kCowXorOp) {
+ xor_op_vec.push_back(cow_op);
+ }
+
+ RAIterNext();
+ num_ops -= 1;
+ nr_consecutive = 1;
+ blocks.push_back(cow_op->new_block);
+
+ if (!overlap_) {
+ CheckOverlap(cow_op);
+ }
+
+ /*
+ * Find number of consecutive blocks
+ */
+ while (!RAIterDone() && num_ops) {
+ const CowOperation* op = GetRAOpIter();
+ uint64_t next_offset = op->source;
+
+ if (cow_op->type == kCowCopyOp) {
+ next_offset *= BLOCK_SZ;
+ }
+
+ // Check for consecutive blocks
+ if (next_offset != (*source_offset + nr_consecutive * BLOCK_SZ)) {
+ break;
+ }
+
+ if (op->type == kCowXorOp) {
+ xor_op_vec.push_back(op);
+ }
+
+ nr_consecutive += 1;
+ num_ops -= 1;
+ blocks.push_back(op->new_block);
+ RAIterNext();
+
+ if (!overlap_) {
+ CheckOverlap(op);
+ }
+ }
+
+ return nr_consecutive;
+}
+
+bool ReadAhead::ReconstructDataFromCow() {
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ 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 (!RAIterDone()) {
+ const CowOperation* op = GetRAOpIter();
+ if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
+ num_ops -= 1;
+ RAIterNext();
+ continue;
+ }
+
+ // 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_->SetMergedBlockCountForNextCommit(total_blocks_merged);
+
+ snapuserd_->FinishReconstructDataFromCow();
+
+ if (!snapuserd_->ReadAheadIOCompleted(true)) {
+ SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+ return true;
+}
+
+bool ReadAhead::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_->ShouldReconstructDataFromCow()) {
+ return ReconstructDataFromCow();
+ }
+
+ std::vector<uint64_t> blocks;
+
+ int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
+ loff_t buffer_offset = 0;
+ int total_blocks_merged = 0;
+ overlap_ = false;
+ dest_blocks_.clear();
+ source_blocks_.clear();
+ std::vector<const CowOperation*> xor_op_vec;
+
+ auto ra_temp_buffer = std::make_unique<uint8_t[]>(snapuserd_->GetBufferDataSize());
+
+ // Number of ops to be merged in this window. This is a fixed size
+ // except for the last window wherein the number of ops can be less
+ // than the size of the RA window.
+ while (num_ops) {
+ uint64_t source_offset;
+
+ int linear_blocks = PrepareNextReadAhead(&source_offset, &num_ops, blocks, xor_op_vec);
+ if (linear_blocks == 0) {
+ // No more blocks to read
+ SNAP_LOG(DEBUG) << " Read-ahead completed....";
+ break;
+ }
+
+ size_t io_size = (linear_blocks * BLOCK_SZ);
+
+ // Read from the base device consecutive set of blocks in one shot
+ if (!android::base::ReadFullyAtOffset(backing_store_fd_,
+ (char*)ra_temp_buffer.get() + buffer_offset, io_size,
+ source_offset)) {
+ SNAP_PLOG(ERROR) << "Ordered-op failed. Read from backing store: "
+ << backing_store_device_ << "at block :" << source_offset / BLOCK_SZ
+ << " offset :" << source_offset % BLOCK_SZ
+ << " buffer_offset : " << buffer_offset << " io_size : " << io_size
+ << " buf-addr : " << read_ahead_buffer_;
+
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ buffer_offset += io_size;
+ total_blocks_merged += linear_blocks;
+ num_ops -= linear_blocks;
+ }
+
+ // Done with merging ordered ops
+ if (RAIterDone() && total_blocks_merged == 0) {
+ return true;
+ }
+
+ loff_t metadata_offset = 0;
+
+ auto ra_temp_meta_buffer = std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+
+ struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+ (char*)ra_temp_meta_buffer.get() + metadata_offset);
+
+ bm->new_block = 0;
+ bm->file_offset = 0;
+
+ loff_t file_offset = snapuserd_->GetBufferDataOffset();
+
+ loff_t offset = 0;
+ CHECK(blocks.size() == total_blocks_merged);
+
+ size_t xor_index = 0;
+ for (size_t block_index = 0; block_index < blocks.size(); block_index++) {
+ void* bufptr = static_cast<void*>((char*)ra_temp_buffer.get() + offset);
+ uint64_t new_block = blocks[block_index];
+
+ if (xor_index < xor_op_vec.size()) {
+ const CowOperation* xor_op = xor_op_vec[xor_index];
+
+ // Check if this block is an XOR op
+ if (xor_op->new_block == new_block) {
+ // Read the xor'ed data from COW
+ if (!reader_->ReadData(*xor_op, &bufsink_)) {
+ SNAP_LOG(ERROR)
+ << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ // Pointer to the data read from base device
+ uint8_t* buffer = reinterpret_cast<uint8_t*>(bufptr);
+ // Get the xor'ed data read from COW device
+ uint8_t* xor_data = reinterpret_cast<uint8_t*>(bufsink_.GetPayloadBufPtr());
+
+ // Retrieve the original data
+ for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) {
+ buffer[byte_offset] ^= xor_data[byte_offset];
+ }
+
+ // Move to next XOR op
+ xor_index += 1;
+ }
+ }
+
+ offset += BLOCK_SZ;
+ // Track the metadata blocks which are stored in scratch space
+ bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer.get() +
+ metadata_offset);
+
+ bm->new_block = new_block;
+ bm->file_offset = file_offset;
+
+ metadata_offset += sizeof(struct ScratchMetadata);
+ file_offset += BLOCK_SZ;
+ }
+
+ // Verify if all the xor blocks were scanned to retrieve the original data
+ CHECK(xor_index == xor_op_vec.size());
+
+ // 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*)ra_temp_meta_buffer.get() +
+ metadata_offset);
+ bm->new_block = 0;
+ bm->file_offset = 0;
+
+ // Wait for the merge to finish for the previous RA window. We shouldn't
+ // be touching the scratch space until merge is complete of previous RA
+ // window. If there is a crash during this time frame, merge should resume
+ // based on the contents of the scratch space.
+ if (!snapuserd_->WaitForMergeReady()) {
+ return false;
+ }
+
+ // Copy the data to scratch space
+ memcpy(metadata_buffer_, ra_temp_meta_buffer.get(), snapuserd_->GetBufferMetadataSize());
+ memcpy(read_ahead_buffer_, ra_temp_buffer.get(), total_blocks_merged * BLOCK_SZ);
+
+ offset = 0;
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ read_ahead_buffer_map.clear();
+
+ for (size_t block_index = 0; block_index < blocks.size(); block_index++) {
+ void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
+ uint64_t new_block = blocks[block_index];
+
+ read_ahead_buffer_map[new_block] = bufptr;
+ offset += BLOCK_SZ;
+ }
+
+ snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged);
+
+ // Flush the data only if we have a overlapping blocks in the region
+ // Notify the Merge thread to resume merging this window
+ if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
+ SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadAhead::RunThread() {
+ if (!InitializeFds()) {
+ return false;
+ }
+
+ InitializeBuffer();
+
+ if (!InitReader()) {
+ return false;
+ }
+
+ InitializeRAIter();
+
+ while (!RAIterDone()) {
+ if (!ReadAheadIOStart()) {
+ break;
+ }
+ }
+
+ CloseFds();
+ reader_->CloseCowFd();
+ SNAP_LOG(INFO) << " ReadAhead thread terminating....";
+ return true;
+}
+
+// Initialization
+bool ReadAhead::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;
+}
+
+bool ReadAhead::InitReader() {
+ reader_ = snapuserd_->CloneReaderForWorker();
+
+ if (!reader_->InitForMerge(std::move(cow_fd_))) {
+ return false;
+ }
+ return true;
+}
+
+void ReadAhead::InitializeRAIter() {
+ cowop_iter_ = reader_->GetMergeOpIter();
+}
+
+bool ReadAhead::RAIterDone() {
+ if (cowop_iter_->Done()) {
+ return true;
+ }
+
+ const CowOperation* cow_op = GetRAOpIter();
+
+ if (!IsOrderedOp(*cow_op)) {
+ return true;
+ }
+
+ return false;
+}
+
+void ReadAhead::RAIterNext() {
+ cowop_iter_->Next();
+}
+
+const CowOperation* ReadAhead::GetRAOpIter() {
+ const CowOperation* cow_op = &cowop_iter_->Get();
+ return cow_op;
+}
+
+void ReadAhead::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());
+ // For xor ops
+ bufsink_.Initialize(PAYLOAD_SIZE);
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
similarity index 66%
copy from fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
copy to fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index 2f87557..a4fd5a0 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -31,7 +31,6 @@
#include <android-base/scopeguard.h>
#include <fs_mgr/file_wait.h>
#include <snapuserd/snapuserd_client.h>
-#include "snapuserd.h"
#include "snapuserd_server.h"
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
@@ -45,26 +44,29 @@
using android::base::borrowed_fd;
using android::base::unique_fd;
-DaemonOperations SnapuserdServer::Resolveop(std::string& input) {
- if (input == "init") return DaemonOperations::INIT;
- if (input == "start") return DaemonOperations::START;
- if (input == "stop") return DaemonOperations::STOP;
- if (input == "query") return DaemonOperations::QUERY;
- if (input == "delete") return DaemonOperations::DELETE;
- if (input == "detach") return DaemonOperations::DETACH;
- if (input == "supports") return DaemonOperations::SUPPORTS;
+DaemonOps SnapuserServer::Resolveop(std::string& input) {
+ if (input == "init") return DaemonOps::INIT;
+ if (input == "start") return DaemonOps::START;
+ if (input == "stop") return DaemonOps::STOP;
+ if (input == "query") return DaemonOps::QUERY;
+ if (input == "delete") return DaemonOps::DELETE;
+ if (input == "detach") return DaemonOps::DETACH;
+ if (input == "supports") return DaemonOps::SUPPORTS;
+ if (input == "initiate_merge") return DaemonOps::INITIATE;
+ if (input == "merge_percent") return DaemonOps::PERCENTAGE;
+ if (input == "getstatus") return DaemonOps::GETSTATUS;
- return DaemonOperations::INVALID;
+ return DaemonOps::INVALID;
}
-SnapuserdServer::~SnapuserdServer() {
+SnapuserServer::~SnapuserServer() {
// Close any client sockets that were added via AcceptClient().
for (size_t i = 1; i < watched_fds_.size(); i++) {
close(watched_fds_[i].fd);
}
}
-std::string SnapuserdServer::GetDaemonStatus() {
+std::string SnapuserServer::GetDaemonStatus() {
std::string msg = "";
if (IsTerminating())
@@ -75,8 +77,8 @@
return msg;
}
-void SnapuserdServer::Parsemsg(std::string const& msg, const char delim,
- std::vector<std::string>& out) {
+void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
+ std::vector<std::string>& out) {
std::stringstream ss(msg);
std::string s;
@@ -85,15 +87,15 @@
}
}
-void SnapuserdServer::ShutdownThreads() {
- StopThreads();
+void SnapuserServer::ShutdownThreads() {
+ terminating_ = true;
JoinAllThreads();
}
-DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd)
+DmUserHandler::DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
: snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
-bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
+bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
if (ret < 0) {
PLOG(ERROR) << "Snapuserd:server: send() failed";
@@ -107,7 +109,7 @@
return true;
}
-bool SnapuserdServer::Recv(android::base::borrowed_fd fd, std::string* data) {
+bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
char msg[MAX_PACKET_SIZE];
ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
if (rv < 0) {
@@ -118,25 +120,25 @@
return true;
}
-bool SnapuserdServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
+bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
const char delim = ',';
std::vector<std::string> out;
Parsemsg(str, delim, out);
- DaemonOperations op = Resolveop(out[0]);
+ DaemonOps op = Resolveop(out[0]);
switch (op) {
- case DaemonOperations::INIT: {
+ case DaemonOps::INIT: {
// Message format:
- // init,<misc_name>,<cow_device_path>,<backing_device>
+ // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
//
// Reads the metadata and send the number of sectors
- if (out.size() != 4) {
+ if (out.size() != 5) {
LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
}
- auto handler = AddHandler(out[1], out[2], out[3]);
+ auto handler = AddHandler(out[1], out[2], out[3], out[4]);
if (!handler) {
return Sendmsg(fd, "fail");
}
@@ -144,7 +146,7 @@
auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
return Sendmsg(fd, retval);
}
- case DaemonOperations::START: {
+ case DaemonOps::START: {
// Message format:
// start,<misc_name>
//
@@ -169,7 +171,7 @@
}
return Sendmsg(fd, "success");
}
- case DaemonOperations::STOP: {
+ case DaemonOps::STOP: {
// Message format: stop
//
// Stop all the threads gracefully and then shutdown the
@@ -178,7 +180,7 @@
ShutdownThreads();
return true;
}
- case DaemonOperations::QUERY: {
+ case DaemonOps::QUERY: {
// Message format: query
//
// As part of transition, Second stage daemon will be
@@ -190,23 +192,41 @@
// be ready to receive control message.
return Sendmsg(fd, GetDaemonStatus());
}
- case DaemonOperations::DELETE: {
+ case DaemonOps::DELETE: {
// Message format:
// delete,<misc_name>
if (out.size() != 2) {
LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
}
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, out[1]);
+ if (iter == dm_users_.end()) {
+ // After merge is completed, we swap dm-user table with
+ // the underlying dm-linear base device. Hence, worker
+ // threads would have terminted and was removed from
+ // the list.
+ LOG(DEBUG) << "Could not find handler: " << out[1];
+ return Sendmsg(fd, "success");
+ }
+
+ if (!(*iter)->ThreadTerminated()) {
+ (*iter)->snapuserd()->NotifyIOTerminated();
+ }
+ }
if (!RemoveAndJoinHandler(out[1])) {
return Sendmsg(fd, "fail");
}
return Sendmsg(fd, "success");
}
- case DaemonOperations::DETACH: {
+ case DaemonOps::DETACH: {
+ std::lock_guard<std::mutex> lock(lock_);
+ TerminateMergeThreads(&lock);
terminating_ = true;
return true;
}
- case DaemonOperations::SUPPORTS: {
+ case DaemonOps::SUPPORTS: {
if (out.size() != 2) {
LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
@@ -216,6 +236,52 @@
}
return Sendmsg(fd, "fail");
}
+ case DaemonOps::INITIATE: {
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
+ return Sendmsg(fd, "fail");
+ }
+ if (out[0] == "initiate_merge") {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, out[1]);
+ if (iter == dm_users_.end()) {
+ LOG(ERROR) << "Could not find handler: " << out[1];
+ return Sendmsg(fd, "fail");
+ }
+
+ if (!StartMerge(*iter)) {
+ return Sendmsg(fd, "fail");
+ }
+
+ return Sendmsg(fd, "success");
+ }
+ return Sendmsg(fd, "fail");
+ }
+ case DaemonOps::PERCENTAGE: {
+ std::lock_guard<std::mutex> lock(lock_);
+ double percentage = GetMergePercentage(&lock);
+
+ return Sendmsg(fd, std::to_string(percentage));
+ }
+ case DaemonOps::GETSTATUS: {
+ // Message format:
+ // getstatus,<misc_name>
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+ return Sendmsg(fd, "snapshot-merge-failed");
+ }
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, out[1]);
+ if (iter == dm_users_.end()) {
+ LOG(ERROR) << "Could not find handler: " << out[1];
+ return Sendmsg(fd, "snapshot-merge-failed");
+ }
+
+ std::string merge_status = GetMergeStatus(*iter);
+ return Sendmsg(fd, merge_status);
+ }
+ }
default: {
LOG(ERROR) << "Received unknown message type from client";
Sendmsg(fd, "fail");
@@ -224,7 +290,7 @@
}
}
-void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
+void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
handler->snapuserd()->SetSocketPresent(is_socket_present_);
@@ -241,6 +307,8 @@
{
std::lock_guard<std::mutex> lock(lock_);
+ num_partitions_merge_complete_ += 1;
+ handler->SetThreadTerminated();
auto iter = FindHandler(&lock, handler->misc_name());
if (iter == dm_users_.end()) {
// RemoveAndJoinHandler() already removed us from the list, and is
@@ -265,10 +333,11 @@
// WaitForDelete() is called, the handler is either in the list, or
// it's not and its resources are guaranteed to be freed.
handler->FreeResources();
+ dm_users_.erase(iter);
}
}
-bool SnapuserdServer::Start(const std::string& socketname) {
+bool SnapuserServer::Start(const std::string& socketname) {
bool start_listening = true;
sockfd_.reset(android_get_control_socket(socketname.c_str()));
@@ -284,7 +353,7 @@
return StartWithSocket(start_listening);
}
-bool SnapuserdServer::StartWithSocket(bool start_listening) {
+bool SnapuserServer::StartWithSocket(bool start_listening) {
if (start_listening && listen(sockfd_.get(), 4) < 0) {
PLOG(ERROR) << "listen socket failed";
return false;
@@ -305,7 +374,7 @@
return true;
}
-bool SnapuserdServer::Run() {
+bool SnapuserServer::Run() {
LOG(INFO) << "Now listening on snapuserd socket";
while (!IsTerminating()) {
@@ -337,7 +406,7 @@
return true;
}
-void SnapuserdServer::JoinAllThreads() {
+void SnapuserServer::JoinAllThreads() {
// Acquire the thread list within the lock.
std::vector<std::shared_ptr<DmUserHandler>> dm_users;
{
@@ -352,14 +421,14 @@
}
}
-void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
+void SnapuserServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
struct pollfd p = {};
p.fd = fd.get();
p.events = events;
watched_fds_.emplace_back(std::move(p));
}
-void SnapuserdServer::AcceptClient() {
+void SnapuserServer::AcceptClient() {
int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << "accept4 failed";
@@ -369,7 +438,7 @@
AddWatchedFd(fd, POLLIN);
}
-bool SnapuserdServer::HandleClient(android::base::borrowed_fd fd, int revents) {
+bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
if (revents & POLLHUP) {
LOG(DEBUG) << "Snapuserd client disconnected";
return false;
@@ -386,16 +455,18 @@
return true;
}
-void SnapuserdServer::Interrupt() {
+void SnapuserServer::Interrupt() {
// Force close the socket so poll() fails.
sockfd_ = {};
SetTerminating();
}
-std::shared_ptr<DmUserHandler> SnapuserdServer::AddHandler(const std::string& misc_name,
- const std::string& cow_device_path,
- const std::string& backing_device) {
- auto snapuserd = std::make_shared<Snapuserd>(misc_name, cow_device_path, backing_device);
+std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& misc_name,
+ const std::string& cow_device_path,
+ const std::string& backing_device,
+ const std::string& base_path_merge) {
+ auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
+ base_path_merge);
if (!snapuserd->InitCowDevice()) {
LOG(ERROR) << "Failed to initialize Snapuserd";
return nullptr;
@@ -418,7 +489,7 @@
return handler;
}
-bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
+bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
if (handler->snapuserd()->IsAttached()) {
LOG(ERROR) << "Handler already attached";
return false;
@@ -426,12 +497,22 @@
handler->snapuserd()->AttachControlDevice();
- handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler));
+ handler->thread() = std::thread(std::bind(&SnapuserServer::RunThread, this, handler));
return true;
}
-auto SnapuserdServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
- const std::string& misc_name) -> HandlerList::iterator {
+bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
+ if (!handler->snapuserd()->IsAttached()) {
+ LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
+ return false;
+ }
+
+ handler->snapuserd()->InitiateMerge();
+ return true;
+}
+
+auto SnapuserServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+ const std::string& misc_name) -> HandlerList::iterator {
CHECK(proof_of_lock);
for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@@ -442,7 +523,51 @@
return dm_users_.end();
}
-bool SnapuserdServer::RemoveAndJoinHandler(const std::string& misc_name) {
+void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
+ CHECK(proof_of_lock);
+
+ for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+ if (!(*iter)->ThreadTerminated()) {
+ (*iter)->snapuserd()->NotifyIOTerminated();
+ }
+ }
+}
+
+std::string SnapuserServer::GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler) {
+ return handler->snapuserd()->GetMergeStatus();
+}
+
+double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
+ CHECK(proof_of_lock);
+ double percentage = 0.0;
+ int n = 0;
+
+ for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+ auto& th = (*iter)->thread();
+ if (th.joinable()) {
+ // Merge percentage by individual partitions wherein merge is still
+ // in-progress
+ percentage += (*iter)->snapuserd()->GetMergePercentage();
+ n += 1;
+ }
+ }
+
+ // Calculate final merge including those partitions where merge was already
+ // completed - num_partitions_merge_complete_ will track them when each
+ // thread exists in RunThread.
+ int total_partitions = n + num_partitions_merge_complete_;
+
+ if (total_partitions) {
+ percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
+ }
+
+ LOG(DEBUG) << "Merge %: " << percentage
+ << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
+ << " total_partitions: " << total_partitions << " n: " << n;
+ return percentage;
+}
+
+bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
std::shared_ptr<DmUserHandler> handler;
{
std::lock_guard<std::mutex> lock(lock_);
@@ -463,7 +588,7 @@
return true;
}
-bool SnapuserdServer::WaitForSocket() {
+bool SnapuserServer::WaitForSocket() {
auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
@@ -517,7 +642,7 @@
return Run();
}
-bool SnapuserdServer::RunForSocketHandoff() {
+bool SnapuserServer::RunForSocketHandoff() {
unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
if (proxy_fd < 0) {
PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
new file mode 100644
index 0000000..e93621c
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -0,0 +1,142 @@
+// Copyright (C) 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.
+
+#pragma once
+
+#include <poll.h>
+
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <future>
+#include <iostream>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+static constexpr uint32_t MAX_PACKET_SIZE = 512;
+
+enum class DaemonOps {
+ INIT,
+ START,
+ QUERY,
+ STOP,
+ DELETE,
+ DETACH,
+ SUPPORTS,
+ INITIATE,
+ PERCENTAGE,
+ GETSTATUS,
+ INVALID,
+};
+
+class DmUserHandler {
+ public:
+ explicit DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
+
+ void FreeResources() {
+ // Each worker thread holds a reference to snapuserd.
+ // Clear them so that all the resources
+ // held by snapuserd is released
+ if (snapuserd_) {
+ snapuserd_->FreeResources();
+ snapuserd_ = nullptr;
+ }
+ }
+ const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
+ std::thread& thread() { return thread_; }
+
+ const std::string& misc_name() const { return misc_name_; }
+ bool ThreadTerminated() { return thread_terminated_; }
+ void SetThreadTerminated() { thread_terminated_ = true; }
+
+ private:
+ std::thread thread_;
+ std::shared_ptr<SnapshotHandler> snapuserd_;
+ std::string misc_name_;
+ bool thread_terminated_ = false;
+};
+
+class SnapuserServer {
+ private:
+ android::base::unique_fd sockfd_;
+ bool terminating_;
+ volatile bool received_socket_signal_ = false;
+ std::vector<struct pollfd> watched_fds_;
+ bool is_socket_present_ = false;
+ int num_partitions_merge_complete_ = 0;
+
+ std::mutex lock_;
+
+ using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
+ HandlerList dm_users_;
+
+ void AddWatchedFd(android::base::borrowed_fd fd, int events);
+ void AcceptClient();
+ bool HandleClient(android::base::borrowed_fd fd, int revents);
+ bool Recv(android::base::borrowed_fd fd, std::string* data);
+ bool Sendmsg(android::base::borrowed_fd fd, const std::string& msg);
+ bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
+
+ void ShutdownThreads();
+ bool RemoveAndJoinHandler(const std::string& control_device);
+ DaemonOps Resolveop(std::string& input);
+ std::string GetDaemonStatus();
+ void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
+
+ bool IsTerminating() { return terminating_; }
+
+ void RunThread(std::shared_ptr<DmUserHandler> handler);
+ void JoinAllThreads();
+ bool StartWithSocket(bool start_listening);
+
+ // Find a DmUserHandler within a lock.
+ HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+ const std::string& misc_name);
+
+ double GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock);
+ void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
+
+ public:
+ SnapuserServer() { terminating_ = false; }
+ ~SnapuserServer();
+
+ bool Start(const std::string& socketname);
+ bool Run();
+ void Interrupt();
+ bool RunForSocketHandoff();
+ bool WaitForSocket();
+
+ std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
+ const std::string& cow_device_path,
+ const std::string& backing_device,
+ const std::string& base_path_merge);
+ bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
+ bool StartMerge(const std::shared_ptr<DmUserHandler>& handler);
+ std::string GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler);
+
+ void SetTerminating() { terminating_ = true; }
+ void ReceivedSocketSignal() { received_socket_signal_ = true; }
+};
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
new file mode 100644
index 0000000..6c91fde
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -0,0 +1,647 @@
+/*
+ * 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_core.h"
+
+/*
+ * Readahead is used to optimize the merge of COPY and XOR Ops.
+ *
+ * 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 |
+ * | |
+ * +-----------------------+
+ *
+ *
+ * * ===================================================================
+ *
+ * Example:
+ *
+ * We have 6 copy operations to be executed in OTA. 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
+ * +--------------+
+ * ...
+ * ...
+ *
+ * ====================================================================
+ *
+ *
+ * Read-ahead thread will process the COW Ops in fixed set. Consider
+ * the following example:
+ *
+ * +--------------------------+
+ * |op-1|op-2|op-3|....|op-510|
+ * +--------------------------+
+ *
+ * <------ One RA Block ------>
+ *
+ * RA thread will read 510 ordered COW ops at a time and will store
+ * the data in the scratch space.
+ *
+ * RA thread and Merge thread will go lock-step wherein RA thread
+ * will make sure that 510 COW operation data are read upfront
+ * and is in memory. Thus, when merge thread will pick up the data
+ * directly from memory and write it back to base device.
+ *
+ *
+ * +--------------------------+------------------------------------+
+ * |op-1|op-2|op-3|....|op-510|op-511|op-512|op-513........|op-1020|
+ * +--------------------------+------------------------------------+
+ *
+ * <------Merge 510 Blocks----><-Prepare 510 blocks for merge by RA->
+ * ^ ^
+ * | |
+ * Merge thread RA thread
+ *
+ * Both Merge and RA thread will strive to work in parallel.
+ *
+ * ===========================================================================
+ *
+ * State transitions and communication between RA thread and Merge thread:
+ *
+ * Merge Thread RA Thread
+ * ----------------------------------------------------------------------------
+ *
+ * | |
+ * WAIT for RA Block N READ one RA Block (N)
+ * for merge |
+ * | |
+ * | |
+ * <--------------MERGE BEGIN--------READ Block N done(copy to scratch)
+ * | |
+ * | |
+ * Merge Begin Block N READ one RA BLock (N+1)
+ * | |
+ * | |
+ * | READ done. Wait for merge complete
+ * | |
+ * | WAIT
+ * | |
+ * Merge done Block N |
+ * ----------------MERGE READY-------------->|
+ * WAIT for RA Block N+1 Copy RA Block (N+1)
+ * for merge to scratch space
+ * | |
+ * <---------------MERGE BEGIN---------BLOCK N+1 Done
+ * | |
+ * | |
+ * Merge Begin Block N+1 READ one RA BLock (N+2)
+ * | |
+ * | |
+ * | READ done. Wait for merge complete
+ * | |
+ * | WAIT
+ * | |
+ * Merge done Block N+1 |
+ * ----------------MERGE READY-------------->|
+ * WAIT for RA Block N+2 Copy RA Block (N+2)
+ * for merge to scratch space
+ * | |
+ * <---------------MERGE BEGIN---------BLOCK N+2 Done
+ */
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+// This is invoked once primarily by update-engine to initiate
+// the merge
+void SnapshotHandler::InitiateMerge() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ merge_initiated_ = true;
+
+ // If there are only REPLACE ops to be merged, then we need
+ // to explicitly set the state to MERGE_BEGIN as there
+ // is no read-ahead thread
+ if (!ra_thread_) {
+ io_state_ = MERGE_IO_TRANSITION::MERGE_BEGIN;
+ }
+ }
+ cv.notify_all();
+}
+
+// Invoked by Merge thread - Waits on RA thread to resume merging. Will
+// be waken up RA thread.
+bool SnapshotHandler::WaitForMergeBegin() {
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!MergeInitiated()) {
+ cv.wait(lock);
+
+ if (io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+ return false;
+ }
+ }
+
+ while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_BEGIN ||
+ io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+ cv.wait(lock);
+ }
+
+ if (io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+// Invoked by RA thread - Flushes the RA block to scratch space if necessary
+// and then notifies the merge thread to resume merging
+bool SnapshotHandler::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 merge thread to resume merging
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ if (io_state_ != MERGE_IO_TRANSITION::IO_TERMINATED &&
+ io_state_ != MERGE_IO_TRANSITION::MERGE_FAILED) {
+ io_state_ = MERGE_IO_TRANSITION::MERGE_BEGIN;
+ }
+ }
+
+ cv.notify_all();
+ return true;
+}
+
+// Invoked by RA thread - Waits for merge thread to finish merging
+// RA Block N - RA thread would be ready will with Block N+1 but
+// will wait to merge thread to finish Block N. Once Block N
+// is merged, RA thread will be woken up by Merge thread and will
+// flush the data of Block N+1 to scratch space
+bool SnapshotHandler::WaitForMergeReady() {
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_READY ||
+ io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+ io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+ cv.wait(lock);
+ }
+
+ // Check if merge failed
+ if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+ io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+ return false;
+ }
+ return true;
+ }
+}
+
+// Invoked by Merge thread - Notify RA thread about Merge completion
+// for Block N and wake up
+void SnapshotHandler::NotifyRAForMergeReady() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ if (io_state_ != MERGE_IO_TRANSITION::IO_TERMINATED &&
+ io_state_ != MERGE_IO_TRANSITION::READ_AHEAD_FAILURE) {
+ io_state_ = MERGE_IO_TRANSITION::MERGE_READY;
+ }
+ }
+
+ cv.notify_all();
+}
+
+// The following transitions are mostly in the failure paths
+void SnapshotHandler::MergeFailed() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = MERGE_IO_TRANSITION::MERGE_FAILED;
+ }
+
+ cv.notify_all();
+}
+
+void SnapshotHandler::MergeCompleted() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = MERGE_IO_TRANSITION::MERGE_COMPLETE;
+ }
+
+ cv.notify_all();
+}
+
+// This is invoked by worker threads.
+//
+// Worker threads are terminated either by two scenarios:
+//
+// 1: If dm-user device is destroyed
+// 2: We had an I/O failure when reading root partitions
+//
+// In case (1), this would be a graceful shutdown. In this case, merge
+// thread and RA thread should have _already_ terminated by this point. We will be
+// destroying the dm-user device only _after_ merge is completed.
+//
+// In case (2), if merge thread had started, then it will be
+// continuing to merge; however, since we had an I/O failure and the
+// I/O on root partitions are no longer served, we will terminate the
+// merge.
+//
+// This functions is about handling case (2)
+void SnapshotHandler::NotifyIOTerminated() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = MERGE_IO_TRANSITION::IO_TERMINATED;
+ }
+
+ cv.notify_all();
+}
+
+bool SnapshotHandler::IsIOTerminated() {
+ std::lock_guard<std::mutex> lock(lock_);
+ return (io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED);
+}
+
+// Invoked by RA thread
+void SnapshotHandler::ReadAheadIOFailed() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = MERGE_IO_TRANSITION::READ_AHEAD_FAILURE;
+ }
+
+ cv.notify_all();
+}
+
+void SnapshotHandler::WaitForMergeComplete() {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+ io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+ cv.wait(lock);
+ }
+}
+
+std::string SnapshotHandler::GetMergeStatus() {
+ bool merge_not_initiated = false;
+ bool merge_failed = false;
+
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ if (!MergeInitiated()) {
+ merge_not_initiated = true;
+ }
+
+ if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED) {
+ merge_failed = true;
+ }
+ }
+
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ bool merge_complete = (ch->num_merge_ops == reader_->get_num_total_data_ops());
+
+ if (merge_not_initiated) {
+ // Merge was not initiated yet; however, we have merge completion
+ // recorded in the COW Header. This can happen if the device was
+ // rebooted during merge. During next reboot, libsnapshot will
+ // query the status and if the merge is completed, then snapshot-status
+ // file will be deleted
+ if (merge_complete) {
+ return "snapshot-merge-complete";
+ }
+
+ // Return the state as "snapshot". If the device was rebooted during
+ // merge, we will return the status as "snapshot". This is ok, as
+ // libsnapshot will explicitly resume the merge. This is slightly
+ // different from kernel snapshot wherein once the snapshot was switched
+ // to merge target, during next boot, we immediately switch to merge
+ // target. We don't do that here because, during first stage init, we
+ // don't want to initiate the merge. The problem is that we have daemon
+ // transition between first and second stage init. If the merge was
+ // started, then we will have to quiesce the merge before switching
+ // the dm tables. Instead, we just wait until second stage daemon is up
+ // before resuming the merge.
+ return "snapshot";
+ }
+
+ if (merge_failed) {
+ return "snapshot-merge-failed";
+ }
+
+ // Merge complete
+ if (merge_complete) {
+ return "snapshot-merge-complete";
+ }
+
+ // Merge is in-progress
+ return "snapshot-merge";
+}
+
+//========== End of Read-ahead state transition functions ====================
+
+/*
+ * Root partitions are mounted off dm-user and the I/O's are served
+ * by snapuserd worker threads.
+ *
+ * When there is an I/O request to be served by worker threads, we check
+ * if the corresponding sector is "changed" due to OTA by doing a lookup.
+ * If the lookup succeeds then the sector has been changed and that can
+ * either fall into 4 COW operations viz: COPY, XOR, REPLACE and ZERO.
+ *
+ * For the case of REPLACE and ZERO ops, there is not much of a concern
+ * as there is no dependency between blocks. Hence all the I/O request
+ * mapped to these two COW operations will be served by reading the COW device.
+ *
+ * However, COPY and XOR ops are tricky. Since the merge operations are
+ * in-progress, we cannot just go and read from the source device. We need
+ * to be in sync with the state of the merge thread before serving the I/O.
+ *
+ * Given that we know merge thread processes a set of COW ops called as RA
+ * Blocks - These set of COW ops are fixed size wherein each Block comprises
+ * of 510 COW ops.
+ *
+ * +--------------------------+
+ * |op-1|op-2|op-3|....|op-510|
+ * +--------------------------+
+ *
+ * <------ Merge Group Block N ------>
+ *
+ * Thus, a Merge Group Block N, will fall into one of these states and will
+ * transition the states in the following order:
+ *
+ * 1: GROUP_MERGE_PENDING
+ * 2: GROUP_MERGE_RA_READY
+ * 2: GROUP_MERGE_IN_PROGRESS
+ * 3: GROUP_MERGE_COMPLETED
+ * 4: GROUP_MERGE_FAILED
+ *
+ * Let's say that we have the I/O request from dm-user whose sector gets mapped
+ * to a COPY operation with op-10 in the above "Merge Group Block N".
+ *
+ * 1: If the Group is in "GROUP_MERGE_PENDING" state:
+ *
+ * Just read the data from source block based on COW op->source field. Note,
+ * that we will take a ref count on "Block N". This ref count will prevent
+ * merge thread to begin merging if there are any pending I/Os. Once the I/O
+ * is completed, ref count on "Group N" is decremented. Merge thread will
+ * resume merging "Group N" if there are no pending I/Os.
+ *
+ * 2: If the Group is in "GROUP_MERGE_IN_PROGRESS" or "GROUP_MERGE_RA_READY" state:
+ *
+ * When the merge thread is ready to process a "Group", it will first move
+ * the state to GROUP_MERGE_PENDING -> GROUP_MERGE_RA_READY. From this point
+ * onwards, I/O will be served from Read-ahead buffer. However, merge thread
+ * cannot start merging this "Group" immediately. If there were any in-flight
+ * I/O requests, merge thread should wait and allow those I/O's to drain.
+ * Once all the in-flight I/O's are completed, merge thread will move the
+ * state from "GROUP_MERGE_RA_READY" -> "GROUP_MERGE_IN_PROGRESS". I/O will
+ * be continued to serve from Read-ahead buffer during the entire duration
+ * of the merge.
+ *
+ * See SetMergeInProgress().
+ *
+ * 3: If the Group is in "GROUP_MERGE_COMPLETED" state:
+ *
+ * This is straightforward. We just read the data directly from "Base"
+ * device. We should not be reading the COW op->source field.
+ *
+ * 4: If the Block is in "GROUP_MERGE_FAILED" state:
+ *
+ * Terminate the I/O with an I/O error as we don't know which "op" in the
+ * "Group" failed.
+ *
+ * Transition ensures that the I/O from root partitions are never made to
+ * wait and are processed immediately. Thus the state transition for any
+ * "Group" is:
+ *
+ * GROUP_MERGE_PENDING
+ * |
+ * |
+ * v
+ * GROUP_MERGE_RA_READY
+ * |
+ * |
+ * v
+ * GROUP_MERGE_IN_PROGRESS
+ * |
+ * |----------------------------(on failure)
+ * | |
+ * v v
+ * GROUP_MERGE_COMPLETED GROUP_MERGE_FAILED
+ *
+ */
+
+// Invoked by Merge thread
+void SnapshotHandler::SetMergeCompleted(size_t ra_index) {
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::lock_guard<std::mutex> lock(blk_state->m_lock);
+
+ CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS);
+ CHECK(blk_state->num_ios_in_progress == 0);
+
+ // Merge is complete - All I/O henceforth should be read directly
+ // from base device
+ blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED;
+ }
+}
+
+// Invoked by Merge thread. This is called just before the beginning
+// of merging a given Block of 510 ops. If there are any in-flight I/O's
+// from dm-user then wait for them to complete.
+void SnapshotHandler::SetMergeInProgress(size_t ra_index) {
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+ CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
+
+ // First set the state to RA_READY so that in-flight I/O will drain
+ // and any new I/O will start reading from RA buffer
+ blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_RA_READY;
+
+ // Wait if there are any in-flight I/O's - we cannot merge at this point
+ while (!(blk_state->num_ios_in_progress == 0)) {
+ blk_state->m_cv.wait(lock);
+ }
+
+ blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS;
+ }
+}
+
+// Invoked by Merge thread on failure
+void SnapshotHandler::SetMergeFailed(size_t ra_index) {
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+ blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_FAILED;
+ }
+}
+
+// Invoked by worker threads when I/O is complete on a "MERGE_PENDING"
+// Block. If there are no more in-flight I/Os, wake up merge thread
+// to resume merging.
+void SnapshotHandler::NotifyIOCompletion(uint64_t new_block) {
+ auto it = block_to_ra_index_.find(new_block);
+ CHECK(it != block_to_ra_index_.end()) << " invalid block: " << new_block;
+
+ bool pending_ios = true;
+
+ int ra_index = it->second;
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+ CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
+ blk_state->num_ios_in_progress -= 1;
+ if (blk_state->num_ios_in_progress == 0) {
+ pending_ios = false;
+ }
+ }
+
+ // Give a chance to merge-thread to resume merge
+ // as there are no pending I/O.
+ if (!pending_ios) {
+ blk_state->m_cv.notify_all();
+ }
+}
+
+bool SnapshotHandler::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);
+
+ if (it == read_ahead_buffer_map_.end()) {
+ SNAP_LOG(ERROR) << "Block: " << block << " not found in RA buffer";
+ return false;
+ }
+
+ memcpy(buffer, it->second, BLOCK_SZ);
+ return true;
+}
+
+// Invoked by worker threads in the I/O path. This is called when a sector
+// is mapped to a COPY/XOR COW op.
+MERGE_GROUP_STATE SnapshotHandler::ProcessMergingBlock(uint64_t new_block, void* buffer) {
+ auto it = block_to_ra_index_.find(new_block);
+ if (it == block_to_ra_index_.end()) {
+ return MERGE_GROUP_STATE::GROUP_INVALID;
+ }
+
+ int ra_index = it->second;
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+ MERGE_GROUP_STATE state = blk_state->merge_state_;
+ switch (state) {
+ case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
+ blk_state->num_ios_in_progress += 1; // ref count
+ [[fallthrough]];
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED: {
+ [[fallthrough]];
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_FAILED: {
+ return state;
+ }
+ // Fetch the data from RA buffer.
+ case MERGE_GROUP_STATE::GROUP_MERGE_RA_READY: {
+ [[fallthrough]];
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS: {
+ if (!GetRABuffer(&lock, new_block, buffer)) {
+ return MERGE_GROUP_STATE::GROUP_INVALID;
+ }
+ return state;
+ }
+ default: {
+ return MERGE_GROUP_STATE::GROUP_INVALID;
+ }
+ }
+ }
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 94e1abb..d631d7a 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -193,13 +193,11 @@
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 &&
@@ -488,18 +486,16 @@
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
std::string fstab_contents = R"fs(
-source none0 swap defaults encryptable,forceencrypt,fileencryption,forcefdeorfbe,keydirectory,length,swapprio,zramsize,max_comp_streams,reservedsize,eraseblk,logicalblk,sysfs_path,zram_backingdev_size
+source none0 swap defaults fileencryption,keydirectory,length,swapprio,zramsize,max_comp_streams,reservedsize,eraseblk,logicalblk,sysfs_path,zram_backingdev_size
-source none1 swap defaults encryptable=,forceencrypt=,fileencryption=,keydirectory=,length=,swapprio=,zramsize=,max_comp_streams=,avb=,reservedsize=,eraseblk=,logicalblk=,sysfs_path=,zram_backingdev_size=
-
-source none2 swap defaults forcefdeorfbe=
+source none1 swap defaults fileencryption=,keydirectory=,length=,swapprio=,zramsize=,max_comp_streams=,avb=,reservedsize=,eraseblk=,logicalblk=,sysfs_path=,zram_backingdev_size=
)fs";
ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_LE(3U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -507,7 +503,6 @@
FstabEntry::FsMgrFlags flags = {};
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- EXPECT_EQ("", entry->key_loc);
EXPECT_EQ("", entry->metadata_key_dir);
EXPECT_EQ(0, entry->length);
EXPECT_EQ("", entry->label);
@@ -526,13 +521,10 @@
EXPECT_EQ("none1", entry->mount_point);
{
FstabEntry::FsMgrFlags flags = {};
- flags.crypt = true;
- flags.force_crypt = true;
flags.file_encryption = true;
flags.avb = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- EXPECT_EQ("", entry->key_loc);
EXPECT_EQ("", entry->metadata_key_dir);
EXPECT_EQ(0, entry->length);
EXPECT_EQ("", entry->label);
@@ -546,24 +538,26 @@
EXPECT_EQ(0, entry->logical_blk_size);
EXPECT_EQ("", entry->sysfs_path);
EXPECT_EQ(0U, entry->zram_backingdev_size);
- entry++;
-
- // forcefdeorfbe has its own encryption_options defaults, so test it separately.
- EXPECT_EQ("none2", entry->mount_point);
- {
- FstabEntry::FsMgrFlags flags = {};
- flags.force_fde_or_fbe = true;
- EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
- }
- EXPECT_EQ("aes-256-xts:aes-256-cts", entry->encryption_options);
- EXPECT_EQ("", entry->key_loc);
}
-TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Encryptable) {
+// FDE is no longer supported, so an fstab with FDE enabled should be rejected.
+TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FDE) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
std::string fstab_contents = R"fs(
-source none0 swap defaults encryptable=/dir/key
+source /data ext4 noatime forceencrypt=footer
+)fs";
+ ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+ Fstab fstab;
+ EXPECT_FALSE(ReadFstabFromFile(tf.path, &fstab));
+}
+
+TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_AdoptableStorage) {
+ TemporaryFile tf;
+ ASSERT_TRUE(tf.fd != -1);
+ std::string fstab_contents = R"fs(
+source none0 swap defaults encryptable=userdata,voldmanaged=sdcard:auto
)fs";
ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
@@ -573,11 +567,11 @@
FstabEntry::FsMgrFlags flags = {};
flags.crypt = true;
+ flags.vold_managed = true;
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
- EXPECT_EQ("/dir/key", entry->key_loc);
}
TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_VoldManaged) {
@@ -725,53 +719,6 @@
EXPECT_EQ(0, entry->zram_size);
}
-TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ForceEncrypt) {
- TemporaryFile tf;
- ASSERT_TRUE(tf.fd != -1);
- std::string fstab_contents = R"fs(
-source none0 swap defaults forceencrypt=/dir/key
-)fs";
-
- ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
-
- Fstab fstab;
- EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_LE(1U, fstab.size());
-
- auto entry = fstab.begin();
- EXPECT_EQ("none0", entry->mount_point);
-
- FstabEntry::FsMgrFlags flags = {};
- flags.force_crypt = true;
- EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
-
- EXPECT_EQ("/dir/key", entry->key_loc);
-}
-
-TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ForceFdeOrFbe) {
- TemporaryFile tf;
- ASSERT_TRUE(tf.fd != -1);
- std::string fstab_contents = R"fs(
-source none0 swap defaults forcefdeorfbe=/dir/key
-)fs";
-
- ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
-
- Fstab fstab;
- EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_LE(1U, fstab.size());
-
- auto entry = fstab.begin();
- EXPECT_EQ("none0", entry->mount_point);
-
- FstabEntry::FsMgrFlags flags = {};
- flags.force_fde_or_fbe = true;
- EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
-
- EXPECT_EQ("/dir/key", entry->key_loc);
- EXPECT_EQ("aes-256-xts:aes-256-cts", entry->encryption_options);
-}
-
TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FileEncryption) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
diff --git a/healthd/Android.bp b/healthd/Android.bp
index ec47f68..eaa8e5b 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -106,6 +106,7 @@
cc_library_static {
name: "libhealthd_draw",
+ vendor_available: true,
export_include_dirs: ["."],
static_libs: [
"libcharger_sysprop",
@@ -117,24 +118,34 @@
header_libs: ["libbatteryservice_headers"],
srcs: ["healthd_draw.cpp"],
+
+ target: {
+ vendor: {
+ exclude_static_libs: [
+ "libcharger_sysprop",
+ ],
+ },
+ },
}
cc_library_static {
- name: "libhealthd_charger",
- local_include_dirs: ["include"],
- export_include_dirs: [".", "include"],
+ name: "libhealthd_charger_ui",
+ vendor_available: true,
+ export_include_dirs: [
+ "include",
+ "include_charger",
+ ],
static_libs: [
- "android.hardware.health@1.0-convert",
+ "android.hardware.health-V1-ndk",
+ "android.hardware.health-translate-ndk",
"libcharger_sysprop",
"libhealthd_draw",
"libhealthloop",
- "libhealth2impl",
"libminui",
],
shared_libs: [
- "android.hardware.health@2.1",
"libbase",
"libcutils",
"liblog",
@@ -143,14 +154,60 @@
"libutils",
],
+ header_libs: [
+ "libhealthd_headers",
+ ],
+
+ export_static_lib_headers: [
+ "android.hardware.health-V1-ndk",
+ ],
+
srcs: [
"healthd_mode_charger.cpp",
"AnimationParser.cpp",
],
+
+ target: {
+ vendor: {
+ exclude_static_libs: [
+ "libcharger_sysprop",
+ ],
+ },
+ },
+}
+
+cc_library_static {
+ name: "libhealthd_charger",
+ export_include_dirs: [
+ "include",
+ "include_charger",
+ ],
+
+ static_libs: [
+ "android.hardware.health@1.0-convert",
+ "libcharger_sysprop",
+ "libhealth2impl",
+ "libhealthd_charger_ui",
+ ],
+
+ shared_libs: [
+ "android.hardware.health@2.1",
+ "libbase",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+
+ srcs: [
+ "healthd_mode_charger_hidl.cpp",
+ ],
}
cc_defaults {
name: "charger_defaults",
+ local_include_dirs: [
+ "include_charger",
+ ],
cflags: [
"-Wall",
@@ -174,6 +231,7 @@
static_libs: [
// common
"android.hardware.health@1.0-convert",
+ "android.hardware.health-V1-ndk",
"libbatterymonitor",
"libcharger_sysprop",
"libhealthd_charger_nops",
@@ -183,6 +241,7 @@
// system charger only
"libhealthd_draw",
"libhealthd_charger",
+ "libhealthd_charger_ui",
"libminui",
"libsuspend",
],
@@ -209,6 +268,7 @@
exclude_static_libs: [
"libhealthd_draw",
"libhealthd_charger",
+ "libhealthd_charger_ui",
"libminui",
"libsuspend",
],
@@ -265,3 +325,29 @@
"system_core_charger_res_images_battery_scale.png",
],
}
+
+// /vendor/etc/res/images/charger/battery_fail.png
+prebuilt_etc {
+ name: "system_core_charger_res_images_battery_fail.png_default_vendor",
+ src: "images/battery_fail.png",
+ relative_install_path: "res/images/charger/default",
+ vendor: true,
+ filename: "battery_fail.png",
+}
+
+// /vendor/etc/res/images/charger/battery_scale.png
+prebuilt_etc {
+ name: "system_core_charger_res_images_battery_scale.png_default_vendor",
+ src: "images/battery_scale.png",
+ relative_install_path: "res/images/charger/default",
+ vendor: true,
+ filename: "battery_scale.png",
+}
+
+phony {
+ name: "charger_res_images_vendor",
+ required: [
+ "system_core_charger_res_images_battery_fail.png_default_vendor",
+ "system_core_charger_res_images_battery_scale.png_default_vendor",
+ ],
+}
diff --git a/healthd/charger.cpp b/healthd/charger.cpp
index d03978d..73e04fe 100644
--- a/healthd/charger.cpp
+++ b/healthd/charger.cpp
@@ -15,9 +15,9 @@
*/
#include <android-base/logging.h>
+#include <charger.sysprop.h>
-#include "charger.sysprop.h"
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
#include "healthd_mode_charger_nops.h"
#ifndef CHARGER_FORCE_NO_UI
diff --git a/healthd/charger_test.cpp b/healthd/charger_test.cpp
index e0bde68..dc5c459 100644
--- a/healthd/charger_test.cpp
+++ b/healthd/charger_test.cpp
@@ -31,7 +31,7 @@
#include <health/utils.h>
#include <health2impl/Health.h>
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
using android::hardware::health::InitHealthdConfig;
using android::hardware::health::V2_1::HealthInfo;
@@ -153,7 +153,7 @@
sp<IHealth> passthrough = new TestHealth(std::move(config));
std::thread bgThread([=] {
- android::Charger charger(passthrough);
+ android::ChargerHidl charger(passthrough);
charger.StartLoop();
});
diff --git a/healthd/healthd_draw.cpp b/healthd/healthd_draw.cpp
index 9a47f6b..4484fa6 100644
--- a/healthd/healthd_draw.cpp
+++ b/healthd/healthd_draw.cpp
@@ -18,19 +18,30 @@
#include <batteryservice/BatteryService.h>
#include <cutils/klog.h>
-#include "charger.sysprop.h"
#include "healthd_draw.h"
+#if !defined(__ANDROID_VNDK__)
+#include "charger.sysprop.h"
+#endif
+
#define LOGE(x...) KLOG_ERROR("charger", x);
#define LOGW(x...) KLOG_WARNING("charger", x);
#define LOGV(x...) KLOG_DEBUG("charger", x);
static bool get_split_screen() {
+#if !defined(__ANDROID_VNDK__)
return android::sysprop::ChargerProperties::draw_split_screen().value_or(false);
+#else
+ return false;
+#endif
}
static int get_split_offset() {
+#if !defined(__ANDROID_VNDK__)
int64_t value = android::sysprop::ChargerProperties::draw_split_offset().value_or(0);
+#else
+ int64_t value = 0;
+#endif
if (value < static_cast<int64_t>(std::numeric_limits<int>::min())) {
LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value,
std::numeric_limits<int>::min());
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index 3ea90b0..0f9779c 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "healthd_mode_charger.h"
+#include <charger/healthd_mode_charger.h>
#include <dirent.h>
#include <errno.h>
@@ -50,27 +50,20 @@
#include <suspend/autosuspend.h>
#include "AnimationParser.h"
-#include "charger.sysprop.h"
-#include "charger_utils.h"
#include "healthd_draw.h"
-#include <android/hardware/health/2.0/IHealthInfoCallback.h>
-#include <health/utils.h>
-#include <health2impl/HalHealthLoop.h>
-#include <health2impl/Health.h>
+#include <aidl/android/hardware/health/BatteryStatus.h>
+#include <health/HealthLoop.h>
#include <healthd/healthd.h>
+#if !defined(__ANDROID_VNDK__)
+#include "charger.sysprop.h"
+#endif
+
using std::string_literals::operator""s;
using namespace android;
-using android::hardware::Return;
-using android::hardware::health::GetHealthServiceOrDefault;
+using aidl::android::hardware::health::BatteryStatus;
using android::hardware::health::HealthLoop;
-using android::hardware::health::V1_0::BatteryStatus;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_1::IHealth;
-using IHealth_2_0 = android::hardware::health::V2_0::IHealth;
-using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
-using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
// main healthd loop
extern int healthd_main(void);
@@ -106,6 +99,13 @@
namespace android {
+#if defined(__ANDROID_VNDK__)
+static constexpr const char* vendor_animation_desc_path =
+ "/vendor/etc/res/values/charger/animation.txt";
+static constexpr const char* vendor_animation_root = "/vendor/etc/res/images/";
+static constexpr const char* vendor_default_animation_root = "/vendor/etc/res/images/default/";
+#else
+
// Legacy animation resources are loaded from this directory.
static constexpr const char* legacy_animation_root = "/res/images/";
@@ -119,6 +119,7 @@
"/product/etc/res/values/charger/animation.txt";
static constexpr const char* product_animation_root = "/product/etc/res/images/";
static constexpr const char* animation_desc_path = "/res/values/charger/animation.txt";
+#endif
static const animation BASE_ANIMATION = {
.text_clock =
@@ -199,8 +200,8 @@
};
}
-Charger::Charger(const sp<IHealth>& service)
- : HalHealthLoop("charger", service), batt_anim_(BASE_ANIMATION) {}
+Charger::Charger(ChargerConfigurationInterface* configuration)
+ : batt_anim_(BASE_ANIMATION), configuration_(configuration) {}
Charger::~Charger() {}
@@ -264,15 +265,18 @@
LOGW("************* END LAST KMSG *************\n");
}
-static int request_suspend(bool enable) {
- if (!android::sysprop::ChargerProperties::enable_suspend().value_or(false)) {
+int Charger::RequestEnableSuspend() {
+ if (!configuration_->ChargerEnableSuspend()) {
return 0;
}
+ return autosuspend_enable();
+}
- if (enable)
- return autosuspend_enable();
- else
- return autosuspend_disable();
+int Charger::RequestDisableSuspend() {
+ if (!configuration_->ChargerEnableSuspend()) {
+ return 0;
+ }
+ return autosuspend_disable();
}
static void kick_animation(animation* anim) {
@@ -291,7 +295,7 @@
if (!batt_anim_.run || now < next_screen_transition_) return;
// If battery level is not ready, keep checking in the defined time
- if (health_info_.batteryLevel == 0 && health_info_.batteryStatus == BatteryStatus::UNKNOWN) {
+ if (health_info_.battery_level == 0 && health_info_.battery_status == BatteryStatus::UNKNOWN) {
if (wait_batt_level_timestamp_ == 0) {
// Set max delay time and skip drawing screen
wait_batt_level_timestamp_ = now + MAX_BATT_LEVEL_WAIT_TIME;
@@ -305,18 +309,15 @@
}
if (healthd_draw_ == nullptr) {
- std::optional<bool> out_screen_on;
- service()->shouldKeepScreenOn([&](Result res, bool screen_on) {
- if (res == Result::SUCCESS) {
- *out_screen_on = screen_on;
- }
- });
+ std::optional<bool> out_screen_on = configuration_->ChargerShouldKeepScreenOn();
if (out_screen_on.has_value()) {
if (!*out_screen_on) {
LOGV("[%" PRId64 "] leave screen off\n", now);
batt_anim_.run = false;
next_screen_transition_ = -1;
- if (charger_online()) request_suspend(true);
+ if (configuration_->ChargerIsOnline()) {
+ RequestEnableSuspend();
+ }
return;
}
}
@@ -324,10 +325,12 @@
healthd_draw_ = HealthdDraw::Create(&batt_anim_);
if (healthd_draw_ == nullptr) return;
+#if !defined(__ANDROID_VNDK__)
if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
healthd_draw_->blank_screen(true);
screen_blanked_ = true;
}
+#endif
}
/* animation is over, blank screen and leave */
@@ -337,7 +340,9 @@
healthd_draw_->blank_screen(true);
screen_blanked_ = true;
LOGV("[%" PRId64 "] animation done\n", now);
- if (charger_online()) request_suspend(true);
+ if (configuration_->ChargerIsOnline()) {
+ RequestEnableSuspend();
+ }
return;
}
@@ -351,9 +356,9 @@
/* animation starting, set up the animation */
if (batt_anim_.cur_frame == 0) {
LOGV("[%" PRId64 "] animation starting\n", now);
- batt_anim_.cur_level = health_info_.batteryLevel;
- batt_anim_.cur_status = (int)health_info_.batteryStatus;
- if (health_info_.batteryLevel >= 0 && batt_anim_.num_frames != 0) {
+ batt_anim_.cur_level = health_info_.battery_level;
+ batt_anim_.cur_status = (int)health_info_.battery_status;
+ if (health_info_.battery_level >= 0 && batt_anim_.num_frames != 0) {
/* find first frame given current battery level */
for (int i = 0; i < batt_anim_.num_frames; i++) {
if (batt_anim_.cur_level >= batt_anim_.frames[i].min_level &&
@@ -363,7 +368,7 @@
}
}
- if (charger_online()) {
+ if (configuration_->ChargerIsOnline()) {
// repeat the first frame first_frame_repeats times
disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time *
batt_anim_.first_frame_repeats;
@@ -394,7 +399,7 @@
/* advance frame cntr to the next valid frame only if we are charging
* if necessary, advance cycle cntr, and reset frame cntr
*/
- if (charger_online()) {
+ if (configuration_->ChargerIsOnline()) {
batt_anim_.cur_frame++;
while (batt_anim_.cur_frame < batt_anim_.num_frames &&
@@ -492,13 +497,13 @@
* rather than on key release
*/
kick_animation(&batt_anim_);
- request_suspend(false);
+ RequestDisableSuspend();
}
} else {
/* if the power key got released, force screen state cycle */
if (key->pending) {
kick_animation(&batt_anim_);
- request_suspend(false);
+ RequestDisableSuspend();
}
}
}
@@ -516,8 +521,8 @@
int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME;
if (!have_battery_state_) return;
- if (!charger_online()) {
- request_suspend(false);
+ if (!configuration_->ChargerIsOnline()) {
+ RequestDisableSuspend();
if (next_pwr_check_ == -1) {
/* Last cycle would have stopped at the extreme top of battery-icon
* Need to show the correct level corresponding to capacity.
@@ -547,7 +552,7 @@
* Reset & kick animation to show complete animation cycles
* when charger connected again.
*/
- request_suspend(false);
+ RequestDisableSuspend();
next_screen_transition_ = now - 1;
reset_animation(&batt_anim_);
kick_animation(&batt_anim_);
@@ -557,7 +562,7 @@
}
}
-void Charger::Heartbeat() {
+void Charger::OnHeartbeat() {
// charger* charger = &charger_state;
int64_t now = curr_time_ms();
@@ -570,22 +575,18 @@
UpdateScreenState(now);
}
-void Charger::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {
- set_charger_online(health_info);
-
+void Charger::OnHealthInfoChanged(const ChargerHealthInfo& health_info) {
if (!have_battery_state_) {
have_battery_state_ = true;
next_screen_transition_ = curr_time_ms() - 1;
- request_suspend(false);
+ RequestDisableSuspend();
reset_animation(&batt_anim_);
kick_animation(&batt_anim_);
}
- health_info_ = health_info.legacy.legacy;
-
- AdjustWakealarmPeriods(charger_online());
+ health_info_ = health_info;
}
-int Charger::PrepareToWait(void) {
+int Charger::OnPrepareToWait(void) {
int64_t now = curr_time_ms();
int64_t next_event = INT64_MAX;
int64_t timeout;
@@ -626,6 +627,16 @@
bool parse_success;
std::string content;
+
+#if defined(__ANDROID_VNDK__)
+ if (base::ReadFileToString(vendor_animation_desc_path, &content)) {
+ parse_success = parse_animation_desc(content, &batt_anim_);
+ batt_anim_.set_resource_root(vendor_animation_root);
+ } else {
+ LOGW("Could not open animation description at %s\n", vendor_animation_desc_path);
+ parse_success = false;
+ }
+#else
if (base::ReadFileToString(product_animation_desc_path, &content)) {
parse_success = parse_animation_desc(content, &batt_anim_);
batt_anim_.set_resource_root(product_animation_root);
@@ -641,17 +652,26 @@
LOGW("Could not open animation description at %s\n", animation_desc_path);
parse_success = false;
}
+#endif
+
+#if defined(__ANDROID_VNDK__)
+ auto default_animation_root = vendor_default_animation_root;
+#else
+ auto default_animation_root = system_animation_root;
+#endif
if (!parse_success) {
- LOGW("Could not parse animation description. Using default animation.\n");
+ LOGW("Could not parse animation description. "
+ "Using default animation with resources at %s\n",
+ default_animation_root);
batt_anim_ = BASE_ANIMATION;
- batt_anim_.animation_file.assign(system_animation_root + "charger/battery_scale.png"s);
+ batt_anim_.animation_file.assign(default_animation_root + "charger/battery_scale.png"s);
InitDefaultAnimationFrames();
batt_anim_.frames = owned_frames_.data();
batt_anim_.num_frames = owned_frames_.size();
}
if (batt_anim_.fail_file.empty()) {
- batt_anim_.fail_file.assign(system_animation_root + "charger/battery_fail.png"s);
+ batt_anim_.fail_file.assign(default_animation_root + "charger/battery_fail.png"s);
}
LOGV("Animation Description:\n");
@@ -672,7 +692,7 @@
}
}
-void Charger::Init(struct healthd_config* config) {
+void Charger::OnInit(struct healthd_config* config) {
int ret;
int i;
int epollfd;
@@ -685,16 +705,18 @@
std::bind(&Charger::InputCallback, this, std::placeholders::_1, std::placeholders::_2));
if (!ret) {
epollfd = ev_get_epollfd();
- RegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD);
+ configuration_->ChargerRegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD);
}
InitAnimation();
ret = CreateDisplaySurface(batt_anim_.fail_file, &surf_unknown_);
if (ret < 0) {
+#if !defined(__ANDROID_VNDK__)
LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret);
ret = CreateDisplaySurface((system_animation_root + "charger/battery_fail.png"s).c_str(),
&surf_unknown_);
+#endif
if (ret < 0) {
LOGE("Cannot load built in battery_fail image\n");
surf_unknown_ = NULL;
@@ -730,7 +752,7 @@
wait_batt_level_timestamp_ = 0;
// Retrieve healthd_config from the existing health HAL.
- HalHealthLoop::Init(config);
+ configuration_->ChargerInitConfig(config);
boot_min_cap_ = config->boot_min_cap;
}
@@ -773,25 +795,3 @@
}
} // namespace android
-
-int healthd_charger_main(int argc, char** argv) {
- int ch;
-
- while ((ch = getopt(argc, argv, "cr")) != -1) {
- switch (ch) {
- case 'c':
- // -c is now a noop
- break;
- case 'r':
- // -r is now a noop
- break;
- case '?':
- default:
- LOGE("Unrecognized charger option: %c\n", optopt);
- exit(1);
- }
- }
-
- Charger charger(GetHealthServiceOrDefault());
- return charger.StartLoop();
-}
diff --git a/healthd/healthd_mode_charger.h b/healthd/healthd_mode_charger.h
deleted file mode 100644
index 6f9ae8c..0000000
--- a/healthd/healthd_mode_charger.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2019 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 <linux/input.h>
-
-#include <memory>
-#include <vector>
-
-#include <android/hardware/health/2.0/IHealthInfoCallback.h>
-#include <android/hardware/health/2.1/IHealth.h>
-#include <health2impl/HalHealthLoop.h>
-
-#include "animation.h"
-
-class GRSurface;
-class HealthdDraw;
-
-namespace android {
-struct key_state {
- bool pending;
- bool down;
- int64_t timestamp;
-};
-
-class Charger : public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
- public:
- using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
- using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
-
- Charger(const sp<android::hardware::health::V2_1::IHealth>& service);
- ~Charger();
-
- protected:
- // HealthLoop overrides.
- void Heartbeat() override;
- int PrepareToWait() override;
- void Init(struct healthd_config* config) override;
- // HalHealthLoop overrides
- void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;
-
- // Allowed to be mocked for testing.
- virtual int CreateDisplaySurface(const std::string& name, GRSurface** surface);
- virtual int CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps,
- GRSurface*** surface);
-
- private:
- void InitDefaultAnimationFrames();
- void UpdateScreenState(int64_t now);
- int SetKeyCallback(int code, int value);
- void UpdateInputState(input_event* ev);
- void SetNextKeyCheck(key_state* key, int64_t timeout);
- void ProcessKey(int code, int64_t now);
- void HandleInputState(int64_t now);
- void HandlePowerSupplyState(int64_t now);
- int InputCallback(int fd, unsigned int epevents);
- void InitAnimation();
-
- bool have_battery_state_ = false;
- bool screen_blanked_ = false;
- int64_t next_screen_transition_ = 0;
- int64_t next_key_check_ = 0;
- int64_t next_pwr_check_ = 0;
- int64_t wait_batt_level_timestamp_ = 0;
-
- key_state keys_[KEY_MAX + 1] = {};
-
- animation batt_anim_;
- GRSurface* surf_unknown_ = nullptr;
- int boot_min_cap_ = 0;
-
- HealthInfo_1_0 health_info_ = {};
- std::unique_ptr<HealthdDraw> healthd_draw_;
- std::vector<animation::frame> owned_frames_;
-};
-} // namespace android
-
-int healthd_charger_main(int argc, char** argv);
diff --git a/healthd/healthd_mode_charger_hidl.cpp b/healthd/healthd_mode_charger_hidl.cpp
new file mode 100644
index 0000000..3a33c02
--- /dev/null
+++ b/healthd/healthd_mode_charger_hidl.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 "healthd_mode_charger_hidl.h"
+
+#include <android/hardware/health/2.0/types.h>
+#include <charger.sysprop.h>
+#include <cutils/klog.h>
+
+#include "charger_utils.h"
+
+using android::hardware::health::GetHealthServiceOrDefault;
+using android::hardware::health::V2_0::Result;
+
+namespace android {
+
+ChargerHidl::ChargerHidl(const sp<android::hardware::health::V2_1::IHealth>& service)
+ : HalHealthLoop("charger", service), charger_(std::make_unique<Charger>(this)) {}
+
+void ChargerHidl::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {
+ set_charger_online(health_info);
+
+ charger_->OnHealthInfoChanged(ChargerHealthInfo{
+ .battery_level = health_info.legacy.legacy.batteryLevel,
+ .battery_status = static_cast<::aidl::android::hardware::health::BatteryStatus>(
+ health_info.legacy.legacy.batteryStatus),
+ });
+
+ AdjustWakealarmPeriods(charger_online());
+}
+
+std::optional<bool> ChargerHidl::ChargerShouldKeepScreenOn() {
+ std::optional<bool> out_screen_on;
+ service()->shouldKeepScreenOn([&](Result res, bool screen_on) {
+ if (res == Result::SUCCESS) {
+ *out_screen_on = screen_on;
+ }
+ });
+ return out_screen_on;
+}
+
+bool ChargerHidl::ChargerEnableSuspend() {
+ return android::sysprop::ChargerProperties::enable_suspend().value_or(false);
+}
+
+} // namespace android
+
+int healthd_charger_main(int argc, char** argv) {
+ int ch;
+
+ while ((ch = getopt(argc, argv, "cr")) != -1) {
+ switch (ch) {
+ case 'c':
+ // -c is now a noop
+ break;
+ case 'r':
+ // -r is now a noop
+ break;
+ case '?':
+ default:
+ KLOG_ERROR("charger", "Unrecognized charger option: %c\n", optopt);
+ exit(1);
+ }
+ }
+
+ android::ChargerHidl charger(GetHealthServiceOrDefault());
+ return charger.StartLoop();
+}
diff --git a/healthd/healthd_mode_charger_hidl.h b/healthd/healthd_mode_charger_hidl.h
new file mode 100644
index 0000000..0149d07
--- /dev/null
+++ b/healthd/healthd_mode_charger_hidl.h
@@ -0,0 +1,55 @@
+/*
+ * 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 <health2impl/HalHealthLoop.h>
+
+#include <charger/healthd_mode_charger.h>
+
+namespace android {
+
+// An implementation of Charger backed by HIDL implementation. Uses HIDL health
+// HAL's HalHealthLoop.
+class ChargerHidl : public ::android::ChargerConfigurationInterface,
+ public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
+ using HalHealthLoop = ::android::hardware::health::V2_1::implementation::HalHealthLoop;
+ using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
+
+ public:
+ explicit ChargerHidl(const sp<android::hardware::health::V2_1::IHealth>& service);
+ std::optional<bool> ChargerShouldKeepScreenOn() override;
+ bool ChargerIsOnline() override { return HalHealthLoop::charger_online(); }
+ void ChargerInitConfig(healthd_config* config) override { return HalHealthLoop::Init(config); }
+ int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) override {
+ return HalHealthLoop::RegisterEvent(fd, func, wakeup);
+ }
+ bool ChargerEnableSuspend() override;
+ // HealthLoop overrides
+ void Heartbeat() override { charger_->OnHeartbeat(); }
+ int PrepareToWait() override { return charger_->OnPrepareToWait(); }
+ void Init(struct healthd_config* config) override { charger_->OnInit(config); }
+ // HalHealthLoop overrides
+ void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;
+
+ private:
+ sp<android::hardware::health::V2_1::IHealth> service_;
+ std::unique_ptr<Charger> charger_;
+};
+
+} // namespace android
+
+int healthd_charger_main(int argc, char** argv);
diff --git a/healthd/healthd_mode_charger_test.cpp b/healthd/healthd_mode_charger_test.cpp
index f444f66..b7aace3 100644
--- a/healthd/healthd_mode_charger_test.cpp
+++ b/healthd/healthd_mode_charger_test.cpp
@@ -23,11 +23,12 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
+#include <android/hardware/health/2.1/IHealth.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <health/utils.h>
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
using android::hardware::Return;
using android::hardware::health::InitHealthdConfig;
@@ -102,12 +103,12 @@
MOCK_METHOD(Return<void>, shouldKeepScreenOn, (shouldKeepScreenOn_cb _hidl_cb));
};
-class TestCharger : public Charger {
+class TestCharger : public ChargerHidl {
public:
// Inherit constructor.
- using Charger::Charger;
+ using ChargerHidl::ChargerHidl;
// Expose protected functions to be used in tests.
- void Init(struct healthd_config* config) override { Charger::Init(config); }
+ void Init(struct healthd_config* config) override { ChargerHidl::Init(config); }
MOCK_METHOD(int, CreateDisplaySurface, (const std::string& name, GRSurface** surface));
MOCK_METHOD(int, CreateMultiDisplaySurface,
(const std::string& name, int* frames, int* fps, GRSurface*** surface));
diff --git a/healthd/include_charger/charger/healthd_mode_charger.h b/healthd/include_charger/charger/healthd_mode_charger.h
new file mode 100644
index 0000000..216e5ad
--- /dev/null
+++ b/healthd/include_charger/charger/healthd_mode_charger.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 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 <linux/input.h>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include <aidl/android/hardware/health/BatteryStatus.h>
+#include <health/HealthLoop.h>
+#include <healthd/healthd.h>
+
+#include "animation.h"
+
+class GRSurface;
+class HealthdDraw;
+
+namespace android {
+struct key_state {
+ bool pending;
+ bool down;
+ int64_t timestamp;
+};
+
+// Health info that interests charger
+struct ChargerHealthInfo {
+ int32_t battery_level;
+ aidl::android::hardware::health::BatteryStatus battery_status;
+};
+
+// Configuration interface for charger. This includes:
+// - HalHealthLoop APIs that interests charger.
+// - configuration values that used to be provided by sysprops
+class ChargerConfigurationInterface {
+ public:
+ virtual ~ChargerConfigurationInterface() = default;
+ // HalHealthLoop related APIs
+ virtual std::optional<bool> ChargerShouldKeepScreenOn() = 0;
+ virtual bool ChargerIsOnline() = 0;
+ virtual void ChargerInitConfig(healthd_config* config) = 0;
+ using BoundFunction =
+ std::function<void(android::hardware::health::HealthLoop*, uint32_t /* epevents */)>;
+ virtual int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) = 0;
+
+ // Other configuration values
+ virtual bool ChargerEnableSuspend() = 0;
+};
+
+// charger UI
+class Charger {
+ public:
+ explicit Charger(ChargerConfigurationInterface* configuration);
+ virtual ~Charger();
+
+ // Hooks for ChargerConfigurationInterface
+ void OnHeartbeat();
+ int OnPrepareToWait();
+ // |cookie| is passed to ChargerConfigurationInterface::ChargerInitConfig
+ void OnInit(struct healthd_config* config);
+ void OnHealthInfoChanged(const ChargerHealthInfo& health_info);
+
+ protected:
+ // Allowed to be mocked for testing.
+ virtual int CreateDisplaySurface(const std::string& name, GRSurface** surface);
+ virtual int CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps,
+ GRSurface*** surface);
+
+ private:
+ void InitDefaultAnimationFrames();
+ void UpdateScreenState(int64_t now);
+ int SetKeyCallback(int code, int value);
+ void UpdateInputState(input_event* ev);
+ void SetNextKeyCheck(key_state* key, int64_t timeout);
+ void ProcessKey(int code, int64_t now);
+ void HandleInputState(int64_t now);
+ void HandlePowerSupplyState(int64_t now);
+ int InputCallback(int fd, unsigned int epevents);
+ void InitAnimation();
+ int RequestEnableSuspend();
+ int RequestDisableSuspend();
+
+ bool have_battery_state_ = false;
+ bool screen_blanked_ = false;
+ int64_t next_screen_transition_ = 0;
+ int64_t next_key_check_ = 0;
+ int64_t next_pwr_check_ = 0;
+ int64_t wait_batt_level_timestamp_ = 0;
+
+ key_state keys_[KEY_MAX + 1] = {};
+
+ animation batt_anim_;
+ GRSurface* surf_unknown_ = nullptr;
+ int boot_min_cap_ = 0;
+
+ ChargerHealthInfo health_info_ = {};
+ std::unique_ptr<HealthdDraw> healthd_draw_;
+ std::vector<animation::frame> owned_frames_;
+
+ ChargerConfigurationInterface* configuration_;
+};
+} // namespace android
diff --git a/init/Android.bp b/init/Android.bp
index 9b02c38..66427dc 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -448,6 +448,7 @@
srcs: [
"devices_test.cpp",
+ "epoll_test.cpp",
"firmware_handler_test.cpp",
"init_test.cpp",
"keychords_test.cpp",
diff --git a/init/README.md b/init/README.md
index 58a8d6b..ebab073 100644
--- a/init/README.md
+++ b/init/README.md
@@ -77,6 +77,43 @@
conflict resolution when multiple services are added to the system, as
each one will go into a separate file.
+Versioned RC files within APEXs
+-------------------------------
+
+With the arrival of mainline on Android Q, the individual mainline
+modules carry their own init.rc files within their boundaries. Init
+processes these files according to the naming pattern `/apex/*/etc/*rc`.
+
+Because APEX modules must run on more than one release of Android,
+they may require different parameters as part of the services they
+define. This is achieved, starting in Android T, by incorporating
+the SDK version information in the name of the init file. The suffix
+is changed from `.rc` to `.#rc` where # is the first SDK where that
+RC file is accepted. An init file specific to SDK=31 might be named
+`init.31rc`. With this scheme, an APEX may include multiple init files. An
+example is appropriate.
+
+For an APEX module with the following files in /apex/sample-module/apex/etc/:
+
+ 1. init.rc
+ 2. init.32rc
+ 4. init.35rc
+
+The selection rule chooses the highest `.#rc` value that does not
+exceed the SDK of the currently running system. The unadorned `.rc`
+is interpreted as sdk=0.
+
+When this APEX is installed on a device with SDK <=31, the system will
+process init.rc. When installed on a device running SDK 32, 33, or 34,
+it will use init.32rc. When installed on a device running SDKs >= 35,
+it will choose init.35rc
+
+This versioning scheme is used only for the init files within APEX
+modules; it does not apply to the init files stored in /system/etc/init,
+/vendor/etc/init, or other directories.
+
+This naming scheme is available after Android S.
+
Actions
-------
Actions are named sequences of commands. Actions have a trigger which
@@ -450,11 +487,6 @@
not already running. See the start entry for more information on
starting services.
-`class_start_post_data <serviceclass>`
-> Like `class_start`, but only considers services that were started
- after /data was mounted, and that were running at the time
- `class_reset_post_data` was called. Only used for FDE devices.
-
`class_stop <serviceclass>`
> Stop and disable all services of the specified class if they are
currently running.
@@ -464,12 +496,9 @@
currently running, without disabling them. They can be restarted
later using `class_start`.
-`class_reset_post_data <serviceclass>`
-> Like `class_reset`, but only considers services that were started
- after /data was mounted. Only used for FDE devices.
-
-`class_restart <serviceclass>`
-> Restarts all services of the specified class.
+`class_restart [--only-enabled] <serviceclass>`
+> Restarts all services of the specified class. If `--only-enabled` is
+ specified, then disabled services are skipped.
`copy <src> <dst>`
> Copies a file. Similar to write, but useful for binary/large
@@ -570,8 +599,7 @@
Properties are expanded within _level_.
`mark_post_data`
-> Used to mark the point right after /data is mounted. Used to implement the
- `class_reset_post_data` and `class_start_post_data` commands.
+> Used to mark the point right after /data is mounted.
`mkdir <path> [<mode>] [<owner>] [<group>] [encryption=<action>] [key=<key>]`
> Create a directory at _path_, optionally with the given mode, owner, and
@@ -696,7 +724,10 @@
`verity_update_state`
> Internal implementation detail used to update dm-verity state and
set the partition._mount-point_.verified properties used by adb remount
- because fs\_mgr can't set them directly itself.
+ because fs\_mgr can't set them directly itself. This is required since
+ Android 12, because CtsNativeVerifiedBootTestCases will read property
+ "partition.${partition}.verified.hash_alg" to check that sha1 is not used.
+ See https://r.android.com/1546980 for more details.
`wait <path> [ <timeout> ]`
> Poll for the existence of the given file and return when found,
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 994eed9..98831e1 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -28,6 +28,7 @@
#include <net/if.h>
#include <sched.h>
#include <signal.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -42,9 +43,9 @@
#include <sys/wait.h>
#include <unistd.h>
+#include <map>
#include <memory>
-#include <ApexProperties.sysprop.h>
#include <InitProperties.sysprop.h>
#include <android-base/chrono_utils.h>
#include <android-base/file.h>
@@ -175,28 +176,6 @@
return {};
}
-static Result<void> do_class_start_post_data(const BuiltinArguments& args) {
- if (args.context != kInitContext) {
- return Error() << "command 'class_start_post_data' only available in init context";
- }
- static bool is_apex_updatable = android::sysprop::ApexProperties::updatable().value_or(false);
-
- if (!is_apex_updatable) {
- // No need to start these on devices that don't support APEX, since they're not
- // stopped either.
- return {};
- }
- for (const auto& service : ServiceList::GetInstance()) {
- if (service->classnames().count(args[1])) {
- if (auto result = service->StartIfPostData(); !result.ok()) {
- LOG(ERROR) << "Could not start service '" << service->name()
- << "' as part of class '" << args[1] << "': " << result.error();
- }
- }
- }
- return {};
-}
-
static Result<void> do_class_stop(const BuiltinArguments& args) {
ForEachServiceInClass(args[1], &Service::Stop);
return {};
@@ -207,24 +186,35 @@
return {};
}
-static Result<void> do_class_reset_post_data(const BuiltinArguments& args) {
- if (args.context != kInitContext) {
- return Error() << "command 'class_reset_post_data' only available in init context";
- }
- static bool is_apex_updatable = android::sysprop::ApexProperties::updatable().value_or(false);
- if (!is_apex_updatable) {
- // No need to stop these on devices that don't support APEX.
- return {};
- }
- ForEachServiceInClass(args[1], &Service::ResetIfPostData);
- return {};
-}
-
static Result<void> do_class_restart(const BuiltinArguments& args) {
// Do not restart a class if it has a property persist.dont_start_class.CLASS set to 1.
if (android::base::GetBoolProperty("persist.init.dont_start_class." + args[1], false))
return {};
- ForEachServiceInClass(args[1], &Service::Restart);
+
+ std::string classname;
+
+ CHECK(args.size() == 2 || args.size() == 3);
+
+ bool only_enabled = false;
+ if (args.size() == 3) {
+ if (args[1] != "--only-enabled") {
+ return Error() << "Unexpected argument: " << args[1];
+ }
+ only_enabled = true;
+ classname = args[2];
+ } else if (args.size() == 2) {
+ classname = args[1];
+ }
+
+ for (const auto& service : ServiceList::GetInstance()) {
+ if (!service->classnames().count(classname)) {
+ continue;
+ }
+ if (only_enabled && !service->IsEnabled()) {
+ continue;
+ }
+ service->Restart();
+ }
return {};
}
@@ -584,32 +574,7 @@
* return code is processed based on input code
*/
static Result<void> queue_fs_event(int code, bool userdata_remount) {
- if (code == FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION) {
- if (userdata_remount) {
- // FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION should only happen on FDE devices. Since we don't
- // support userdata remount on FDE devices, this should never been triggered. Time to
- // panic!
- LOG(ERROR) << "Userdata remount is not supported on FDE devices. How did you get here?";
- trigger_shutdown("reboot,requested-userdata-remount-on-fde-device");
- }
- ActionManager::GetInstance().QueueEventTrigger("encrypt");
- return {};
- } else if (code == FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED) {
- if (userdata_remount) {
- // FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED should only happen on FDE devices. Since we
- // don't support userdata remount on FDE devices, this should never been triggered.
- // Time to panic!
- LOG(ERROR) << "Userdata remount is not supported on FDE devices. How did you get here?";
- trigger_shutdown("reboot,requested-userdata-remount-on-fde-device");
- }
- SetProperty("ro.crypto.state", "encrypted");
- ActionManager::GetInstance().QueueEventTrigger("defaultcrypto");
- return {};
- } else if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
- SetProperty("ro.crypto.state", "unencrypted");
- ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
- return {};
- } else if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
+ if (code == FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
SetProperty("ro.crypto.state", "unsupported");
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
return {};
@@ -1117,17 +1082,6 @@
}
static Result<void> do_load_persist_props(const BuiltinArguments& args) {
- // Devices with FDE have load_persist_props called twice; the first time when the temporary
- // /data partition is mounted and then again once /data is truly mounted. We do not want to
- // read persistent properties from the temporary /data partition or mark persistent properties
- // as having been loaded during the first call, so we return in that case.
- std::string crypto_state = android::base::GetProperty("ro.crypto.state", "");
- std::string crypto_type = android::base::GetProperty("ro.crypto.type", "");
- if (crypto_state == "encrypted" && crypto_type == "block") {
- static size_t num_calls = 0;
- if (++num_calls == 1) return {};
- }
-
SendLoadPersistentPropertiesMessage();
start_waiting_for_property("ro.persistent_properties.ready", "true");
@@ -1313,7 +1267,7 @@
static Result<void> parse_apex_configs() {
glob_t glob_result;
- static constexpr char glob_pattern[] = "/apex/*/etc/*.rc";
+ static constexpr char glob_pattern[] = "/apex/*/etc/*rc";
const int ret = glob(glob_pattern, GLOB_MARK, nullptr, &glob_result);
if (ret != 0 && ret != GLOB_NOMATCH) {
globfree(&glob_result);
@@ -1330,17 +1284,66 @@
if (paths.size() >= 3 && paths[2].find('@') != std::string::npos) {
continue;
}
+ // Filter directories
+ if (path.back() == '/') {
+ continue;
+ }
configs.push_back(path);
}
globfree(&glob_result);
- bool success = true;
+ // Compare all files /apex/path.#rc and /apex/path.rc with the same "/apex/path" prefix,
+ // choosing the one with the highest # that doesn't exceed the system's SDK.
+ // (.rc == .0rc for ranking purposes)
+ //
+ int active_sdk = android::base::GetIntProperty("ro.build.version.sdk", INT_MAX);
+
+ std::map<std::string, std::pair<std::string, int>> script_map;
+
for (const auto& c : configs) {
- if (c.back() == '/') {
- // skip if directory
+ int sdk = 0;
+ const std::vector<std::string> parts = android::base::Split(c, ".");
+ std::string base;
+ if (parts.size() < 2) {
continue;
}
- success &= parser.ParseConfigFile(c);
+
+ // parts[size()-1], aka the suffix, should be "rc" or "#rc"
+ // any other pattern gets discarded
+
+ const auto& suffix = parts[parts.size() - 1];
+ if (suffix == "rc") {
+ sdk = 0;
+ } else {
+ char trailer[9] = {0};
+ int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer);
+ if (r != 2) {
+ continue;
+ }
+ if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) {
+ continue;
+ }
+ }
+
+ if (sdk < 0 || sdk > active_sdk) {
+ continue;
+ }
+
+ base = parts[0];
+ for (unsigned int i = 1; i < parts.size() - 1; i++) {
+ base = base + "." + parts[i];
+ }
+
+ // is this preferred over what we already have
+ auto it = script_map.find(base);
+ if (it == script_map.end() || it->second.second < sdk) {
+ script_map[base] = std::make_pair(c, sdk);
+ }
+ }
+
+ bool success = true;
+ for (const auto& m : script_map) {
+ success &= parser.ParseConfigFile(m.second.first);
}
ServiceList::GetInstance().MarkServicesUpdate();
if (success) {
@@ -1413,10 +1416,8 @@
{"chmod", {2, 2, {true, do_chmod}}},
{"chown", {2, 3, {true, do_chown}}},
{"class_reset", {1, 1, {false, do_class_reset}}},
- {"class_reset_post_data", {1, 1, {false, do_class_reset_post_data}}},
- {"class_restart", {1, 1, {false, do_class_restart}}},
+ {"class_restart", {1, 2, {false, do_class_restart}}},
{"class_start", {1, 1, {false, do_class_start}}},
- {"class_start_post_data", {1, 1, {false, do_class_start_post_data}}},
{"class_stop", {1, 1, {false, do_class_stop}}},
{"copy", {2, 2, {true, do_copy}}},
{"copy_per_line", {2, 2, {true, do_copy_per_line}}},
diff --git a/init/epoll.cpp b/init/epoll.cpp
index 17d63fa..74d8aac 100644
--- a/init/epoll.cpp
+++ b/init/epoll.cpp
@@ -38,11 +38,12 @@
return {};
}
-Result<void> Epoll::RegisterHandler(int fd, std::function<void()> handler, uint32_t events) {
+Result<void> Epoll::RegisterHandler(int fd, Handler handler, uint32_t events) {
if (!events) {
return Error() << "Must specify events";
}
- auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(handler));
+ auto sp = std::make_shared<decltype(handler)>(std::move(handler));
+ auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(sp));
if (!inserted) {
return Error() << "Cannot specify two epoll handlers for a given FD";
}
@@ -69,7 +70,7 @@
return {};
}
-Result<std::vector<std::function<void()>*>> Epoll::Wait(
+Result<std::vector<std::shared_ptr<Epoll::Handler>>> Epoll::Wait(
std::optional<std::chrono::milliseconds> timeout) {
int timeout_ms = -1;
if (timeout && timeout->count() < INT_MAX) {
@@ -81,9 +82,10 @@
if (num_events == -1) {
return ErrnoError() << "epoll_wait failed";
}
- std::vector<std::function<void()>*> pending_functions;
+ std::vector<std::shared_ptr<Handler>> pending_functions;
for (int i = 0; i < num_events; ++i) {
- pending_functions.emplace_back(reinterpret_cast<std::function<void()>*>(ev[i].data.ptr));
+ auto sp = *reinterpret_cast<std::shared_ptr<Handler>*>(ev[i].data.ptr);
+ pending_functions.emplace_back(std::move(sp));
}
return pending_functions;
diff --git a/init/epoll.h b/init/epoll.h
index c32a661..0df5289 100644
--- a/init/epoll.h
+++ b/init/epoll.h
@@ -22,6 +22,7 @@
#include <chrono>
#include <functional>
#include <map>
+#include <memory>
#include <optional>
#include <vector>
@@ -36,15 +37,17 @@
public:
Epoll();
+ typedef std::function<void()> Handler;
+
Result<void> Open();
- Result<void> RegisterHandler(int fd, std::function<void()> handler, uint32_t events = EPOLLIN);
+ Result<void> RegisterHandler(int fd, Handler handler, uint32_t events = EPOLLIN);
Result<void> UnregisterHandler(int fd);
- Result<std::vector<std::function<void()>*>> Wait(
+ Result<std::vector<std::shared_ptr<Handler>>> Wait(
std::optional<std::chrono::milliseconds> timeout);
private:
android::base::unique_fd epoll_fd_;
- std::map<int, std::function<void()>> epoll_handlers_;
+ std::map<int, std::shared_ptr<Handler>> epoll_handlers_;
};
} // namespace init
diff --git a/init/epoll_test.cpp b/init/epoll_test.cpp
new file mode 100644
index 0000000..9236cd5
--- /dev/null
+++ b/init/epoll_test.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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 "epoll.h"
+
+#include <sys/unistd.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace init {
+
+std::unordered_set<void*> sValidObjects;
+
+class CatchDtor final {
+ public:
+ CatchDtor() { sValidObjects.emplace(this); }
+ CatchDtor(const CatchDtor&) { sValidObjects.emplace(this); }
+ ~CatchDtor() {
+ auto iter = sValidObjects.find(this);
+ if (iter != sValidObjects.end()) {
+ sValidObjects.erase(iter);
+ }
+ }
+};
+
+TEST(epoll, UnregisterHandler) {
+ Epoll epoll;
+ ASSERT_RESULT_OK(epoll.Open());
+
+ int fds[2];
+ ASSERT_EQ(pipe(fds), 0);
+
+ CatchDtor catch_dtor;
+ bool handler_invoked;
+ auto handler = [&, catch_dtor]() -> void {
+ auto result = epoll.UnregisterHandler(fds[0]);
+ ASSERT_EQ(result.ok(), !handler_invoked);
+ handler_invoked = true;
+ ASSERT_NE(sValidObjects.find((void*)&catch_dtor), sValidObjects.end());
+ };
+
+ epoll.RegisterHandler(fds[0], std::move(handler));
+
+ uint8_t byte = 0xee;
+ ASSERT_TRUE(android::base::WriteFully(fds[1], &byte, sizeof(byte)));
+
+ auto results = epoll.Wait({});
+ ASSERT_RESULT_OK(results);
+ ASSERT_EQ(results->size(), size_t(1));
+
+ for (const auto& function : *results) {
+ (*function)();
+ (*function)();
+ }
+ ASSERT_TRUE(handler_invoked);
+}
+
+} // namespace init
+} // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 84ed58e..70e26ec 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -100,6 +100,7 @@
constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
constexpr auto DIGEST_SIZE_USED = 8;
+constexpr auto API_LEVEL_CURRENT = 10000;
static bool persistent_properties_loaded = false;
@@ -1017,29 +1018,37 @@
}
}
+static int read_api_level_props(const std::vector<std::string>& api_level_props) {
+ int api_level = API_LEVEL_CURRENT;
+ for (const auto& api_level_prop : api_level_props) {
+ api_level = android::base::GetIntProperty(api_level_prop, API_LEVEL_CURRENT);
+ if (api_level != API_LEVEL_CURRENT) {
+ break;
+ }
+ }
+ return api_level;
+}
+
static void property_initialize_ro_vendor_api_level() {
// ro.vendor.api_level shows the api_level that the vendor images (vendor, odm, ...) are
// required to support.
constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
- // Candidate api levels. The order of the properties must be kept.
- const char* VENDOR_API_LEVEL_PROPS[] = {
- "ro.board.api_level", "ro.board.first_api_level", "ro.product.first_api_level",
- "ro.vndk.version", "ro.vendor.build.version.sdk", "ro.build.version.sdk"};
- for (const auto& api_level_prop : VENDOR_API_LEVEL_PROPS) {
- int api_level = android::base::GetIntProperty(api_level_prop, 0);
- if (api_level != 0) {
- std::string error;
- uint32_t res = PropertySet(VENDOR_API_LEVEL_PROP, std::to_string(api_level), &error);
- if (res != PROP_SUCCESS) {
- LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << api_level
- << ": " << error;
- }
- return;
- }
+ // Api level properties of the board. The order of the properties must be kept.
+ std::vector<std::string> BOARD_API_LEVEL_PROPS = {
+ "ro.board.api_level", "ro.board.first_api_level", "ro.vendor.build.version.sdk"};
+ // Api level properties of the device. The order of the properties must be kept.
+ std::vector<std::string> DEVICE_API_LEVEL_PROPS = {"ro.product.first_api_level",
+ "ro.build.version.sdk"};
+
+ int api_level = std::min(read_api_level_props(BOARD_API_LEVEL_PROPS),
+ read_api_level_props(DEVICE_API_LEVEL_PROPS));
+ std::string error;
+ uint32_t res = PropertySet(VENDOR_API_LEVEL_PROP, std::to_string(api_level), &error);
+ if (res != PROP_SUCCESS) {
+ LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << api_level << ": "
+ << error << "(" << res << ")";
}
- // If no api integers are found from the vendor api level properties, ro.vendor.api_level
- // will not be set.
}
void PropertyLoadBootDefaults() {
@@ -1166,10 +1175,8 @@
LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
&property_infos);
}
- if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
- &property_infos)) {
- // Fallback to nonplat_* if vendor_* doesn't exist.
- LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
+ if (access("/vendor/etc/selinux/vendor_property_contexts", R_OK) != -1) {
+ LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
&property_infos);
}
if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
@@ -1184,10 +1191,7 @@
return;
}
LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos);
- if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
- // Fallback to nonplat_* if vendor_* doesn't exist.
- LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
- }
+ LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos);
LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
}
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index ac6b7b2..5f34cc4 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -99,7 +99,7 @@
std::string vbmeta_digest = GetProperty("ro.boot.vbmeta.digest", "");
ASSERT_GE(vbmeta_digest.size(), 8u);
- std::string build_id = GetProperty("ro.boot.build.id", "");
+ std::string build_id = GetProperty("ro.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);
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 29c0ff3..28cd012 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -27,7 +27,7 @@
// file located at /sepolicy and is directly loaded into the kernel SELinux subsystem.
// The split policy is for supporting treble devices. It splits the SEPolicy across files on
-// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'nonplat'
+// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'vendor'
// portion of the policy). This is necessary to allow the system image to be updated independently
// of the vendor image, while maintaining contributions from both partitions in the SEPolicy. This
// is especially important for VTS testing, where the SEPolicy on the Google System Image may not be
@@ -320,12 +320,12 @@
};
bool OpenSplitPolicy(PolicyFile* policy_file) {
- // IMPLEMENTATION NOTE: Split policy consists of three CIL files:
+ // IMPLEMENTATION NOTE: Split policy consists of three or more CIL files:
// * platform -- policy needed due to logic contained in the system image,
- // * non-platform -- policy needed due to logic contained in the vendor image,
+ // * vendor -- policy needed due to logic contained in the vendor image,
// * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
// with newer versions of platform policy.
- //
+ // * (optional) policy needed due to logic on product, system_ext, or odm images.
// secilc is invoked to compile the above three policy files into a single monolithic policy
// file. This file is then loaded into the kernel.
@@ -404,17 +404,14 @@
product_mapping_file.clear();
}
- // vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace
- // nonplat_sepolicy.cil.
- std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
-
if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
- // For backward compatibility.
- // TODO: remove this after no device is using nonplat_sepolicy.cil.
- vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";
- plat_pub_versioned_cil_file.clear();
- } else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
+ LOG(ERROR) << "Missing " << vendor_policy_cil_file;
+ return false;
+ }
+
+ std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
+ if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
return false;
}
diff --git a/init/service.cpp b/init/service.cpp
index 489dd67..f7318cb 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -661,25 +661,6 @@
StopOrReset(SVC_RESET);
}
-void Service::ResetIfPostData() {
- if (post_data_) {
- if (flags_ & SVC_RUNNING) {
- running_at_post_data_reset_ = true;
- }
- StopOrReset(SVC_RESET);
- }
-}
-
-Result<void> Service::StartIfPostData() {
- // Start the service, but only if it was started after /data was mounted,
- // and it was still running when we reset the post-data services.
- if (running_at_post_data_reset_) {
- return Start();
- }
-
- return {};
-}
-
void Service::Stop() {
StopOrReset(SVC_DISABLED);
}
diff --git a/init/service.h b/init/service.h
index ccf6899..3289f54 100644
--- a/init/service.h
+++ b/init/service.h
@@ -80,10 +80,8 @@
Result<void> ExecStart();
Result<void> Start();
Result<void> StartIfNotDisabled();
- Result<void> StartIfPostData();
Result<void> Enable();
void Reset();
- void ResetIfPostData();
void Stop();
void Terminate();
void Timeout();
@@ -214,8 +212,6 @@
bool post_data_ = false;
- bool running_at_post_data_reset_ = false;
-
std::optional<std::string> on_failure_reboot_target_;
bool from_apex_ = false;
diff --git a/libcutils/include/cutils/list.h b/libcutils/include/cutils/list.h
index dfdc53b..7eb8725 100644
--- a/libcutils/include/cutils/list.h
+++ b/libcutils/include/cutils/list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2013 The Android Open Source Project
+ * Copyright (C) 2008 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.
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _CUTILS_LIST_H_
-#define _CUTILS_LIST_H_
+#pragma once
#include <stddef.h>
@@ -38,9 +37,6 @@
.prev = &(name), \
}
-#define list_for_each(node, list) \
- for ((node) = (list)->next; (node) != (list); (node) = (node)->next)
-
#define list_for_each_reverse(node, list) \
for ((node) = (list)->prev; (node) != (list); (node) = (node)->prev)
@@ -49,6 +45,10 @@
(node) != (list); \
(node) = (n), (n) = (node)->next)
+#define list_for_each(node, list) \
+ for (struct listnode* __n = ((node) = (list)->next)->next; (node) != (list); \
+ (node) = __n, __n = (node)->next)
+
static inline void list_init(struct listnode *node)
{
node->next = node;
@@ -84,5 +84,3 @@
#ifdef __cplusplus
};
#endif /* __cplusplus */
-
-#endif
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index c471fa0..e65fe92 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -130,6 +130,7 @@
#define AID_VIRTUALIZATIONSERVICE 1081 /* VirtualizationService daemon */
#define AID_ARTD 1082 /* ART Service daemon */
#define AID_UWB 1083 /* UWB subsystem */
+#define AID_THREAD_NETWORK 1084 /* Thread Network 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/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 0734f25..352847a 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -34,6 +34,7 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <cgroup_map.h>
#include <json/reader.h>
@@ -41,6 +42,7 @@
#include <processgroup/processgroup.h>
using android::base::GetBoolProperty;
+using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::base::WriteStringToFile;
@@ -204,6 +206,24 @@
return CgroupController(nullptr);
}
+CgroupController CgroupMap::FindControllerByPath(const std::string& path) const {
+ if (!loaded_) {
+ LOG(ERROR) << "CgroupMap::FindControllerByPath called for [" << getpid()
+ << "] failed, RC file was not initialized properly";
+ return CgroupController(nullptr);
+ }
+
+ auto controller_count = ACgroupFile_getControllerCount();
+ for (uint32_t i = 0; i < controller_count; ++i) {
+ const ACgroupController* controller = ACgroupFile_getController(i);
+ if (StartsWith(path, ACgroupController_getPath(controller))) {
+ return CgroupController(controller);
+ }
+ }
+
+ return CgroupController(nullptr);
+}
+
int CgroupMap::ActivateControllers(const std::string& path) const {
if (__builtin_available(android 30, *)) {
auto controller_count = ACgroupFile_getControllerCount();
diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h
index 22d717b..5cdf8b2 100644
--- a/libprocessgroup/cgroup_map.h
+++ b/libprocessgroup/cgroup_map.h
@@ -62,6 +62,7 @@
static CgroupMap& GetInstance();
CgroupController FindController(const std::string& name) const;
+ CgroupController FindControllerByPath(const std::string& path) const;
int ActivateControllers(const std::string& path) const;
private:
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index fa2642d..be34f95 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -26,6 +26,7 @@
static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
+bool CgroupGetControllerFromPath(const std::string& path, std::string* cgroup_name);
bool CgroupGetAttributePath(const std::string& attr_name, std::string* path);
bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index faf945c..0320b02 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -69,6 +69,20 @@
return true;
}
+bool CgroupGetControllerFromPath(const std::string& path, std::string* cgroup_name) {
+ auto controller = CgroupMap::GetInstance().FindControllerByPath(path);
+
+ if (!controller.HasValue()) {
+ return false;
+ }
+
+ if (cgroup_name) {
+ *cgroup_name = controller.name();
+ }
+
+ return true;
+}
+
bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) {
const TaskProfiles& tp = TaskProfiles::GetInstance();
const ProfileAttribute* attr = tp.GetAttribute(attr_name);
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 449a505..45d3c7c 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -183,7 +183,19 @@
}
]
},
-
+ {
+ "Name": "Dex2oatPerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "cpu",
+ "Path": "dex2oat"
+ }
+ }
+ ]
+ },
{
"Name": "CpuPolicySpread",
"Actions": [
@@ -638,7 +650,7 @@
},
{
"Name": "Dex2OatBootComplete",
- "Profiles": [ "SCHED_SP_BACKGROUND" ]
+ "Profiles": [ "Dex2oatPerformance", "LowIoPriority", "TimerSlackHigh" ]
}
]
}
diff --git a/libprocessgroup/profiles/task_profiles_28.json b/libprocessgroup/profiles/task_profiles_28.json
index 9f83785..e7be548 100644
--- a/libprocessgroup/profiles/task_profiles_28.json
+++ b/libprocessgroup/profiles/task_profiles_28.json
@@ -40,6 +40,19 @@
]
},
{
+ "Name": "ServicePerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
+ {
"Name": "HighPerformance",
"Actions": [
{
@@ -104,7 +117,19 @@
}
]
},
-
+ {
+ "Name": "Dex2oatPerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
{
"Name": "CpuPolicySpread",
"Actions": [
diff --git a/libprocessgroup/profiles/task_profiles_29.json b/libprocessgroup/profiles/task_profiles_29.json
index 9f83785..6174c8d 100644
--- a/libprocessgroup/profiles/task_profiles_29.json
+++ b/libprocessgroup/profiles/task_profiles_29.json
@@ -53,6 +53,19 @@
]
},
{
+ "Name": "ServicePerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
+ {
"Name": "MaxPerformance",
"Actions": [
{
@@ -104,7 +117,19 @@
}
]
},
-
+ {
+ "Name": "Dex2oatPerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
{
"Name": "CpuPolicySpread",
"Actions": [
diff --git a/libprocessgroup/profiles/task_profiles_30.json b/libprocessgroup/profiles/task_profiles_30.json
index 9f83785..e7be548 100644
--- a/libprocessgroup/profiles/task_profiles_30.json
+++ b/libprocessgroup/profiles/task_profiles_30.json
@@ -40,6 +40,19 @@
]
},
{
+ "Name": "ServicePerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
+ {
"Name": "HighPerformance",
"Actions": [
{
@@ -104,7 +117,19 @@
}
]
},
-
+ {
+ "Name": "Dex2oatPerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
{
"Name": "CpuPolicySpread",
"Actions": [
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index cf74e65..e935f99 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -194,22 +194,39 @@
fd_.reset(FDS_NOT_CACHED);
}
-bool SetCgroupAction::AddTidToCgroup(int tid, int fd) {
+bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
if (tid <= 0) {
return true;
}
std::string value = std::to_string(tid);
- if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) < 0) {
- // If the thread is in the process of exiting, don't flag an error
- if (errno != ESRCH) {
- PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
- return false;
- }
+ if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) == value.length()) {
+ return true;
}
- return true;
+ // If the thread is in the process of exiting, don't flag an error
+ if (errno == ESRCH) {
+ return true;
+ }
+
+ // ENOSPC is returned when cpuset cgroup that we are joining has no online cpus
+ if (errno == ENOSPC && !strcmp(controller_name, "cpuset")) {
+ // This is an abnormal case happening only in testing, so report it only once
+ static bool empty_cpuset_reported = false;
+
+ if (empty_cpuset_reported) {
+ return true;
+ }
+
+ LOG(ERROR) << "Failed to add task '" << value
+ << "' into cpuset because all cpus in that cpuset are offline";
+ empty_cpuset_reported = true;
+ } else {
+ PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
+ }
+
+ return false;
}
bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
@@ -219,7 +236,7 @@
PLOG(WARNING) << "Failed to open " << procs_path;
return false;
}
- if (!AddTidToCgroup(pid, tmp_fd)) {
+ if (!AddTidToCgroup(pid, tmp_fd, controller()->name())) {
LOG(ERROR) << "Failed to add task into cgroup";
return false;
}
@@ -231,7 +248,7 @@
std::lock_guard<std::mutex> lock(fd_mutex_);
if (IsFdValid()) {
// fd is cached, reuse it
- if (!AddTidToCgroup(tid, fd_)) {
+ if (!AddTidToCgroup(tid, fd_, controller()->name())) {
LOG(ERROR) << "Failed to add task into cgroup";
return false;
}
@@ -256,7 +273,7 @@
PLOG(WARNING) << "Failed to open " << tasks_path << ": " << strerror(errno);
return false;
}
- if (!AddTidToCgroup(tid, tmp_fd)) {
+ if (!AddTidToCgroup(tid, tmp_fd, controller()->name())) {
LOG(ERROR) << "Failed to add task into cgroup";
return false;
}
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 25a84b0..97c38f4 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -134,7 +134,7 @@
mutable std::mutex fd_mutex_;
static bool IsAppDependentPath(const std::string& path);
- static bool AddTidToCgroup(int tid, int fd);
+ static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
bool IsFdValid() const { return fd_ > FDS_INACCESSIBLE; }
};
diff --git a/libprocessgroup/tools/Android.bp b/libprocessgroup/tools/Android.bp
new file mode 100644
index 0000000..91418e1
--- /dev/null
+++ b/libprocessgroup/tools/Android.bp
@@ -0,0 +1,30 @@
+// 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_binary {
+ name: "settaskprofile",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ srcs: ["settaskprofile.cpp"],
+ shared_libs: [
+ "libprocessgroup",
+ ],
+}
diff --git a/libprocessgroup/tools/settaskprofile.cpp b/libprocessgroup/tools/settaskprofile.cpp
new file mode 100644
index 0000000..f83944a
--- /dev/null
+++ b/libprocessgroup/tools/settaskprofile.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 <stdlib.h>
+
+#include <iostream>
+
+#include <processgroup/processgroup.h>
+
+[[noreturn]] static void usage(int exit_status) {
+ std::cerr << "Usage: " << getprogname() << " <tid> <profile> [... profileN]" << std::endl
+ << " tid Thread ID to apply the profiles to." << std::endl
+ << " profile Name of the profile to apply." << std::endl
+ << "Applies listed profiles to the thread with specified ID." << std::endl
+ << "Profiles are applied in the order specified in the command line." << std::endl
+ << "If applying a profile fails, remaining profiles are ignored." << std::endl;
+ exit(exit_status);
+}
+
+int main(int argc, char* argv[]) {
+ if (argc < 3) {
+ usage(EXIT_FAILURE);
+ }
+
+ int tid = atoi(argv[1]);
+ if (tid == 0) {
+ std::cerr << "Invalid thread id" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ for (int i = 2; i < argc; i++) {
+ if (!SetTaskProfiles(tid, {argv[i]})) {
+ std::cerr << "Failed to apply " << argv[i] << " profile" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ std::cout << "Profile " << argv[i] << " is applied successfully!" << std::endl;
+ }
+
+ return 0;
+}
diff --git a/libsuspend/Android.bp b/libsuspend/Android.bp
index 671de4d..144b4b6 100644
--- a/libsuspend/Android.bp
+++ b/libsuspend/Android.bp
@@ -6,6 +6,7 @@
cc_library {
name: "libsuspend",
+ vendor_available: true,
srcs: [
"autosuspend.c",
"autosuspend_wakeup_count.cpp",
diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 3b6cfd8..515cc10 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -31,14 +31,41 @@
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/personality.h>
#include <sys/socket.h>
#include <sys/types.h>
+#include <sys/utsname.h>
+
+#include <android-base/parseint.h>
+#include <log/log.h>
+#include <sysutils/NetlinkEvent.h>
+
+using android::base::ParseInt;
/* From kernel's net/netfilter/xt_quota2.c */
const int LOCAL_QLOG_NL_EVENT = 112;
const int LOCAL_NFLOG_PACKET = NFNL_SUBSYS_ULOG << 8 | NFULNL_MSG_PACKET;
-/* From deprecated ipt_ULOG.h to parse QLOG_NL_EVENT. */
+/******************************************************************************
+ * WARNING: HERE BE DRAGONS! *
+ * *
+ * This is here to provide for compatibility with both 32 and 64-bit kernels *
+ * from 32-bit userspace. *
+ * *
+ * The kernel definition of this struct uses types (like long) that are not *
+ * the same across 32-bit and 64-bit builds, and there is no compatibility *
+ * layer to fix it up before it reaches userspace. *
+ * As such we need to detect the bit-ness of the kernel and deal with it. *
+ * *
+ ******************************************************************************/
+
+/*
+ * This is the verbatim kernel declaration from net/netfilter/xt_quota2.c,
+ * it is *NOT* of a well defined layout and is included here for compile
+ * time assertions only.
+ *
+ * It got there from deprecated ipt_ULOG.h to parse QLOG_NL_EVENT.
+ */
#define ULOG_MAC_LEN 80
#define ULOG_PREFIX_LEN 32
typedef struct ulog_packet_msg {
@@ -55,11 +82,117 @@
unsigned char payload[0];
} ulog_packet_msg_t;
-#include <android-base/parseint.h>
-#include <log/log.h>
-#include <sysutils/NetlinkEvent.h>
+// On Linux int is always 32 bits, while sizeof(long) == sizeof(void*),
+// thus long on a 32-bit Linux kernel is 32-bits, like int always is
+typedef int long32;
+typedef unsigned int ulong32;
+static_assert(sizeof(long32) == 4);
+static_assert(sizeof(ulong32) == 4);
-using android::base::ParseInt;
+// Here's the same structure definition with the assumption the kernel
+// is compiled for 32-bits.
+typedef struct {
+ ulong32 mark;
+ long32 timestamp_sec;
+ long32 timestamp_usec;
+ unsigned int hook;
+ char indev_name[IFNAMSIZ];
+ char outdev_name[IFNAMSIZ];
+ ulong32 data_len;
+ char prefix[ULOG_PREFIX_LEN];
+ unsigned char mac_len;
+ unsigned char mac[ULOG_MAC_LEN];
+ unsigned char payload[0];
+} ulog_packet_msg32_t;
+
+// long on a 64-bit kernel is 64-bits with 64-bit alignment,
+// while long long is 64-bit but may have 32-bit aligment.
+typedef long long __attribute__((__aligned__(8))) long64;
+typedef unsigned long long __attribute__((__aligned__(8))) ulong64;
+static_assert(sizeof(long64) == 8);
+static_assert(sizeof(ulong64) == 8);
+
+// Here's the same structure definition with the assumption the kernel
+// is compiled for 64-bits.
+typedef struct {
+ ulong64 mark;
+ long64 timestamp_sec;
+ long64 timestamp_usec;
+ unsigned int hook;
+ char indev_name[IFNAMSIZ];
+ char outdev_name[IFNAMSIZ];
+ ulong64 data_len;
+ char prefix[ULOG_PREFIX_LEN];
+ unsigned char mac_len;
+ unsigned char mac[ULOG_MAC_LEN];
+ unsigned char payload[0];
+} ulog_packet_msg64_t;
+
+// One expects the 32-bit version to be smaller than the 64-bit version.
+static_assert(sizeof(ulog_packet_msg32_t) < sizeof(ulog_packet_msg64_t));
+// And either way the 'native' version should match either the 32 or 64 bit one.
+static_assert(sizeof(ulog_packet_msg_t) == sizeof(ulog_packet_msg32_t) ||
+ sizeof(ulog_packet_msg_t) == sizeof(ulog_packet_msg64_t));
+
+// In practice these sizes are always simply (for both x86 and arm):
+static_assert(sizeof(ulog_packet_msg32_t) == 168);
+static_assert(sizeof(ulog_packet_msg64_t) == 192);
+
+// Figure out the bitness of userspace.
+// Trivial and known at compile time.
+static bool isUserspace64bit(void) {
+ return sizeof(long) == 8;
+}
+
+// Figure out the bitness of the kernel.
+static bool isKernel64Bit(void) {
+ // a 64-bit userspace requires a 64-bit kernel
+ if (isUserspace64bit()) return true;
+
+ static bool init = false;
+ static bool cache = false;
+ if (init) return cache;
+
+ // Retrieve current personality - on Linux this system call *cannot* fail.
+ int p = personality(0xffffffff);
+ // But if it does just assume kernel and userspace (which is 32-bit) match...
+ if (p == -1) return false;
+
+ // This will effectively mask out the bottom 8 bits, and switch to 'native'
+ // personality, and then return the previous personality of this thread
+ // (likely PER_LINUX or PER_LINUX32) with any extra options unmodified.
+ int q = personality((p & ~PER_MASK) | PER_LINUX);
+ // Per man page this theoretically could error out with EINVAL,
+ // but kernel code analysis suggests setting PER_LINUX cannot fail.
+ // Either way, assume kernel and userspace (which is 32-bit) match...
+ if (q != p) return false;
+
+ struct utsname u;
+ (void)uname(&u); // only possible failure is EFAULT, but u is on stack.
+
+ // Switch back to previous personality.
+ // Theoretically could fail with EINVAL on arm64 with no 32-bit support,
+ // but then we wouldn't have fetched 'p' from the kernel in the first place.
+ // Either way there's nothing meaningul we can do in case of error.
+ // Since PER_LINUX32 vs PER_LINUX only affects uname.machine it doesn't
+ // really hurt us either. We're really just switching back to be 'clean'.
+ (void)personality(p);
+
+ // Possible values of utsname.machine observed on x86_64 desktop (arm via qemu):
+ // x86_64 i686 aarch64 armv7l
+ // additionally observed on arm device:
+ // armv8l
+ // presumably also might just be possible:
+ // i386 i486 i586
+ // and there might be other weird arm32 cases.
+ // We note that the 64 is present in both 64-bit archs,
+ // and in general is likely to be present in only 64-bit archs.
+ cache = !!strstr(u.machine, "64");
+ init = true;
+ return cache;
+}
+
+/******************************************************************************/
NetlinkEvent::NetlinkEvent() {
mAction = Action::kUnknown;
@@ -280,13 +413,22 @@
* Parse a QLOG_NL_EVENT message.
*/
bool NetlinkEvent::parseUlogPacketMessage(const struct nlmsghdr *nh) {
- const char *devname;
- ulog_packet_msg_t *pm = (ulog_packet_msg_t *) NLMSG_DATA(nh);
- if (!checkRtNetlinkLength(nh, sizeof(*pm)))
- return false;
+ const char* alert;
+ const char* devname;
- devname = pm->indev_name[0] ? pm->indev_name : pm->outdev_name;
- asprintf(&mParams[0], "ALERT_NAME=%s", pm->prefix);
+ if (isKernel64Bit()) {
+ ulog_packet_msg64_t* pm64 = (ulog_packet_msg64_t*)NLMSG_DATA(nh);
+ if (!checkRtNetlinkLength(nh, sizeof(*pm64))) return false;
+ alert = pm64->prefix;
+ devname = pm64->indev_name[0] ? pm64->indev_name : pm64->outdev_name;
+ } else {
+ ulog_packet_msg32_t* pm32 = (ulog_packet_msg32_t*)NLMSG_DATA(nh);
+ if (!checkRtNetlinkLength(nh, sizeof(*pm32))) return false;
+ alert = pm32->prefix;
+ devname = pm32->indev_name[0] ? pm32->indev_name : pm32->outdev_name;
+ }
+
+ asprintf(&mParams[0], "ALERT_NAME=%s", alert);
asprintf(&mParams[1], "INTERFACE=%s", devname);
mSubsystem = strdup("qlog");
mAction = Action::kChange;
diff --git a/libutils/String16.cpp b/libutils/String16.cpp
index c42cada..68642d8 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -199,99 +199,59 @@
return NO_MEMORY;
}
-status_t String16::append(const String16& other)
-{
- const size_t myLen = size();
- const size_t otherLen = other.size();
- if (myLen == 0) {
- setTo(other);
- return OK;
- } else if (otherLen == 0) {
- return OK;
- }
-
- if (myLen >= SIZE_MAX / sizeof(char16_t) - otherLen) {
- android_errorWriteLog(0x534e4554, "73826242");
- abort();
- }
-
- SharedBuffer* buf =
- static_cast<SharedBuffer*>(editResize((myLen + otherLen + 1) * sizeof(char16_t)));
- if (buf) {
- char16_t* str = (char16_t*)buf->data();
- memcpy(str+myLen, other, (otherLen+1)*sizeof(char16_t));
- mString = str;
- return OK;
- }
- return NO_MEMORY;
+status_t String16::append(const String16& other) {
+ return append(other.string(), other.size());
}
-status_t String16::append(const char16_t* chrs, size_t otherLen)
-{
+status_t String16::append(const char16_t* chrs, size_t otherLen) {
const size_t myLen = size();
- if (myLen == 0) {
- setTo(chrs, otherLen);
- return OK;
- } else if (otherLen == 0) {
- return OK;
- }
- if (myLen >= SIZE_MAX / sizeof(char16_t) - otherLen) {
- android_errorWriteLog(0x534e4554, "73826242");
- abort();
- }
+ if (myLen == 0) return setTo(chrs, otherLen);
- SharedBuffer* buf =
- static_cast<SharedBuffer*>(editResize((myLen + otherLen + 1) * sizeof(char16_t)));
- if (buf) {
- char16_t* str = (char16_t*)buf->data();
- memcpy(str+myLen, chrs, otherLen*sizeof(char16_t));
- str[myLen+otherLen] = 0;
- mString = str;
- return OK;
- }
- return NO_MEMORY;
+ if (otherLen == 0) return OK;
+
+ size_t size = myLen;
+ if (__builtin_add_overflow(size, otherLen, &size) ||
+ __builtin_add_overflow(size, 1, &size) ||
+ __builtin_mul_overflow(size, sizeof(char16_t), &size)) return NO_MEMORY;
+
+ SharedBuffer* buf = static_cast<SharedBuffer*>(editResize(size));
+ if (!buf) return NO_MEMORY;
+
+ char16_t* str = static_cast<char16_t*>(buf->data());
+ memcpy(str + myLen, chrs, otherLen * sizeof(char16_t));
+ str[myLen + otherLen] = 0;
+ mString = str;
+ return OK;
}
-status_t String16::insert(size_t pos, const char16_t* chrs)
-{
+status_t String16::insert(size_t pos, const char16_t* chrs) {
return insert(pos, chrs, strlen16(chrs));
}
-status_t String16::insert(size_t pos, const char16_t* chrs, size_t len)
-{
+status_t String16::insert(size_t pos, const char16_t* chrs, size_t otherLen) {
const size_t myLen = size();
- if (myLen == 0) {
- return setTo(chrs, len);
- return OK;
- } else if (len == 0) {
- return OK;
- }
+
+ if (myLen == 0) return setTo(chrs, otherLen);
+
+ if (otherLen == 0) return OK;
if (pos > myLen) pos = myLen;
- #if 0
- printf("Insert in to %s: pos=%d, len=%d, myLen=%d, chrs=%s\n",
- String8(*this).string(), pos,
- len, myLen, String8(chrs, len).string());
- #endif
+ size_t size = myLen;
+ if (__builtin_add_overflow(size, otherLen, &size) ||
+ __builtin_add_overflow(size, 1, &size) ||
+ __builtin_mul_overflow(size, sizeof(char16_t), &size)) return NO_MEMORY;
- SharedBuffer* buf =
- static_cast<SharedBuffer*>(editResize((myLen + len + 1) * sizeof(char16_t)));
- if (buf) {
- char16_t* str = (char16_t*)buf->data();
- if (pos < myLen) {
- memmove(str+pos+len, str+pos, (myLen-pos)*sizeof(char16_t));
- }
- memcpy(str+pos, chrs, len*sizeof(char16_t));
- str[myLen+len] = 0;
- mString = str;
- #if 0
- printf("Result (%d chrs): %s\n", size(), String8(*this).string());
- #endif
- return OK;
- }
- return NO_MEMORY;
+ SharedBuffer* buf = static_cast<SharedBuffer*>(editResize(size));
+ if (!buf) return NO_MEMORY;
+
+ char16_t* str = static_cast<char16_t*>(buf->data());
+ if (pos < myLen) memmove(str + pos + otherLen, str + pos, (myLen - pos) * sizeof(char16_t));
+ memcpy(str + pos, chrs, otherLen * sizeof(char16_t));
+ str[myLen + otherLen] = 0;
+ mString = str;
+ return OK;
}
ssize_t String16::findFirst(char16_t c) const
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index 7d7230e..c6e6f74 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -19,7 +19,7 @@
#include <gtest/gtest.h>
-namespace android {
+using namespace android;
::testing::AssertionResult Char16_tStringEquals(const char16_t* a, const char16_t* b) {
if (strcmp16(a, b) != 0) {
@@ -224,4 +224,36 @@
EXPECT_STR16EQ(another, u"abcdef");
}
-} // namespace android
+TEST(String16Test, append) {
+ String16 s;
+ EXPECT_EQ(OK, s.append(String16(u"foo")));
+ EXPECT_STR16EQ(u"foo", s);
+ EXPECT_EQ(OK, s.append(String16(u"bar")));
+ EXPECT_STR16EQ(u"foobar", s);
+ EXPECT_EQ(OK, s.append(u"baz", 0));
+ EXPECT_STR16EQ(u"foobar", s);
+ EXPECT_EQ(NO_MEMORY, s.append(u"baz", SIZE_MAX));
+ EXPECT_STR16EQ(u"foobar", s);
+}
+
+TEST(String16Test, insert) {
+ String16 s;
+
+ // Inserting into the empty string inserts at the start.
+ EXPECT_EQ(OK, s.insert(123, u"foo"));
+ EXPECT_STR16EQ(u"foo", s);
+
+ // Inserting zero characters at any position is okay, but won't expand the string.
+ EXPECT_EQ(OK, s.insert(123, u"foo", 0));
+ EXPECT_STR16EQ(u"foo", s);
+
+ // Inserting past the end of a non-empty string appends.
+ EXPECT_EQ(OK, s.insert(123, u"bar"));
+ EXPECT_STR16EQ(u"foobar", s);
+
+ EXPECT_EQ(OK, s.insert(3, u"!"));
+ EXPECT_STR16EQ(u"foo!bar", s);
+
+ EXPECT_EQ(NO_MEMORY, s.insert(3, u"", SIZE_MAX));
+ EXPECT_STR16EQ(u"foo!bar", s);
+}
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 8511da9..419b2de 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -313,8 +313,8 @@
if (n > 0) {
size_t oldLength = length();
- if ((size_t)n > SIZE_MAX - 1 ||
- oldLength > SIZE_MAX - (size_t)n - 1) {
+ if (n > std::numeric_limits<size_t>::max() - 1 ||
+ oldLength > std::numeric_limits<size_t>::max() - n - 1) {
return NO_MEMORY;
}
char* buf = lockBuffer(oldLength + n);
@@ -327,21 +327,23 @@
return result;
}
-status_t String8::real_append(const char* other, size_t otherLen)
-{
+status_t String8::real_append(const char* other, size_t otherLen) {
const size_t myLen = bytes();
- SharedBuffer* buf = SharedBuffer::bufferFromData(mString)
- ->editResize(myLen+otherLen+1);
- if (buf) {
- char* str = (char*)buf->data();
- mString = str;
- str += myLen;
- memcpy(str, other, otherLen);
- str[otherLen] = '\0';
- return OK;
+ SharedBuffer* buf;
+ size_t newLen;
+ if (__builtin_add_overflow(myLen, otherLen, &newLen) ||
+ __builtin_add_overflow(newLen, 1, &newLen) ||
+ (buf = SharedBuffer::bufferFromData(mString)->editResize(newLen)) == nullptr) {
+ return NO_MEMORY;
}
- return NO_MEMORY;
+
+ char* str = (char*)buf->data();
+ mString = str;
+ str += myLen;
+ memcpy(str, other, otherLen);
+ str[otherLen] = '\0';
+ return OK;
}
char* String8::lockBuffer(size_t size)
diff --git a/libutils/String8_test.cpp b/libutils/String8_test.cpp
index 9efcc6f..1356cd0 100644
--- a/libutils/String8_test.cpp
+++ b/libutils/String8_test.cpp
@@ -15,13 +15,14 @@
*/
#define LOG_TAG "String8_test"
+
#include <utils/Log.h>
#include <utils/String8.h>
#include <utils/String16.h>
#include <gtest/gtest.h>
-namespace android {
+using namespace android;
class String8Test : public testing::Test {
protected:
@@ -101,4 +102,15 @@
String8 valid = String8(String16(tmp));
EXPECT_STREQ(valid, "abcdef");
}
+
+TEST_F(String8Test, append) {
+ String8 s;
+ EXPECT_EQ(OK, s.append("foo"));
+ EXPECT_STREQ("foo", s);
+ EXPECT_EQ(OK, s.append("bar"));
+ EXPECT_STREQ("foobar", s);
+ EXPECT_EQ(OK, s.append("baz", 0));
+ EXPECT_STREQ("foobar", s);
+ EXPECT_EQ(NO_MEMORY, s.append("baz", SIZE_MAX));
+ EXPECT_STREQ("foobar", s);
}
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 540dcf4..6e293c7 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -86,8 +86,10 @@
// A new thread will be in its parent's sched group by default,
// so we just need to handle the background case.
+ // currently set to system_background group which is different
+ // from background group for app.
if (prio >= ANDROID_PRIORITY_BACKGROUND) {
- SetTaskProfiles(0, {"SCHED_SP_BACKGROUND"}, true);
+ SetTaskProfiles(0, {"SCHED_SP_SYSTEM"}, true);
}
if (name) {
@@ -313,7 +315,7 @@
}
if (pri >= ANDROID_PRIORITY_BACKGROUND) {
- rc = SetTaskProfiles(tid, {"SCHED_SP_BACKGROUND"}, true) ? 0 : -1;
+ rc = SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
} else if (curr_pri >= ANDROID_PRIORITY_BACKGROUND) {
SchedPolicy policy = SP_FOREGROUND;
// Change to the sched policy group of the process.
diff --git a/libvndksupport/linker.cpp b/libvndksupport/linker.cpp
index 30b9c2e..ad4fb31 100644
--- a/libvndksupport/linker.cpp
+++ b/libvndksupport/linker.cpp
@@ -39,7 +39,7 @@
static VendorNamespace get_vendor_namespace() {
static VendorNamespace result = ([] {
- for (const char* name : {"sphal", "default"}) {
+ for (const char* name : {"sphal", "vendor", "default"}) {
if (android_namespace_t* ns = android_get_exported_namespace(name)) {
return VendorNamespace{ns, name};
}
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index c4c58ee..42602e9 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -1283,8 +1283,7 @@
llkEnableSysrqT &= !llkLowRam;
if (debuggable) {
llkEnableSysrqT |= llkCheckEng(LLK_ENABLE_SYSRQ_T_PROPERTY);
- if (!LLK_ENABLE_DEFAULT) { // NB: default is currently true ...
- llkEnable |= llkCheckEng(LLK_ENABLE_PROPERTY);
+ if (!LLK_ENABLE_DEFAULT) {
khtEnable |= llkCheckEng(KHT_ENABLE_PROPERTY);
}
}
diff --git a/llkd/llkd-debuggable.rc b/llkd/llkd-debuggable.rc
index 8697e9a..8355e9d 100644
--- a/llkd/llkd-debuggable.rc
+++ b/llkd/llkd-debuggable.rc
@@ -1,5 +1,5 @@
on property:ro.debuggable=1
- setprop llk.enable ${ro.llk.enable:-1}
+ setprop llk.enable ${ro.llk.enable:-0}
setprop khungtask.enable ${ro.khungtask.enable:-1}
on property:ro.llk.enable=eng
diff --git a/llkd/tests/llkd_test.cpp b/llkd/tests/llkd_test.cpp
index 475512c..8eb9b00 100644
--- a/llkd/tests/llkd_test.cpp
+++ b/llkd/tests/llkd_test.cpp
@@ -69,13 +69,9 @@
seconds llkdSleepPeriod(char state) {
auto default_eng = android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng";
auto default_enable = LLK_ENABLE_DEFAULT;
- if (!LLK_ENABLE_DEFAULT && default_eng &&
- android::base::GetBoolProperty("ro.debuggable", false)) {
- default_enable = true;
- }
default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable);
if (default_eng) {
- GTEST_LOG_INFO << LLK_ENABLE_PROPERTY " defaults to \"eng\" thus "
+ GTEST_LOG_INFO << LLK_ENABLE_PROPERTY " defaults to "
<< (default_enable ? "true" : "false") << "\n";
}
// Hail Mary hope is unconfigured.
@@ -108,10 +104,6 @@
rest();
}
default_enable = LLK_ENABLE_DEFAULT;
- if (!LLK_ENABLE_DEFAULT && (android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng") &&
- android::base::GetBoolProperty("ro.debuggable", false)) {
- default_enable = true;
- }
default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable);
if (default_enable) {
execute("start llkd-1");
diff --git a/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp b/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
index 3907413..77cbdd4 100644
--- a/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
+++ b/property_service/libpropertyinfoserializer/property_info_serializer_test.cpp
@@ -490,7 +490,6 @@
{"media.recorder.show_manufacturer_and_model", "u:object_r:default_prop:s0"},
{"net.bt.name", "u:object_r:system_prop:s0"},
{"net.lte.ims.data.enabled", "u:object_r:net_radio_prop:s0"},
- {"net.qtaguid_enabled", "u:object_r:system_prop:s0"},
{"net.tcp.default_init_rwnd", "u:object_r:system_prop:s0"},
{"nfc.initialized", "u:object_r:nfc_prop:s0"},
{"persist.audio.fluence.speaker", "u:object_r:audio_prop:s0"},
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 3831693..b09c2f1 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -155,6 +155,7 @@
mkdir /dev/cpuctl/rt
mkdir /dev/cpuctl/system
mkdir /dev/cpuctl/system-background
+ mkdir /dev/cpuctl/dex2oat
chown system system /dev/cpuctl
chown system system /dev/cpuctl/foreground
chown system system /dev/cpuctl/background
@@ -162,6 +163,7 @@
chown system system /dev/cpuctl/rt
chown system system /dev/cpuctl/system
chown system system /dev/cpuctl/system-background
+ chown system system /dev/cpuctl/dex2oat
chown system system /dev/cpuctl/tasks
chown system system /dev/cpuctl/foreground/tasks
chown system system /dev/cpuctl/background/tasks
@@ -169,6 +171,7 @@
chown system system /dev/cpuctl/rt/tasks
chown system system /dev/cpuctl/system/tasks
chown system system /dev/cpuctl/system-background/tasks
+ chown system system /dev/cpuctl/dex2oat/tasks
chmod 0664 /dev/cpuctl/tasks
chmod 0664 /dev/cpuctl/foreground/tasks
chmod 0664 /dev/cpuctl/background/tasks
@@ -176,6 +179,7 @@
chmod 0664 /dev/cpuctl/rt/tasks
chmod 0664 /dev/cpuctl/system/tasks
chmod 0664 /dev/cpuctl/system-background/tasks
+ chmod 0664 /dev/cpuctl/dex2oat/tasks
# Create a cpu group for NNAPI HAL processes
mkdir /dev/cpuctl/nnapi-hal
@@ -456,11 +460,6 @@
class_stop charger
trigger late-init
-on load_persist_props_action
- load_persist_props
- start logd
- start logd-reinit
-
# Indicate to fw loaders that the relevant mounts are up.
on firmware_mounts_complete
rm /dev/.booting
@@ -487,9 +486,6 @@
# /data, which in turn can only be loaded when system properties are present.
trigger post-fs-data
- # Load persist properties and override properties (if enabled) from /data.
- trigger load_persist_props_action
-
# Should be before netd, but after apex, properties and logging is available.
trigger load_bpf_programs
@@ -579,6 +575,7 @@
restorecon_recursive /metadata/apex
mkdir /metadata/staged-install 0770 root system
+ mkdir /metadata/sepolicy 0700 root root
on late-fs
# Ensure that tracefs has the correct permissions.
# This does not work correctly if it is called in post-fs.
@@ -672,6 +669,18 @@
# use of MAX_BOOT_LEVEL keys.
exec - system system -- /system/bin/vdc keymaster earlyBootEnded
+ # Multi-installed APEXes are selected using persist props.
+ # Load persist properties and override properties (if enabled) from /data,
+ # before starting apexd.
+ load_persist_props
+ start logd
+ start logd-reinit
+ # Some existing vendor rc files use 'on load_persist_props_action' to know
+ # when persist props are ready. These are difficult to change due to GRF,
+ # so continue triggering this action here even though props are already loaded
+ # by the 'load_persist_props' call above.
+ trigger load_persist_props_action
+
# /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
@@ -1097,6 +1106,9 @@
# Define default initial receive window size in segments.
setprop net.tcp_def_init_rwnd 60
+ # Update dm-verity state and set partition.*.verified properties.
+ verity_update_state
+
# Start standard binderized HAL daemons
class_start hal
@@ -1112,37 +1124,6 @@
on charger
class_start charger
-on property:vold.decrypt=trigger_load_persist_props
- load_persist_props
- start logd
- start logd-reinit
-
-on property:vold.decrypt=trigger_post_fs_data
- trigger post-fs-data
- trigger zygote-start
-
-on property:vold.decrypt=trigger_restart_min_framework
- # A/B update verifier that marks a successful boot.
- exec_start update_verifier
- class_start main
-
-on property:vold.decrypt=trigger_restart_framework
- # A/B update verifier that marks a successful boot.
- exec_start update_verifier
- class_start_post_data hal
- class_start_post_data core
- class_start main
- class_start late_start
- setprop service.bootanim.exit 0
- setprop service.bootanim.progress 0
- start bootanim
-
-on property:vold.decrypt=trigger_shutdown_framework
- class_reset late_start
- class_reset main
- class_reset_post_data core
- class_reset_post_data hal
-
on property:sys.boot_completed=1
bootchart stop
# Setup per_boot directory so other .rc could start to use it on boot_completed
diff --git a/rootdir/init.zygote32.rc b/rootdir/init.zygote32.rc
index 0090841..63b09c0 100644
--- a/rootdir/init.zygote32.rc
+++ b/rootdir/init.zygote32.rc
@@ -10,6 +10,7 @@
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
+ onrestart restart media.tuner
onrestart restart netd
onrestart restart wificond
task_profiles ProcessCapacityHigh
diff --git a/rootdir/init.zygote64.rc b/rootdir/init.zygote64.rc
index 63772bd..5bde5f4 100644
--- a/rootdir/init.zygote64.rc
+++ b/rootdir/init.zygote64.rc
@@ -10,6 +10,7 @@
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
+ onrestart restart media.tuner
onrestart restart netd
onrestart restart wificond
task_profiles ProcessCapacityHigh
diff --git a/rootdir/init.zygote64_32.rc b/rootdir/init.zygote64_32.rc
index 3eee180..efb30d6 100644
--- a/rootdir/init.zygote64_32.rc
+++ b/rootdir/init.zygote64_32.rc
@@ -10,6 +10,7 @@
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
+ onrestart restart media.tuner
onrestart restart netd
onrestart restart wificond
task_profiles ProcessCapacityHigh MaxPerformance
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index 97e8d8e..d85f6ed 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -26,6 +26,7 @@
"mkshrc",
"newfs_msdos",
"reboot",
+ "settaskprofile",
"sh",
"simpleperf",
"simpleperf_app_runner",
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index 7bd1d10..99d9e56 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -117,6 +117,7 @@
"libkeymint",
"liblog",
"libtrusty",
+ "libutils",
],
required: [
"android.hardware.hardware_keystore.xml",
@@ -142,6 +143,7 @@
"libtrusty",
"libhardware",
"libkeymaster_messages",
+ "libutils",
"libxml2",
],
export_include_dirs: ["include"],
@@ -169,6 +171,7 @@
"libtrusty",
"libhardware",
"libkeymaster_messages",
+ "libutils",
"libxml2",
],
cflags: [
diff --git a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
index 2d44009..db1a9f4 100644
--- a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
+++ b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
@@ -19,6 +19,7 @@
// TODO: make this generic in libtrusty
#include <errno.h>
+#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
@@ -33,11 +34,15 @@
#include <trusty_keymaster/ipc/keymaster_ipc.h>
#include <trusty_keymaster/ipc/trusty_keymaster_ipc.h>
+#include <utils/Timers.h>
#define TRUSTY_DEVICE_NAME "/dev/trusty-ipc-dev0"
static int handle_ = -1;
+static const int timeout_ms = 10 * 1000;
+static const int max_timeout_ms = 60 * 1000;
+
int trusty_keymaster_connect() {
int rc = tipc_connect(TRUSTY_DEVICE_NAME, KEYMASTER_PORT);
if (rc < 0) {
@@ -84,7 +89,38 @@
msg->cmd = cmd;
memcpy(msg->payload, in, in_size);
+ nsecs_t start_time_ns = systemTime(SYSTEM_TIME_MONOTONIC);
+ bool timed_out = false;
+ int poll_timeout_ms = timeout_ms;
+ while (true) {
+ struct pollfd pfd;
+ pfd.fd = handle_;
+ pfd.events = POLLOUT;
+ pfd.revents = 0;
+
+ int p = poll(&pfd, 1, poll_timeout_ms);
+ if (p == 0) {
+ ALOGW("write for cmd %d is taking more than %lld nsecs", cmd,
+ (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+ timed_out = true;
+ poll_timeout_ms *= 2;
+ if (poll_timeout_ms > max_timeout_ms) {
+ poll_timeout_ms = max_timeout_ms;
+ }
+ continue;
+ } else if (p < 0) {
+ ALOGE("write poll error: %d", errno);
+ } else if (pfd.revents != POLLOUT) {
+ ALOGW("unexpected poll() result: %d", pfd.revents);
+ }
+ break;
+ }
+
ssize_t rc = write(handle_, msg, msg_size);
+ if (timed_out) {
+ ALOGW("write for cmd %d finished after %lld nsecs", cmd,
+ (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+ }
free(msg);
if (rc < 0) {
@@ -122,8 +158,37 @@
return -EOVERFLOW;
}
iov[1] = {.iov_base = write_pos, .iov_len = buffer_size};
+ start_time_ns = systemTime(SYSTEM_TIME_MONOTONIC);
+ timed_out = false;
+ poll_timeout_ms = timeout_ms;
+ while (true) {
+ struct pollfd pfd;
+ pfd.fd = handle_;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+ int p = poll(&pfd, 1, poll_timeout_ms);
+ if (p == 0) {
+ ALOGW("readv for cmd %d is taking more than %lld nsecs", cmd,
+ (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+ timed_out = true;
+ poll_timeout_ms *= 2;
+ if (poll_timeout_ms > max_timeout_ms) {
+ poll_timeout_ms = max_timeout_ms;
+ }
+ continue;
+ } else if (p < 0) {
+ ALOGE("read poll error: %d", errno);
+ } else if (pfd.revents != POLLIN) {
+ ALOGW("unexpected poll() result: %d", pfd.revents);
+ }
+ break;
+ }
rc = readv(handle_, iov, 2);
+ if (timed_out) {
+ ALOGW("readv for cmd %d finished after %lld nsecs", cmd,
+ (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+ }
if (rc < 0) {
ALOGE("failed to retrieve response for cmd (%d) to %s: %s\n", cmd, KEYMASTER_PORT,
strerror(errno));
diff --git a/trusty/keymaster/keymint/service.cpp b/trusty/keymaster/keymint/service.cpp
index 4060278..d5a77fb 100644
--- a/trusty/keymaster/keymint/service.cpp
+++ b/trusty/keymaster/keymint/service.cpp
@@ -31,7 +31,7 @@
template <typename T, class... Args>
std::shared_ptr<T> addService(Args&&... args) {
- std::shared_ptr<T> service = std::make_shared<T>(std::forward<Args>(args)...);
+ std::shared_ptr<T> service = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
auto instanceName = std::string(T::descriptor) + "/default";
LOG(ERROR) << "Adding service instance: " << instanceName;
auto status = AServiceManager_addService(service->asBinder().get(), instanceName.c_str());
diff --git a/trusty/storage/interface/include/trusty/interface/storage.h b/trusty/storage/interface/include/trusty/interface/storage.h
index b196d88..3f1dcb8 100644
--- a/trusty/storage/interface/include/trusty/interface/storage.h
+++ b/trusty/storage/interface/include/trusty/interface/storage.h
@@ -112,26 +112,30 @@
/**
* enum storage_msg_flag - protocol-level flags in struct storage_msg
- * @STORAGE_MSG_FLAG_BATCH: if set, command belongs to a batch transaction.
- * No response will be sent by the server until
- * it receives a command with this flag unset, at
- * which point a cummulative result for all messages
- * sent with STORAGE_MSG_FLAG_BATCH will be sent.
- * This is only supported by the non-secure disk proxy
- * server.
- * @STORAGE_MSG_FLAG_PRE_COMMIT: if set, indicates that server need to commit
- * pending changes before processing this message.
- * @STORAGE_MSG_FLAG_POST_COMMIT: if set, indicates that server need to commit
- * pending changes after processing this message.
- * @STORAGE_MSG_FLAG_TRANSACT_COMPLETE: if set, indicates that server need to commit
- * current transaction after processing this message.
- * It is an alias for STORAGE_MSG_FLAG_POST_COMMIT.
+ * @STORAGE_MSG_FLAG_BATCH: if set, command belongs to a batch transaction.
+ * No response will be sent by the server until
+ * it receives a command with this flag unset, at
+ * which point a cumulative result for all messages
+ * sent with STORAGE_MSG_FLAG_BATCH will be sent.
+ * This is only supported by the non-secure disk proxy
+ * server.
+ * @STORAGE_MSG_FLAG_PRE_COMMIT: if set, indicates that server need to commit
+ * pending changes before processing this message.
+ * @STORAGE_MSG_FLAG_POST_COMMIT: if set, indicates that server need to commit
+ * pending changes after processing this message.
+ * @STORAGE_MSG_FLAG_TRANSACT_COMPLETE: if set, indicates that server need to commit
+ * current transaction after processing this message.
+ * It is an alias for STORAGE_MSG_FLAG_POST_COMMIT.
+ * @STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT: if set, indicates that server needs to ensure
+ * that there is not a pending checkpoint for
+ * userdata before processing this message.
*/
enum storage_msg_flag {
- STORAGE_MSG_FLAG_BATCH = 0x1,
- STORAGE_MSG_FLAG_PRE_COMMIT = 0x2,
- STORAGE_MSG_FLAG_POST_COMMIT = 0x4,
- STORAGE_MSG_FLAG_TRANSACT_COMPLETE = STORAGE_MSG_FLAG_POST_COMMIT,
+ STORAGE_MSG_FLAG_BATCH = 0x1,
+ STORAGE_MSG_FLAG_PRE_COMMIT = 0x2,
+ STORAGE_MSG_FLAG_POST_COMMIT = 0x4,
+ STORAGE_MSG_FLAG_TRANSACT_COMPLETE = STORAGE_MSG_FLAG_POST_COMMIT,
+ STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT = 0x8,
};
/*
diff --git a/trusty/storage/proxy/Android.bp b/trusty/storage/proxy/Android.bp
index d67089f..38d8685 100644
--- a/trusty/storage/proxy/Android.bp
+++ b/trusty/storage/proxy/Android.bp
@@ -23,6 +23,7 @@
vendor: true,
srcs: [
+ "checkpoint_handling.cpp",
"ipc.c",
"rpmb.c",
"storage.c",
@@ -30,12 +31,14 @@
],
shared_libs: [
+ "libbase",
"liblog",
"libhardware_legacy",
],
header_libs: ["libcutils_headers"],
static_libs: [
+ "libfstab",
"libtrustystorageinterface",
"libtrusty",
],
diff --git a/trusty/storage/proxy/checkpoint_handling.cpp b/trusty/storage/proxy/checkpoint_handling.cpp
new file mode 100644
index 0000000..6c2fd36
--- /dev/null
+++ b/trusty/storage/proxy/checkpoint_handling.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 "checkpoint_handling.h"
+#include "log.h"
+
+#include <fstab/fstab.h>
+#include <cstring>
+#include <string>
+
+namespace {
+
+bool checkpointingDoneForever = false;
+
+} // namespace
+
+int is_data_checkpoint_active(bool* active) {
+ if (!active) {
+ ALOGE("active out parameter is null");
+ return 0;
+ }
+
+ *active = false;
+
+ if (checkpointingDoneForever) {
+ return 0;
+ }
+
+ android::fs_mgr::Fstab procMounts;
+ bool success = android::fs_mgr::ReadFstabFromFile("/proc/mounts", &procMounts);
+ if (!success) {
+ ALOGE("Could not parse /proc/mounts\n");
+ /* Really bad. Tell the caller to abort the write. */
+ return -1;
+ }
+
+ android::fs_mgr::FstabEntry* dataEntry =
+ android::fs_mgr::GetEntryForMountPoint(&procMounts, "/data");
+ if (dataEntry == NULL) {
+ ALOGE("/data is not mounted yet\n");
+ return 0;
+ }
+
+ /* We can't handle e.g., ext4. Nothing we can do about it for now. */
+ if (dataEntry->fs_type != "f2fs") {
+ ALOGW("Checkpoint status not supported for filesystem %s\n", dataEntry->fs_type.c_str());
+ checkpointingDoneForever = true;
+ return 0;
+ }
+
+ /*
+ * The data entry looks like "... blah,checkpoint=disable:0,blah ...".
+ * checkpoint=disable means checkpointing is on (yes, arguably reversed).
+ */
+ size_t checkpointPos = dataEntry->fs_options.find("checkpoint=disable");
+ if (checkpointPos == std::string::npos) {
+ /* Assumption is that once checkpointing turns off, it stays off */
+ checkpointingDoneForever = true;
+ } else {
+ *active = true;
+ }
+
+ return 0;
+}
diff --git a/trusty/storage/proxy/checkpoint_handling.h b/trusty/storage/proxy/checkpoint_handling.h
new file mode 100644
index 0000000..f1bf27c
--- /dev/null
+++ b/trusty/storage/proxy/checkpoint_handling.h
@@ -0,0 +1,37 @@
+/*
+ * 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 <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * is_data_checkpoint_active() - Check for an active, uncommitted checkpoint of
+ * /data. If a checkpoint is active, storage should not commit any
+ * rollback-protected writes to /data.
+ * @active: Out parameter that will be set to the result of the check.
+ *
+ * Return: 0 if active was set and is valid, non-zero otherwise.
+ */
+int is_data_checkpoint_active(bool* active);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index e230941..c690a28 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -26,6 +26,7 @@
#include <cutils/android_filesystem_config.h>
+#include "checkpoint_handling.h"
#include "ipc.h"
#include "log.h"
#include "rpmb.h"
@@ -130,6 +131,21 @@
}
}
+ if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT) {
+ bool is_checkpoint_active = false;
+
+ rc = is_data_checkpoint_active(&is_checkpoint_active);
+ if (rc != 0) {
+ ALOGE("is_data_checkpoint_active failed in an unexpected way. Aborting.\n");
+ msg->result = STORAGE_ERR_GENERIC;
+ return ipc_respond(msg, NULL, 0);
+ } else if (is_checkpoint_active) {
+ ALOGE("Checkpoint in progress, dropping write ...\n");
+ msg->result = STORAGE_ERR_GENERIC;
+ return ipc_respond(msg, NULL, 0);
+ }
+ }
+
switch (msg->cmd) {
case STORAGE_FILE_DELETE:
rc = storage_file_delete(msg, req, req_len);